mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 12:09:22 +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 \
|
||||
--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 \
|
||||
|
||||
funding-wallet-local:
|
||||
|
@ -133,6 +123,23 @@ funding-wallet-local:
|
|||
--rpc-access-control-origins http://localhost:8080 \
|
||||
--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
|
||||
APP_EXT :=
|
||||
ifeq ($(OS),Windows_NT)
|
||||
|
|
|
@ -607,7 +607,7 @@ public final class XmrConnectionService {
|
|||
long targetHeight = lastInfo.getTargetHeight();
|
||||
long blocksLeft = targetHeight - 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -116,18 +116,16 @@ public class OfferBookService {
|
|||
@Override
|
||||
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
||||
synchronized (offerBookChangedListeners) {
|
||||
offerBookChangedListeners.forEach(listener -> {
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||
maybeInitializeKeyImagePoller();
|
||||
keyImagePoller.addKeyImages(offerPayload.getReserveTxKeyImages());
|
||||
Offer offer = new Offer(offerPayload);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
setReservedFundsSpent(offer);
|
||||
listener.onAdded(offer);
|
||||
}
|
||||
});
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||
maybeInitializeKeyImagePoller();
|
||||
keyImagePoller.addKeyImages(offerPayload.getReserveTxKeyImages());
|
||||
Offer offer = new Offer(offerPayload);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
setReservedFundsSpent(offer);
|
||||
synchronized (offerBookChangedListeners) {
|
||||
offerBookChangedListeners.forEach(listener -> listener.onAdded(offer));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -135,18 +133,16 @@ public class OfferBookService {
|
|||
@Override
|
||||
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
|
||||
protectedStorageEntries.forEach(protectedStorageEntry -> {
|
||||
synchronized (offerBookChangedListeners) {
|
||||
offerBookChangedListeners.forEach(listener -> {
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||
maybeInitializeKeyImagePoller();
|
||||
keyImagePoller.removeKeyImages(offerPayload.getReserveTxKeyImages());
|
||||
Offer offer = new Offer(offerPayload);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
setReservedFundsSpent(offer);
|
||||
listener.onRemoved(offer);
|
||||
}
|
||||
});
|
||||
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
|
||||
maybeInitializeKeyImagePoller();
|
||||
keyImagePoller.removeKeyImages(offerPayload.getReserveTxKeyImages());
|
||||
Offer offer = new Offer(offerPayload);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
setReservedFundsSpent(offer);
|
||||
synchronized (offerBookChangedListeners) {
|
||||
offerBookChangedListeners.forEach(listener -> listener.onRemoved(offer));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -34,8 +34,6 @@
|
|||
|
||||
package haveno.core.offer;
|
||||
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.proto.ProtoUtil;
|
||||
import haveno.core.trade.Tradable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
|
@ -55,9 +53,6 @@ import java.util.Optional;
|
|||
@EqualsAndHashCode
|
||||
@Slf4j
|
||||
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 {
|
||||
SCHEDULED,
|
||||
|
@ -227,13 +222,6 @@ public final class OpenOffer implements Tradable {
|
|||
public void setState(State state) {
|
||||
this.state = 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() {
|
||||
|
@ -252,26 +240,6 @@ public final class OpenOffer implements Tradable {
|
|||
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
|
||||
public String toString() {
|
||||
return "OpenOffer{" +
|
||||
|
|
|
@ -971,8 +971,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
// return if awaiting scheduled tx
|
||||
if (openOffer.getScheduledTxHashes() != null) return null;
|
||||
|
||||
// cache all transactions including from pool
|
||||
List<MoneroTxWallet> allTxs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
||||
// get all transactions including from pool
|
||||
List<MoneroTxWallet> allTxs = xmrWalletService.getTransactions(false);
|
||||
|
||||
if (preferredSubaddressIndex != null) {
|
||||
|
||||
|
|
|
@ -766,6 +766,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
trade.getSelf().getUpdatedMultisigHex(),
|
||||
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
|
||||
receiverPeer.setDisputeClosedMessage(disputeClosedMessage);
|
||||
|
||||
// send dispute closed message
|
||||
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 tradePhaseSubscription;
|
||||
transient private Subscription payoutStateSubscription;
|
||||
transient private TaskLooper txPollLooper;
|
||||
transient private TaskLooper pollLooper;
|
||||
transient private Long walletRefreshPeriodMs;
|
||||
transient private Long syncNormalStartTimeMs;
|
||||
|
||||
|
@ -890,6 +890,10 @@ public abstract class Trade implements Tradable, Model {
|
|||
|
||||
public void saveWallet() {
|
||||
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());
|
||||
xmrWalletService.saveWallet(wallet);
|
||||
maybeBackupWallet();
|
||||
|
@ -1195,7 +1199,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
return trader.getDepositTx();
|
||||
} catch (MoneroError e) {
|
||||
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
|
||||
setPayoutTxHex(null);
|
||||
for (TradePeer peer : getPeers()) {
|
||||
for (TradePeer peer : getAllTradeParties()) {
|
||||
peer.setUnsignedPayoutTxHex(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() {
|
||||
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>();
|
||||
peers.add(getMaker());
|
||||
peers.add(getTaker());
|
||||
peers.add(getArbitrator());
|
||||
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of 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
|
||||
}
|
||||
|
||||
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() {
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
||||
}
|
||||
|
@ -1883,7 +1901,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
public BigInteger getFrozenAmount() {
|
||||
BigInteger sum = BigInteger.ZERO;
|
||||
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());
|
||||
}
|
||||
return sum;
|
||||
|
@ -2077,23 +2095,23 @@ public abstract class Trade implements Tradable, Model {
|
|||
synchronized (walletLock) {
|
||||
if (isShutDownStarted || isPollInProgress()) return;
|
||||
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
|
||||
txPollLooper = new TaskLooper(() -> pollWallet());
|
||||
txPollLooper.start(walletRefreshPeriodMs);
|
||||
pollLooper = new TaskLooper(() -> pollWallet());
|
||||
pollLooper.start(walletRefreshPeriodMs);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPolling() {
|
||||
synchronized (walletLock) {
|
||||
if (isPollInProgress()) {
|
||||
txPollLooper.stop();
|
||||
txPollLooper = null;
|
||||
pollLooper.stop();
|
||||
pollLooper = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPollInProgress() {
|
||||
synchronized (walletLock) {
|
||||
return txPollLooper != null;
|
||||
return pollLooper != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2117,8 +2135,14 @@ public abstract class Trade implements Tradable, Model {
|
|||
// skip if payout unlocked
|
||||
if (isPayoutUnlocked()) return;
|
||||
|
||||
// rescan spent outputs to detect payout tx after deposits unlocked
|
||||
if (isDepositsUnlocked() && !isPayoutPublished()) wallet.rescanSpent();
|
||||
// rescan spent outputs to detect unconfirmed payout tx after payment received message
|
||||
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
|
||||
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?
|
||||
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
|
||||
|
@ -2189,9 +2213,8 @@ public abstract class Trade implements Tradable, Model {
|
|||
if (isConnectionRefused) forceRestartTradeWallet();
|
||||
else {
|
||||
boolean isWalletConnected = isWalletConnectedToDaemon();
|
||||
if (!isWalletConnected) xmrConnectionService.checkConnection(); // check connection if wallet is not connected
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1287,7 +1287,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
|
||||
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) {
|
||||
|
||||
// unreserve taker key images
|
||||
|
|
|
@ -134,14 +134,16 @@ public class BuyerProtocol extends DisputeProtocol {
|
|||
BuyerSendPaymentSentMessageToArbitrator.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
this.errorMessageHandler = null;
|
||||
resultHandler.handleResult();
|
||||
handleTaskRunnerSuccess(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);
|
||||
} catch (Exception e) {
|
||||
errorMessageHandler.handleErrorMessage("Error confirming payment sent: " + e.getMessage());
|
||||
|
|
|
@ -131,13 +131,15 @@ public class SellerProtocol extends DisputeProtocol {
|
|||
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||
.using(new TradeTaskRunner(trade, () -> {
|
||||
stopTimeout();
|
||||
this.errorMessageHandler = null;
|
||||
handleTaskRunnerSuccess(event);
|
||||
resultHandler.handleResult();
|
||||
}, (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);
|
||||
} catch (Exception e) {
|
||||
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");
|
||||
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
||||
}
|
||||
processModel.getTradeManager().requestPersistence(); // in case importing multisig hex fails
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
|
||||
// persist and complete
|
||||
// persist
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// try to import multisig hex (retry later)
|
||||
try {
|
||||
trade.importMultisigHex();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -95,7 +95,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
|||
}
|
||||
trade.requestPersistence();
|
||||
|
||||
|
||||
// process payout tx unless already unlocked
|
||||
if (!trade.isPayoutUnlocked()) processPayoutTx(message);
|
||||
|
||||
|
|
|
@ -61,8 +61,12 @@ public class ProcessPaymentSentMessage extends TradeTask {
|
|||
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
||||
trade.requestPersistence();
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
// try to import multisig hex (retry later)
|
||||
try {
|
||||
trade.importMultisigHex();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// update state
|
||||
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
|
||||
reservedOfferBalance = BigInteger.ZERO;
|
||||
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 (Trade trade : trades) {
|
||||
|
|
|
@ -11,7 +11,7 @@ public class DownloadListener {
|
|||
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
|
||||
|
||||
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() {
|
||||
|
|
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.RemoveOffer;
|
||||
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.SellerSendPaymentReceivedMessageToBuyer;
|
||||
import haveno.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
|
@ -87,7 +86,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
VerifyPeersAccountAgeWitness.class,
|
||||
|
||||
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishDepositTx.class,
|
||||
SellerPublishTradeStatistics.class,
|
||||
|
||||
ProcessPaymentSentMessage.class,
|
||||
|
@ -140,7 +138,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
RemoveOffer.class,
|
||||
|
||||
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishDepositTx.class,
|
||||
SellerPublishTradeStatistics.class,
|
||||
|
||||
ProcessPaymentSentMessage.class,
|
||||
|
|
|
@ -36,6 +36,8 @@ package haveno.desktop.main.funds.deposit;
|
|||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import haveno.common.ThreadUtils;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.app.DevEnv;
|
||||
import haveno.common.util.Tuple3;
|
||||
|
@ -78,6 +80,7 @@ import javafx.fxml.FXML;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
|
@ -111,6 +114,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||
private Button generateNewAddressButton;
|
||||
private TitledGroupBg titledGroupBg;
|
||||
private InputTextField amountTextField;
|
||||
private static final String THREAD_ID = DepositView.class.getName();
|
||||
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final Preferences preferences;
|
||||
|
@ -146,142 +150,155 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||
confirmationsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations")));
|
||||
usageColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.usage")));
|
||||
|
||||
// trigger creation of at least 1 address
|
||||
try {
|
||||
xmrWalletService.getFreshAddressEntry();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get wallet txs to initialize DepositView");
|
||||
e.printStackTrace();
|
||||
}
|
||||
// set loading placeholder
|
||||
Label placeholderLabel = new Label("Loading...");
|
||||
tableView.setPlaceholder(placeholderLabel);
|
||||
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.deposit.noAddresses")));
|
||||
tableViewSelectionListener = (observableValue, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
fillForm(newValue.getAddressString());
|
||||
GUIUtil.requestFocus(amountTextField);
|
||||
ThreadUtils.execute(() -> {
|
||||
|
||||
// trigger creation of at least 1 address
|
||||
try {
|
||||
xmrWalletService.getFreshAddressEntry();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to create fresh address entry to initialize DepositView");
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
setAddressColumnCellFactory();
|
||||
setBalanceColumnCellFactory();
|
||||
setUsageColumnCellFactory();
|
||||
setConfidenceColumnCellFactory();
|
||||
|
||||
addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString));
|
||||
balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsBI));
|
||||
confirmationsColumn.setComparator(Comparator.comparingLong(o -> o.getNumConfirmationsSinceFirstUsed()));
|
||||
usageColumn.setComparator(Comparator.comparing(DepositListItem::getUsage));
|
||||
tableView.getSortOrder().add(usageColumn);
|
||||
tableView.setItems(sortedList);
|
||||
|
||||
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 4, Res.get("funds.deposit.fundWallet"));
|
||||
titledGroupBg.getStyleClass().add("last");
|
||||
|
||||
qrCodeImageView = new ImageView();
|
||||
qrCodeImageView.setFitHeight(150);
|
||||
qrCodeImageView.setFitWidth(150);
|
||||
qrCodeImageView.getStyleClass().add("qr-code");
|
||||
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
||||
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
||||
() -> new QRCodeWindow(getPaymentUri()).show(),
|
||||
200, TimeUnit.MILLISECONDS));
|
||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||
GridPane.setRowSpan(qrCodeImageView, 4);
|
||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 10));
|
||||
gridPane.getChildren().add(qrCodeImageView);
|
||||
|
||||
addressTextField = addAddressTextField(gridPane, ++gridRow, Res.get("shared.address"), Layout.FIRST_ROW_DISTANCE);
|
||||
addressTextField.setPaymentLabel(paymentLabelString);
|
||||
|
||||
|
||||
amountTextField = addInputTextField(gridPane, ++gridRow, Res.get("funds.deposit.amount"));
|
||||
amountTextField.setMaxWidth(380);
|
||||
if (DevEnv.isDevMode())
|
||||
amountTextField.setText("10");
|
||||
|
||||
titledGroupBg.setVisible(false);
|
||||
titledGroupBg.setManaged(false);
|
||||
qrCodeImageView.setVisible(false);
|
||||
qrCodeImageView.setManaged(false);
|
||||
addressTextField.setVisible(false);
|
||||
addressTextField.setManaged(false);
|
||||
amountTextField.setManaged(false);
|
||||
|
||||
Tuple3<Button, CheckBox, HBox> buttonCheckBoxHBox = addButtonCheckBoxWithBox(gridPane, ++gridRow,
|
||||
Res.get("funds.deposit.generateAddress"),
|
||||
null,
|
||||
15);
|
||||
buttonCheckBoxHBox.third.setSpacing(25);
|
||||
generateNewAddressButton = buttonCheckBoxHBox.first;
|
||||
|
||||
generateNewAddressButton.setOnAction(event -> {
|
||||
boolean hasUnusedAddress = !xmrWalletService.getUnusedAddressEntries().isEmpty();
|
||||
if (hasUnusedAddress) {
|
||||
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));
|
||||
UserThread.execute(() -> {
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.deposit.noAddresses")));
|
||||
tableViewSelectionListener = (observableValue, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
fillForm(newValue.getAddressString());
|
||||
GUIUtil.requestFocus(amountTextField);
|
||||
}
|
||||
};
|
||||
|
||||
setAddressColumnCellFactory();
|
||||
setBalanceColumnCellFactory();
|
||||
setUsageColumnCellFactory();
|
||||
setConfidenceColumnCellFactory();
|
||||
|
||||
addressColumn.setComparator(Comparator.comparing(DepositListItem::getAddressString));
|
||||
balanceColumn.setComparator(Comparator.comparing(DepositListItem::getBalanceAsBI));
|
||||
confirmationsColumn.setComparator(Comparator.comparingLong(o -> o.getNumConfirmationsSinceFirstUsed()));
|
||||
usageColumn.setComparator(Comparator.comparing(DepositListItem::getUsage));
|
||||
tableView.getSortOrder().add(usageColumn);
|
||||
tableView.setItems(sortedList);
|
||||
|
||||
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 4, Res.get("funds.deposit.fundWallet"));
|
||||
titledGroupBg.getStyleClass().add("last");
|
||||
|
||||
qrCodeImageView = new ImageView();
|
||||
qrCodeImageView.setFitHeight(150);
|
||||
qrCodeImageView.setFitWidth(150);
|
||||
qrCodeImageView.getStyleClass().add("qr-code");
|
||||
Tooltip.install(qrCodeImageView, new Tooltip(Res.get("shared.openLargeQRWindow")));
|
||||
qrCodeImageView.setOnMouseClicked(e -> UserThread.runAfter(
|
||||
() -> new QRCodeWindow(getPaymentUri()).show(),
|
||||
200, TimeUnit.MILLISECONDS));
|
||||
GridPane.setRowIndex(qrCodeImageView, gridRow);
|
||||
GridPane.setRowSpan(qrCodeImageView, 4);
|
||||
GridPane.setColumnIndex(qrCodeImageView, 1);
|
||||
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 10));
|
||||
gridPane.getChildren().add(qrCodeImageView);
|
||||
|
||||
addressTextField = addAddressTextField(gridPane, ++gridRow, Res.get("shared.address"), Layout.FIRST_ROW_DISTANCE);
|
||||
addressTextField.setPaymentLabel(paymentLabelString);
|
||||
amountTextField = addInputTextField(gridPane, ++gridRow, Res.get("funds.deposit.amount"));
|
||||
amountTextField.setMaxWidth(380);
|
||||
if (DevEnv.isDevMode())
|
||||
amountTextField.setText("10");
|
||||
|
||||
titledGroupBg.setVisible(false);
|
||||
titledGroupBg.setManaged(false);
|
||||
qrCodeImageView.setVisible(false);
|
||||
qrCodeImageView.setManaged(false);
|
||||
addressTextField.setVisible(false);
|
||||
addressTextField.setManaged(false);
|
||||
amountTextField.setManaged(false);
|
||||
|
||||
Tuple3<Button, CheckBox, HBox> buttonCheckBoxHBox = addButtonCheckBoxWithBox(gridPane, ++gridRow,
|
||||
Res.get("funds.deposit.generateAddress"),
|
||||
null,
|
||||
15);
|
||||
buttonCheckBoxHBox.third.setSpacing(25);
|
||||
generateNewAddressButton = buttonCheckBoxHBox.first;
|
||||
|
||||
generateNewAddressButton.setOnAction(event -> {
|
||||
boolean hasUnusedAddress = !xmrWalletService.getUnusedAddressEntries().isEmpty();
|
||||
if (hasUnusedAddress) {
|
||||
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
|
||||
public void onBalanceChanged(BigInteger balance) {
|
||||
updateList();
|
||||
}
|
||||
};
|
||||
|
||||
walletListener = new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onNewBlock(long height) {
|
||||
updateList();
|
||||
}
|
||||
};
|
||||
|
||||
GUIUtil.focusWhenAddedToScene(amountTextField);
|
||||
|
||||
balanceListener = new XmrBalanceListener() {
|
||||
@Override
|
||||
public void onBalanceChanged(BigInteger balance) {
|
||||
updateList();
|
||||
}
|
||||
};
|
||||
|
||||
walletListener = new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onNewBlock(long height) {
|
||||
updateList();
|
||||
}
|
||||
};
|
||||
|
||||
GUIUtil.focusWhenAddedToScene(amountTextField);
|
||||
});
|
||||
}, THREAD_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
|
||||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||
|
||||
// try to update deposits list
|
||||
try {
|
||||
updateList();
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not update deposits list");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
xmrWalletService.addBalanceListener(balanceListener);
|
||||
xmrWalletService.addWalletListener(walletListener);
|
||||
|
||||
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
|
||||
addressTextField.setAmount(HavenoUtils.parseXmr(t));
|
||||
updateQRCode();
|
||||
});
|
||||
|
||||
if (tableView.getSelectionModel().getSelectedItem() == null && !sortedList.isEmpty())
|
||||
tableView.getSelectionModel().select(0);
|
||||
ThreadUtils.execute(() -> {
|
||||
UserThread.execute(() -> {
|
||||
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
|
||||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||
|
||||
// try to update deposits list
|
||||
try {
|
||||
updateList();
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not update deposits list");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
xmrWalletService.addBalanceListener(balanceListener);
|
||||
xmrWalletService.addWalletListener(walletListener);
|
||||
|
||||
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
|
||||
addressTextField.setAmount(HavenoUtils.parseXmr(t));
|
||||
updateQRCode();
|
||||
});
|
||||
|
||||
if (tableView.getSelectionModel().getSelectedItem() == null && !sortedList.isEmpty())
|
||||
tableView.getSelectionModel().select(0);
|
||||
});
|
||||
}, THREAD_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
tableView.getSelectionModel().selectedItemProperty().removeListener(tableViewSelectionListener);
|
||||
sortedList.comparatorProperty().unbind();
|
||||
observableList.forEach(DepositListItem::cleanup);
|
||||
xmrWalletService.removeBalanceListener(balanceListener);
|
||||
xmrWalletService.removeWalletListener(walletListener);
|
||||
amountTextFieldSubscription.unsubscribe();
|
||||
ThreadUtils.execute(() -> {
|
||||
tableView.getSelectionModel().selectedItemProperty().removeListener(tableViewSelectionListener);
|
||||
sortedList.comparatorProperty().unbind();
|
||||
observableList.forEach(DepositListItem::cleanup);
|
||||
xmrWalletService.removeBalanceListener(balanceListener);
|
||||
xmrWalletService.removeWalletListener(walletListener);
|
||||
amountTextFieldSubscription.unsubscribe();
|
||||
}, THREAD_ID);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -156,7 +156,6 @@ public class BuyerStep2View extends TradeStepView {
|
|||
statusLabel.setText(Res.get("shared.preparingConfirmation"));
|
||||
break;
|
||||
case BUYER_SENT_PAYMENT_SENT_MSG:
|
||||
case BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG:
|
||||
busyAnimation.play();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||
timeoutTimer = UserThread.runAfter(() -> {
|
||||
|
@ -168,6 +167,7 @@ public class BuyerStep2View extends TradeStepView {
|
|||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||
break;
|
||||
case BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG:
|
||||
case SELLER_RECEIVED_PAYMENT_SENT_MSG:
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||
|
@ -442,7 +442,8 @@ public class BuyerStep2View extends TradeStepView {
|
|||
|
||||
private boolean confirmPaymentSentPermitted() {
|
||||
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:
|
||||
busyAnimation.play();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||
|
||||
timeoutTimer = UserThread.runAfter(() -> {
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
|
||||
}, 30);
|
||||
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:
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||
break;
|
||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||
break;
|
||||
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
||||
// We get a popup and the trade closed, so we dont need to show anything here
|
||||
busyAnimation.stop();
|
||||
|
@ -290,7 +289,8 @@ public class SellerStep3View extends TradeStepView {
|
|||
|
||||
private boolean confirmPaymentReceivedPermitted() {
|
||||
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.
|
||||
|
||||
> **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.
|
||||
|
||||
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