mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-22 11:39:29 +00:00
arbitrator sends deposit responses on error or timeout
This commit is contained in:
parent
a6d827c369
commit
7887c450c7
7 changed files with 209 additions and 298 deletions
|
@ -39,6 +39,7 @@ import com.google.protobuf.ByteString;
|
|||
import com.google.protobuf.Message;
|
||||
import haveno.common.ThreadUtils;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.app.Capability;
|
||||
import haveno.common.crypto.Encryption;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.proto.ProtoUtil;
|
||||
|
@ -66,12 +67,14 @@ import haveno.core.trade.protocol.ProcessModelServiceProvider;
|
|||
import haveno.core.trade.protocol.TradeListener;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.core.trade.protocol.TradeProtocol;
|
||||
import haveno.core.trade.statistics.TradeStatistics3;
|
||||
import haveno.core.util.VolumeUtil;
|
||||
import haveno.core.xmr.model.XmrAddressEntry;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
import haveno.network.p2p.AckMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.P2PService;
|
||||
import haveno.network.p2p.network.TorNetworkNode;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
|
@ -137,7 +140,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
private static final String MONERO_TRADE_WALLET_PREFIX = "xmr_trade_";
|
||||
private static final long SHUTDOWN_TIMEOUT_MS = 60000;
|
||||
private static final long SYNC_EVERY_NUM_BLOCKS = 360; // ~1/2 day
|
||||
private static final long DELETE_AFTER_NUM_BLOCKS = 1; // if deposit requested but not published
|
||||
private static final long DELETE_AFTER_NUM_BLOCKS = 2; // if deposit requested but not published
|
||||
private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS;
|
||||
private final Object walletLock = new Object();
|
||||
private final Object pollLock = new Object();
|
||||
|
@ -651,7 +654,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
if (!isInitialized || isShutDownStarted) return;
|
||||
ThreadUtils.submitToPool(() -> {
|
||||
if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling();
|
||||
if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages());
|
||||
if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished();
|
||||
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
|
||||
if (isPaymentReceived()) {
|
||||
UserThread.execute(() -> {
|
||||
|
@ -956,15 +959,17 @@ public abstract class Trade implements Tradable, Model {
|
|||
try {
|
||||
|
||||
// ensure wallet is initialized
|
||||
boolean syncedWallet = false;
|
||||
if (wallet == null) {
|
||||
log.warn("Wallet is not initialized for {} {}, opening", getClass().getSimpleName(), getId());
|
||||
getWallet();
|
||||
syncWallet(true);
|
||||
syncedWallet = true;
|
||||
}
|
||||
|
||||
// wallet must be synced
|
||||
if (isDepositRequested() && isWalletBehind()) {
|
||||
log.warn("Wallet is not synced for {} {}, syncing", getClass().getSimpleName(), getId());
|
||||
// sync wallet if deposit requested and payout not unlocked
|
||||
if (isDepositRequested() && !isPayoutUnlocked() && !syncedWallet) {
|
||||
log.warn("Syncing wallet on deletion for trade {} {}, syncing", getClass().getSimpleName(), getId());
|
||||
syncWallet(true);
|
||||
}
|
||||
|
||||
|
@ -1407,14 +1412,14 @@ public abstract class Trade implements Tradable, Model {
|
|||
return;
|
||||
}
|
||||
|
||||
// unreserve taker key images
|
||||
// unreserve taker's key images
|
||||
if (this instanceof TakerTrade) {
|
||||
ThreadUtils.submitToPool(() -> {
|
||||
xmrWalletService.thawOutputs(getSelf().getReserveTxKeyImages());
|
||||
});
|
||||
}
|
||||
|
||||
// unreserve open offer
|
||||
// unreserve maker's open offer
|
||||
Optional<OpenOffer> openOffer = processModel.getOpenOfferManager().getOpenOfferById(this.getId());
|
||||
if (this instanceof MakerTrade && openOffer.isPresent()) {
|
||||
processModel.getOpenOfferManager().unreserveOpenOffer(openOffer.get());
|
||||
|
@ -2504,6 +2509,47 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
}
|
||||
|
||||
private void onDepositsPublished() {
|
||||
|
||||
// skip if arbitrator
|
||||
if (this instanceof ArbitratorTrade) return;
|
||||
|
||||
// freeze outputs until spent
|
||||
xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages());
|
||||
|
||||
// close open offer or reset address entries
|
||||
if (this instanceof MakerTrade) {
|
||||
processModel.getOpenOfferManager().closeOpenOffer(getOffer());
|
||||
} else {
|
||||
getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
|
||||
}
|
||||
|
||||
// seller publishes trade statistics
|
||||
if (this instanceof SellerTrade) {
|
||||
checkNotNull(getSeller().getDepositTx());
|
||||
processModel.getP2PService().findPeersCapabilities(getTradePeer().getNodeAddress())
|
||||
.filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3))
|
||||
.ifPresentOrElse(capabilities -> {
|
||||
|
||||
// Our peer has updated, so as we are the seller we will publish the trade statistics.
|
||||
// The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added)
|
||||
String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null);
|
||||
boolean isTorNetworkNode = getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode;
|
||||
TradeStatistics3 tradeStatistics = TradeStatistics3.from(this, referralId, isTorNetworkNode);
|
||||
if (tradeStatistics.isValid()) {
|
||||
log.info("Publishing trade statistics");
|
||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
} else {
|
||||
log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
log.info("Our peer does not has updated yet, so they will publish the trade statistics. " +
|
||||
"To avoid duplicates we do not publish from our side.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -84,7 +84,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||
latchTrade();
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_REQUESTED)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
|
@ -99,8 +99,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
}))
|
||||
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||
})))
|
||||
.executeTasks(true);
|
||||
awaitTradeLatch();
|
||||
}
|
||||
|
@ -117,4 +116,13 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||
public Class<? extends TradeTask>[] getDepositsConfirmedTasks() {
|
||||
return new Class[] { SendDepositsConfirmedMessageToBuyer.class, SendDepositsConfirmedMessageToSeller.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(String errorMessage) {
|
||||
// set trade state to send deposit responses with nack
|
||||
if (trade instanceof ArbitratorTrade && trade.getState() == Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST) {
|
||||
trade.setState(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
}
|
||||
super.handleError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,8 +69,6 @@ import haveno.core.trade.protocol.tasks.ProcessPaymentReceivedMessage;
|
|||
import haveno.core.trade.protocol.tasks.ProcessPaymentSentMessage;
|
||||
import haveno.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||
import haveno.core.trade.protocol.tasks.SendDepositRequest;
|
||||
import haveno.core.trade.protocol.tasks.RemoveOffer;
|
||||
import haveno.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
||||
import haveno.core.trade.protocol.tasks.MaybeResendDisputeClosedMessageWithPayout;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import haveno.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
|
@ -434,13 +432,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
Validator.checkTradeId(processModel.getOfferId(), response);
|
||||
latchTrade();
|
||||
processModel.setTradeMessage(response);
|
||||
expect(anyState(Trade.State.SENT_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS, Trade.State.DEPOSIT_TXS_SEEN_IN_NETWORK)
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED)
|
||||
.with(response)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessDepositResponse.class,
|
||||
RemoveOffer.class,
|
||||
SellerPublishTradeStatistics.class)
|
||||
ProcessDepositResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
|
@ -672,8 +668,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
}
|
||||
|
||||
if (ackMessage.isSuccess()) {
|
||||
log.info("Received AckMessage for {} from {} with tradeId {} and uid {}",
|
||||
ackMessage.getSourceMsgClassName(), sender, trade.getId(), ackMessage.getSourceUid());
|
||||
log.info("Received AckMessage for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
|
||||
// handle ack for DepositsConfirmedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositsConfirmedMessage.class.getSimpleName())) {
|
||||
|
@ -689,8 +684,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
else peer.setDepositsConfirmedMessageAcked(true);
|
||||
}
|
||||
} else {
|
||||
String err = "Received AckMessage with error state for " + ackMessage.getSourceMsgClassName() + " from "+ sender + " with tradeId " + trade.getId() + " and errorMessage=" + ackMessage.getErrorMessage();
|
||||
log.warn(err);
|
||||
log.warn("Received AckMessage with error state for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
|
||||
// set trade state on deposit request nack
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
||||
|
|
|
@ -43,7 +43,7 @@ import java.util.UUID;
|
|||
@Slf4j
|
||||
public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
|
||||
private boolean depositTxsRelayed = false;
|
||||
private Throwable error;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ArbitratorProcessDepositRequest(TaskRunner taskHandler, Trade trade) {
|
||||
|
@ -52,132 +52,155 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
|
||||
@Override
|
||||
protected void run() {
|
||||
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// get contract and signature
|
||||
String contractAsJson = trade.getContractAsJson();
|
||||
DepositRequest request = (DepositRequest) processModel.getTradeMessage(); // TODO (woodser): verify response
|
||||
byte[] signature = request.getContractSignature();
|
||||
|
||||
// get trader info
|
||||
TradePeer trader = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||
if (trader == null) throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
|
||||
PubKeyRing peerPubKeyRing = trader.getPubKeyRing();
|
||||
|
||||
// verify signature
|
||||
if (!HavenoUtils.isSignatureValid(peerPubKeyRing, contractAsJson, signature)) {
|
||||
throw new RuntimeException("Peer's contract signature is invalid");
|
||||
}
|
||||
|
||||
// set peer's signature
|
||||
trader.setContractSignature(signature);
|
||||
|
||||
// collect expected values
|
||||
Offer offer = trade.getOffer();
|
||||
boolean isFromTaker = trader == trade.getTaker();
|
||||
boolean isFromBuyer = trader == trade.getBuyer();
|
||||
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
||||
BigInteger sendTradeAmount = isFromBuyer ? BigInteger.ZERO : trade.getAmount();
|
||||
BigInteger securityDeposit = isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||
String depositAddress = processModel.getMultisigAddress();
|
||||
|
||||
// verify deposit tx
|
||||
MoneroTx verifiedTx;
|
||||
try {
|
||||
verifiedTx = trade.getXmrWalletService().verifyDepositTx(
|
||||
offer.getId(),
|
||||
tradeFee,
|
||||
trade.getProcessModel().getTradeFeeAddress(),
|
||||
sendTradeAmount,
|
||||
securityDeposit,
|
||||
depositAddress,
|
||||
trader.getDepositTxHash(),
|
||||
request.getDepositTxHex(),
|
||||
request.getDepositTxKey(),
|
||||
null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + trader.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
// extend timeout
|
||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while verifying deposit tx for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
trade.startProtocolTimeout();
|
||||
|
||||
// set deposit info
|
||||
trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
|
||||
trader.setDepositTxFee(verifiedTx.getFee());
|
||||
trader.setDepositTxHex(request.getDepositTxHex());
|
||||
trader.setDepositTxKey(request.getDepositTxKey());
|
||||
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
||||
|
||||
// relay deposit txs when both available
|
||||
if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) {
|
||||
|
||||
// update trade state
|
||||
trade.setState(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// relay txs
|
||||
MoneroSubmitTxResult makerResult = daemon.submitTxHex(processModel.getMaker().getDepositTxHex(), true);
|
||||
MoneroSubmitTxResult takerResult = daemon.submitTxHex(processModel.getTaker().getDepositTxHex(), true);
|
||||
if (!makerResult.isGood()) throw new RuntimeException("Error submitting maker deposit tx: " + JsonUtils.serialize(makerResult));
|
||||
if (!takerResult.isGood()) throw new RuntimeException("Error submitting taker deposit tx: " + JsonUtils.serialize(takerResult));
|
||||
daemon.relayTxsByHash(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()));
|
||||
depositTxsRelayed = true;
|
||||
|
||||
// update trade state
|
||||
log.info("Arbitrator submitted deposit txs for trade " + trade.getId());
|
||||
trade.setState(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// create deposit response
|
||||
DepositResponse response = new DepositResponse(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
null,
|
||||
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||
trade.getSeller().getSecurityDeposit().longValue());
|
||||
|
||||
// send deposit response to maker and taker
|
||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||
sendDepositResponse(trade.getTaker().getNodeAddress(), trade.getTaker().getPubKeyRing(), response);
|
||||
} else {
|
||||
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
||||
if (processModel.getTaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
||||
}
|
||||
// check if trade is failed
|
||||
if (trade.getState() == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) throw new RuntimeException("Cannot process deposit request because trade is already failed, tradeId=" + trade.getId());
|
||||
|
||||
// update trade state
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// process request
|
||||
processDepositRequest();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
|
||||
// handle error before deposits relayed
|
||||
if (!depositTxsRelayed) {
|
||||
try {
|
||||
daemon.flushTxPool(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// create deposit response with error
|
||||
DepositResponse response = new DepositResponse(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
t.getMessage(),
|
||||
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||
trade.getSeller().getSecurityDeposit().longValue());
|
||||
|
||||
// send deposit response to maker and taker
|
||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||
sendDepositResponse(trade.getTaker().getNodeAddress(), trade.getTaker().getPubKeyRing(), response);
|
||||
}
|
||||
this.error = t;
|
||||
t.printStackTrace();
|
||||
trade.setState(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
failed(t);
|
||||
}
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
private void processDepositRequest() {
|
||||
|
||||
// get contract and signature
|
||||
String contractAsJson = trade.getContractAsJson();
|
||||
DepositRequest request = (DepositRequest) processModel.getTradeMessage(); // TODO (woodser): verify response
|
||||
byte[] signature = request.getContractSignature();
|
||||
|
||||
// get trader info
|
||||
TradePeer trader = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||
if (trader == null) throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
|
||||
PubKeyRing peerPubKeyRing = trader.getPubKeyRing();
|
||||
|
||||
// verify signature
|
||||
if (!HavenoUtils.isSignatureValid(peerPubKeyRing, contractAsJson, signature)) {
|
||||
throw new RuntimeException("Peer's contract signature is invalid");
|
||||
}
|
||||
|
||||
// set peer's signature
|
||||
trader.setContractSignature(signature);
|
||||
|
||||
// collect expected values
|
||||
Offer offer = trade.getOffer();
|
||||
boolean isFromTaker = trader == trade.getTaker();
|
||||
boolean isFromBuyer = trader == trade.getBuyer();
|
||||
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
||||
BigInteger sendTradeAmount = isFromBuyer ? BigInteger.ZERO : trade.getAmount();
|
||||
BigInteger securityDeposit = isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||
String depositAddress = processModel.getMultisigAddress();
|
||||
|
||||
// verify deposit tx
|
||||
MoneroTx verifiedTx;
|
||||
try {
|
||||
verifiedTx = trade.getXmrWalletService().verifyDepositTx(
|
||||
offer.getId(),
|
||||
tradeFee,
|
||||
trade.getProcessModel().getTradeFeeAddress(),
|
||||
sendTradeAmount,
|
||||
securityDeposit,
|
||||
depositAddress,
|
||||
trader.getDepositTxHash(),
|
||||
request.getDepositTxHex(),
|
||||
request.getDepositTxKey(),
|
||||
null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + trader.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
// update trade state
|
||||
trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
|
||||
trader.setDepositTxFee(verifiedTx.getFee());
|
||||
trader.setDepositTxHex(request.getDepositTxHex());
|
||||
trader.setDepositTxKey(request.getDepositTxKey());
|
||||
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// relay deposit txs when both available
|
||||
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
||||
if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) {
|
||||
|
||||
// check timeout and extend just before relaying
|
||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out before relaying deposit txs for {} {}" + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
trade.addInitProgressStep();
|
||||
|
||||
try {
|
||||
|
||||
// submit txs to pool but do not relay
|
||||
MoneroSubmitTxResult makerResult = daemon.submitTxHex(processModel.getMaker().getDepositTxHex(), true);
|
||||
if (!makerResult.isGood()) throw new RuntimeException("Error submitting maker deposit tx: " + JsonUtils.serialize(makerResult));
|
||||
MoneroSubmitTxResult takerResult = daemon.submitTxHex(processModel.getTaker().getDepositTxHex(), true);
|
||||
if (!takerResult.isGood()) throw new RuntimeException("Error submitting taker deposit tx: " + JsonUtils.serialize(takerResult));
|
||||
|
||||
// relay txs
|
||||
daemon.relayTxsByHash(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()));
|
||||
|
||||
// update trade state
|
||||
log.info("Arbitrator published deposit txs for trade " + trade.getId());
|
||||
trade.setState(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
||||
} catch (Exception e) {
|
||||
|
||||
// flush txs from pool
|
||||
try {
|
||||
daemon.flushTxPool(processModel.getMaker().getDepositTxHash());
|
||||
} catch (Exception e2) {
|
||||
e2.printStackTrace();
|
||||
}
|
||||
try {
|
||||
daemon.flushTxPool(processModel.getTaker().getDepositTxHash());
|
||||
} catch (Exception e2) {
|
||||
e2.printStackTrace();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
|
||||
// subscribe to trade state once to send responses with ack or nack
|
||||
trade.stateProperty().addListener((obs, oldState, newState) -> {
|
||||
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
|
||||
sendDepositResponses(error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : error.getMessage());
|
||||
} else if (newState == Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) {
|
||||
sendDepositResponses(null);
|
||||
}
|
||||
});
|
||||
|
||||
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
||||
if (processModel.getTaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTimedOut() {
|
||||
return !processModel.getTradeManager().hasOpenTrade(trade);
|
||||
}
|
||||
|
||||
private void sendDepositResponses(String errorMessage) {
|
||||
|
||||
// create deposit response
|
||||
DepositResponse response = new DepositResponse(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
errorMessage,
|
||||
trade.getBuyer().getSecurityDeposit().longValue(),
|
||||
trade.getSeller().getSecurityDeposit().longValue());
|
||||
|
||||
// send deposit response to maker and taker
|
||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||
sendDepositResponse(trade.getTaker().getNodeAddress(), trade.getTaker().getPubKeyRing(), response);
|
||||
}
|
||||
|
||||
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
|
||||
|
@ -191,12 +214,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
|||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), nodeAddress, trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isTimedOut() {
|
||||
return !processModel.getTradeManager().hasOpenTrade(trade);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class RemoveOffer extends TradeTask {
|
||||
public RemoveOffer(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
if (trade instanceof MakerTrade) {
|
||||
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
|
||||
} else {
|
||||
trade.getXmrWalletService().resetAddressEntriesForOpenOffer(trade.getId());
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +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.app.Capability;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.SellerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.statistics.TradeStatistics3;
|
||||
import haveno.network.p2p.network.TorNetworkNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class SellerPublishTradeStatistics extends TradeTask {
|
||||
public SellerPublishTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if not seller
|
||||
if (!(trade instanceof SellerTrade)) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
checkNotNull(trade.getSeller().getDepositTx());
|
||||
processModel.getP2PService().findPeersCapabilities(trade.getTradePeer().getNodeAddress())
|
||||
.filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3))
|
||||
.ifPresentOrElse(capabilities -> {
|
||||
// Our peer has updated, so as we are the seller we will publish the trade statistics.
|
||||
// The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added)
|
||||
|
||||
String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null);
|
||||
boolean isTorNetworkNode = model.getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode;
|
||||
TradeStatistics3 tradeStatistics = TradeStatistics3.from(trade, referralId, isTorNetworkNode);
|
||||
if (tradeStatistics.isValid()) {
|
||||
log.info("Publishing trade statistics");
|
||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
} else {
|
||||
log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics);
|
||||
}
|
||||
|
||||
complete();
|
||||
},
|
||||
() -> {
|
||||
log.info("Our peer does not has updated yet, so they will publish the trade statistics. " +
|
||||
"To avoid duplicates we do not publish from our side.");
|
||||
complete();
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,9 +31,7 @@ import haveno.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
|
|||
import haveno.core.trade.protocol.tasks.MakerSetLockTime;
|
||||
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.SellerPublishTradeStatistics;
|
||||
import haveno.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToBuyer;
|
||||
import haveno.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import haveno.desktop.common.view.FxmlView;
|
||||
|
@ -85,9 +83,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
ApplyFilter.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
|
||||
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishTradeStatistics.class,
|
||||
|
||||
ProcessPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
|
||||
|
@ -104,8 +99,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
VerifyPeersAccountAgeWitness.class,
|
||||
MakerSetLockTime.class,
|
||||
|
||||
RemoveOffer.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
BuyerPreparePaymentSentMessage.class,
|
||||
BuyerSendPaymentSentMessage.class,
|
||||
|
@ -134,18 +127,11 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
VerifyPeersAccountAgeWitness.class,
|
||||
MakerSetLockTime.class,
|
||||
|
||||
//SellerAsMakerProcessDepositTxMessage.class,
|
||||
RemoveOffer.class,
|
||||
|
||||
//SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishTradeStatistics.class,
|
||||
|
||||
ProcessPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
SellerPreparePaymentReceivedMessage.class,
|
||||
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
||||
SellerSendPaymentReceivedMessageToBuyer.class
|
||||
)
|
||||
));
|
||||
|
|
Loading…
Reference in a new issue