mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
fixes from congestion testing
- refactor main wallet polling - restart main wallet if connection changes before initial sync - use cached wallet state throughout app - avoid rescanning spent outputs until payout tx expected - allow payment sent/received buttons to be clicked until arrived - apply timeout to payment sent/received buttons - load DepositView asynchronously - remove separate timeout from OpenOffer - tolerate error importing multisig hex until necessary
This commit is contained in:
parent
9cbf042da2
commit
ca2d7704ab
22 changed files with 802 additions and 680 deletions
27
Makefile
27
Makefile
|
@ -112,16 +112,6 @@ monerod3-local:
|
||||||
--fixed-difficulty 500 \
|
--fixed-difficulty 500 \
|
||||||
--disable-rpc-ban \
|
--disable-rpc-ban \
|
||||||
|
|
||||||
funding-wallet-stagenet:
|
|
||||||
./.localnet/monero-wallet-rpc \
|
|
||||||
--stagenet \
|
|
||||||
--rpc-bind-port 18084 \
|
|
||||||
--rpc-login rpc_user:abc123 \
|
|
||||||
--rpc-access-control-origins http://localhost:8080 \
|
|
||||||
--wallet-dir ./.localnet \
|
|
||||||
--daemon-ssl-allow-any-cert \
|
|
||||||
--daemon-address http://127.0.0.1:38081 \
|
|
||||||
|
|
||||||
#--proxy 127.0.0.1:49775 \
|
#--proxy 127.0.0.1:49775 \
|
||||||
|
|
||||||
funding-wallet-local:
|
funding-wallet-local:
|
||||||
|
@ -133,6 +123,23 @@ funding-wallet-local:
|
||||||
--rpc-access-control-origins http://localhost:8080 \
|
--rpc-access-control-origins http://localhost:8080 \
|
||||||
--wallet-dir ./.localnet \
|
--wallet-dir ./.localnet \
|
||||||
|
|
||||||
|
funding-wallet-stagenet:
|
||||||
|
./.localnet/monero-wallet-rpc \
|
||||||
|
--stagenet \
|
||||||
|
--rpc-bind-port 38084 \
|
||||||
|
--rpc-login rpc_user:abc123 \
|
||||||
|
--rpc-access-control-origins http://localhost:8080 \
|
||||||
|
--wallet-dir ./.localnet \
|
||||||
|
--daemon-ssl-allow-any-cert \
|
||||||
|
--daemon-address http://127.0.0.1:38081 \
|
||||||
|
|
||||||
|
funding-wallet-mainnet:
|
||||||
|
./.localnet/monero-wallet-rpc \
|
||||||
|
--rpc-bind-port 18084 \
|
||||||
|
--rpc-login rpc_user:abc123 \
|
||||||
|
--rpc-access-control-origins http://localhost:8080 \
|
||||||
|
--wallet-dir ./.localnet \
|
||||||
|
|
||||||
# use .bat extension for windows binaries
|
# use .bat extension for windows binaries
|
||||||
APP_EXT :=
|
APP_EXT :=
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
|
|
|
@ -607,7 +607,7 @@ public final class XmrConnectionService {
|
||||||
long targetHeight = lastInfo.getTargetHeight();
|
long targetHeight = lastInfo.getTargetHeight();
|
||||||
long blocksLeft = targetHeight - lastInfo.getHeight();
|
long blocksLeft = targetHeight - lastInfo.getHeight();
|
||||||
if (syncStartHeight == null) syncStartHeight = lastInfo.getHeight();
|
if (syncStartHeight == null) syncStartHeight = lastInfo.getHeight();
|
||||||
double percent = targetHeight == syncStartHeight ? 1.0 : ((double) Math.max(1, lastInfo.getHeight() - syncStartHeight) / (double) (targetHeight - syncStartHeight)) * 100d; // grant at least 1 block to show progress
|
double percent = Math.min(1.0, targetHeight == syncStartHeight ? 1.0 : ((double) Math.max(1, lastInfo.getHeight() - syncStartHeight) / (double) (targetHeight - syncStartHeight))); // grant at least 1 block to show progress
|
||||||
downloadListener.progress(percent, blocksLeft, null);
|
downloadListener.progress(percent, blocksLeft, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,18 +116,16 @@ public class OfferBookService {
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
||||||
synchronized (offerBookChangedListeners) {
|
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||||
offerBookChangedListeners.forEach(listener -> {
|
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
maybeInitializeKeyImagePoller();
|
||||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
keyImagePoller.addKeyImages(offerPayload.getReserveTxKeyImages());
|
||||||
maybeInitializeKeyImagePoller();
|
Offer offer = new Offer(offerPayload);
|
||||||
keyImagePoller.addKeyImages(offerPayload.getReserveTxKeyImages());
|
offer.setPriceFeedService(priceFeedService);
|
||||||
Offer offer = new Offer(offerPayload);
|
setReservedFundsSpent(offer);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
synchronized (offerBookChangedListeners) {
|
||||||
setReservedFundsSpent(offer);
|
offerBookChangedListeners.forEach(listener -> listener.onAdded(offer));
|
||||||
listener.onAdded(offer);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -135,18 +133,16 @@ public class OfferBookService {
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
||||||
synchronized (offerBookChangedListeners) {
|
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||||
offerBookChangedListeners.forEach(listener -> {
|
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
maybeInitializeKeyImagePoller();
|
||||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
keyImagePoller.removeKeyImages(offerPayload.getReserveTxKeyImages());
|
||||||
maybeInitializeKeyImagePoller();
|
Offer offer = new Offer(offerPayload);
|
||||||
keyImagePoller.removeKeyImages(offerPayload.getReserveTxKeyImages());
|
offer.setPriceFeedService(priceFeedService);
|
||||||
Offer offer = new Offer(offerPayload);
|
setReservedFundsSpent(offer);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
synchronized (offerBookChangedListeners) {
|
||||||
setReservedFundsSpent(offer);
|
offerBookChangedListeners.forEach(listener -> listener.onRemoved(offer));
|
||||||
listener.onRemoved(offer);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,6 @@
|
||||||
|
|
||||||
package haveno.core.offer;
|
package haveno.core.offer;
|
||||||
|
|
||||||
import haveno.common.Timer;
|
|
||||||
import haveno.common.UserThread;
|
|
||||||
import haveno.common.proto.ProtoUtil;
|
import haveno.common.proto.ProtoUtil;
|
||||||
import haveno.core.trade.Tradable;
|
import haveno.core.trade.Tradable;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
@ -55,9 +53,6 @@ import java.util.Optional;
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class OpenOffer implements Tradable {
|
public final class OpenOffer implements Tradable {
|
||||||
// Timeout for offer reservation during takeoffer process. If deposit tx is not completed in that time we reset the offer to AVAILABLE state.
|
|
||||||
private static final long TIMEOUT = 60;
|
|
||||||
transient private Timer timeoutTimer;
|
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
SCHEDULED,
|
SCHEDULED,
|
||||||
|
@ -227,13 +222,6 @@ public final class OpenOffer implements Tradable {
|
||||||
public void setState(State state) {
|
public void setState(State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
stateProperty.set(state);
|
stateProperty.set(state);
|
||||||
|
|
||||||
// We keep it reserved for a limited time, if trade preparation fails we revert to available state
|
|
||||||
if (this.state == State.RESERVED) { // TODO (woodser): remove this?
|
|
||||||
startTimeout();
|
|
||||||
} else {
|
|
||||||
stopTimeout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
|
@ -252,26 +240,6 @@ public final class OpenOffer implements Tradable {
|
||||||
return state == State.DEACTIVATED;
|
return state == State.DEACTIVATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startTimeout() {
|
|
||||||
stopTimeout();
|
|
||||||
|
|
||||||
timeoutTimer = UserThread.runAfter(() -> {
|
|
||||||
log.debug("Timeout for resetting State.RESERVED reached");
|
|
||||||
if (state == State.RESERVED) {
|
|
||||||
// we do not need to persist that as at startup any RESERVED state would be reset to AVAILABLE anyway
|
|
||||||
setState(State.AVAILABLE);
|
|
||||||
}
|
|
||||||
}, TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopTimeout() {
|
|
||||||
if (timeoutTimer != null) {
|
|
||||||
timeoutTimer.stop();
|
|
||||||
timeoutTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "OpenOffer{" +
|
return "OpenOffer{" +
|
||||||
|
|
|
@ -971,8 +971,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
// return if awaiting scheduled tx
|
// return if awaiting scheduled tx
|
||||||
if (openOffer.getScheduledTxHashes() != null) return null;
|
if (openOffer.getScheduledTxHashes() != null) return null;
|
||||||
|
|
||||||
// cache all transactions including from pool
|
// get all transactions including from pool
|
||||||
List<MoneroTxWallet> allTxs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
List<MoneroTxWallet> allTxs = xmrWalletService.getTransactions(false);
|
||||||
|
|
||||||
if (preferredSubaddressIndex != null) {
|
if (preferredSubaddressIndex != null) {
|
||||||
|
|
||||||
|
|
|
@ -766,6 +766,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
trade.getSelf().getUpdatedMultisigHex(),
|
trade.getSelf().getUpdatedMultisigHex(),
|
||||||
receiver.getUnsignedPayoutTxHex(), // include dispute payout tx if arbitrator has their updated multisig info
|
receiver.getUnsignedPayoutTxHex(), // include dispute payout tx if arbitrator has their updated multisig info
|
||||||
deferPublishPayout); // instruct trader to defer publishing payout tx because peer is expected to publish imminently
|
deferPublishPayout); // instruct trader to defer publishing payout tx because peer is expected to publish imminently
|
||||||
|
receiverPeer.setDisputeClosedMessage(disputeClosedMessage);
|
||||||
|
|
||||||
// send dispute closed message
|
// send dispute closed message
|
||||||
log.info("Send {} to trader {}. tradeId={}, {}.uid={}, chatMessage.uid={}",
|
log.info("Send {} to trader {}. tradeId={}, {}.uid={}, chatMessage.uid={}",
|
||||||
|
|
|
@ -413,7 +413,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
transient private Subscription tradeStateSubscription;
|
transient private Subscription tradeStateSubscription;
|
||||||
transient private Subscription tradePhaseSubscription;
|
transient private Subscription tradePhaseSubscription;
|
||||||
transient private Subscription payoutStateSubscription;
|
transient private Subscription payoutStateSubscription;
|
||||||
transient private TaskLooper txPollLooper;
|
transient private TaskLooper pollLooper;
|
||||||
transient private Long walletRefreshPeriodMs;
|
transient private Long walletRefreshPeriodMs;
|
||||||
transient private Long syncNormalStartTimeMs;
|
transient private Long syncNormalStartTimeMs;
|
||||||
|
|
||||||
|
@ -890,6 +890,10 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
public void saveWallet() {
|
public void saveWallet() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
|
if (!walletExists()) {
|
||||||
|
log.warn("Cannot save wallet for {} {} because it does not exist", getClass().getSimpleName(), getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (wallet == null) throw new RuntimeException("Trade wallet is not open for trade " + getId());
|
if (wallet == null) throw new RuntimeException("Trade wallet is not open for trade " + getId());
|
||||||
xmrWalletService.saveWallet(wallet);
|
xmrWalletService.saveWallet(wallet);
|
||||||
maybeBackupWallet();
|
maybeBackupWallet();
|
||||||
|
@ -1195,7 +1199,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return trader.getDepositTx();
|
return trader.getDepositTx();
|
||||||
} catch (MoneroError e) {
|
} catch (MoneroError e) {
|
||||||
log.error("Error getting {} deposit tx {}: {}", getPeerRole(trader), depositId, e.getMessage()); // TODO: peer.getRole()
|
log.error("Error getting {} deposit tx {}: {}", getPeerRole(trader), depositId, e.getMessage()); // TODO: peer.getRole()
|
||||||
return null;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1264,9 +1268,12 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
// TODO: clear other process data
|
// TODO: clear other process data
|
||||||
setPayoutTxHex(null);
|
setPayoutTxHex(null);
|
||||||
for (TradePeer peer : getPeers()) {
|
for (TradePeer peer : getAllTradeParties()) {
|
||||||
peer.setUnsignedPayoutTxHex(null);
|
peer.setUnsignedPayoutTxHex(null);
|
||||||
peer.setUpdatedMultisigHex(null);
|
peer.setUpdatedMultisigHex(null);
|
||||||
|
peer.setDisputeClosedMessage(null);
|
||||||
|
peer.setPaymentSentMessage(null);
|
||||||
|
peer.setPaymentReceivedMessage(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1597,11 +1604,16 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TradePeer> getPeers() {
|
private List<TradePeer> getPeers() {
|
||||||
|
List<TradePeer> peers = getAllTradeParties();
|
||||||
|
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of peers");
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TradePeer> getAllTradeParties() {
|
||||||
List<TradePeer> peers = new ArrayList<TradePeer>();
|
List<TradePeer> peers = new ArrayList<TradePeer>();
|
||||||
peers.add(getMaker());
|
peers.add(getMaker());
|
||||||
peers.add(getTaker());
|
peers.add(getTaker());
|
||||||
peers.add(getArbitrator());
|
peers.add(getArbitrator());
|
||||||
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of peers");
|
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1801,6 +1813,12 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return (isSeller() ? getBuyer() : getSeller()).getPaymentReceivedMessage() != null; // seller stores message to buyer and arbitrator, peers store message from seller
|
return (isSeller() ? getBuyer() : getSeller()).getPaymentReceivedMessage() != null; // seller stores message to buyer and arbitrator, peers store message from seller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasDisputeClosedMessage() {
|
||||||
|
|
||||||
|
// arbitrator stores message to buyer and seller, peers store message from arbitrator
|
||||||
|
return isArbitrator() ? getBuyer().getDisputeClosedMessage() != null || getSeller().getDisputeClosedMessage() != null : getArbitrator().getDisputeClosedMessage() != null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPaymentReceived() {
|
public boolean isPaymentReceived() {
|
||||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
||||||
}
|
}
|
||||||
|
@ -1883,7 +1901,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
public BigInteger getFrozenAmount() {
|
public BigInteger getFrozenAmount() {
|
||||||
BigInteger sum = BigInteger.ZERO;
|
BigInteger sum = BigInteger.ZERO;
|
||||||
for (String keyImage : getSelf().getReserveTxKeyImages()) {
|
for (String keyImage : getSelf().getReserveTxKeyImages()) {
|
||||||
List<MoneroOutputWallet> outputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false).setKeyImage(new MoneroKeyImage(keyImage))); // TODO: will this check tx pool? avoid
|
List<MoneroOutputWallet> outputs = xmrWalletService.getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false).setKeyImage(new MoneroKeyImage(keyImage)));
|
||||||
if (!outputs.isEmpty()) sum = sum.add(outputs.get(0).getAmount());
|
if (!outputs.isEmpty()) sum = sum.add(outputs.get(0).getAmount());
|
||||||
}
|
}
|
||||||
return sum;
|
return sum;
|
||||||
|
@ -2077,23 +2095,23 @@ public abstract class Trade implements Tradable, Model {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
if (isShutDownStarted || isPollInProgress()) return;
|
if (isShutDownStarted || isPollInProgress()) return;
|
||||||
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
|
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
txPollLooper = new TaskLooper(() -> pollWallet());
|
pollLooper = new TaskLooper(() -> pollWallet());
|
||||||
txPollLooper.start(walletRefreshPeriodMs);
|
pollLooper.start(walletRefreshPeriodMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopPolling() {
|
private void stopPolling() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
if (isPollInProgress()) {
|
if (isPollInProgress()) {
|
||||||
txPollLooper.stop();
|
pollLooper.stop();
|
||||||
txPollLooper = null;
|
pollLooper = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPollInProgress() {
|
private boolean isPollInProgress() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
return txPollLooper != null;
|
return pollLooper != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2117,8 +2135,14 @@ public abstract class Trade implements Tradable, Model {
|
||||||
// skip if payout unlocked
|
// skip if payout unlocked
|
||||||
if (isPayoutUnlocked()) return;
|
if (isPayoutUnlocked()) return;
|
||||||
|
|
||||||
// rescan spent outputs to detect payout tx after deposits unlocked
|
// rescan spent outputs to detect unconfirmed payout tx after payment received message
|
||||||
if (isDepositsUnlocked() && !isPayoutPublished()) wallet.rescanSpent();
|
if (!isPayoutPublished() && (hasPaymentReceivedMessage() || hasDisputeClosedMessage())) {
|
||||||
|
try {
|
||||||
|
wallet.rescanSpent();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error rescanning spent outputs to detect payout tx for {} {}, errorMessage={}", getClass().getSimpleName(), getShortId(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get txs from trade wallet
|
// get txs from trade wallet
|
||||||
boolean payoutExpected = isPaymentReceived() || getSeller().getPaymentReceivedMessage() != null || disputeState.ordinal() >= DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG.ordinal() || getArbitrator().getDisputeClosedMessage() != null;
|
boolean payoutExpected = isPaymentReceived() || getSeller().getPaymentReceivedMessage() != null || disputeState.ordinal() >= DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG.ordinal() || getArbitrator().getDisputeClosedMessage() != null;
|
||||||
|
@ -2129,7 +2153,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
// warn on double spend // TODO: other handling?
|
// warn on double spend // TODO: other handling?
|
||||||
for (MoneroTxWallet tx : txs) {
|
for (MoneroTxWallet tx : txs) {
|
||||||
if (Boolean.TRUE.equals(tx.isDoubleSpendSeen())) log.warn("Double spend seen for tx {} for {} {}", tx.getHash(), getClass().getSimpleName(), getId());
|
if (Boolean.TRUE.equals(tx.isDoubleSpendSeen())) log.warn("Double spend seen for tx {} for {} {}", tx.getHash(), getClass().getSimpleName(), getShortId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// check deposit txs
|
// check deposit txs
|
||||||
|
@ -2189,9 +2213,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
if (isConnectionRefused) forceRestartTradeWallet();
|
if (isConnectionRefused) forceRestartTradeWallet();
|
||||||
else {
|
else {
|
||||||
boolean isWalletConnected = isWalletConnectedToDaemon();
|
boolean isWalletConnected = isWalletConnectedToDaemon();
|
||||||
if (!isWalletConnected) xmrConnectionService.checkConnection(); // check connection if wallet is not connected
|
|
||||||
if (!isShutDownStarted && wallet != null && isWalletConnected) {
|
if (!isShutDownStarted && wallet != null && isWalletConnected) {
|
||||||
log.warn("Error polling trade wallet for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getConnectionService().getConnection());
|
log.warn("Error polling trade wallet for {} {}, errorMessage={}. Monerod={}", getClass().getSimpleName(), getShortId(), e.getMessage(), getXmrWalletService().getConnectionService().getConnection());
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1287,7 +1287,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeTradeOnError(Trade trade) {
|
private void removeTradeOnError(Trade trade) {
|
||||||
log.warn("TradeManager.removeTradeOnError() tradeId={}, state={}", trade.getId(), trade.getState());
|
log.warn("TradeManager.removeTradeOnError() trade={}, tradeId={}, state={}", trade.getClass().getSimpleName(), trade.getShortId(), trade.getState());
|
||||||
synchronized (tradableList) {
|
synchronized (tradableList) {
|
||||||
|
|
||||||
// unreserve taker key images
|
// unreserve taker key images
|
||||||
|
|
|
@ -134,14 +134,16 @@ public class BuyerProtocol extends DisputeProtocol {
|
||||||
BuyerSendPaymentSentMessageToArbitrator.class)
|
BuyerSendPaymentSentMessageToArbitrator.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
|
stopTimeout();
|
||||||
this.errorMessageHandler = null;
|
this.errorMessageHandler = null;
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
handleTaskRunnerSuccess(event);
|
handleTaskRunnerSuccess(event);
|
||||||
},
|
},
|
||||||
(errorMessage) -> {
|
(errorMessage) -> {
|
||||||
handleTaskRunnerFault(event, errorMessage);
|
handleTaskRunnerFault(event, errorMessage);
|
||||||
})))
|
}))
|
||||||
.run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_PAYMENT_SENT))
|
.withTimeout(TradeProtocol.TRADE_TIMEOUT_SECONDS))
|
||||||
|
.run(() -> trade.advanceState(Trade.State.BUYER_CONFIRMED_PAYMENT_SENT))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
errorMessageHandler.handleErrorMessage("Error confirming payment sent: " + e.getMessage());
|
errorMessageHandler.handleErrorMessage("Error confirming payment sent: " + e.getMessage());
|
||||||
|
|
|
@ -131,13 +131,15 @@ public class SellerProtocol extends DisputeProtocol {
|
||||||
SellerSendPaymentReceivedMessageToBuyer.class,
|
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||||
SellerSendPaymentReceivedMessageToArbitrator.class)
|
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||||
.using(new TradeTaskRunner(trade, () -> {
|
.using(new TradeTaskRunner(trade, () -> {
|
||||||
|
stopTimeout();
|
||||||
this.errorMessageHandler = null;
|
this.errorMessageHandler = null;
|
||||||
handleTaskRunnerSuccess(event);
|
handleTaskRunnerSuccess(event);
|
||||||
resultHandler.handleResult();
|
resultHandler.handleResult();
|
||||||
}, (errorMessage) -> {
|
}, (errorMessage) -> {
|
||||||
handleTaskRunnerFault(event, errorMessage);
|
handleTaskRunnerFault(event, errorMessage);
|
||||||
})))
|
}))
|
||||||
.run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT))
|
.withTimeout(TradeProtocol.TRADE_TIMEOUT_SECONDS))
|
||||||
|
.run(() -> trade.advanceState(Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
errorMessageHandler.handleErrorMessage("Error confirming payment received: " + e.getMessage());
|
errorMessageHandler.handleErrorMessage("Error confirming payment received: " + e.getMessage());
|
||||||
|
|
|
@ -61,13 +61,17 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
|
||||||
log.info(trade.getClass().getSimpleName() + " decrypting using seller payment account key");
|
log.info(trade.getClass().getSimpleName() + " decrypting using seller payment account key");
|
||||||
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
||||||
}
|
}
|
||||||
processModel.getTradeManager().requestPersistence(); // in case importing multisig hex fails
|
|
||||||
|
|
||||||
// import multisig hex
|
// persist
|
||||||
trade.importMultisigHex();
|
|
||||||
|
|
||||||
// persist and complete
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
|
||||||
|
// try to import multisig hex (retry later)
|
||||||
|
try {
|
||||||
|
trade.importMultisigHex();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
|
|
@ -95,7 +95,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||||
}
|
}
|
||||||
trade.requestPersistence();
|
trade.requestPersistence();
|
||||||
|
|
||||||
|
|
||||||
// process payout tx unless already unlocked
|
// process payout tx unless already unlocked
|
||||||
if (!trade.isPayoutUnlocked()) processPayoutTx(message);
|
if (!trade.isPayoutUnlocked()) processPayoutTx(message);
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,12 @@ public class ProcessPaymentSentMessage extends TradeTask {
|
||||||
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
||||||
trade.requestPersistence();
|
trade.requestPersistence();
|
||||||
|
|
||||||
// import multisig hex
|
// try to import multisig hex (retry later)
|
||||||
trade.importMultisigHex();
|
try {
|
||||||
|
trade.importMultisigHex();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Bisq.
|
|
||||||
*
|
|
||||||
* Bisq is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package haveno.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
|
||||||
import haveno.core.trade.Trade;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class SellerPublishDepositTx extends TradeTask {
|
|
||||||
public SellerPublishDepositTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
throw new RuntimeException("SellerPublishesDepositTx not implemented for xmr");
|
|
||||||
|
|
||||||
// final Transaction depositTx = processModel.getDepositTx();
|
|
||||||
// processModel.getTradeWalletService().broadcastTx(depositTx,
|
|
||||||
// new TxBroadcaster.Callback() {
|
|
||||||
// @Override
|
|
||||||
// public void onSuccess(Transaction transaction) {
|
|
||||||
// if (!completed) {
|
|
||||||
// // Now as we have published the deposit tx we set it in trade
|
|
||||||
// trade.applyDepositTx(depositTx);
|
|
||||||
//
|
|
||||||
// trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX);
|
|
||||||
//
|
|
||||||
// processModel.getBtcWalletService().swapAddressEntryToAvailable(processModel.getOffer().getId(),
|
|
||||||
// AddressEntry.Context.RESERVED_FOR_TRADE);
|
|
||||||
//
|
|
||||||
// processModel.getTradeManager().requestPersistence();
|
|
||||||
//
|
|
||||||
// complete();
|
|
||||||
// } else {
|
|
||||||
// log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onFailure(TxBroadcastException exception) {
|
|
||||||
// if (!completed) {
|
|
||||||
// failed(exception);
|
|
||||||
// } else {
|
|
||||||
// log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -140,7 +140,7 @@ public class Balances {
|
||||||
// calculate reserved offer balance
|
// calculate reserved offer balance
|
||||||
reservedOfferBalance = BigInteger.ZERO;
|
reservedOfferBalance = BigInteger.ZERO;
|
||||||
if (xmrWalletService.getWallet() != null) {
|
if (xmrWalletService.getWallet() != null) {
|
||||||
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
|
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
|
||||||
for (MoneroOutputWallet frozenOutput : frozenOutputs) reservedOfferBalance = reservedOfferBalance.add(frozenOutput.getAmount());
|
for (MoneroOutputWallet frozenOutput : frozenOutputs) reservedOfferBalance = reservedOfferBalance.add(frozenOutput.getAmount());
|
||||||
}
|
}
|
||||||
for (Trade trade : trades) {
|
for (Trade trade : trades) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ public class DownloadListener {
|
||||||
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
|
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
|
||||||
|
|
||||||
public void progress(double percentage, long blocksLeft, Date date) {
|
public void progress(double percentage, long blocksLeft, Date date) {
|
||||||
UserThread.await(() -> this.percentage.set(percentage / 100d));
|
UserThread.await(() -> this.percentage.set(percentage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doneDownload() {
|
public void doneDownload() {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,7 +33,6 @@ import haveno.core.trade.protocol.tasks.ProcessPaymentReceivedMessage;
|
||||||
import haveno.core.trade.protocol.tasks.ProcessPaymentSentMessage;
|
import haveno.core.trade.protocol.tasks.ProcessPaymentSentMessage;
|
||||||
import haveno.core.trade.protocol.tasks.RemoveOffer;
|
import haveno.core.trade.protocol.tasks.RemoveOffer;
|
||||||
import haveno.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
|
import haveno.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
|
||||||
import haveno.core.trade.protocol.tasks.SellerPublishDepositTx;
|
|
||||||
import haveno.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
import haveno.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
||||||
import haveno.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToBuyer;
|
import haveno.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToBuyer;
|
||||||
import haveno.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
import haveno.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||||
|
@ -87,7 +86,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
VerifyPeersAccountAgeWitness.class,
|
VerifyPeersAccountAgeWitness.class,
|
||||||
|
|
||||||
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||||
SellerPublishDepositTx.class,
|
|
||||||
SellerPublishTradeStatistics.class,
|
SellerPublishTradeStatistics.class,
|
||||||
|
|
||||||
ProcessPaymentSentMessage.class,
|
ProcessPaymentSentMessage.class,
|
||||||
|
@ -140,7 +138,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
RemoveOffer.class,
|
RemoveOffer.class,
|
||||||
|
|
||||||
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||||
SellerPublishDepositTx.class,
|
|
||||||
SellerPublishTradeStatistics.class,
|
SellerPublishTradeStatistics.class,
|
||||||
|
|
||||||
ProcessPaymentSentMessage.class,
|
ProcessPaymentSentMessage.class,
|
||||||
|
|
|
@ -36,6 +36,8 @@ package haveno.desktop.main.funds.deposit;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
import haveno.common.app.DevEnv;
|
import haveno.common.app.DevEnv;
|
||||||
import haveno.common.util.Tuple3;
|
import haveno.common.util.Tuple3;
|
||||||
|
@ -78,6 +80,7 @@ import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TableCell;
|
import javafx.scene.control.TableCell;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
|
@ -111,6 +114,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||||
private Button generateNewAddressButton;
|
private Button generateNewAddressButton;
|
||||||
private TitledGroupBg titledGroupBg;
|
private TitledGroupBg titledGroupBg;
|
||||||
private InputTextField amountTextField;
|
private InputTextField amountTextField;
|
||||||
|
private static final String THREAD_ID = DepositView.class.getName();
|
||||||
|
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
|
@ -146,142 +150,155 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
||||||
confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations")));
|
confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations")));
|
||||||
usageColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.usage")));
|
usageColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.usage")));
|
||||||
|
|
||||||
// trigger creation of at least 1 address
|
// set loading placeholder
|
||||||
try {
|
Label placeholderLabel = new Label("Loading...");
|
||||||
xmrWalletService.getFreshAddressEntry();
|
tableView.setPlaceholder(placeholderLabel);
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to get wallet txs to initialize DepositView");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
ThreadUtils.execute(() -> {
|
||||||
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.deposit.noAddresses")));
|
|
||||||
tableViewSelectionListener = (observableValue, oldValue, newValue) -> {
|
// trigger creation of at least 1 address
|
||||||
if (newValue != null) {
|
try {
|
||||||
fillForm(newValue.getAddressString());
|
xmrWalletService.getFreshAddressEntry();
|
||||||
GUIUtil.requestFocus(amountTextField);
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to create fresh address entry to initialize DepositView");
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
setAddressColumnCellFactory();
|
UserThread.execute(() -> {
|
||||||
setBalanceColumnCellFactory();
|
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
setUsageColumnCellFactory();
|
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.deposit.noAddresses")));
|
||||||
setConfidenceColumnCellFactory();
|
tableViewSelectionListener = (observableValue, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString));
|
fillForm(newValue.getAddressString());
|
||||||
balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsBI));
|
GUIUtil.requestFocus(amountTextField);
|
||||||
confirmationsColumn.setComparator(Comparator.comparingLong(o -> o.getNumConfirmationsSinceFirstUsed()));
|
}
|
||||||
usageColumn.setComparator(Comparator.comparing(DepositListItem::getUsage));
|
};
|
||||||
tableView.getSortOrder().add(usageColumn);
|
|
||||||
tableView.setItems(sortedList);
|
setAddressColumnCellFactory();
|
||||||
|
setBalanceColumnCellFactory();
|
||||||
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 4, Res.get("funds.deposit.fundWallet"));
|
setUsageColumnCellFactory();
|
||||||
titledGroupBg.getStyleClass().add("last");
|
setConfidenceColumnCellFactory();
|
||||||
|
|
||||||
qrCodeImageView = new ImageView();
|
addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString));
|
||||||
qrCodeImageView.setFitHeight(150);
|
balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsBI));
|
||||||
qrCodeImageView.setFitWidth(150);
|
confirmationsColumn.setComparator(Comparator.comparingLong(o -> o.getNumConfirmationsSinceFirstUsed()));
|
||||||
qrCodeImageView.getStyleClass().add("qr-code");
|
usageColumn.setComparator(Comparator.comparing(DepositListItem::getUsage));
|
||||||
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
tableView.getSortOrder().add(usageColumn);
|
||||||
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
tableView.setItems(sortedList);
|
||||||
() -> new QRCodeWindow(getPaymentUri()).show(),
|
|
||||||
200, TimeUnit.MILLISECONDS));
|
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 4, Res.get("funds.deposit.fundWallet"));
|
||||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
titledGroupBg.getStyleClass().add("last");
|
||||||
GridPane.setRowSpan(qrCodeImageView, 4);
|
|
||||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
qrCodeImageView = new ImageView();
|
||||||
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 10));
|
qrCodeImageView.setFitHeight(150);
|
||||||
gridPane.getChildren().add(qrCodeImageView);
|
qrCodeImageView.setFitWidth(150);
|
||||||
|
qrCodeImageView.getStyleClass().add("qr-code");
|
||||||
addressTextField = addAddressTextField(gridPane, ++gridRow, Res.get("shared.address"), Layout.FIRST_ROW_DISTANCE);
|
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
||||||
addressTextField.setPaymentLabel(paymentLabelString);
|
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
||||||
|
() -> new QRCodeWindow(getPaymentUri()).show(),
|
||||||
|
200, TimeUnit.MILLISECONDS));
|
||||||
amountTextField = addInputTextField(gridPane, ++gridRow, Res.get("funds.deposit.amount"));
|
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||||
amountTextField.setMaxWidth(380);
|
GridPane.setRowSpan(qrCodeImageView, 4);
|
||||||
if (DevEnv.isDevMode())
|
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||||
amountTextField.setText("10");
|
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 10));
|
||||||
|
gridPane.getChildren().add(qrCodeImageView);
|
||||||
titledGroupBg.setVisible(false);
|
|
||||||
titledGroupBg.setManaged(false);
|
addressTextField = addAddressTextField(gridPane, ++gridRow, Res.get("shared.address"), Layout.FIRST_ROW_DISTANCE);
|
||||||
qrCodeImageView.setVisible(false);
|
addressTextField.setPaymentLabel(paymentLabelString);
|
||||||
qrCodeImageView.setManaged(false);
|
amountTextField = addInputTextField(gridPane, ++gridRow, Res.get("funds.deposit.amount"));
|
||||||
addressTextField.setVisible(false);
|
amountTextField.setMaxWidth(380);
|
||||||
addressTextField.setManaged(false);
|
if (DevEnv.isDevMode())
|
||||||
amountTextField.setManaged(false);
|
amountTextField.setText("10");
|
||||||
|
|
||||||
Tuple3<Button, CheckBox, HBox> buttonCheckBoxHBox = addButtonCheckBoxWithBox(gridPane, ++gridRow,
|
titledGroupBg.setVisible(false);
|
||||||
Res.get("funds.deposit.generateAddress"),
|
titledGroupBg.setManaged(false);
|
||||||
null,
|
qrCodeImageView.setVisible(false);
|
||||||
15);
|
qrCodeImageView.setManaged(false);
|
||||||
buttonCheckBoxHBox.third.setSpacing(25);
|
addressTextField.setVisible(false);
|
||||||
generateNewAddressButton = buttonCheckBoxHBox.first;
|
addressTextField.setManaged(false);
|
||||||
|
amountTextField.setManaged(false);
|
||||||
generateNewAddressButton.setOnAction(event -> {
|
|
||||||
boolean hasUnusedAddress = !xmrWalletService.getUnusedAddressEntries().isEmpty();
|
Tuple3<Button, CheckBox, HBox> buttonCheckBoxHBox = addButtonCheckBoxWithBox(gridPane, ++gridRow,
|
||||||
if (hasUnusedAddress) {
|
Res.get("funds.deposit.generateAddress"),
|
||||||
new Popup().warning(Res.get("funds.deposit.selectUnused")).show();
|
null,
|
||||||
} else {
|
15);
|
||||||
XmrAddressEntry newSavingsAddressEntry = xmrWalletService.getNewAddressEntry();
|
buttonCheckBoxHBox.third.setSpacing(25);
|
||||||
updateList();
|
generateNewAddressButton = buttonCheckBoxHBox.first;
|
||||||
UserThread.execute(() -> {
|
|
||||||
observableList.stream()
|
generateNewAddressButton.setOnAction(event -> {
|
||||||
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
boolean hasUnusedAddress = !xmrWalletService.getUnusedAddressEntries().isEmpty();
|
||||||
.findAny()
|
if (hasUnusedAddress) {
|
||||||
.ifPresent(depositListItem -> tableView.getSelectionModel().select(depositListItem));
|
new Popup().warning(Res.get("funds.deposit.selectUnused")).show();
|
||||||
|
} else {
|
||||||
|
XmrAddressEntry newSavingsAddressEntry = xmrWalletService.getNewAddressEntry();
|
||||||
|
updateList();
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
observableList.stream()
|
||||||
|
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
|
||||||
|
.findAny()
|
||||||
|
.ifPresent(depositListItem -> tableView.getSelectionModel().select(depositListItem));
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
balanceListener = new XmrBalanceListener() {
|
||||||
|
@Override
|
||||||
balanceListener = new XmrBalanceListener() {
|
public void onBalanceChanged(BigInteger balance) {
|
||||||
@Override
|
updateList();
|
||||||
public void onBalanceChanged(BigInteger balance) {
|
}
|
||||||
updateList();
|
};
|
||||||
}
|
|
||||||
};
|
walletListener = new MoneroWalletListener() {
|
||||||
|
@Override
|
||||||
walletListener = new MoneroWalletListener() {
|
public void onNewBlock(long height) {
|
||||||
@Override
|
updateList();
|
||||||
public void onNewBlock(long height) {
|
}
|
||||||
updateList();
|
};
|
||||||
}
|
|
||||||
};
|
GUIUtil.focusWhenAddedToScene(amountTextField);
|
||||||
|
});
|
||||||
GUIUtil.focusWhenAddedToScene(amountTextField);
|
}, THREAD_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void activate() {
|
protected void activate() {
|
||||||
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
|
ThreadUtils.execute(() -> {
|
||||||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
UserThread.execute(() -> {
|
||||||
|
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
|
||||||
// try to update deposits list
|
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||||
try {
|
|
||||||
updateList();
|
// try to update deposits list
|
||||||
} catch (Exception e) {
|
try {
|
||||||
log.warn("Could not update deposits list");
|
updateList();
|
||||||
e.printStackTrace();
|
} catch (Exception e) {
|
||||||
}
|
log.warn("Could not update deposits list");
|
||||||
|
e.printStackTrace();
|
||||||
xmrWalletService.addBalanceListener(balanceListener);
|
}
|
||||||
xmrWalletService.addWalletListener(walletListener);
|
|
||||||
|
xmrWalletService.addBalanceListener(balanceListener);
|
||||||
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
|
xmrWalletService.addWalletListener(walletListener);
|
||||||
addressTextField.setAmount(HavenoUtils.parseXmr(t));
|
|
||||||
updateQRCode();
|
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
|
||||||
});
|
addressTextField.setAmount(HavenoUtils.parseXmr(t));
|
||||||
|
updateQRCode();
|
||||||
if (tableView.getSelectionModel().getSelectedItem() == null && !sortedList.isEmpty())
|
});
|
||||||
tableView.getSelectionModel().select(0);
|
|
||||||
|
if (tableView.getSelectionModel().getSelectedItem() == null && !sortedList.isEmpty())
|
||||||
|
tableView.getSelectionModel().select(0);
|
||||||
|
});
|
||||||
|
}, THREAD_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void deactivate() {
|
protected void deactivate() {
|
||||||
tableView.getSelectionModel().selectedItemProperty().removeListener(tableViewSelectionListener);
|
ThreadUtils.execute(() -> {
|
||||||
sortedList.comparatorProperty().unbind();
|
tableView.getSelectionModel().selectedItemProperty().removeListener(tableViewSelectionListener);
|
||||||
observableList.forEach(DepositListItem::cleanup);
|
sortedList.comparatorProperty().unbind();
|
||||||
xmrWalletService.removeBalanceListener(balanceListener);
|
observableList.forEach(DepositListItem::cleanup);
|
||||||
xmrWalletService.removeWalletListener(walletListener);
|
xmrWalletService.removeBalanceListener(balanceListener);
|
||||||
amountTextFieldSubscription.unsubscribe();
|
xmrWalletService.removeWalletListener(walletListener);
|
||||||
|
amountTextFieldSubscription.unsubscribe();
|
||||||
|
}, THREAD_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,6 @@ public class BuyerStep2View extends TradeStepView {
|
||||||
statusLabel.setText(Res.get("shared.preparingConfirmation"));
|
statusLabel.setText(Res.get("shared.preparingConfirmation"));
|
||||||
break;
|
break;
|
||||||
case BUYER_SENT_PAYMENT_SENT_MSG:
|
case BUYER_SENT_PAYMENT_SENT_MSG:
|
||||||
case BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG:
|
|
||||||
busyAnimation.play();
|
busyAnimation.play();
|
||||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||||
timeoutTimer = UserThread.runAfter(() -> {
|
timeoutTimer = UserThread.runAfter(() -> {
|
||||||
|
@ -168,6 +167,7 @@ public class BuyerStep2View extends TradeStepView {
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||||
break;
|
break;
|
||||||
|
case BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG:
|
||||||
case SELLER_RECEIVED_PAYMENT_SENT_MSG:
|
case SELLER_RECEIVED_PAYMENT_SENT_MSG:
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||||
|
@ -442,7 +442,8 @@ public class BuyerStep2View extends TradeStepView {
|
||||||
|
|
||||||
private boolean confirmPaymentSentPermitted() {
|
private boolean confirmPaymentSentPermitted() {
|
||||||
if (!trade.confirmPermitted()) return false;
|
if (!trade.confirmPermitted()) return false;
|
||||||
return trade.isDepositsUnlocked() && trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal();
|
if (trade.getState() == Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG) return false;
|
||||||
|
return trade.isDepositsUnlocked() && trade.getState().ordinal() < Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -123,20 +123,19 @@ public class SellerStep3View extends TradeStepView {
|
||||||
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
||||||
busyAnimation.play();
|
busyAnimation.play();
|
||||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||||
|
|
||||||
timeoutTimer = UserThread.runAfter(() -> {
|
timeoutTimer = UserThread.runAfter(() -> {
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
|
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
|
||||||
}, 30);
|
}, 30);
|
||||||
break;
|
break;
|
||||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
|
||||||
busyAnimation.stop();
|
|
||||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
|
||||||
break;
|
|
||||||
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||||
break;
|
break;
|
||||||
|
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||||
|
busyAnimation.stop();
|
||||||
|
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||||
|
break;
|
||||||
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
||||||
// We get a popup and the trade closed, so we dont need to show anything here
|
// We get a popup and the trade closed, so we dont need to show anything here
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
|
@ -290,7 +289,8 @@ public class SellerStep3View extends TradeStepView {
|
||||||
|
|
||||||
private boolean confirmPaymentReceivedPermitted() {
|
private boolean confirmPaymentReceivedPermitted() {
|
||||||
if (!trade.confirmPermitted()) return false;
|
if (!trade.confirmPermitted()) return false;
|
||||||
return trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal() && trade.getState().ordinal() < Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal();
|
if (trade.getState() == Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG) return false;
|
||||||
|
return trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal() && trade.getState().ordinal() <= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -116,6 +116,9 @@ First rebuild Haveno: `make skip-tests`.
|
||||||
|
|
||||||
Run `make arbitrator-desktop` to run an arbitrator on Monero's mainnet or `make arbitrator-desktop-stagenet` to run an arbitrator on Monero's stagenet.
|
Run `make arbitrator-desktop` to run an arbitrator on Monero's mainnet or `make arbitrator-desktop-stagenet` to run an arbitrator on Monero's stagenet.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Unregister the arbitrator before retiring the app, or clients will continue to try to connect for some time.
|
||||||
|
|
||||||
The Haveno GUI will open. If on mainnet, ignore the error about not receiving a filter object which is not added yet. Click on the `Account` tab and then press `ctrl + r`. A prompt will open asking to enter the key to register the arbitrator. Use a key generated in the previous steps and complete the registration. The arbitrator is now registered and ready to accept requests of dispute resolution.
|
The Haveno GUI will open. If on mainnet, ignore the error about not receiving a filter object which is not added yet. Click on the `Account` tab and then press `ctrl + r`. A prompt will open asking to enter the key to register the arbitrator. Use a key generated in the previous steps and complete the registration. The arbitrator is now registered and ready to accept requests of dispute resolution.
|
||||||
|
|
||||||
Arbitrators should remain online as much as possible in order to balance trades and avoid clients spending time trying to contact offline arbitrators. A VPS or dedicated machine running 24/7 is highly recommended.
|
Arbitrators should remain online as much as possible in order to balance trades and avoid clients spending time trying to contact offline arbitrators. A VPS or dedicated machine running 24/7 is highly recommended.
|
||||||
|
|
Loading…
Reference in a new issue