mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-13 17:05:40 -04:00
stability fixes on tor
optimize when multisig info imported fetch updates for tx progress indicators off main thread add synchronization locks refactor address entry management add totalTxFee to process model prevent same user from taking same offer at same time set refresh rate to 30s for tor
This commit is contained in:
parent
36cf91e093
commit
1b753e4f29
33 changed files with 498 additions and 354 deletions
|
@ -471,16 +471,24 @@ public class HavenoUtils {
|
|||
}
|
||||
|
||||
public static void executeTasks(Collection<Runnable> tasks, int maxConcurrency) {
|
||||
executeTasks(tasks, maxConcurrency, null);
|
||||
}
|
||||
|
||||
public static void executeTasks(Collection<Runnable> tasks, int maxConcurrency, Long timeoutSeconds) {
|
||||
if (tasks.isEmpty()) return;
|
||||
ExecutorService pool = Executors.newFixedThreadPool(maxConcurrency);
|
||||
List<Future<?>> futures = new ArrayList<Future<?>>();
|
||||
for (Runnable task : tasks) futures.add(pool.submit(task));
|
||||
pool.shutdown();
|
||||
try {
|
||||
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) pool.shutdownNow();
|
||||
} catch (InterruptedException e) {
|
||||
pool.shutdownNow();
|
||||
throw new RuntimeException(e);
|
||||
|
||||
// interrupt after timeout
|
||||
if (timeoutSeconds != null) {
|
||||
try {
|
||||
if (!pool.awaitTermination(timeoutSeconds, TimeUnit.SECONDS)) pool.shutdownNow();
|
||||
} catch (InterruptedException e) {
|
||||
pool.shutdownNow();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// throw exception from any tasks
|
||||
|
|
|
@ -317,6 +317,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
@Getter
|
||||
private final Offer offer;
|
||||
private final long takerFee;
|
||||
private final long totalTxFee;
|
||||
|
||||
// Added in 1.5.1
|
||||
@Getter
|
||||
|
@ -362,8 +363,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
// Transient
|
||||
// Immutable
|
||||
@Getter
|
||||
transient final private BigInteger totalTxFee;
|
||||
@Getter
|
||||
transient final private XmrWalletService xmrWalletService;
|
||||
|
||||
transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state);
|
||||
|
@ -385,6 +384,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
// Mutable
|
||||
@Getter
|
||||
transient private boolean isInitialized;
|
||||
@Getter
|
||||
transient private boolean isShutDown;
|
||||
|
||||
// Added in v1.2.0
|
||||
|
@ -465,7 +465,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
this.offer = offer;
|
||||
this.amount = tradeAmount.longValueExact();
|
||||
this.takerFee = takerFee.longValueExact();
|
||||
this.totalTxFee = BigInteger.valueOf(0); // TODO: sum tx fees
|
||||
this.totalTxFee = 0l; // TODO: sum tx fees
|
||||
this.price = tradePrice;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.processModel = processModel;
|
||||
|
@ -585,6 +585,18 @@ public abstract class Trade implements Tradable, Model {
|
|||
return;
|
||||
}
|
||||
|
||||
// reset payment sent state if no ack receive
|
||||
if (getState().ordinal() >= Trade.State.BUYER_CONFIRMED_IN_UI_PAYMENT_SENT.ordinal() && getState().ordinal() < Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
setState(Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
}
|
||||
|
||||
// reset payment received state if no ack receive
|
||||
if (getState().ordinal() >= Trade.State.SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT.ordinal() && getState().ordinal() < Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
setState(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
}
|
||||
|
||||
// handle trade state events
|
||||
tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> {
|
||||
if (isDepositsPublished() && !isPayoutUnlocked()) updateWalletRefreshPeriod();
|
||||
|
@ -621,10 +633,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
if (!isInitialized) return;
|
||||
log.info("Payout unlocked for {} {}, deleting multisig wallet", getClass().getSimpleName(), getId());
|
||||
deleteWallet();
|
||||
if (txPollLooper != null) {
|
||||
txPollLooper.stop();
|
||||
txPollLooper = null;
|
||||
}
|
||||
if (idlePayoutSyncer != null) {
|
||||
xmrWalletService.removeWalletListener(idlePayoutSyncer);
|
||||
idlePayoutSyncer = null;
|
||||
|
@ -702,6 +710,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
synchronized (walletLock) {
|
||||
if (wallet != null) return wallet;
|
||||
if (!walletExists()) return null;
|
||||
if (isShutDown) throw new RuntimeException("Cannot open wallet for " + getClass().getSimpleName() + " " + getId() + " because trade is shut down");
|
||||
if (!isShutDown) wallet = xmrWalletService.openWallet(getWalletName());
|
||||
return wallet;
|
||||
}
|
||||
|
@ -746,7 +755,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
} catch (Exception e) {
|
||||
if (!isShutDown) {
|
||||
log.warn("Error syncing trade wallet for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -784,6 +792,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
private void closeWallet() {
|
||||
synchronized (walletLock) {
|
||||
if (wallet == null) throw new RuntimeException("Trade wallet to close was not previously opened for trade " + getId());
|
||||
stopPolling();
|
||||
xmrWalletService.closeWallet(wallet, true);
|
||||
wallet = null;
|
||||
}
|
||||
|
@ -977,10 +986,21 @@ public abstract class Trade implements Tradable, Model {
|
|||
if (sign) {
|
||||
|
||||
// sign tx
|
||||
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx");
|
||||
payoutTxHex = result.getSignedMultisigTxHex();
|
||||
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex); // update described set
|
||||
try {
|
||||
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx");
|
||||
payoutTxHex = result.getSignedMultisigTxHex();
|
||||
} catch (Exception e) {
|
||||
if (getPayoutTxHex() != null) {
|
||||
log.info("Reusing previous payout tx for {} {} because signing failed with error \"{}\"", getClass().getSimpleName(), getId(), e.getMessage()); // in case previous message with signed tx failed to send
|
||||
payoutTxHex = getPayoutTxHex();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// describe result
|
||||
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex);
|
||||
payoutTx = describedTxSet.getTxs().get(0);
|
||||
|
||||
// verify fee is within tolerance by recreating payout tx
|
||||
|
@ -1049,6 +1069,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
|
||||
private MoneroTx getDepositTx(TradePeer trader) {
|
||||
String depositId = trader.getDepositTxHash();
|
||||
if (depositId == null) return null;
|
||||
try {
|
||||
if (trader.getDepositTx() == null || !trader.getDepositTx().isConfirmed()) {
|
||||
trader.setDepositTx(getTxFromWalletOrDaemon(depositId));
|
||||
|
@ -1106,21 +1127,18 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
|
||||
public void shutDown() {
|
||||
synchronized (walletLock) {
|
||||
synchronized (this) {
|
||||
log.info("Shutting down {} {}", getClass().getSimpleName(), getId());
|
||||
isInitialized = false;
|
||||
isShutDown = true;
|
||||
if (wallet != null) closeWallet();
|
||||
if (txPollLooper != null) {
|
||||
txPollLooper.stop();
|
||||
txPollLooper = null;
|
||||
}
|
||||
if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe();
|
||||
if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe();
|
||||
if (idlePayoutSyncer != null) {
|
||||
xmrWalletService.removeWalletListener(idlePayoutSyncer);
|
||||
idlePayoutSyncer = null;
|
||||
}
|
||||
log.info("Done shutting down {} {}", getClass().getSimpleName(), getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1558,6 +1576,10 @@ public abstract class Trade implements Tradable, Model {
|
|||
return BigInteger.valueOf(takerFee);
|
||||
}
|
||||
|
||||
public BigInteger getTotalTxFee() {
|
||||
return BigInteger.valueOf(totalTxFee);
|
||||
}
|
||||
|
||||
public BigInteger getBuyerSecurityDeposit() {
|
||||
if (getBuyer().getDepositTxHash() == null) return null;
|
||||
return getBuyer().getSecurityDeposit();
|
||||
|
@ -1621,34 +1643,38 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
|
||||
private void setDaemonConnection(MoneroRpcConnection connection) {
|
||||
MoneroWallet wallet = getWallet();
|
||||
if (wallet == null) return;
|
||||
log.info("Setting daemon connection for trade wallet {}: {}", getId() , connection == null ? null : connection.getUri());
|
||||
wallet.setDaemonConnection(connection);
|
||||
|
||||
// sync and reprocess messages on new thread
|
||||
if (connection != null && !Boolean.FALSE.equals(connection.isConnected())) {
|
||||
HavenoUtils.submitTask(() -> {
|
||||
updateSyncing();
|
||||
|
||||
// reprocess pending payout messages
|
||||
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||
});
|
||||
synchronized (walletLock) {
|
||||
if (isShutDown) return;
|
||||
MoneroWallet wallet = getWallet();
|
||||
if (wallet == null) return;
|
||||
log.info("Setting daemon connection for trade wallet {}: {}", getId() , connection == null ? null : connection.getUri());
|
||||
wallet.setDaemonConnection(connection);
|
||||
updateWalletRefreshPeriod();
|
||||
|
||||
// sync and reprocess messages on new thread
|
||||
if (connection != null && !Boolean.FALSE.equals(connection.isConnected())) {
|
||||
HavenoUtils.submitTask(() -> {
|
||||
updateSyncing();
|
||||
|
||||
// reprocess pending payout messages
|
||||
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSyncing() {
|
||||
if (isShutDown) return;
|
||||
if (!isIdling()) {
|
||||
trySyncWallet();
|
||||
updateWalletRefreshPeriod();
|
||||
trySyncWallet();
|
||||
} else {
|
||||
long startSyncingInMs = ThreadLocalRandom.current().nextLong(0, getWalletRefreshPeriod()); // random time to start syncing
|
||||
UserThread.runAfter(() -> {
|
||||
if (!isShutDown) {
|
||||
trySyncWallet();
|
||||
updateWalletRefreshPeriod();
|
||||
trySyncWallet();
|
||||
}
|
||||
}, startSyncingInMs / 1000l);
|
||||
}
|
||||
|
@ -1659,27 +1685,35 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
|
||||
private void setWalletRefreshPeriod(long walletRefreshPeriod) {
|
||||
if (this.isShutDown) return;
|
||||
if (this.walletRefreshPeriod != null && this.walletRefreshPeriod == walletRefreshPeriod) return;
|
||||
this.walletRefreshPeriod = walletRefreshPeriod;
|
||||
synchronized (walletLock) {
|
||||
if (this.isShutDown) return;
|
||||
if (this.walletRefreshPeriod != null && this.walletRefreshPeriod == walletRefreshPeriod) return;
|
||||
this.walletRefreshPeriod = walletRefreshPeriod;
|
||||
if (getWallet() != null) {
|
||||
log.info("Setting wallet refresh rate for {} {} to {}", getClass().getSimpleName(), getId(), walletRefreshPeriod);
|
||||
getWallet().startSyncing(getWalletRefreshPeriod()); // TODO (monero-project): wallet rpc waits until last sync period finishes before starting new sync period
|
||||
}
|
||||
if (txPollLooper != null) {
|
||||
txPollLooper.stop();
|
||||
txPollLooper = null;
|
||||
}
|
||||
stopPolling();
|
||||
}
|
||||
startPolling();
|
||||
}
|
||||
|
||||
private void startPolling() {
|
||||
if (txPollLooper != null) return;
|
||||
log.info("Listening for payout tx for {} {}", getClass().getSimpleName(), getId());
|
||||
txPollLooper = new TaskLooper(() -> { pollWallet(); });
|
||||
txPollLooper.start(walletRefreshPeriod);
|
||||
synchronized (walletLock) {
|
||||
if (txPollLooper != null) return;
|
||||
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
|
||||
txPollLooper = new TaskLooper(() -> { pollWallet(); });
|
||||
txPollLooper.start(walletRefreshPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPolling() {
|
||||
synchronized (walletLock) {
|
||||
if (txPollLooper != null) {
|
||||
txPollLooper.stop();
|
||||
txPollLooper = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pollWallet() {
|
||||
|
@ -1698,6 +1732,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
.setHashes(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()))
|
||||
.setIncludeOutputs(true));
|
||||
} catch (Exception e) {
|
||||
if (!isShutDown) log.info("Could not fetch deposit txs from wallet for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage()); // expected at first
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1750,7 +1785,10 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (!isShutDown && getWallet() != null && isWalletConnected()) log.warn("Error polling trade wallet {}: {}", getId(), e.getMessage());
|
||||
if (!isShutDown && getWallet() != null && isWalletConnected()) {
|
||||
log.warn("Error polling trade wallet {}: {}", getId(), e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1844,6 +1882,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
protobuf.Trade.Builder builder = protobuf.Trade.newBuilder()
|
||||
.setOffer(offer.toProtoMessage())
|
||||
.setTakerFee(takerFee)
|
||||
.setTotalTxFee(totalTxFee)
|
||||
.setTakeOfferDate(takeOfferDate)
|
||||
.setProcessModel(processModel.toProtoMessage())
|
||||
.setAmount(amount)
|
||||
|
@ -1868,7 +1907,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
|
||||
Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
|
||||
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
|
||||
Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxHex(payoutTxKey));
|
||||
Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxKey(payoutTxKey));
|
||||
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
|
||||
Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name()));
|
||||
return builder.build();
|
||||
|
@ -1913,6 +1952,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
return "Trade{" +
|
||||
"\n offer=" + offer +
|
||||
",\n takerFee=" + takerFee +
|
||||
",\n totalTxFee=" + totalTxFee +
|
||||
",\n takeOfferDate=" + takeOfferDate +
|
||||
",\n processModel=" + processModel +
|
||||
",\n payoutTxId='" + payoutTxId + '\'' +
|
||||
|
|
|
@ -353,16 +353,16 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
|
||||
public TradeProtocol getTradeProtocol(Trade trade) {
|
||||
String uid = trade.getUid();
|
||||
if (tradeProtocolByTradeId.containsKey(uid)) {
|
||||
return tradeProtocolByTradeId.get(uid);
|
||||
} else {
|
||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||
TradeProtocol prev = tradeProtocolByTradeId.put(uid, tradeProtocol);
|
||||
if (prev != null) {
|
||||
log.error("We had already an entry with uid {}", trade.getUid());
|
||||
}
|
||||
synchronized (tradeProtocolByTradeId) {
|
||||
return tradeProtocolByTradeId.get(trade.getUid());
|
||||
}
|
||||
}
|
||||
|
||||
public TradeProtocol createTradeProtocol(Trade trade) {
|
||||
synchronized (tradeProtocolByTradeId) {
|
||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
|
||||
if (prev != null) log.error("We had already an entry with uid {}", trade.getUid());
|
||||
return tradeProtocol;
|
||||
}
|
||||
}
|
||||
|
@ -377,6 +377,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
List<Trade> trades = getAllTrades();
|
||||
|
||||
// open trades in parallel since each may open a multisig wallet
|
||||
log.info("Initializing trades");
|
||||
int threadPoolSize = 10;
|
||||
Set<Runnable> tasks = new HashSet<Runnable>();
|
||||
for (Trade trade : trades) {
|
||||
|
@ -387,8 +388,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
});
|
||||
};
|
||||
log.info("Initializing persisted trades");
|
||||
HavenoUtils.executeTasks(tasks, threadPoolSize);
|
||||
log.info("Done initializing trades");
|
||||
|
||||
// reset any available address entries
|
||||
if (isShutDown) return;
|
||||
|
@ -419,7 +420,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
|
||||
private void initPersistedTrade(Trade trade) {
|
||||
if (isShutDown) return;
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
initTradeAndProtocol(trade, createTradeProtocol(trade));
|
||||
requestPersistence();
|
||||
scheduleDeletionIfUnfunded(trade);
|
||||
}
|
||||
|
@ -463,7 +464,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
}
|
||||
if (offer == null) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because no offer is on the books", sender, request.getTradeId());
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because offer is not on the books", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -489,38 +490,39 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
} else {
|
||||
|
||||
// verify request is from taker
|
||||
if (!sender.equals(request.getTakerNodeAddress())) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from taker when trade is not initialized", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
// verify request is from taker
|
||||
if (!sender.equals(request.getTakerNodeAddress())) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from taker when trade is not initialized", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
|
||||
// get expected taker fee
|
||||
BigInteger takerFee = HavenoUtils.getTakerFee(BigInteger.valueOf(offer.getOfferPayload().getAmount()));
|
||||
// get expected taker fee
|
||||
BigInteger takerFee = HavenoUtils.getTakerFee(BigInteger.valueOf(offer.getOfferPayload().getAmount()));
|
||||
|
||||
// create arbitrator trade
|
||||
trade = new ArbitratorTrade(offer,
|
||||
BigInteger.valueOf(offer.getOfferPayload().getAmount()),
|
||||
takerFee,
|
||||
offer.getOfferPayload().getPrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString(),
|
||||
request.getMakerNodeAddress(),
|
||||
request.getTakerNodeAddress(),
|
||||
request.getArbitratorNodeAddress());
|
||||
// create arbitrator trade
|
||||
trade = new ArbitratorTrade(offer,
|
||||
BigInteger.valueOf(offer.getOfferPayload().getAmount()),
|
||||
takerFee,
|
||||
offer.getOfferPayload().getPrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString(),
|
||||
request.getMakerNodeAddress(),
|
||||
request.getTakerNodeAddress(),
|
||||
request.getArbitratorNodeAddress());
|
||||
|
||||
// set reserve tx hash if available
|
||||
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId());
|
||||
if (signedOfferOptional.isPresent()) {
|
||||
SignedOffer signedOffer = signedOfferOptional.get();
|
||||
trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash());
|
||||
}
|
||||
// set reserve tx hash if available
|
||||
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId());
|
||||
if (signedOfferOptional.isPresent()) {
|
||||
SignedOffer signedOffer = signedOfferOptional.get();
|
||||
trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash());
|
||||
}
|
||||
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
synchronized (tradableList) {
|
||||
tradableList.add(trade);
|
||||
}
|
||||
// initialize trade protocol
|
||||
initTradeAndProtocol(trade, createTradeProtocol(trade));
|
||||
synchronized (tradableList) {
|
||||
tradableList.add(trade);
|
||||
}
|
||||
}
|
||||
|
||||
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||
|
@ -596,7 +598,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
|
||||
trade.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing());
|
||||
trade.getMaker().setPubKeyRing(trade.getOffer().getPubKeyRing());
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
initTradeAndProtocol(trade, createTradeProtocol(trade));
|
||||
trade.getSelf().setPaymentAccountId(offer.getOfferPayload().getMakerPaymentAccountId());
|
||||
trade.getSelf().setReserveTxHash(openOffer.getReserveTxHash()); // TODO (woodser): initialize in initTradeAndProtocol?
|
||||
trade.getSelf().setReserveTxHex(openOffer.getReserveTxHex());
|
||||
|
@ -782,11 +784,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
trade.getSelf().setPubKeyRing(model.getPubKeyRing());
|
||||
trade.getSelf().setPaymentAccountId(paymentAccountId);
|
||||
|
||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
|
||||
if (prev != null) {
|
||||
log.error("We had already an entry with uid {}", trade.getUid());
|
||||
}
|
||||
// ensure trade is not already open
|
||||
Optional<Trade> tradeOptional = getOpenTrade(offer.getId());
|
||||
if (tradeOptional.isPresent()) throw new RuntimeException("Cannot create trade protocol because trade with ID " + trade.getId() + " is already open");
|
||||
|
||||
// initialize trade protocol
|
||||
TradeProtocol tradeProtocol = createTradeProtocol(trade);
|
||||
synchronized (tradableList) {
|
||||
tradableList.add(trade);
|
||||
}
|
||||
|
@ -804,11 +807,14 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
requestPersistence();
|
||||
} else {
|
||||
log.warn("Cannot take offer {} because it's not available, state={}", offer.getId(), offer.getState());
|
||||
}
|
||||
},
|
||||
errorMessage -> {
|
||||
log.warn("Taker error during check offer availability: " + errorMessage);
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
|
||||
requestPersistence();
|
||||
|
@ -958,32 +964,32 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
.collect(Collectors.toSet()));
|
||||
tradesIdSet.addAll(closedTradableManager.getTradesStreamWithFundsLockedIn()
|
||||
.map(trade -> {
|
||||
MoneroTx makerDepositTx = trade.getMakerDepositTx();
|
||||
if (makerDepositTx != null) {
|
||||
if (!makerDepositTx.isConfirmed()) {
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); // TODO (woodser): rename to closedTradeWithLockedDepositTx
|
||||
MoneroTx makerDepositTx = trade.getMakerDepositTx();
|
||||
if (makerDepositTx != null) {
|
||||
if (!makerDepositTx.isConfirmed()) {
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId()))); // TODO (woodser): rename to closedTradeWithLockedDepositTx
|
||||
} else {
|
||||
log.warn("We found a closed trade with locked up funds. " +
|
||||
"That should never happen. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
}
|
||||
} else {
|
||||
log.warn("We found a closed trade with locked up funds. " +
|
||||
"That should never happen. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getId(), trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
log.warn("Closed trade with locked up funds missing maker deposit tx. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
|
||||
}
|
||||
} else {
|
||||
log.warn("Closed trade with locked up funds missing maker deposit tx. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getId(), trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
|
||||
}
|
||||
|
||||
MoneroTx takerDepositTx = trade.getTakerDepositTx();
|
||||
if (takerDepositTx != null) {
|
||||
if (!takerDepositTx.isConfirmed()) {
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId())));
|
||||
MoneroTx takerDepositTx = trade.getTakerDepositTx();
|
||||
if (takerDepositTx != null) {
|
||||
if (!takerDepositTx.isConfirmed()) {
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithUnconfirmedDepositTx", trade.getShortId())));
|
||||
} else {
|
||||
log.warn("We found a closed trade with locked up funds. " +
|
||||
"That should never happen. trade ID={} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
}
|
||||
} else {
|
||||
log.warn("We found a closed trade with locked up funds. " +
|
||||
"That should never happen. trade ID={} ID={}, state={}, payoutState={}, disputeState={}", trade.getId(), trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
log.warn("Closed trade with locked up funds missing taker deposit tx. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
|
||||
}
|
||||
} else {
|
||||
log.warn("Closed trade with locked up funds missing taker deposit tx. {} ID={}, state={}, payoutState={}, disputeState={}", trade.getId(), trade.getClass().getSimpleName(), trade.getId(), trade.getState(), trade.getPayoutState(), trade.getDisputeState());
|
||||
tradeTxException.set(new TradeTxException(Res.get("error.closedTradeWithNoDepositTx", trade.getShortId())));
|
||||
}
|
||||
return trade.getId();
|
||||
return trade.getId();
|
||||
})
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
|
|
|
@ -162,7 +162,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
|
||||
// TODO (woodser): this method only necessary because isPubKeyValid not called with sender argument, so it's validated before
|
||||
private void handleMailboxCollectionSkipValidation(Collection<DecryptedMessageWithPubKey> collection) {
|
||||
log.warn("TradeProtocol.handleMailboxCollectionSkipValidation");
|
||||
collection.stream()
|
||||
.map(DecryptedMessageWithPubKey::getNetworkEnvelope)
|
||||
.filter(this::isMyMessage)
|
||||
|
@ -817,6 +816,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
|
||||
protected void latchTrade() {
|
||||
if (tradeLatch != null) throw new RuntimeException("Trade latch is not null. That should never happen.");
|
||||
if (trade.isShutDown()) throw new RuntimeException("Cannot latch trade " + trade.getId() + " for protocol because it's shut down");
|
||||
tradeLatch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
// verify deposit tx
|
||||
try {
|
||||
trade.getXmrWalletService().verifyTradeTx(
|
||||
offer.getId(),
|
||||
tradeFee,
|
||||
sendAmount,
|
||||
securityDeposit,
|
||||
|
|
|
@ -60,6 +60,7 @@ public class ArbitratorProcessReserveTx extends TradeTask {
|
|||
Tuple2<MoneroTx, BigInteger> txResult;
|
||||
try {
|
||||
txResult = trade.getXmrWalletService().verifyTradeTx(
|
||||
offer.getId(),
|
||||
tradeFee,
|
||||
sendAmount,
|
||||
securityDeposit,
|
||||
|
|
|
@ -62,6 +62,9 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
|||
// create payout tx if we have seller's updated multisig hex
|
||||
if (trade.getSeller().getUpdatedMultisigHex() != null) {
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
|
||||
// create payout tx
|
||||
log.info("Buyer creating unsigned payout tx");
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
|
|
|
@ -70,7 +70,7 @@ public class MakerSendInitTradeRequest extends TradeTask {
|
|||
trade.getSelf().getReserveTxHash(),
|
||||
trade.getSelf().getReserveTxHex(),
|
||||
trade.getSelf().getReserveTxKey(),
|
||||
model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(),
|
||||
null);
|
||||
|
||||
// send request to arbitrator
|
||||
|
|
|
@ -55,7 +55,6 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
|
|||
|
||||
// update multisig hex
|
||||
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
||||
trade.importMultisigHex();
|
||||
|
||||
// decrypt seller payment account payload if key given
|
||||
if (request.getSellerPaymentAccountKey() != null && trade.getTradePeer().getPaymentAccountPayload() == null) {
|
||||
|
|
|
@ -126,6 +126,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
|||
trade.verifyPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
||||
} else {
|
||||
try {
|
||||
if (trade.getProcessModel().getPaymentSentMessage() == null) throw new RuntimeException("Process model does not have payment sent message for " + trade.getClass().getSimpleName() + " " + trade.getId());
|
||||
if (StringUtils.equals(trade.getPayoutTxHex(), trade.getProcessModel().getPaymentSentMessage().getPayoutTxHex())) { // unsigned
|
||||
log.info("{} {} verifying, signing, and publishing seller's payout tx", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.verifyPayoutTx(message.getUnsignedPayoutTxHex(), true, true);
|
||||
|
|
|
@ -44,22 +44,17 @@ public class ProcessPaymentSentMessage extends TradeTask {
|
|||
// verify signature of payment sent message
|
||||
HavenoUtils.verifyPaymentSentMessage(trade, message);
|
||||
|
||||
// set state
|
||||
processModel.setPaymentSentMessage(message);
|
||||
trade.setPayoutTxHex(message.getPayoutTxHex());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
trade.getSeller().setAccountAgeWitness(message.getSellerAccountAgeWitness());
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
// update latest peer address
|
||||
trade.getBuyer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
|
||||
// if seller, decrypt buyer's payment account payload
|
||||
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
||||
|
||||
// update latest peer address
|
||||
trade.getBuyer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
|
||||
// set state
|
||||
// update state
|
||||
processModel.setPaymentSentMessage(message);
|
||||
trade.setPayoutTxHex(message.getPayoutTxHex());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
trade.getSeller().setAccountAgeWitness(message.getSellerAccountAgeWitness());
|
||||
String counterCurrencyTxId = message.getCounterCurrencyTxId();
|
||||
if (counterCurrencyTxId != null && counterCurrencyTxId.length() < 100) trade.setCounterCurrencyTxId(counterCurrencyTxId);
|
||||
String counterCurrencyExtraData = message.getCounterCurrencyExtraData();
|
||||
|
|
|
@ -43,7 +43,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||
BigInteger takerFee = trade.getTakerFee();
|
||||
BigInteger sendAmount = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getAmount() : BigInteger.valueOf(0);
|
||||
BigInteger securityDeposit = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getSellerSecurityDeposit() : trade.getOffer().getBuyerSecurityDeposit();
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
String returnAddress = model.getXmrWalletService().getAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString();
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, sendAmount, securityDeposit, returnAddress);
|
||||
|
||||
// collect reserved key images
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue