mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
refactor payout protocol to work with or without updated multisig
This commit is contained in:
parent
bb95b4b1d6
commit
32070fbafb
50 changed files with 1026 additions and 899 deletions
2
Makefile
2
Makefile
|
@ -15,7 +15,7 @@ haveno:
|
|||
./gradlew build
|
||||
|
||||
# build haveno without tests
|
||||
no-tests:
|
||||
skip-tests:
|
||||
./gradlew build -x test
|
||||
|
||||
# quick build desktop and daemon apps without tests
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
|||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
|
@ -170,8 +170,8 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
continue;
|
||||
} else {
|
||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(PAYMENT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade);
|
||||
|
@ -190,8 +190,8 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
|||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||
t.getState().equals(SELLER_RECEIVED_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
|
|
|
@ -38,7 +38,7 @@ import static bisq.apitest.config.ApiTestConfig.BTC;
|
|||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
|
@ -173,8 +173,8 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
} else {
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(PAYMENT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade);
|
||||
|
@ -193,8 +193,8 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
|||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||
t.getState().equals(SELLER_RECEIVED_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
|
|
|
@ -13,18 +13,26 @@ import bisq.core.btc.model.XmrAddressEntry;
|
|||
import bisq.core.btc.model.XmrAddressEntryList;
|
||||
import bisq.core.btc.setup.MoneroWalletRpcManager;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.SellerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import com.google.common.util.concurrent.Service.State;
|
||||
import com.google.inject.name.Named;
|
||||
import common.utils.JsonUtils;
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
@ -36,8 +44,12 @@ import monero.common.MoneroRpcConnection;
|
|||
import monero.common.MoneroUtils;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroNetworkType;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.daemon.model.MoneroSubmitTxResult;
|
||||
import monero.daemon.model.MoneroTx;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.MoneroWalletRpc;
|
||||
import monero.wallet.model.MoneroCheckTx;
|
||||
import monero.wallet.model.MoneroDestination;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroSubaddress;
|
||||
|
@ -217,6 +229,140 @@ public class XmrWalletService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the reserve tx and freeze its inputs. The deposit amount is returned
|
||||
* to the sender's payout address. Additional funds are reserved to allow
|
||||
* fluctuations in the mining fee.
|
||||
*
|
||||
* @param tradeFee is the trade fee
|
||||
* @param depositAmount the amount needed for the trade minus the trade fee
|
||||
* @return a transaction to reserve a trade
|
||||
*/
|
||||
public MoneroTxWallet createReserveTx(BigInteger tradeFee, String returnAddress, BigInteger depositAmount) {
|
||||
MoneroWallet wallet = getWallet();
|
||||
synchronized (wallet) {
|
||||
|
||||
// get expected mining fee
|
||||
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(returnAddress, depositAmount));
|
||||
BigInteger miningFee = miningFeeTx.getFee();
|
||||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
|
||||
|
||||
// freeze inputs
|
||||
for (MoneroOutput input : reserveTx.getInputs()) {
|
||||
wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
}
|
||||
|
||||
return reserveTx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the multisig deposit tx and freeze its inputs.
|
||||
*
|
||||
* @return MoneroTxWallet the multisig deposit tx
|
||||
*/
|
||||
public MoneroTxWallet createDepositTx(Trade trade) {
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
|
||||
Offer offer = trade.getProcessModel().getOffer();
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(trade instanceof SellerTrade ? offer.getAmount().add(offer.getSellerSecurityDeposit()) : offer.getBuyerSecurityDeposit());
|
||||
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
||||
MoneroWallet wallet = getWallet();
|
||||
synchronized (wallet) {
|
||||
|
||||
// create deposit tx
|
||||
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(multisigAddress, depositAmount));
|
||||
|
||||
// freeze deposit inputs
|
||||
for (MoneroOutput input : depositTx.getInputs()) {
|
||||
wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
}
|
||||
|
||||
return depositTx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a reserve or deposit transaction used during trading.
|
||||
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
|
||||
* The transaction is submitted but not relayed to the pool then flushed.
|
||||
*
|
||||
* @param depositAddress is the expected destination address for the deposit amount
|
||||
* @param depositAmount is the expected amount deposited to multisig
|
||||
* @param tradeFee is the expected fee for trading
|
||||
* @param txHash is the transaction hash
|
||||
* @param txHex is the transaction hex
|
||||
* @param txKey is the transaction key
|
||||
* @param keyImages are expected key images of inputs, ignored if null
|
||||
* @param miningFeePadding verifies depositAmount has additional funds to cover mining fee increase
|
||||
*/
|
||||
public void verifyTradeTx(String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, List<String> keyImages, boolean miningFeePadding) {
|
||||
boolean submittedToPool = false;
|
||||
MoneroDaemon daemon = getDaemon();
|
||||
MoneroWallet wallet = getWallet();
|
||||
try {
|
||||
|
||||
// get tx from daemon
|
||||
MoneroTx tx = daemon.getTx(txHash);
|
||||
|
||||
// if tx is not submitted, submit but do not relay
|
||||
if (tx == null) {
|
||||
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
|
||||
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
||||
submittedToPool = true;
|
||||
tx = daemon.getTx(txHash);
|
||||
} else if (tx.isRelayed()) {
|
||||
throw new RuntimeException("Trade tx must not be relayed");
|
||||
}
|
||||
|
||||
// verify reserved key images
|
||||
if (keyImages != null) {
|
||||
Set<String> txKeyImages = new HashSet<String>();
|
||||
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
||||
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Reserve tx's inputs do not match claimed key images");
|
||||
}
|
||||
|
||||
// verify the unlock height
|
||||
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||
|
||||
// verify trade fee
|
||||
String feeAddress = TradeUtils.FEE_ADDRESS;
|
||||
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
||||
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
||||
|
||||
// verify mining fee
|
||||
BigInteger feeEstimate = daemon.getFeeEstimate().multiply(BigInteger.valueOf(txHex.length())); // TODO (woodser): fee estimates are too high, use more accurate estimate
|
||||
BigInteger feeThreshold = feeEstimate.multiply(BigInteger.valueOf(1l)).divide(BigInteger.valueOf(2l)); // must be at least 50% of estimated fee
|
||||
tx = daemon.getTx(txHash);
|
||||
if (tx.getFee().compareTo(feeThreshold) < 0) {
|
||||
throw new RuntimeException("Mining fee is not enough, needed " + feeThreshold + " but was " + tx.getFee());
|
||||
}
|
||||
|
||||
// verify deposit amount
|
||||
check = wallet.checkTxKey(txHash, txKey, depositAddress);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
||||
BigInteger depositThreshold = depositAmount;
|
||||
if (miningFeePadding) depositThreshold = depositThreshold.add(feeThreshold.multiply(BigInteger.valueOf(3l))); // prove reserve of at least deposit amount + (3 * min mining fee)
|
||||
if (check.getReceivedAmount().compareTo(depositThreshold) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositThreshold + " but was " + check.getReceivedAmount());
|
||||
} finally {
|
||||
|
||||
// flush tx from pool if we added it
|
||||
if (submittedToPool) daemon.flushTxPool(txHash);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
closeAllWallets();
|
||||
}
|
||||
|
|
|
@ -76,12 +76,12 @@ public class TradeEvents {
|
|||
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
||||
msg = Res.get("account.notifications.trade.message.msg.conf", shortId);
|
||||
break;
|
||||
case FIAT_SENT:
|
||||
case PAYMENT_SENT:
|
||||
// We only notify the seller
|
||||
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getSellerPubKeyRing()))
|
||||
msg = Res.get("account.notifications.trade.message.msg.started", shortId);
|
||||
break;
|
||||
case FIAT_RECEIVED:
|
||||
case PAYMENT_RECEIVED:
|
||||
break;
|
||||
case PAYOUT_PUBLISHED:
|
||||
// We only notify the buyer
|
||||
|
|
|
@ -664,9 +664,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
Offer offer = new Offer(request.getOfferPayload());
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(offer.getDirection() == OfferPayload.Direction.BUY ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
||||
TradeUtils.processTradeTx(
|
||||
xmrWalletService.getDaemon(),
|
||||
xmrWalletService.getWallet(),
|
||||
xmrWalletService.verifyTradeTx(
|
||||
request.getPayoutAddress(),
|
||||
depositAmount,
|
||||
tradeFee,
|
||||
|
|
|
@ -22,13 +22,11 @@ import bisq.common.taskrunner.TaskRunner;
|
|||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
public class MakerReservesTradeFunds extends Task<PlaceOfferModel> {
|
||||
|
@ -45,26 +43,22 @@ public class MakerReservesTradeFunds extends Task<PlaceOfferModel> {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// synchronize on wallet to reserve key images
|
||||
synchronized (model.getXmrWalletService().getWallet()) {
|
||||
// freeze trade funds and get reserve tx
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer());
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(makerFee, returnAddress, depositAmount);
|
||||
|
||||
// create transaction to reserve trade
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer());
|
||||
MoneroTxWallet reserveTx = TradeUtils.reserveTradeFunds(model.getXmrWalletService(), offer.getId(), makerFee, returnAddress, depositAmount);
|
||||
// collect reserved key images // TODO (woodser): switch to proof of reserve?
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// collect reserved key images // TODO (woodser): switch to proof of reserve?
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// save offer state
|
||||
// TODO (woodser): persist
|
||||
model.setReserveTx(reserveTx);
|
||||
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
||||
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field
|
||||
complete();
|
||||
}
|
||||
// save offer state
|
||||
// TODO (woodser): persist
|
||||
model.setReserveTx(reserveTx);
|
||||
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
||||
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
offer.setErrorMessage("An error occurred.\n" +
|
||||
"Error message:\n"
|
||||
|
|
|
@ -38,7 +38,8 @@ import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
|
|||
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.messages.DepositRequest;
|
||||
|
@ -52,7 +53,7 @@ import bisq.core.trade.messages.InputsForDepositTxResponse;
|
|||
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
|
@ -182,11 +183,13 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||
case DEPOSIT_TX_AND_DELAYED_PAYOUT_TX_MESSAGE:
|
||||
return DepositTxAndDelayedPayoutTxMessage.fromProto(proto.getDepositTxAndDelayedPayoutTxMessage(), messageVersion);
|
||||
|
||||
case COUNTER_CURRENCY_TRANSFER_STARTED_MESSAGE:
|
||||
return CounterCurrencyTransferStartedMessage.fromProto(proto.getCounterCurrencyTransferStartedMessage(), messageVersion);
|
||||
|
||||
case PAYMENT_SENT_MESSAGE:
|
||||
return PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion);
|
||||
case PAYMENT_RECEIVED_MESSAGE:
|
||||
return PaymentReceivedMessage.fromProto(proto.getPaymentReceivedMessage(), messageVersion);
|
||||
case PAYOUT_TX_PUBLISHED_MESSAGE:
|
||||
return PayoutTxPublishedMessage.fromProto(proto.getPayoutTxPublishedMessage(), messageVersion);
|
||||
|
||||
case PEER_PUBLISHED_DELAYED_PAYOUT_TX_MESSAGE:
|
||||
return PeerPublishedDelayedPayoutTxMessage.fromProto(proto.getPeerPublishedDelayedPayoutTxMessage(), messageVersion);
|
||||
case TRADER_SIGNED_WITNESS_MESSAGE:
|
||||
|
|
|
@ -326,7 +326,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
|
||||
// update arbitrator's multisig wallet
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
|
||||
multisigWallet.importMultisigHex(openNewDisputeMessage.getUpdatedMultisigHex());
|
||||
log.info("Arbitrator multisig wallet updated on new dispute message for trade " + dispute.getTradeId());
|
||||
|
||||
// close multisig wallet
|
||||
|
|
|
@ -380,7 +380,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
// update multisig wallet
|
||||
if (xmrWalletService.multisigWalletExists(tradeId)) { // TODO: multisig wallet may already be deleted if peer completed trade with arbitrator. refactor trade completion?
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||
multisigWallet.importMultisigHex(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex());
|
||||
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
||||
xmrWalletService.closeMultisigWallet(tradeId);
|
||||
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
||||
|
@ -434,7 +434,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
// update arbitrator's multisig wallet with co-signer's multisig hex
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
try {
|
||||
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
|
||||
multisigWallet.importMultisigHex(request.getUpdatedMultisigHex());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to import multisig hex from payout co-signer for trade id " + tradeId);
|
||||
return;
|
||||
|
@ -550,7 +550,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
if (!expectedLoserAmount.equals(actualLoserAmount)) throw new RuntimeException("Unexpected loser payout: " + expectedLoserAmount + " vs " + actualLoserAmount);
|
||||
|
||||
// update multisig wallet from arbitrator
|
||||
multisigWallet.importMultisigHex(Arrays.asList(disputeResult.getArbitratorUpdatedMultisigHex()));
|
||||
multisigWallet.importMultisigHex(disputeResult.getArbitratorUpdatedMultisigHex());
|
||||
|
||||
// sign arbitrator-signed payout tx
|
||||
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.monetary.Price;
|
||||
|
@ -35,6 +36,7 @@ import bisq.core.trade.protocol.ProcessModelServiceProvider;
|
|||
import bisq.core.trade.protocol.TradeListener;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.trade.txproof.AssetTxProofResult;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.VolumeUtil;
|
||||
import bisq.network.p2p.AckMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
@ -43,10 +45,9 @@ import bisq.common.crypto.PubKeyRing;
|
|||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.taskrunner.Model;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
|
@ -61,7 +62,7 @@ import javafx.beans.property.StringProperty;
|
|||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
@ -85,6 +86,10 @@ import monero.common.MoneroError;
|
|||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroTx;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroDestination;
|
||||
import monero.wallet.model.MoneroMultisigSignResult;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxSet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
|
@ -148,21 +153,21 @@ public abstract class Trade implements Tradable, Model {
|
|||
DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN(Phase.DEPOSIT_CONFIRMED),
|
||||
|
||||
|
||||
// #################### Phase FIAT_SENT
|
||||
BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED(Phase.FIAT_SENT),
|
||||
BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
|
||||
BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
|
||||
BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
|
||||
BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
|
||||
// #################### Phase PAYMENT_SENT
|
||||
BUYER_CONFIRMED_IN_UI_PAYMENT_INITIATED(Phase.PAYMENT_SENT),
|
||||
BUYER_SENT_PAYMENT_INITIATED_MSG(Phase.PAYMENT_SENT),
|
||||
BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG(Phase.PAYMENT_SENT),
|
||||
BUYER_STORED_IN_MAILBOX_PAYMENT_INITIATED_MSG(Phase.PAYMENT_SENT),
|
||||
BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG(Phase.PAYMENT_SENT),
|
||||
|
||||
SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
|
||||
SELLER_RECEIVED_PAYMENT_INITIATED_MSG(Phase.PAYMENT_SENT),
|
||||
|
||||
// #################### Phase FIAT_RECEIVED
|
||||
// #################### Phase PAYMENT_RECEIVED
|
||||
// note that this state can also be triggered by auto confirmation feature
|
||||
SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT(Phase.FIAT_RECEIVED),
|
||||
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT(Phase.PAYMENT_RECEIVED),
|
||||
|
||||
// #################### Phase PAYOUT_PUBLISHED
|
||||
SELLER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED),
|
||||
SELLER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED), // TODO (woodser): this enum is over used, like during arbitration
|
||||
|
||||
SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
||||
SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
||||
|
@ -172,6 +177,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
||||
// Alternatively the maker could have seen the payout tx earlier before he received the PAYOUT_TX_PUBLISHED_MSG
|
||||
BUYER_SAW_PAYOUT_TX_IN_NETWORK(Phase.PAYOUT_PUBLISHED),
|
||||
BUYER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED),
|
||||
|
||||
|
||||
// #################### Phase WITHDRAWN
|
||||
|
@ -212,8 +218,8 @@ public abstract class Trade implements Tradable, Model {
|
|||
TAKER_FEE_PUBLISHED, // TODO (woodser): remove unused phases
|
||||
DEPOSIT_PUBLISHED,
|
||||
DEPOSIT_CONFIRMED, // TODO (woodser): rename to or add DEPOSIT_UNLOCKED
|
||||
FIAT_SENT,
|
||||
FIAT_RECEIVED,
|
||||
PAYMENT_SENT,
|
||||
PAYMENT_RECEIVED,
|
||||
PAYOUT_PUBLISHED,
|
||||
WITHDRAWN;
|
||||
|
||||
|
@ -699,6 +705,157 @@ public abstract class Trade implements Tradable, Model {
|
|||
else throw new RuntimeException("Unknown trade type: " + this.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a contract based on the current state.
|
||||
*
|
||||
* @param trade is the trade to create the contract from
|
||||
* @return the contract
|
||||
*/
|
||||
public Contract createContract() {
|
||||
boolean isBuyerMakerAndSellerTaker = getOffer().getDirection() == Direction.BUY;
|
||||
Contract contract = new Contract(
|
||||
getOffer().getOfferPayload(),
|
||||
checkNotNull(getTradeAmount()).value,
|
||||
getTradePrice().getValue(),
|
||||
isBuyerMakerAndSellerTaker ? getMakerNodeAddress() : getTakerNodeAddress(), // buyer node address // TODO (woodser): use maker and taker node address instead of buyer and seller node address for consistency
|
||||
isBuyerMakerAndSellerTaker ? getTakerNodeAddress() : getMakerNodeAddress(), // seller node address
|
||||
getArbitratorNodeAddress(),
|
||||
isBuyerMakerAndSellerTaker,
|
||||
this instanceof MakerTrade ? processModel.getAccountId() : getMaker().getAccountId(), // maker account id
|
||||
this instanceof TakerTrade ? processModel.getAccountId() : getTaker().getAccountId(), // taker account id
|
||||
checkNotNull(this instanceof MakerTrade ? processModel.getPaymentAccountPayload(this).getPaymentMethodId() : getOffer().getOfferPayload().getPaymentMethodId()), // maker payment method id
|
||||
checkNotNull(this instanceof TakerTrade ? processModel.getPaymentAccountPayload(this).getPaymentMethodId() : getTaker().getPaymentMethodId()), // taker payment method id
|
||||
this instanceof MakerTrade ? processModel.getPaymentAccountPayload(this).getHash() : getMaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
|
||||
this instanceof TakerTrade ? processModel.getPaymentAccountPayload(this).getHash() : getTaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
|
||||
getMakerPubKeyRing(),
|
||||
getTakerPubKeyRing(),
|
||||
this instanceof MakerTrade ? xmrWalletService.getAddressEntry(getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : getMaker().getPayoutAddressString(), // maker payout address
|
||||
this instanceof TakerTrade ? xmrWalletService.getAddressEntry(getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : getTaker().getPayoutAddressString(), // taker payout address
|
||||
getLockTime(),
|
||||
getMaker().getDepositTxHash(),
|
||||
getTaker().getDepositTxHash()
|
||||
);
|
||||
return contract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the payout tx.
|
||||
*
|
||||
* @return MoneroTxWallet the payout tx when the trade is successfully completed
|
||||
*/
|
||||
public MoneroTxWallet createPayoutTx() {
|
||||
|
||||
// gather relevant info
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(this.getId());
|
||||
String sellerPayoutAddress = this.getSeller().getPayoutAddressString();
|
||||
String buyerPayoutAddress = this.getBuyer().getPayoutAddressString();
|
||||
Preconditions.checkNotNull(sellerPayoutAddress, "Seller payout address must not be null");
|
||||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
||||
BigInteger sellerDepositAmount = multisigWallet.getTx(this.getSeller().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger buyerDepositAmount = multisigWallet.getTx(this.getBuyer().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(this.getTradeAmount());
|
||||
BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
|
||||
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
||||
|
||||
// create transaction to get fee estimate
|
||||
if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Cannot create payout tx because multisig import is needed");
|
||||
MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(9)).divide(BigInteger.valueOf(10))) // reduce payment amount to compute fee of similar tx
|
||||
.addDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(9)).divide(BigInteger.valueOf(10)))
|
||||
.setRelay(false)
|
||||
);
|
||||
|
||||
// attempt to create payout tx by increasing estimated fee until successful
|
||||
MoneroTxWallet payoutTx = null;
|
||||
int numAttempts = 0;
|
||||
while (payoutTx == null && numAttempts < 50) {
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful
|
||||
try {
|
||||
numAttempts++;
|
||||
payoutTx = multisigWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2)))) // split fee subtracted from each payout amount
|
||||
.addDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))
|
||||
.setRelay(false));
|
||||
} catch (MoneroError e) {
|
||||
// exception expected
|
||||
}
|
||||
}
|
||||
|
||||
if (payoutTx == null) throw new RuntimeException("Failed to generate payout tx after " + numAttempts + " attempts");
|
||||
log.info("Payout transaction generated on attempt {}: {}", numAttempts, payoutTx);
|
||||
return payoutTx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify and sign a payout tx.
|
||||
*
|
||||
* @param payoutTxHex is the payout tx hex to verify
|
||||
* @return String the signed payout tx hex
|
||||
*/
|
||||
public void verifySignAndPublishPayoutTx(String payoutTxHex) {
|
||||
log.info("Verifying payout tx");
|
||||
|
||||
// gather relevant info
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(getId());
|
||||
Contract contract = getContract();
|
||||
BigInteger sellerDepositAmount = multisigWallet.getTx(getSeller().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs this.getDepositTxId() necessary or avoidable?
|
||||
BigInteger buyerDepositAmount = multisigWallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getTradeAmount());
|
||||
|
||||
// parse payout tx
|
||||
MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
||||
if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack
|
||||
MoneroTxWallet payoutTx = parsedTxSet.getTxs().get(0);
|
||||
|
||||
// verify payout tx has exactly 2 destinations
|
||||
if (payoutTx.getOutgoingTransfer() == null || payoutTx.getOutgoingTransfer().getDestinations() == null || payoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Payout tx does not have exactly two destinations");
|
||||
|
||||
// get buyer and seller destinations (order not preserved)
|
||||
boolean buyerFirst = payoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString());
|
||||
MoneroDestination buyerPayoutDestination = payoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1);
|
||||
MoneroDestination sellerPayoutDestination = payoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0);
|
||||
|
||||
// verify payout addresses
|
||||
if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new RuntimeException("Buyer payout address does not match contract");
|
||||
if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new RuntimeException("Seller payout address does not match contract");
|
||||
|
||||
// verify change address is multisig's primary address
|
||||
if (!payoutTx.getChangeAmount().equals(BigInteger.ZERO) && !payoutTx.getChangeAddress().equals(multisigWallet.getPrimaryAddress())) throw new RuntimeException("Change address is not multisig wallet's primary address");
|
||||
|
||||
// verify sum of outputs = destination amounts + change amount
|
||||
if (!payoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(payoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount");
|
||||
|
||||
// verify buyer destination amount is deposit amount + this amount - 1/2 tx costs
|
||||
BigInteger txCost = payoutTx.getFee().add(payoutTx.getChangeAmount());
|
||||
BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2)));
|
||||
if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not deposit amount + trade amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
||||
|
||||
// verify seller destination amount is deposit amount - this amount - 1/2 tx costs
|
||||
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2)));
|
||||
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
||||
|
||||
// TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
|
||||
|
||||
// sign payout tx
|
||||
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing payout tx");
|
||||
String signedPayoutTxHex = result.getSignedMultisigTxHex();
|
||||
|
||||
// submit payout tx
|
||||
multisigWallet.submitMultisigTxHex(signedPayoutTxHex);
|
||||
walletService.closeMultisigWallet(getId());
|
||||
|
||||
// update trade state
|
||||
this.getSelf().setPayoutTxHex(signedPayoutTxHex);
|
||||
this.setPayoutTx(parsedTxSet.getTxs().get(0));
|
||||
this.setPayoutTxId(parsedTxSet.getTxs().get(0).getHash());
|
||||
this.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for deposit transactions to unlock and then apply the transactions.
|
||||
*
|
||||
|
@ -822,32 +979,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
this.delayedPayoutTxBytes = delayedPayoutTxBytes;
|
||||
}
|
||||
|
||||
// @Nullable
|
||||
// public Transaction getDelayedPayoutTx() {
|
||||
// return getDelayedPayoutTx(processModel.getBtcWalletService());
|
||||
// }
|
||||
//
|
||||
// // If called from a not initialized trade (or a closed or failed trade)
|
||||
// // we need to pass the xmrWalletService
|
||||
// @Nullable
|
||||
// public Transaction getDelayedPayoutTx(XmrWalletService xmrWalletService) {
|
||||
// if (delayedPayoutTx == null) {
|
||||
// if (xmrWalletService == null) {
|
||||
// log.warn("xmrWalletService is null. You might call that method before the tradeManager has " +
|
||||
// "initialized all trades");
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// if (delayedPayoutTxBytes == null) {
|
||||
// log.warn("delayedPayoutTxBytes are null");
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// delayedPayoutTx = xmrWalletService.getTxFromSerializedTx(delayedPayoutTxBytes);
|
||||
// }
|
||||
// return delayedPayoutTx;
|
||||
// }
|
||||
|
||||
public void addAndPersistChatMessage(ChatMessage chatMessage) {
|
||||
if (!chatMessages.contains(chatMessage)) {
|
||||
chatMessages.add(chatMessage);
|
||||
|
@ -1163,11 +1294,11 @@ public abstract class Trade implements Tradable, Model {
|
|||
}
|
||||
|
||||
public boolean isFiatSent() {
|
||||
return getState().getPhase().ordinal() >= Phase.FIAT_SENT.ordinal();
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_SENT.ordinal();
|
||||
}
|
||||
|
||||
public boolean isFiatReceived() {
|
||||
return getState().getPhase().ordinal() >= Phase.FIAT_RECEIVED.ordinal();
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
||||
}
|
||||
|
||||
public boolean isPayoutPublished() {
|
||||
|
|
|
@ -17,33 +17,16 @@
|
|||
|
||||
package bisq.core.trade;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferPayload.Direction;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import common.utils.JsonUtils;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.daemon.model.MoneroSubmitTxResult;
|
||||
import monero.daemon.model.MoneroTx;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroCheckTx;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
/**
|
||||
* Collection of utilities for trading.
|
||||
|
@ -136,162 +119,6 @@ public class TradeUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction to reserve a trade and freeze its funds. The deposit
|
||||
* amount is returned to the sender's payout address. Additional funds are
|
||||
* reserved to allow fluctuations in the mining fee.
|
||||
*
|
||||
* @param xmrWalletService
|
||||
* @param offerId
|
||||
* @param tradeFee
|
||||
* @param depositAmount
|
||||
* @return a transaction to reserve a trade
|
||||
*/
|
||||
public static MoneroTxWallet reserveTradeFunds(XmrWalletService xmrWalletService, String offerId, BigInteger tradeFee, String returnAddress, BigInteger depositAmount) {
|
||||
|
||||
// get expected mining fee
|
||||
MoneroWallet wallet = xmrWalletService.getWallet();
|
||||
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(returnAddress, depositAmount));
|
||||
BigInteger miningFee = miningFeeTx.getFee();
|
||||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
|
||||
|
||||
// freeze trade funds
|
||||
for (MoneroOutput input : reserveTx.getInputs()) {
|
||||
wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
}
|
||||
|
||||
return reserveTx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction to deposit funds to the multisig wallet.
|
||||
*
|
||||
* @param xmrWalletService
|
||||
* @param tradeFee
|
||||
* @param destinationAddress
|
||||
* @param depositAddress
|
||||
* @return MoneroTxWallet
|
||||
*/
|
||||
public static MoneroTxWallet createDepositTx(XmrWalletService xmrWalletService, BigInteger tradeFee, String depositAddress, BigInteger depositAmount) {
|
||||
return xmrWalletService.getWallet().createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(depositAddress, depositAmount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a reserve or deposit transaction used during trading.
|
||||
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
|
||||
* The transaction is submitted but not relayed to the pool then flushed.
|
||||
*
|
||||
* @param daemon is the Monero daemon to check for double spends
|
||||
* @param wallet is the Monero wallet to verify the tx
|
||||
* @param depositAddress is the expected destination address for the deposit amount
|
||||
* @param depositAmount is the expected amount deposited to multisig
|
||||
* @param tradeFee is the expected fee for trading
|
||||
* @param txHash is the transaction hash
|
||||
* @param txHex is the transaction hex
|
||||
* @param txKey is the transaction key
|
||||
* @param keyImages are expected key images of inputs, ignored if null
|
||||
* @param miningFeePadding verifies depositAmount has additional funds to cover mining fee increase
|
||||
*/
|
||||
public static void processTradeTx(MoneroDaemon daemon, MoneroWallet wallet, String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, List<String> keyImages, boolean miningFeePadding) {
|
||||
boolean submittedToPool = false;
|
||||
try {
|
||||
|
||||
// get tx from daemon
|
||||
MoneroTx tx = daemon.getTx(txHash);
|
||||
|
||||
// if tx is not submitted, submit but do not relay
|
||||
if (tx == null) {
|
||||
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
|
||||
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
||||
submittedToPool = true;
|
||||
tx = daemon.getTx(txHash);
|
||||
} else if (tx.isRelayed()) {
|
||||
throw new RuntimeException("Trade tx must not be relayed");
|
||||
}
|
||||
|
||||
// verify reserved key images
|
||||
if (keyImages != null) {
|
||||
Set<String> txKeyImages = new HashSet<String>();
|
||||
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
||||
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Reserve tx's inputs do not match claimed key images");
|
||||
}
|
||||
|
||||
// verify the unlock height
|
||||
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||
|
||||
// verify trade fee
|
||||
String feeAddress = TradeUtils.FEE_ADDRESS;
|
||||
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
||||
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
||||
|
||||
// verify mining fee
|
||||
BigInteger feeEstimate = daemon.getFeeEstimate().multiply(BigInteger.valueOf(txHex.length())); // TODO (woodser): fee estimates are too high, use more accurate estimate
|
||||
BigInteger feeThreshold = feeEstimate.multiply(BigInteger.valueOf(1l)).divide(BigInteger.valueOf(2l)); // must be at least 50% of estimated fee
|
||||
tx = daemon.getTx(txHash);
|
||||
if (tx.getFee().compareTo(feeThreshold) < 0) {
|
||||
throw new RuntimeException("Mining fee is not enough, needed " + feeThreshold + " but was " + tx.getFee());
|
||||
}
|
||||
|
||||
// verify deposit amount
|
||||
check = wallet.checkTxKey(txHash, txKey, depositAddress);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
||||
BigInteger depositThreshold = depositAmount;
|
||||
if (miningFeePadding) depositThreshold = depositThreshold.add(feeThreshold.multiply(BigInteger.valueOf(3l))); // prove reserve of at least deposit amount + (3 * min mining fee)
|
||||
if (check.getReceivedAmount().compareTo(depositThreshold) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositThreshold + " but was " + check.getReceivedAmount());
|
||||
} finally {
|
||||
|
||||
// flush tx from pool if we added it
|
||||
if (submittedToPool) daemon.flushTxPool(txHash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a contract from a trade.
|
||||
*
|
||||
* TODO (woodser): refactor/reduce trade, process model, and trading peer models
|
||||
*
|
||||
* @param trade is the trade to create the contract from
|
||||
* @return the contract
|
||||
*/
|
||||
public static Contract createContract(Trade trade) {
|
||||
boolean isBuyerMakerAndSellerTaker = trade.getOffer().getDirection() == Direction.BUY;
|
||||
Contract contract = new Contract(
|
||||
trade.getOffer().getOfferPayload(),
|
||||
checkNotNull(trade.getTradeAmount()).value,
|
||||
trade.getTradePrice().getValue(),
|
||||
isBuyerMakerAndSellerTaker ? trade.getMakerNodeAddress() : trade.getTakerNodeAddress(), // buyer node address // TODO (woodser): use maker and taker node address instead of buyer and seller node address for consistency
|
||||
isBuyerMakerAndSellerTaker ? trade.getTakerNodeAddress() : trade.getMakerNodeAddress(), // seller node address
|
||||
trade.getArbitratorNodeAddress(),
|
||||
isBuyerMakerAndSellerTaker,
|
||||
trade instanceof MakerTrade ? trade.getProcessModel().getAccountId() : trade.getMaker().getAccountId(), // maker account id
|
||||
trade instanceof TakerTrade ? trade.getProcessModel().getAccountId() : trade.getTaker().getAccountId(), // taker account id
|
||||
checkNotNull(trade instanceof MakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getPaymentMethodId() : trade.getOffer().getOfferPayload().getPaymentMethodId()), // maker payment method id
|
||||
checkNotNull(trade instanceof TakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getPaymentMethodId() : trade.getTaker().getPaymentMethodId()), // taker payment method id
|
||||
trade instanceof MakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getHash() : trade.getMaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
|
||||
trade instanceof TakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getHash() : trade.getTaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
|
||||
trade.getMakerPubKeyRing(),
|
||||
trade.getTakerPubKeyRing(),
|
||||
trade instanceof MakerTrade ? trade.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : trade.getMaker().getPayoutAddressString(), // maker payout address
|
||||
trade instanceof TakerTrade ? trade.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : trade.getTaker().getPayoutAddressString(), // taker payout address
|
||||
trade.getLockTime(),
|
||||
trade.getMaker().getDepositTxHash(),
|
||||
trade.getTaker().getDepositTxHash()
|
||||
);
|
||||
return contract;
|
||||
}
|
||||
|
||||
// TODO (woodser): remove the following utitilites?
|
||||
|
||||
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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 bisq.core.trade.messages;
|
||||
|
||||
import bisq.core.account.sign.SignedWitness;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Slf4j
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final String payoutTxHex;
|
||||
|
||||
// Added in v1.4.0
|
||||
@Nullable
|
||||
private final SignedWitness signedWitness;
|
||||
|
||||
public PaymentReceivedMessage(String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
@Nullable SignedWitness signedWitness,
|
||||
String signedPayoutTxHex) {
|
||||
this(tradeId,
|
||||
senderNodeAddress,
|
||||
signedWitness,
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
signedPayoutTxHex);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private PaymentReceivedMessage(String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
@Nullable SignedWitness signedWitness,
|
||||
String uid,
|
||||
String messageVersion,
|
||||
String signedPayoutTxHex) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.signedWitness = signedWitness;
|
||||
this.payoutTxHex = signedPayoutTxHex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
protobuf.PaymentReceivedMessage.Builder builder = protobuf.PaymentReceivedMessage.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setUid(uid)
|
||||
.setPayoutTxHex(payoutTxHex);
|
||||
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
|
||||
return getNetworkEnvelopeBuilder().setPaymentReceivedMessage(builder).build();
|
||||
}
|
||||
|
||||
public static NetworkEnvelope fromProto(protobuf.PaymentReceivedMessage proto, String messageVersion) {
|
||||
// There is no method to check for a nullable non-primitive data type object but we know that all fields
|
||||
// are empty/null, so we check for the signature to see if we got a valid signedWitness.
|
||||
protobuf.SignedWitness protoSignedWitness = proto.getSignedWitness();
|
||||
SignedWitness signedWitness = !protoSignedWitness.getSignature().isEmpty() ?
|
||||
SignedWitness.fromProto(protoSignedWitness) :
|
||||
null;
|
||||
return new PaymentReceivedMessage(proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
signedWitness,
|
||||
proto.getUid(),
|
||||
messageVersion,
|
||||
proto.getPayoutTxHex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SellerReceivedPaymentMessage{" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n signedWitness=" + signedWitness +
|
||||
",\n payoutTxHex=" + payoutTxHex +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -31,33 +31,38 @@ import javax.annotation.Nullable;
|
|||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMessage {
|
||||
public final class PaymentSentMessage extends TradeMailboxMessage {
|
||||
private final String buyerPayoutAddress;
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final String buyerPayoutTxSigned;
|
||||
@Nullable
|
||||
private final String counterCurrencyTxId;
|
||||
@Nullable
|
||||
private final String payoutTxHex;
|
||||
@Nullable
|
||||
private final String updatedMultisigHex;
|
||||
|
||||
// Added after v1.3.7
|
||||
// We use that for the XMR txKey but want to keep it generic to be flexible for data of other payment methods or assets.
|
||||
@Nullable
|
||||
private String counterCurrencyExtraData;
|
||||
|
||||
public CounterCurrencyTransferStartedMessage(String tradeId,
|
||||
public PaymentSentMessage(String tradeId,
|
||||
String buyerPayoutAddress,
|
||||
NodeAddress senderNodeAddress,
|
||||
String buyerPayoutTxSigned,
|
||||
@Nullable String counterCurrencyTxId,
|
||||
@Nullable String counterCurrencyExtraData,
|
||||
String uid) {
|
||||
String uid,
|
||||
String signedPayoutTxHex,
|
||||
String updatedMultisigHex) {
|
||||
this(tradeId,
|
||||
buyerPayoutAddress,
|
||||
senderNodeAddress,
|
||||
buyerPayoutTxSigned,
|
||||
counterCurrencyTxId,
|
||||
counterCurrencyExtraData,
|
||||
uid,
|
||||
Version.getP2PMessageVersion());
|
||||
Version.getP2PMessageVersion(),
|
||||
signedPayoutTxHex,
|
||||
updatedMultisigHex);
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,59 +70,64 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMailboxMes
|
|||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private CounterCurrencyTransferStartedMessage(String tradeId,
|
||||
private PaymentSentMessage(String tradeId,
|
||||
String buyerPayoutAddress,
|
||||
NodeAddress senderNodeAddress,
|
||||
String buyerPayoutTxSigned,
|
||||
@Nullable String counterCurrencyTxId,
|
||||
@Nullable String counterCurrencyExtraData,
|
||||
String uid,
|
||||
String messageVersion) {
|
||||
String messageVersion,
|
||||
@Nullable String signedPayoutTxHex,
|
||||
@Nullable String updatedMultisigHex) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.buyerPayoutAddress = buyerPayoutAddress;
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.buyerPayoutTxSigned = buyerPayoutTxSigned;
|
||||
this.counterCurrencyTxId = counterCurrencyTxId;
|
||||
this.counterCurrencyExtraData = counterCurrencyExtraData;
|
||||
this.payoutTxHex = signedPayoutTxHex;
|
||||
this.updatedMultisigHex = updatedMultisigHex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
final protobuf.CounterCurrencyTransferStartedMessage.Builder builder = protobuf.CounterCurrencyTransferStartedMessage.newBuilder();
|
||||
final protobuf.PaymentSentMessage.Builder builder = protobuf.PaymentSentMessage.newBuilder();
|
||||
builder.setTradeId(tradeId)
|
||||
.setBuyerPayoutAddress(buyerPayoutAddress)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setBuyerPayoutTxSigned(buyerPayoutTxSigned)
|
||||
.setUid(uid);
|
||||
|
||||
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
|
||||
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
|
||||
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
|
||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||
|
||||
return getNetworkEnvelopeBuilder().setCounterCurrencyTransferStartedMessage(builder).build();
|
||||
return getNetworkEnvelopeBuilder().setPaymentSentMessage(builder).build();
|
||||
}
|
||||
|
||||
public static CounterCurrencyTransferStartedMessage fromProto(protobuf.CounterCurrencyTransferStartedMessage proto,
|
||||
public static PaymentSentMessage fromProto(protobuf.PaymentSentMessage proto,
|
||||
String messageVersion) {
|
||||
return new CounterCurrencyTransferStartedMessage(proto.getTradeId(),
|
||||
return new PaymentSentMessage(proto.getTradeId(),
|
||||
proto.getBuyerPayoutAddress(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
proto.getBuyerPayoutTxSigned(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()),
|
||||
proto.getUid(),
|
||||
messageVersion);
|
||||
messageVersion,
|
||||
ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CounterCurrencyTransferStartedMessage{" +
|
||||
return "PaymentSentMessage{" +
|
||||
"\n buyerPayoutAddress='" + buyerPayoutAddress + '\'' +
|
||||
",\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n counterCurrencyTxId=" + counterCurrencyTxId +
|
||||
",\n counterCurrencyExtraData=" + counterCurrencyExtraData +
|
||||
",\n uid='" + uid + '\'' +
|
||||
",\n buyerPayoutTxSigned=" + buyerPayoutTxSigned +
|
||||
",\n payoutTxHex=" + payoutTxHex +
|
||||
",\n updatedMultisigHex=" + updatedMultisigHex +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -37,23 +37,23 @@ import javax.annotation.Nullable;
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
|
||||
private final String signedMultisigTxHex;
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final String payoutTxHex;
|
||||
|
||||
// Added in v1.4.0
|
||||
@Nullable
|
||||
private final SignedWitness signedWitness;
|
||||
|
||||
public PayoutTxPublishedMessage(String tradeId,
|
||||
String signedMultisigTxHex,
|
||||
NodeAddress senderNodeAddress,
|
||||
@Nullable SignedWitness signedWitness) {
|
||||
@Nullable SignedWitness signedWitness,
|
||||
String payoutTxHex) {
|
||||
this(tradeId,
|
||||
signedMultisigTxHex,
|
||||
senderNodeAddress,
|
||||
signedWitness,
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion());
|
||||
Version.getP2PMessageVersion(),
|
||||
payoutTxHex);
|
||||
}
|
||||
|
||||
|
||||
|
@ -62,24 +62,24 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private PayoutTxPublishedMessage(String tradeId,
|
||||
String signedMultisigTxHex,
|
||||
NodeAddress senderNodeAddress,
|
||||
@Nullable SignedWitness signedWitness,
|
||||
String uid,
|
||||
String messageVersion) {
|
||||
String messageVersion,
|
||||
String payoutTxHex) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.signedMultisigTxHex = signedMultisigTxHex;
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.signedWitness = signedWitness;
|
||||
this.payoutTxHex = payoutTxHex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
protobuf.PayoutTxPublishedMessage.Builder builder = protobuf.PayoutTxPublishedMessage.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setSignedMultisigTxHex(signedMultisigTxHex)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setUid(uid);
|
||||
.setUid(uid)
|
||||
.setPayoutTxHex(payoutTxHex);
|
||||
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
|
||||
return getNetworkEnvelopeBuilder().setPayoutTxPublishedMessage(builder).build();
|
||||
}
|
||||
|
@ -92,19 +92,19 @@ public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
|
|||
SignedWitness.fromProto(protoSignedWitness) :
|
||||
null;
|
||||
return new PayoutTxPublishedMessage(proto.getTradeId(),
|
||||
proto.getSignedMultisigTxHex(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
signedWitness,
|
||||
proto.getUid(),
|
||||
messageVersion);
|
||||
messageVersion,
|
||||
proto.getPayoutTxHex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PayoutTxPublishedMessage{" +
|
||||
"\n signedMultisigTxHex=" + signedMultisigTxHex +
|
||||
",\n senderNodeAddress=" + senderNodeAddress +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n signedWitness=" + signedWitness +
|
||||
",\n payoutTxHex=" + payoutTxHex +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import lombok.Value;
|
|||
|
||||
/**
|
||||
* Not used anymore since v1.4.0
|
||||
* We do the re-sending of the payment sent message via the BuyerSendCounterCurrencyTransferStartedMessage task on the
|
||||
* We do the re-sending of the payment sent message via the BuyerSendPaymentSentMessage task on the
|
||||
* buyer side, so seller do not need to do anything interactively.
|
||||
*/
|
||||
@Deprecated
|
||||
|
|
|
@ -26,7 +26,7 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
|||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||
|
@ -279,7 +279,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
|||
|
||||
// We keep the handler here in as well to make it more transparent which messages we expect
|
||||
@Override
|
||||
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
|
||||
protected void handle(PaymentReceivedMessage message, NodeAddress peer) {
|
||||
super.handle(message, peer);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
|||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
|
@ -296,7 +296,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||
|
||||
// We keep the handler here in as well to make it more transparent which messages we expect
|
||||
@Override
|
||||
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
|
||||
protected void handle(PaymentReceivedMessage message, NodeAddress peer) {
|
||||
super.handle(message, peer);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,15 +21,15 @@ import bisq.core.trade.BuyerTrade;
|
|||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerPreparesPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessesPaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
@ -63,16 +63,16 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
.setup(tasks(SetupDepositTxsListener.class))
|
||||
.executeTasks();
|
||||
|
||||
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
||||
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(BuyerEvent.STARTUP))
|
||||
.setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup?
|
||||
.executeTasks();
|
||||
|
||||
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
||||
.anyState(Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG,
|
||||
Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
|
||||
.anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_INITIATED_MSG,
|
||||
Trade.State.BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG)
|
||||
.with(BuyerEvent.STARTUP))
|
||||
.setup(tasks(BuyerSendCounterCurrencyTransferStartedMessage.class))
|
||||
.setup(tasks(BuyerSendsPaymentSentMessage.class))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
@ -82,8 +82,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
|
||||
if (message instanceof DepositTxAndDelayedPayoutTxMessage) {
|
||||
handle((DepositTxAndDelayedPayoutTxMessage) message, peer);
|
||||
} else if (message instanceof PayoutTxPublishedMessage) {
|
||||
handle((PayoutTxPublishedMessage) message, peer);
|
||||
} else if (message instanceof PaymentReceivedMessage) {
|
||||
handle((PaymentReceivedMessage) message, peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,25 +131,28 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
synchronized (trade) { // TODO (woodser): UpdateMultisigWithTradingPeer sends UpdateMultisigRequest and waits for UpdateMultisigResponse which is new thread, so synchronized (trade) in subsequent pipeline blocks forever if we hold on with countdown latch in this function
|
||||
System.out.println("BuyerProtocol.onPaymentStarted() has the lock!!!");
|
||||
BuyerEvent event = BuyerEvent.PAYMENT_SENT;
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
expect(phase(Trade.Phase.DEPOSIT_CONFIRMED)
|
||||
.with(event)
|
||||
.preCondition(trade.confirmPermitted()))
|
||||
.setup(tasks(ApplyFilter.class,
|
||||
getVerifyPeersFeePaymentClass(),
|
||||
UpdateMultisigWithTradingPeer.class,
|
||||
BuyerCreateAndSignPayoutTx.class,
|
||||
BuyerSetupPayoutTxListener.class,
|
||||
BuyerSendCounterCurrencyTransferStartedMessage.class)
|
||||
//UpdateMultisigWithTradingPeer.class, // TODO (woodser): can use this to test protocol with updated multisig from peer. peer should attempt to send updated multisig hex earlier as part of protocol. cannot use with countdown latch because response comes back in a separate thread and blocks on trade
|
||||
BuyerPreparesPaymentSentMessage.class,
|
||||
//BuyerSetupPayoutTxListener.class,
|
||||
BuyerSendsPaymentSentMessage.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
latch.countDown();
|
||||
resultHandler.handleResult();
|
||||
handleTaskRunnerSuccess(event);
|
||||
},
|
||||
(errorMessage) -> {
|
||||
latch.countDown();
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED))
|
||||
.run(() -> trade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_PAYMENT_INITIATED))
|
||||
.executeTasks();
|
||||
}
|
||||
}
|
||||
|
@ -158,18 +161,18 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
// Incoming message Payout tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
|
||||
log.info("BuyerProtocol.handle(PayoutTxPublishedMessage)");
|
||||
protected void handle(PaymentReceivedMessage message, NodeAddress peer) {
|
||||
log.info("BuyerProtocol.handle(SellerReceivedPaymentMessage)");
|
||||
synchronized (trade) {
|
||||
processModel.setTradeMessage(message);
|
||||
processModel.setTempTradingPeerNodeAddress(peer);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
expect(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
|
||||
expect(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(
|
||||
getVerifyPeersFeePaymentClass(),
|
||||
BuyerProcessPayoutTxPublishedMessage.class)
|
||||
BuyerProcessesPaymentReceivedMessage.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
latch.countDown();
|
||||
|
@ -200,8 +203,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
handle((DelayedPayoutTxSignatureRequest) message, peer);
|
||||
} else if (message instanceof DepositTxAndDelayedPayoutTxMessage) {
|
||||
handle((DepositTxAndDelayedPayoutTxMessage) message, peer);
|
||||
} else if (message instanceof PayoutTxPublishedMessage) {
|
||||
handle((PayoutTxPublishedMessage) message, peer);
|
||||
} else if (message instanceof PaymentReceivedMessage) {
|
||||
handle((PaymentReceivedMessage) message, peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,8 +62,8 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
|||
public void onAcceptMediationResult(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED;
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED)
|
||||
Trade.Phase.PAYMENT_SENT,
|
||||
Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(event)
|
||||
.preCondition(trade.getTradingPeer().getMediatedPayoutTxSignature() == null,
|
||||
() -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer."))
|
||||
|
@ -89,8 +89,8 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
|||
public void onFinalizeMediationResultPayout(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED;
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED)
|
||||
Trade.Phase.PAYMENT_SENT,
|
||||
Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(event)
|
||||
.preCondition(trade.getPayoutTx() == null,
|
||||
() -> errorMessageHandler.handleErrorMessage("Payout tx is already published.")))
|
||||
|
@ -118,8 +118,8 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
|||
|
||||
protected void handle(MediatedPayoutTxSignatureMessage message, NodeAddress peer) {
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED)
|
||||
Trade.Phase.PAYMENT_SENT,
|
||||
Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(ProcessMediatedPayoutSignatureMessage.class))
|
||||
|
@ -128,8 +128,8 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
|||
|
||||
protected void handle(MediatedPayoutTxPublishedMessage message, NodeAddress peer) {
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED)
|
||||
Trade.Phase.PAYMENT_SENT,
|
||||
Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(ProcessMediatedPayoutTxPublishedMessage.class))
|
||||
|
@ -168,8 +168,8 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
|||
|
||||
private void handle(PeerPublishedDelayedPayoutTxMessage message, NodeAddress peer) {
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED,
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED)
|
||||
Trade.Phase.PAYMENT_SENT,
|
||||
Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(ProcessPeerPublishedDelayedPayoutTxMessage.class))
|
||||
|
|
|
@ -289,7 +289,7 @@ public class FluentProtocol {
|
|||
return Result.VALID.info(info);
|
||||
} else {
|
||||
String info = MessageFormat.format("We received a {0} but we are are not in the expected phase.\n" +
|
||||
"This can be an expected case if we get a repeated CounterCurrencyTransferStartedMessage " +
|
||||
"This can be an expected case if we get a repeated PaymentSentMessage " +
|
||||
"after we have already received one as the peer re-sends that message at each startup.\n" +
|
||||
"Expected phases={1},\nTrade phase={2},\nTrade state= {3},\ntradeId={4}",
|
||||
trigger,
|
||||
|
|
|
@ -186,12 +186,10 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||
@Getter
|
||||
@Setter
|
||||
private boolean multisigSetupComplete; // TODO (woodser): redundant with multisigAddress existing, remove
|
||||
@Nullable
|
||||
transient private MoneroTxWallet buyerSignedPayoutTx; // TODO (woodser): remove
|
||||
|
||||
|
||||
// We want to indicate the user the state of the message delivery of the
|
||||
// CounterCurrencyTransferStartedMessage. As well we do an automatic re-send in case it was not ACKed yet.
|
||||
// PaymentSentMessage. As well we do an automatic re-send in case it was not ACKed yet.
|
||||
// To enable that even after restart we persist the state.
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentStartedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
|
@ -421,13 +419,4 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||
public KeyRing getKeyRing() {
|
||||
return provider.getKeyRing();
|
||||
}
|
||||
|
||||
public void setBuyerSignedPayoutTx(MoneroTxWallet buyerSignedPayoutTx) {
|
||||
this.buyerSignedPayoutTx = buyerSignedPayoutTx;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MoneroTxWallet getBuyerSignedPayoutTx() {
|
||||
return buyerSignedPayoutTx;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ package bisq.core.trade.protocol;
|
|||
import bisq.core.trade.SellerAsMakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.Trade.State;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
|
@ -322,7 +322,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
|||
|
||||
// We keep the handler here in as well to make it more transparent which messages we expect
|
||||
@Override
|
||||
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
|
||||
protected void handle(PaymentSentMessage message, NodeAddress peer) {
|
||||
super.handle(message, peer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import bisq.core.trade.SellerAsTakerTrade;
|
|||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.Trade.State;
|
||||
import bisq.core.trade.handlers.TradeResultHandler;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
|
@ -279,7 +279,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||
|
||||
// We keep the handler here in as well to make it more transparent which messages we expect
|
||||
@Override
|
||||
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
|
||||
protected void handle(PaymentSentMessage message, NodeAddress peer) {
|
||||
super.handle(message, peer);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,15 +19,15 @@ package bisq.core.trade.protocol;
|
|||
|
||||
import bisq.core.trade.SellerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessesPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendsPaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPreparesPaymentReceivedMessage;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
@ -66,8 +66,8 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
public void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
||||
super.onMailboxMessage(message, peerNodeAddress);
|
||||
|
||||
if (message instanceof CounterCurrencyTransferStartedMessage) {
|
||||
handle((CounterCurrencyTransferStartedMessage) message, peerNodeAddress);
|
||||
if (message instanceof PaymentSentMessage) {
|
||||
handle((PaymentSentMessage) message, peerNodeAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,39 +76,31 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
// Incoming message when buyer has clicked payment started button
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
|
||||
log.info("SellerProtocol.handle(CounterCurrencyTransferStartedMessage)");
|
||||
protected void handle(PaymentSentMessage message, NodeAddress peer) {
|
||||
log.info("SellerProtocol.handle(PaymentSentMessage)");
|
||||
// We are more tolerant with expected phase and allow also DEPOSIT_PUBLISHED as it can be the case
|
||||
// that the wallet is still syncing and so the DEPOSIT_CONFIRMED state to yet triggered when we received
|
||||
// a mailbox message with CounterCurrencyTransferStartedMessage.
|
||||
// a mailbox message with PaymentSentMessage.
|
||||
// TODO A better fix would be to add a listener for the wallet sync state and process
|
||||
// the mailbox msg once wallet is ready and trade state set.
|
||||
synchronized (trade) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
//CountDownLatch latch = new CountDownLatch(1); // TODO: apply latch countdown
|
||||
expect(anyPhase(Trade.Phase.DEPOSIT_CONFIRMED, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(message)
|
||||
.from(peer)
|
||||
.preCondition(trade.getPayoutTx() == null,
|
||||
() -> {
|
||||
log.warn("We received a CounterCurrencyTransferStartedMessage but we have already created the payout tx " +
|
||||
log.warn("We received a PaymentSentMessage but we have already created the payout tx " +
|
||||
"so we ignore the message. This can happen if the ACK message to the peer did not " +
|
||||
"arrive and the peer repeats sending us the message. We send another ACK msg.");
|
||||
sendAckMessage(peer, message, true, null);
|
||||
removeMailboxMessageAfterProcessing(message);
|
||||
}))
|
||||
.setup(tasks(
|
||||
SellerProcessCounterCurrencyTransferStartedMessage.class,
|
||||
SellerProcessesPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
getVerifyPeersFeePaymentClass())
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
latch.countDown();
|
||||
},
|
||||
(errorMessage) -> {
|
||||
latch.countDown();
|
||||
})))
|
||||
getVerifyPeersFeePaymentClass()))
|
||||
.executeTasks();
|
||||
wait(latch);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,40 +111,34 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
log.info("SellerProtocol.onPaymentReceived()");
|
||||
synchronized (trade) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
SellerEvent event = SellerEvent.PAYMENT_RECEIVED;
|
||||
expect(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
|
||||
// CountDownLatch latch = new CountDownLatch(1); // TODO (woodser): user countdown latch, but freezes legacy app
|
||||
expect(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYOUT_PUBLISHED)
|
||||
.with(event)
|
||||
.preCondition(trade.confirmPermitted()))
|
||||
.setup(tasks(
|
||||
ApplyFilter.class,
|
||||
getVerifyPeersFeePaymentClass(),
|
||||
SellerSignAndPublishPayoutTx.class,
|
||||
// SellerSignAndFinalizePayoutTx.class,
|
||||
// SellerBroadcastPayoutTx.class,
|
||||
SellerSendPayoutTxPublishedMessage.class)
|
||||
SellerPreparesPaymentReceivedMessage.class,
|
||||
SellerSendsPaymentReceivedMessage.class)
|
||||
.using(new TradeTaskRunner(trade, () -> {
|
||||
latch.countDown();
|
||||
resultHandler.handleResult();
|
||||
handleTaskRunnerSuccess(event);
|
||||
}, (errorMessage) -> {
|
||||
latch.countDown();
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT))
|
||||
.run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT))
|
||||
.executeTasks();
|
||||
wait(latch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
||||
super.onTradeMessage(message, peer);
|
||||
|
||||
if (message instanceof CounterCurrencyTransferStartedMessage) {
|
||||
handle((CounterCurrencyTransferStartedMessage) message, peer);
|
||||
if (message instanceof PaymentSentMessage) {
|
||||
handle((PaymentSentMessage) message, peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import bisq.core.offer.Offer;
|
|||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.handlers.TradeResultHandler;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
|
@ -294,10 +294,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
|
||||
// TODO (woodser): support notifications of ack messages
|
||||
private void onAckMessage(AckMessage ackMessage, NodeAddress peer) {
|
||||
// We handle the ack for CounterCurrencyTransferStartedMessage and DepositTxAndDelayedPayoutTxMessage
|
||||
// We handle the ack for PaymentSentMessage and DepositTxAndDelayedPayoutTxMessage
|
||||
// as we support automatic re-send of the msg in case it was not ACKed after a certain time
|
||||
// TODO (woodser): add AckMessage for InitTradeRequest and support automatic re-send ?
|
||||
if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) {
|
||||
if (ackMessage.getSourceMsgClassName().equals(PaymentSentMessage.class.getSimpleName())) {
|
||||
processModel.setPaymentStartedAckMessage(ackMessage);
|
||||
} else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) {
|
||||
processModel.setDepositTxSentAckMessage(ackMessage);
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.stream.Collectors;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
||||
|
@ -108,13 +108,17 @@ public final class TradingPeer implements PersistablePayload {
|
|||
@Nullable
|
||||
private String madeMultisigHex;
|
||||
@Nullable
|
||||
private String signedPayoutTxHex;
|
||||
@Nullable
|
||||
private String depositTxHash;
|
||||
@Nullable
|
||||
private String depositTxHex;
|
||||
@Nullable
|
||||
private String depositTxKey;
|
||||
@Nullable
|
||||
transient private MoneroTxWallet payoutTx;
|
||||
@Nullable
|
||||
private String payoutTxHex;
|
||||
@Nullable
|
||||
private String updatedMultisigHex;
|
||||
|
||||
public TradingPeer() {
|
||||
}
|
||||
|
@ -146,10 +150,11 @@ public final class TradingPeer implements PersistablePayload {
|
|||
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
||||
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
|
||||
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
||||
Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
|
||||
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
|
||||
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
|
||||
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
|
||||
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||
|
||||
builder.setCurrentDate(currentDate);
|
||||
return builder.build();
|
||||
|
@ -189,10 +194,11 @@ public final class TradingPeer implements PersistablePayload {
|
|||
tradingPeer.setReserveTxKeyImages(proto.getReserveTxKeyImagesList());
|
||||
tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
|
||||
tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
||||
tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()));
|
||||
tradingPeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
|
||||
tradingPeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
|
||||
tradingPeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
||||
tradingPeer.setPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()));
|
||||
tradingPeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
||||
return tradingPeer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ import bisq.common.app.Version;
|
|||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.trade.messages.DepositRequest;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
|
@ -37,7 +37,6 @@ import java.util.Date;
|
|||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
@Slf4j
|
||||
public class ArbitratorProcessesDepositRequest extends TradeTask {
|
||||
|
@ -86,14 +85,12 @@ public class ArbitratorProcessesDepositRequest extends TradeTask {
|
|||
else throw new RuntimeException("DepositRequest is not from maker or taker");
|
||||
|
||||
// flush reserve tx from pool
|
||||
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
||||
XmrWalletService xmrWalletService = trade.getXmrWalletService();
|
||||
MoneroDaemon daemon = xmrWalletService.getDaemon();
|
||||
daemon.flushTxPool(trader.getReserveTxHash());
|
||||
|
||||
// process and verify deposit tx
|
||||
TradeUtils.processTradeTx(
|
||||
daemon,
|
||||
trade.getXmrWalletService().getWallet(),
|
||||
depositAddress,
|
||||
// verify deposit tx
|
||||
xmrWalletService.verifyTradeTx(depositAddress,
|
||||
depositAmount,
|
||||
tradeFee,
|
||||
trader.getDepositTxHash(),
|
||||
|
|
|
@ -55,9 +55,7 @@ public class ArbitratorProcessesReserveTx extends TradeTask {
|
|||
// process reserve tx with expected terms
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
||||
TradeUtils.processTradeTx(
|
||||
processModel.getXmrWalletService().getDaemon(),
|
||||
processModel.getXmrWalletService().getWallet(),
|
||||
trade.getXmrWalletService().verifyTradeTx(
|
||||
request.getPayoutAddress(),
|
||||
depositAmount,
|
||||
tradeFee,
|
||||
|
|
|
@ -27,7 +27,6 @@ import bisq.core.trade.ArbitratorTrade;
|
|||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.Trade.State;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
|
@ -71,7 +70,7 @@ public class ProcessSignContractRequest extends TradeTask {
|
|||
}
|
||||
|
||||
// create and sign contract
|
||||
Contract contract = TradeUtils.createContract(trade);
|
||||
Contract contract = trade.createContract();
|
||||
String contractAsJson = Utilities.objectToJson(contract);
|
||||
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);
|
||||
|
||||
|
|
|
@ -72,9 +72,9 @@ public class ProcessUpdateMultisigRequest extends TradeTask {
|
|||
String updatedMultisigHex = multisigWallet.getMultisigHex();
|
||||
|
||||
// import the multisig hex
|
||||
int numOutputsSigned = multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
|
||||
int numOutputsSigned = multisigWallet.importMultisigHex(request.getUpdatedMultisigHex());
|
||||
System.out.println("Num outputs signed by imported multisig hex: " + numOutputsSigned);
|
||||
|
||||
|
||||
// close multisig wallet
|
||||
processModel.getProvider().getXmrWalletService().closeMultisigWallet(trade.getId());
|
||||
|
||||
|
|
|
@ -18,25 +18,14 @@
|
|||
package bisq.core.trade.protocol.tasks;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.SellerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.protocol.TradeListener;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.network.p2p.AckMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
|
@ -57,88 +46,78 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
synchronized (trade.getXmrWalletService().getWallet()) { // synchronize on wallet to create deposit tx and freeze funds
|
||||
|
||||
// skip if multisig wallet not complete
|
||||
if (!processModel.isMultisigSetupComplete()) {
|
||||
complete();
|
||||
return; // TODO: woodser: this does not ack original request?
|
||||
}
|
||||
|
||||
// skip if deposit tx already created
|
||||
if (processModel.getDepositTxXmr() != null) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// thaw reserved outputs
|
||||
MoneroWallet wallet = trade.getXmrWalletService().getWallet();
|
||||
for (String reserveTxKeyImage : trade.getSelf().getReserveTxKeyImages()) {
|
||||
wallet.thawOutput(reserveTxKeyImage);
|
||||
}
|
||||
|
||||
// create deposit tx
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
|
||||
Offer offer = processModel.getOffer();
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(trade instanceof SellerTrade ? offer.getAmount().add(offer.getSellerSecurityDeposit()) : offer.getBuyerSecurityDeposit());
|
||||
String multisigAddress = processModel.getMultisigAddress();
|
||||
MoneroTxWallet depositTx = TradeUtils.createDepositTx(trade.getXmrWalletService(), tradeFee, multisigAddress, depositAmount);
|
||||
|
||||
// freeze deposit outputs
|
||||
// TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
|
||||
for (MoneroOutput input : depositTx.getInputs()) {
|
||||
wallet.freezeOutput(input.getKeyImage().getHex());
|
||||
}
|
||||
|
||||
// save process state
|
||||
processModel.setDepositTxXmr(depositTx);
|
||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||
|
||||
// create request for peer and arbitrator to sign contract
|
||||
SignContractRequest request = new SignContractRequest(
|
||||
trade.getOffer().getId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getPubKeyRing(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
trade.getProcessModel().getAccountId(),
|
||||
trade.getProcessModel().getPaymentAccountPayload(trade).getHash(),
|
||||
trade.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(),
|
||||
depositTx.getHash());
|
||||
|
||||
// send request to trading peer
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId());
|
||||
ack1 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// send request to arbitrator
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId());
|
||||
ack2 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
// skip if multisig wallet not complete
|
||||
if (!processModel.isMultisigSetupComplete()) {
|
||||
complete();
|
||||
return; // TODO: woodser: this does not ack original request?
|
||||
}
|
||||
|
||||
// skip if deposit tx already created
|
||||
if (processModel.getDepositTxXmr() != null) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// thaw reserved outputs
|
||||
MoneroWallet wallet = trade.getXmrWalletService().getWallet();
|
||||
for (String reserveTxKeyImage : trade.getSelf().getReserveTxKeyImages()) {
|
||||
wallet.thawOutput(reserveTxKeyImage);
|
||||
}
|
||||
|
||||
// create deposit tx and freeze inputs
|
||||
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
||||
|
||||
// TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
|
||||
|
||||
// save process state
|
||||
processModel.setDepositTxXmr(depositTx);
|
||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address?
|
||||
|
||||
// create request for peer and arbitrator to sign contract
|
||||
SignContractRequest request = new SignContractRequest(
|
||||
trade.getOffer().getId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getPubKeyRing(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
trade.getProcessModel().getAccountId(),
|
||||
trade.getProcessModel().getPaymentAccountPayload(trade).getHash(),
|
||||
trade.getSelf().getPayoutAddressString(),
|
||||
depositTx.getHash());
|
||||
|
||||
// send request to trading peer
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId());
|
||||
ack1 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// send request to arbitrator
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId());
|
||||
ack2 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ public class UpdateMultisigWithTradingPeer extends TradeTask {
|
|||
|
||||
// fetch relevant trade info
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // closed in BuyerCreateAndSignPayoutTx
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // closed in BuyerPreparesPaymentStartedMessage
|
||||
|
||||
// skip if multisig wallet does not need updated
|
||||
if (!multisigWallet.isMultisigImportNeeded()) {
|
||||
|
@ -72,8 +72,8 @@ public class UpdateMultisigWithTradingPeer extends TradeTask {
|
|||
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) {
|
||||
if (!(message instanceof UpdateMultisigResponse)) return;
|
||||
UpdateMultisigResponse response = (UpdateMultisigResponse) message;
|
||||
multisigWallet.importMultisigHex(Arrays.asList(response.getUpdatedMultisigHex()));
|
||||
multisigWallet.sync();
|
||||
multisigWallet.importMultisigHex(response.getUpdatedMultisigHex());
|
||||
trade.removeListener(updateMultisigResponseListener);
|
||||
complete();
|
||||
}
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
package bisq.core.trade.protocol.tasks.buyer;
|
||||
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
|
@ -38,18 +36,16 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
|
||||
|
||||
import monero.common.MoneroError;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroAccount;
|
||||
import monero.wallet.model.MoneroSubaddress;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerCreateAndSignPayoutTx extends TradeTask {
|
||||
public class BuyerPreparesPaymentSentMessage extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public BuyerCreateAndSignPayoutTx(TaskRunner taskHandler, Trade trade) {
|
||||
public BuyerPreparesPaymentSentMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
|
@ -57,61 +53,36 @@ public class BuyerCreateAndSignPayoutTx extends TradeTask {
|
|||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
|
||||
// validate state
|
||||
Preconditions.checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
|
||||
Preconditions.checkNotNull(trade.getMakerDepositTx(), "trade.getMakerDepositTx() must not be null");
|
||||
Preconditions.checkNotNull(trade.getTakerDepositTx(), "trade.getTakerDepositTx() must not be null");
|
||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
|
||||
// gather relevant trade info
|
||||
|
||||
// get multisig wallet
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
||||
String sellerPayoutAddress = trade.getTradingPeer().getPayoutAddressString();
|
||||
String buyerPayoutAddress = trade instanceof MakerTrade ? trade.getContract().getMakerPayoutAddressString() : trade.getContract().getTakerPayoutAddressString();
|
||||
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
|
||||
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
|
||||
BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getTaker().getDepositTxHash() : processModel.getMaker().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getMaker().getDepositTxHash() : processModel.getTaker().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(trade.getTradeAmount());
|
||||
BigInteger buyerPayoutAmount = buyerDepositAmount.add(tradeAmount);
|
||||
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
||||
|
||||
// create transaction to get fee estimate
|
||||
if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Multisig import is still needed!!!");
|
||||
MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(9)).divide(BigInteger.valueOf(10))) // reduce payment amount to compute fee of similar tx
|
||||
.addDestination(sellerPayoutAddress, sellerPayoutAmount.multiply(BigInteger.valueOf(9)).divide(BigInteger.valueOf(10)))
|
||||
.setRelay(false)
|
||||
);
|
||||
|
||||
// attempt to create payout tx by increasing estimated fee until successful
|
||||
MoneroTxWallet payoutTx = null;
|
||||
int numAttempts = 0;
|
||||
while (payoutTx == null && numAttempts < 50) {
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee().add(feeEstimateTx.getFee().multiply(BigInteger.valueOf(numAttempts)).divide(BigInteger.valueOf(10))); // add 1/10 of fee until tx is successful
|
||||
try {
|
||||
numAttempts++;
|
||||
payoutTx = multisigWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(buyerPayoutAddress, buyerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2)))) // split fee subtracted from each payout amount
|
||||
.addDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))
|
||||
.setRelay(false));
|
||||
} catch (MoneroError e) {
|
||||
// exception expected
|
||||
}
|
||||
// create payout tx if we have seller's updated multisig hex
|
||||
if (!multisigWallet.isMultisigImportNeeded()) {
|
||||
log.info("Buyer creating unsigned payout tx");
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
trade.getBuyer().setPayoutTx(payoutTx);
|
||||
trade.getBuyer().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
} else {
|
||||
trade.getSelf().setUpdatedMultisigHex(multisigWallet.getMultisigHex());
|
||||
}
|
||||
|
||||
if (payoutTx == null) throw new RuntimeException("Failed to generate payout tx after " + numAttempts + " attempts");
|
||||
log.info("Payout transaction generated on attempt {}: {}", numAttempts, payoutTx);
|
||||
processModel.setBuyerSignedPayoutTx(payoutTx);
|
||||
|
||||
// close multisig wallet
|
||||
walletService.closeMultisigWallet(trade.getId());
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (woodser): move these to gen utils
|
||||
|
||||
/**
|
||||
* Generic parameterized pair.
|
|
@ -20,7 +20,7 @@ package bisq.core.trade.protocol.tasks.buyer;
|
|||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
|
@ -38,8 +38,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
import monero.wallet.MoneroWallet;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerProcessPayoutTxPublishedMessage extends TradeTask {
|
||||
public BuyerProcessPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
public class BuyerProcessesPaymentReceivedMessage extends TradeTask {
|
||||
public BuyerProcessesPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
|
@ -48,23 +48,34 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask {
|
|||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
PayoutTxPublishedMessage message = (PayoutTxPublishedMessage) processModel.getTradeMessage();
|
||||
PaymentReceivedMessage message = (PaymentReceivedMessage) processModel.getTradeMessage();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkNotNull(message);
|
||||
checkArgument(message.getSignedMultisigTxHex() != null);
|
||||
checkArgument(message.getPayoutTxHex() != null);
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
||||
|
||||
// handle if payout tx is not seen on network
|
||||
if (trade.getPayoutTx() == null) {
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
||||
List<String> txHashes = multisigWallet.submitMultisigTxHex(message.getSignedMultisigTxHex());
|
||||
trade.setPayoutTx(multisigWallet.getTx(txHashes.get(0)));
|
||||
XmrWalletService.printTxs("payoutTx received from peer", trade.getPayoutTx());
|
||||
trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
walletService.closeMultisigWallet(trade.getId());
|
||||
//processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
|
||||
|
||||
// publish payout tx if signed. otherwise verify, sign, and publish payout tx
|
||||
boolean fullySigned = trade.getSelf().getPayoutTx() != null;
|
||||
if (fullySigned) {
|
||||
log.info("Buyer publishing signed payout tx from seller");
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
||||
List<String> txHashes = multisigWallet.submitMultisigTxHex(message.getPayoutTxHex());
|
||||
trade.setPayoutTx(multisigWallet.getTx(txHashes.get(0)));
|
||||
XmrWalletService.printTxs("payoutTx received from peer", trade.getPayoutTx());
|
||||
trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
walletService.closeMultisigWallet(trade.getId());
|
||||
} else {
|
||||
log.info("Buyer verifying, signing, and publishing seller's payout tx");
|
||||
trade.verifySignAndPublishPayoutTx(message.getPayoutTxHex());
|
||||
trade.setState(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
|
||||
// TODO (woodser): send PayoutTxPublishedMessage to arbitrator and seller
|
||||
}
|
||||
} else {
|
||||
log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
|
||||
}
|
|
@ -21,7 +21,7 @@ import bisq.core.btc.model.XmrAddressEntry;
|
|||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.network.MessageState;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.messages.TradeMailboxMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.SendMailboxMessageTask;
|
||||
|
@ -35,9 +35,10 @@ import javafx.beans.value.ChangeListener;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
/**
|
||||
* We send the seller the BuyerSendCounterCurrencyTransferStartedMessage.
|
||||
* We send the seller the BuyerSendPaymentSentMessage.
|
||||
* We wait to receive a ACK message back and resend the message
|
||||
* in case that does not happen in 10 minutes or if the message was stored in mailbox or failed. We keep repeating that
|
||||
* with doubling the interval each time and until the MAX_RESEND_ATTEMPTS is reached.
|
||||
|
@ -46,15 +47,15 @@ import lombok.extern.slf4j.Slf4j;
|
|||
* online he will process it.
|
||||
*/
|
||||
@Slf4j
|
||||
public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxMessageTask {
|
||||
public class BuyerSendsPaymentSentMessage extends SendMailboxMessageTask {
|
||||
private static final int MAX_RESEND_ATTEMPTS = 10;
|
||||
private int delayInMin = 15;
|
||||
private int resendCounter = 0;
|
||||
private CounterCurrencyTransferStartedMessage message;
|
||||
private PaymentSentMessage message;
|
||||
private ChangeListener<MessageState> listener;
|
||||
private Timer timer;
|
||||
|
||||
public BuyerSendCounterCurrencyTransferStartedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
public BuyerSendsPaymentSentMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
|
@ -66,21 +67,21 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
final String id = processModel.getOfferId();
|
||||
XmrAddressEntry payoutAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT);
|
||||
String payoutTxHex = processModel.getBuyerSignedPayoutTx().getTxSet().getMultisigTxHex();
|
||||
|
||||
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
||||
// peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox
|
||||
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
||||
// other data stays the same when we re-send the message at any time later.
|
||||
String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress();
|
||||
message = new CounterCurrencyTransferStartedMessage(
|
||||
message = new PaymentSentMessage(
|
||||
tradeId,
|
||||
payoutAddressEntry.getAddressString(),
|
||||
processModel.getMyNodeAddress(),
|
||||
payoutTxHex,
|
||||
trade.getCounterCurrencyTxId(),
|
||||
trade.getCounterCurrencyExtraData(),
|
||||
deterministicId
|
||||
deterministicId,
|
||||
trade.getBuyer().getPayoutTxHex(),
|
||||
trade.getBuyer().getUpdatedMultisigHex()
|
||||
);
|
||||
}
|
||||
return message;
|
||||
|
@ -88,8 +89,8 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG.ordinal()) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG);
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_INITIATED_MSG.ordinal()) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_INITIATED_MSG);
|
||||
}
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
@ -111,7 +112,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_INITIATED_MSG);
|
||||
if (!trade.isPayoutPublished()) {
|
||||
tryToSendAgainLater();
|
||||
}
|
||||
|
@ -126,7 +127,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG);
|
||||
if (!trade.isPayoutPublished()) {
|
||||
tryToSendAgainLater();
|
||||
}
|
||||
|
@ -165,7 +166,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
private void tryToSendAgainLater() {
|
||||
if (resendCounter >= MAX_RESEND_ATTEMPTS) {
|
||||
cleanup();
|
||||
log.warn("We never received an ACK message when sending the CounterCurrencyTransferStartedMessage to the peer. " +
|
||||
log.warn("We never received an ACK message when sending the PaymentSentMessage to the peer. " +
|
||||
"We stop now and complete the protocol task.");
|
||||
complete();
|
||||
return;
|
||||
|
@ -192,7 +193,7 @@ public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxM
|
|||
// Once we receive an ACK from our msg we know the peer has received the msg and we stop.
|
||||
if (newValue == MessageState.ACKNOWLEDGED) {
|
||||
// We treat a ACK like BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 bisq.core.trade.protocol.tasks.seller;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
@Slf4j
|
||||
public class SellerPreparesPaymentReceivedMessage extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public SellerPreparesPaymentReceivedMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// verify, sign, and publish payout tx if given. otherwise create payout tx
|
||||
if (trade.getBuyer().getPayoutTxHex() != null) {
|
||||
log.info("Seller verifying, signing, and publishing payout tx");
|
||||
trade.verifySignAndPublishPayoutTx(trade.getBuyer().getPayoutTxHex());
|
||||
} else {
|
||||
log.info("Seller creating unsigned payout tx");
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
System.out.println("created payout tx: " + payoutTx);
|
||||
trade.getSeller().setPayoutTx(payoutTx);
|
||||
trade.getSeller().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,20 +17,21 @@
|
|||
|
||||
package bisq.core.trade.protocol.tasks.seller;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.PaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.Validator;
|
||||
import java.util.Arrays;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
@Slf4j
|
||||
public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTask {
|
||||
public SellerProcessCounterCurrencyTransferStartedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
public class SellerProcessesPaymentSentMessage extends TradeTask {
|
||||
public SellerProcessesPaymentSentMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
|
@ -39,12 +40,21 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas
|
|||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
CounterCurrencyTransferStartedMessage message = (CounterCurrencyTransferStartedMessage) processModel.getTradeMessage();
|
||||
PaymentSentMessage message = (PaymentSentMessage) processModel.getTradeMessage();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkNotNull(message);
|
||||
|
||||
trade.getTradingPeer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); // TODO (woodser): verify against contract
|
||||
trade.getTradingPeer().setSignedPayoutTxHex(message.getBuyerPayoutTxSigned());
|
||||
trade.getBuyer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); // TODO (woodser): verify against contract
|
||||
trade.getBuyer().setPayoutTxHex(message.getPayoutTxHex());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
|
||||
// sync and update multisig wallet
|
||||
if (trade.getBuyer().getUpdatedMultisigHex() != null) {
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // TODO: ensure sync() always called before importMultisigHex()
|
||||
multisigWallet.importMultisigHex(trade.getBuyer().getUpdatedMultisigHex());
|
||||
walletService.closeMultisigWallet(trade.getId());
|
||||
}
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct // TODO (woodser): update to latest peer addresses where needed
|
||||
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||
|
@ -59,7 +69,7 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas
|
|||
trade.setCounterCurrencyExtraData(counterCurrencyExtraData);
|
||||
}
|
||||
|
||||
trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG);
|
||||
trade.setState(Trade.State.SELLER_RECEIVED_PAYMENT_INITIATED_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
|
@ -20,7 +20,7 @@ package bisq.core.trade.protocol.tasks.seller;
|
|||
import bisq.core.account.sign.SignedWitness;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||
import bisq.core.trade.messages.TradeMailboxMessage;
|
||||
import bisq.core.trade.protocol.tasks.SendMailboxMessageTask;
|
||||
|
||||
|
@ -33,71 +33,21 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
||||
public class SellerSendsPaymentReceivedMessage extends SendMailboxMessageTask {
|
||||
SignedWitness signedWitness = null;
|
||||
|
||||
public SellerSendPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
public SellerSendsPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String id) {
|
||||
checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null");
|
||||
|
||||
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
|
||||
if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
|
||||
// Broadcast is done in accountAgeWitness domain.
|
||||
accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
|
||||
}
|
||||
|
||||
return new PayoutTxPublishedMessage(
|
||||
id,
|
||||
trade.getPayoutTx().getTxSet().getMultisigTxHex(),
|
||||
processModel.getMyNodeAddress(),
|
||||
signedWitness
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.setState(Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("Sent PayoutTxPublishedMessage: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setState(Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("PayoutTxPublishedMessage arrived: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("PayoutTxPublishedMessage storedInMailbox: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setState(Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.error("PayoutTxPublishedMessage failed: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
if (trade.getPayoutTx() == null) {
|
||||
log.error("PayoutTx is null");
|
||||
failed("PayoutTx is null");
|
||||
if (trade.getSeller().getPayoutTxHex() == null) {
|
||||
log.error("Payout tx is null");
|
||||
failed("Payout tx is null");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -106,4 +56,54 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String id) {
|
||||
checkNotNull(trade.getSeller().getPayoutTxHex(), "Payout tx must not be null");
|
||||
|
||||
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
|
||||
if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
|
||||
// Broadcast is done in accountAgeWitness domain.
|
||||
accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
|
||||
}
|
||||
|
||||
return new PaymentReceivedMessage(
|
||||
id,
|
||||
processModel.getMyNodeAddress(),
|
||||
signedWitness,
|
||||
trade.getSeller().getPayoutTxHex()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.setState(Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("Sent SellerReceivedPaymentMessage: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setState(Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("SellerReceivedPaymentMessage arrived: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.info("SellerReceivedPaymentMessage storedInMailbox: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setState(Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
log.error("SellerReceivedPaymentMessage failed: tradeId={} at peer {} SignedWitness {}",
|
||||
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* 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 bisq.core.trade.protocol.tasks.seller;
|
||||
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroDestination;
|
||||
import monero.wallet.model.MoneroMultisigSignResult;
|
||||
import monero.wallet.model.MoneroTxSet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
@Slf4j
|
||||
public class SellerSignAndPublishPayoutTx extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public SellerSignAndPublishPayoutTx(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// gather relevant trade info
|
||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
||||
String buyerSignedPayoutTxHex = trade.getTradingPeer().getSignedPayoutTxHex();
|
||||
Contract contract = trade.getContract();
|
||||
BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getMaker().getDepositTxHash() : processModel.getTaker().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs trade.getDepositTxId() necessary or avoidable?
|
||||
BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getTaker().getDepositTxHash() : processModel.getMaker().getDepositTxHash()).getIncomingAmount();
|
||||
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(trade.getTradeAmount());
|
||||
|
||||
// parse buyer-signed payout tx
|
||||
MoneroTxSet parsedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(buyerSignedPayoutTxHex));
|
||||
if (parsedTxSet.getTxs() == null || parsedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad buyer-signed payout tx"); // TODO (woodser): test nack
|
||||
MoneroTxWallet buyerSignedPayoutTx = parsedTxSet.getTxs().get(0);
|
||||
|
||||
// verify payout tx has exactly 2 destinations
|
||||
log.info("Seller verifying buyer-signed payout tx");
|
||||
if (buyerSignedPayoutTx.getOutgoingTransfer() == null || buyerSignedPayoutTx.getOutgoingTransfer().getDestinations() == null || buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new RuntimeException("Buyer-signed payout tx does not have exactly two destinations");
|
||||
|
||||
// get buyer and seller destinations (order not preserved)
|
||||
boolean buyerFirst = buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().get(0).getAddress().equals(contract.getBuyerPayoutAddressString());
|
||||
MoneroDestination buyerPayoutDestination = buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 0 : 1);
|
||||
MoneroDestination sellerPayoutDestination = buyerSignedPayoutTx.getOutgoingTransfer().getDestinations().get(buyerFirst ? 1 : 0);
|
||||
|
||||
// verify payout addresses
|
||||
if (!buyerPayoutDestination.getAddress().equals(contract.getBuyerPayoutAddressString())) throw new RuntimeException("Buyer payout address does not match contract");
|
||||
if (!sellerPayoutDestination.getAddress().equals(contract.getSellerPayoutAddressString())) throw new RuntimeException("Seller payout address does not match contract");
|
||||
|
||||
// verify change address is multisig's primary address
|
||||
if (!buyerSignedPayoutTx.getChangeAmount().equals(BigInteger.ZERO) && !buyerSignedPayoutTx.getChangeAddress().equals(multisigWallet.getPrimaryAddress())) throw new RuntimeException("Change address is not multisig wallet's primary address");
|
||||
|
||||
// verify sum of outputs = destination amounts + change amount
|
||||
if (!buyerSignedPayoutTx.getOutputSum().equals(buyerPayoutDestination.getAmount().add(sellerPayoutDestination.getAmount()).add(buyerSignedPayoutTx.getChangeAmount()))) throw new RuntimeException("Sum of outputs != destination amounts + change amount");
|
||||
|
||||
// verify buyer destination amount is deposit amount + trade amount - 1/2 tx costs
|
||||
BigInteger txCost = buyerSignedPayoutTx.getFee().add(buyerSignedPayoutTx.getChangeAmount());
|
||||
BigInteger expectedBuyerPayout = buyerDepositAmount.add(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2)));
|
||||
if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not deposit amount + trade amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
||||
|
||||
// verify seller destination amount is deposit amount - trade amount - 1/2 tx costs
|
||||
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCost.divide(BigInteger.valueOf(2)));
|
||||
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new RuntimeException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
||||
|
||||
// TODO (woodser): verify fee is reasonable (e.g. within 2x of fee estimate tx)
|
||||
|
||||
// sign buyer-signed payout tx
|
||||
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(buyerSignedPayoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing buyer-signed payout tx");
|
||||
String signedMultisigTxHex = result.getSignedMultisigTxHex();
|
||||
|
||||
// submit fully signed payout tx to the network
|
||||
multisigWallet.submitMultisigTxHex(signedMultisigTxHex);
|
||||
|
||||
// close multisig wallet
|
||||
walletService.closeMultisigWallet(trade.getId());
|
||||
|
||||
// update trade state
|
||||
parsedTxSet.setMultisigTxHex(signedMultisigTxHex); // TODO (woodser): better place to store this?
|
||||
trade.setPayoutTx(parsedTxSet.getTxs().get(0));
|
||||
trade.setPayoutTxId(parsedTxSet.getTxs().get(0).getHash());
|
||||
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,14 +20,12 @@ package bisq.core.trade.protocol.tasks.taker;
|
|||
import bisq.common.taskrunner.TaskRunner;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
public class TakerReservesTradeFunds extends TradeTask {
|
||||
|
@ -41,27 +39,23 @@ public class TakerReservesTradeFunds extends TradeTask {
|
|||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// synchronize on wallet to reserve key images
|
||||
synchronized (model.getXmrWalletService().getWallet()) {
|
||||
|
||||
// create transaction to reserve trade
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
BigInteger takerFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.centinerosToAtomicUnits(processModel.getFundsNeededForTradeAsLong());
|
||||
MoneroTxWallet reserveTx = TradeUtils.reserveTradeFunds(model.getXmrWalletService(), trade.getId(), takerFee, returnAddress, depositAmount);
|
||||
|
||||
// collect reserved key images // TODO (woodser): switch to proof of reserve?
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// save process state
|
||||
// TODO (woodser): persist
|
||||
processModel.setReserveTx(reserveTx);
|
||||
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
||||
trade.setTakerFeeTxId(reserveTx.getHash()); // TODO (woodser): this should be multisig deposit tx id? how is it used?
|
||||
//trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); // TODO (woodser): fee tx is not broadcast separate, update states
|
||||
complete();
|
||||
}
|
||||
// freeze trade funds and get reserve tx
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
BigInteger takerFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.centinerosToAtomicUnits(processModel.getFundsNeededForTradeAsLong());
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, returnAddress, depositAmount);
|
||||
|
||||
// collect reserved key images // TODO (woodser): switch to proof of reserve?
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// save process state
|
||||
// TODO (woodser): persist
|
||||
processModel.setReserveTx(reserveTx);
|
||||
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
||||
trade.setTakerFeeTxId(reserveTx.getHash()); // TODO (woodser): this should be multisig deposit tx id? how is it used?
|
||||
//trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); // TODO (woodser): fee tx is not broadcast separate, update states
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
trade.setErrorMessage("An error occurred.\n" +
|
||||
"Error message:\n"
|
||||
|
|
|
@ -346,7 +346,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
|||
}
|
||||
|
||||
private boolean isExpectedTradeState(Trade.State newValue) {
|
||||
return newValue == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
return newValue == Trade.State.SELLER_RECEIVED_PAYMENT_INITIATED_MSG;
|
||||
}
|
||||
|
||||
private boolean is32BitHexStringInValid(String hexString) {
|
||||
|
|
|
@ -28,11 +28,11 @@ import bisq.core.offer.placeoffer.tasks.MakerReservesTradeFunds;
|
|||
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerPreparesPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessesPaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
|
||||
|
@ -48,13 +48,13 @@ import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
|
|||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessesPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignAndPublishPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendsPaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPreparesPaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
|
||||
|
@ -135,15 +135,15 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
SellerPublishesDepositTx.class,
|
||||
SellerPublishesTradeStatistics.class,
|
||||
|
||||
SellerProcessCounterCurrencyTransferStartedMessage.class,
|
||||
SellerProcessesPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
SellerSignAndPublishPayoutTx.class,
|
||||
SellerPreparesPaymentReceivedMessage.class,
|
||||
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
||||
SellerSendPayoutTxPublishedMessage.class
|
||||
SellerSendsPaymentReceivedMessage.class
|
||||
|
||||
)
|
||||
));
|
||||
|
@ -167,11 +167,11 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
BuyerCreateAndSignPayoutTx.class,
|
||||
BuyerPreparesPaymentSentMessage.class,
|
||||
BuyerSetupPayoutTxListener.class,
|
||||
BuyerSendCounterCurrencyTransferStartedMessage.class,
|
||||
BuyerSendsPaymentSentMessage.class,
|
||||
|
||||
BuyerProcessPayoutTxPublishedMessage.class
|
||||
BuyerProcessesPaymentReceivedMessage.class
|
||||
)
|
||||
));
|
||||
|
||||
|
@ -199,11 +199,11 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
BuyerCreateAndSignPayoutTx.class,
|
||||
BuyerPreparesPaymentSentMessage.class,
|
||||
BuyerSetupPayoutTxListener.class,
|
||||
BuyerSendCounterCurrencyTransferStartedMessage.class,
|
||||
BuyerSendsPaymentSentMessage.class,
|
||||
|
||||
BuyerProcessPayoutTxPublishedMessage.class)
|
||||
BuyerProcessesPaymentReceivedMessage.class)
|
||||
));
|
||||
addGroup("SellerAsMakerProtocol",
|
||||
FXCollections.observableArrayList(Arrays.asList(
|
||||
|
@ -227,15 +227,15 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
SellerPublishesDepositTx.class,
|
||||
SellerPublishesTradeStatistics.class,
|
||||
|
||||
SellerProcessCounterCurrencyTransferStartedMessage.class,
|
||||
SellerProcessesPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
SellerSignAndPublishPayoutTx.class,
|
||||
SellerPreparesPaymentReceivedMessage.class,
|
||||
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
||||
SellerSendPayoutTxPublishedMessage.class
|
||||
SellerSendsPaymentReceivedMessage.class
|
||||
)
|
||||
));
|
||||
}
|
||||
|
|
|
@ -409,8 +409,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
appendMsg = Res.get("takeOffer.error.feePaid");
|
||||
break;
|
||||
case DEPOSIT_PUBLISHED:
|
||||
case FIAT_SENT:
|
||||
case FIAT_RECEIVED:
|
||||
case PAYMENT_SENT:
|
||||
case PAYMENT_RECEIVED:
|
||||
appendMsg = Res.get("takeOffer.error.depositPublished");
|
||||
break;
|
||||
case PAYOUT_PUBLISHED:
|
||||
|
|
|
@ -194,7 +194,7 @@ public class NotificationCenter {
|
|||
|
||||
if (trade instanceof BuyerTrade && phase.ordinal() == Trade.Phase.DEPOSIT_CONFIRMED.ordinal())
|
||||
message = Res.get("notification.trade.confirmed");
|
||||
else if (trade instanceof SellerTrade && phase.ordinal() == Trade.Phase.FIAT_SENT.ordinal())
|
||||
else if (trade instanceof SellerTrade && phase.ordinal() == Trade.Phase.PAYMENT_SENT.ordinal())
|
||||
message = Res.get("notification.trade.paymentStarted");
|
||||
}
|
||||
|
||||
|
|
|
@ -468,27 +468,33 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
|||
TradeChatSession tradeChatSession = new TradeChatSession(trade, isTaker);
|
||||
|
||||
tradeStateListener = (observable, oldValue, newValue) -> {
|
||||
if (trade.isPayoutPublished()) {
|
||||
if (chatPopupStage.isShowing()) {
|
||||
chatPopupStage.hide();
|
||||
UserThread.execute(() -> {
|
||||
if (trade.isPayoutPublished()) {
|
||||
if (chatPopupStage.isShowing()) {
|
||||
chatPopupStage.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
trade.stateProperty().addListener(tradeStateListener);
|
||||
|
||||
disputeStateListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue == Trade.DisputeState.DISPUTE_CLOSED || newValue == Trade.DisputeState.REFUND_REQUEST_CLOSED) {
|
||||
chatPopupStage.hide();
|
||||
}
|
||||
UserThread.execute(() -> {
|
||||
if (newValue == Trade.DisputeState.DISPUTE_CLOSED || newValue == Trade.DisputeState.REFUND_REQUEST_CLOSED) {
|
||||
chatPopupStage.hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
trade.disputeStateProperty().addListener(disputeStateListener);
|
||||
|
||||
mediationResultStateListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue == MediationResultState.PAYOUT_TX_PUBLISHED ||
|
||||
newValue == MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG ||
|
||||
newValue == MediationResultState.PAYOUT_TX_SEEN_IN_NETWORK) {
|
||||
chatPopupStage.hide();
|
||||
}
|
||||
UserThread.execute(() -> {
|
||||
if (newValue == MediationResultState.PAYOUT_TX_PUBLISHED ||
|
||||
newValue == MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG ||
|
||||
newValue == MediationResultState.PAYOUT_TX_SEEN_IN_NETWORK) {
|
||||
chatPopupStage.hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
trade.mediationResultStateProperty().addListener(mediationResultStateListener);
|
||||
|
||||
|
@ -559,21 +565,23 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
|||
}
|
||||
|
||||
private void updateChatMessageCount(Trade trade, JFXBadge badge) {
|
||||
if (!trade.getId().equals(tradeIdOfOpenChat)) {
|
||||
updateNewChatMessagesByTradeMap();
|
||||
long num = newChatMessagesByTradeMap.get(trade.getId());
|
||||
if (num > 0) {
|
||||
badge.setText(String.valueOf(num));
|
||||
badge.setEnabled(true);
|
||||
UserThread.execute(() -> {
|
||||
if (!trade.getId().equals(tradeIdOfOpenChat)) {
|
||||
updateNewChatMessagesByTradeMap();
|
||||
long num = newChatMessagesByTradeMap.get(trade.getId());
|
||||
if (num > 0) {
|
||||
badge.setText(String.valueOf(num));
|
||||
badge.setEnabled(true);
|
||||
} else {
|
||||
badge.setText("");
|
||||
badge.setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
badge.setText("");
|
||||
badge.setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
badge.setText("");
|
||||
badge.setEnabled(false);
|
||||
}
|
||||
badge.refreshBadge();
|
||||
badge.refreshBadge();
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -452,27 +452,27 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||
break;
|
||||
|
||||
// buyer step 3
|
||||
case BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED: // UI action
|
||||
case BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG sent
|
||||
case BUYER_CONFIRMED_IN_UI_PAYMENT_INITIATED: // UI action
|
||||
case BUYER_SENT_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG sent
|
||||
// We don't switch the UI before we got the feedback of the msg delivery
|
||||
buyerState.set(BuyerState.STEP2);
|
||||
break;
|
||||
case BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG arrived
|
||||
case BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG in mailbox
|
||||
case BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG arrived
|
||||
case BUYER_STORED_IN_MAILBOX_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG in mailbox
|
||||
buyerState.set(BuyerState.STEP3);
|
||||
break;
|
||||
case BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG failed
|
||||
case BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG failed
|
||||
// if failed we need to repeat sending so back to step 2
|
||||
buyerState.set(BuyerState.STEP2);
|
||||
break;
|
||||
|
||||
// seller step 3
|
||||
case SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG received
|
||||
case SELLER_RECEIVED_PAYMENT_INITIATED_MSG: // FIAT_PAYMENT_INITIATED_MSG received
|
||||
sellerState.set(SellerState.STEP3);
|
||||
break;
|
||||
|
||||
// seller step 4
|
||||
case SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT: // UI action
|
||||
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT: // UI action
|
||||
case SELLER_PUBLISHED_PAYOUT_TX: // payout tx broad casted
|
||||
case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG: // PAYOUT_TX_PUBLISHED_MSG sent
|
||||
sellerState.set(SellerState.STEP3);
|
||||
|
@ -487,6 +487,8 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||
case BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG:
|
||||
// Alternatively the maker could have seen the payout tx earlier before he received the PAYOUT_TX_PUBLISHED_MSG:
|
||||
case BUYER_SAW_PAYOUT_TX_IN_NETWORK:
|
||||
// Alternatively the buyer could fully sign and publish the payout tx
|
||||
case BUYER_PUBLISHED_PAYOUT_TX:
|
||||
buyerState.set(BuyerState.STEP4);
|
||||
break;
|
||||
|
||||
|
|
|
@ -135,12 +135,12 @@ public class BuyerStep2View extends TradeStepView {
|
|||
|
||||
if (trade.isDepositConfirmed() && !trade.isFiatSent()) {
|
||||
showPopup();
|
||||
} else if (state.ordinal() <= Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG.ordinal()) {
|
||||
} else if (state.ordinal() <= Trade.State.BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG.ordinal()) {
|
||||
if (!trade.hasFailed()) {
|
||||
UserThread.execute(() -> {
|
||||
switch (state) {
|
||||
case BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED:
|
||||
case BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG:
|
||||
case BUYER_CONFIRMED_IN_UI_PAYMENT_INITIATED:
|
||||
case BUYER_SENT_PAYMENT_INITIATED_MSG:
|
||||
busyAnimation.play();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||
model.setMessageStateProperty(MessageState.SENT);
|
||||
|
@ -149,17 +149,17 @@ public class BuyerStep2View extends TradeStepView {
|
|||
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
|
||||
}, 10);
|
||||
break;
|
||||
case BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG:
|
||||
case BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG:
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||
model.setMessageStateProperty(MessageState.ARRIVED);
|
||||
break;
|
||||
case BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG:
|
||||
case BUYER_STORED_IN_MAILBOX_PAYMENT_INITIATED_MSG:
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||
model.setMessageStateProperty(MessageState.STORED_IN_MAILBOX);
|
||||
break;
|
||||
case BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG:
|
||||
case BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG:
|
||||
// We get a popup and the trade closed, so we dont need to show anything here
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText("");
|
||||
|
|
|
@ -116,7 +116,7 @@ public class SellerStep3View extends TradeStepView {
|
|||
} else if (trade.isFiatReceived()) {
|
||||
if (!trade.hasFailed()) {
|
||||
switch (state) {
|
||||
case SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT:
|
||||
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT:
|
||||
case SELLER_PUBLISHED_PAYOUT_TX:
|
||||
case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG:
|
||||
busyAnimation.play();
|
||||
|
|
|
@ -39,34 +39,32 @@ message NetworkEnvelope {
|
|||
InputsForDepositTxRequest inputs_for_deposit_tx_request = 17;
|
||||
InputsForDepositTxResponse inputs_for_deposit_tx_response = 18;
|
||||
DepositTxMessage deposit_tx_message = 19;
|
||||
CounterCurrencyTransferStartedMessage counter_currency_transfer_started_message = 20;
|
||||
PayoutTxPublishedMessage payout_tx_published_message = 21;
|
||||
|
||||
OpenNewDisputeMessage open_new_dispute_message = 22;
|
||||
PeerOpenedDisputeMessage peer_opened_dispute_message = 23;
|
||||
ChatMessage chat_message = 24;
|
||||
DisputeResultMessage dispute_result_message = 25;
|
||||
PeerPublishedDisputePayoutTxMessage peer_published_dispute_payout_tx_message = 26;
|
||||
OpenNewDisputeMessage open_new_dispute_message = 20;
|
||||
PeerOpenedDisputeMessage peer_opened_dispute_message = 21;
|
||||
ChatMessage chat_message = 22;
|
||||
DisputeResultMessage dispute_result_message = 23;
|
||||
PeerPublishedDisputePayoutTxMessage peer_published_dispute_payout_tx_message = 24;
|
||||
|
||||
PrivateNotificationMessage private_notification_message = 27;
|
||||
PrivateNotificationMessage private_notification_message = 25;
|
||||
|
||||
AddPersistableNetworkPayloadMessage add_persistable_network_payload_message = 28;
|
||||
AckMessage ack_message = 29;
|
||||
AddPersistableNetworkPayloadMessage add_persistable_network_payload_message = 26;
|
||||
AckMessage ack_message = 27;
|
||||
|
||||
BundleOfEnvelopes bundle_of_envelopes = 30;
|
||||
MediatedPayoutTxSignatureMessage mediated_payout_tx_signature_message = 31;
|
||||
MediatedPayoutTxPublishedMessage mediated_payout_tx_published_message = 32;
|
||||
BundleOfEnvelopes bundle_of_envelopes = 28;
|
||||
MediatedPayoutTxSignatureMessage mediated_payout_tx_signature_message = 29;
|
||||
MediatedPayoutTxPublishedMessage mediated_payout_tx_published_message = 30;
|
||||
|
||||
DelayedPayoutTxSignatureRequest delayed_payout_tx_signature_request = 33;
|
||||
DelayedPayoutTxSignatureResponse delayed_payout_tx_signature_response = 34;
|
||||
DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 35;
|
||||
PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 36;
|
||||
DelayedPayoutTxSignatureRequest delayed_payout_tx_signature_request = 31;
|
||||
DelayedPayoutTxSignatureResponse delayed_payout_tx_signature_response = 32;
|
||||
DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 33;
|
||||
PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 34;
|
||||
|
||||
RefreshTradeStateRequest refresh_trade_state_request = 37 [deprecated = true];
|
||||
TraderSignedWitnessMessage trader_signed_witness_message = 38 [deprecated = true];
|
||||
RefreshTradeStateRequest refresh_trade_state_request = 35 [deprecated = true];
|
||||
TraderSignedWitnessMessage trader_signed_witness_message = 36 [deprecated = true];
|
||||
|
||||
GetInventoryRequest get_inventory_request = 39;
|
||||
GetInventoryResponse get_inventory_response = 40;
|
||||
GetInventoryRequest get_inventory_request = 37;
|
||||
GetInventoryResponse get_inventory_response = 38;
|
||||
|
||||
SignOfferRequest sign_offer_request = 1001;
|
||||
SignOfferResponse sign_offer_response = 1002;
|
||||
|
@ -77,10 +75,13 @@ message NetworkEnvelope {
|
|||
DepositRequest deposit_request = 1007;
|
||||
DepositResponse deposit_response = 1008;
|
||||
PaymentAccountPayloadRequest payment_account_payload_request = 1009;
|
||||
UpdateMultisigRequest update_multisig_request = 1010;
|
||||
UpdateMultisigResponse update_multisig_response = 1011;
|
||||
ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1012;
|
||||
ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1013;
|
||||
PaymentSentMessage payment_sent_message = 1010;
|
||||
PaymentReceivedMessage payment_received_message = 1011;
|
||||
PayoutTxPublishedMessage payout_tx_published_message = 1012;
|
||||
UpdateMultisigRequest update_multisig_request = 1013;
|
||||
UpdateMultisigResponse update_multisig_response = 1014;
|
||||
ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1015;
|
||||
ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1016;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -428,16 +429,6 @@ message PeerPublishedDelayedPayoutTxMessage {
|
|||
NodeAddress sender_node_address = 3;
|
||||
}
|
||||
|
||||
message CounterCurrencyTransferStartedMessage {
|
||||
string trade_id = 1;
|
||||
string buyer_payout_address = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
string buyer_payout_tx_signed = 4;
|
||||
string counter_currency_tx_id = 5;
|
||||
string uid = 6;
|
||||
string counter_currency_extra_data = 7;
|
||||
}
|
||||
|
||||
message FinalizePayoutTxRequest {
|
||||
string trade_id = 1;
|
||||
bytes seller_signature = 2;
|
||||
|
@ -446,6 +437,33 @@ message FinalizePayoutTxRequest {
|
|||
string uid = 5;
|
||||
}
|
||||
|
||||
message PaymentSentMessage {
|
||||
string trade_id = 1;
|
||||
string buyer_payout_address = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
string counter_currency_tx_id = 4;
|
||||
string uid = 5;
|
||||
string counter_currency_extra_data = 6;
|
||||
string payout_tx_hex = 7;
|
||||
string updated_multisig_hex = 8;
|
||||
}
|
||||
|
||||
message PaymentReceivedMessage {
|
||||
string trade_id = 1;
|
||||
NodeAddress sender_node_address = 2;
|
||||
string uid = 3;
|
||||
SignedWitness signed_witness = 4; // Added in v1.4.0
|
||||
string payout_tx_hex = 5;
|
||||
}
|
||||
|
||||
message PayoutTxPublishedMessage {
|
||||
string trade_id = 1;
|
||||
NodeAddress sender_node_address = 2;
|
||||
string uid = 3;
|
||||
SignedWitness signed_witness = 4; // Added in v1.4.0
|
||||
string payout_tx_hex = 5;
|
||||
}
|
||||
|
||||
message ArbitratorPayoutTxRequest {
|
||||
Dispute dispute = 1; // TODO (woodser): replace with trade id
|
||||
NodeAddress sender_node_address = 2;
|
||||
|
@ -462,14 +480,6 @@ message ArbitratorPayoutTxResponse {
|
|||
string arbitrator_signed_payout_tx_hex = 5;
|
||||
}
|
||||
|
||||
message PayoutTxPublishedMessage {
|
||||
string trade_id = 1;
|
||||
string signed_multisig_tx_hex = 2;
|
||||
NodeAddress sender_node_address = 3;
|
||||
string uid = 4;
|
||||
SignedWitness signed_witness = 5; // Added in v1.4.0
|
||||
}
|
||||
|
||||
message MediatedPayoutTxPublishedMessage {
|
||||
string trade_id = 1;
|
||||
bytes payout_tx = 2;
|
||||
|
@ -1514,13 +1524,13 @@ message Trade {
|
|||
MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 16;
|
||||
MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 17;
|
||||
DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN = 18;
|
||||
BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED = 19;
|
||||
BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG = 20;
|
||||
BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG = 21;
|
||||
BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG = 22;
|
||||
BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG = 23;
|
||||
SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG = 24;
|
||||
SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT = 25;
|
||||
BUYER_CONFIRMED_IN_UI_PAYMENT_INITIATED = 19;
|
||||
BUYER_SENT_PAYMENT_INITIATED_MSG = 20;
|
||||
BUYER_SAW_ARRIVED_PAYMENT_INITIATED_MSG = 21;
|
||||
BUYER_STORED_IN_MAILBOX_PAYMENT_INITIATED_MSG = 22;
|
||||
BUYER_SEND_FAILED_PAYMENT_INITIATED_MSG = 23;
|
||||
SELLER_RECEIVED_PAYMENT_INITIATED_MSG = 24;
|
||||
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 25;
|
||||
SELLER_PUBLISHED_PAYOUT_TX = 26;
|
||||
SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG = 27;
|
||||
SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG = 28;
|
||||
|
@ -1528,7 +1538,8 @@ message Trade {
|
|||
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 30;
|
||||
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 31;
|
||||
BUYER_SAW_PAYOUT_TX_IN_NETWORK = 32;
|
||||
WITHDRAW_COMPLETED = 33;
|
||||
BUYER_PUBLISHED_PAYOUT_TX = 33;
|
||||
WITHDRAW_COMPLETED = 34;
|
||||
}
|
||||
|
||||
enum Phase {
|
||||
|
@ -1537,8 +1548,8 @@ message Trade {
|
|||
TAKER_FEE_PUBLISHED = 2;
|
||||
DEPOSIT_PUBLISHED = 3;
|
||||
DEPOSIT_CONFIRMED = 4;
|
||||
FIAT_SENT = 5;
|
||||
FIAT_RECEIVED = 6;
|
||||
PAYMENT_SENT = 5;
|
||||
PAYMENT_RECEIVED = 6;
|
||||
PAYOUT_PUBLISHED = 7;
|
||||
WITHDRAWN = 8;
|
||||
}
|
||||
|
@ -1686,10 +1697,11 @@ message TradingPeer {
|
|||
repeated string reserve_tx_key_images = 1004;
|
||||
string prepared_multisig_hex = 1005;
|
||||
string made_multisig_hex = 1006;
|
||||
string signed_payout_tx_hex = 1007;
|
||||
string payout_tx_hex = 1007;
|
||||
string deposit_tx_hash = 1008;
|
||||
string deposit_tx_hex = 1009;
|
||||
string deposit_tx_key = 1010;
|
||||
string updated_multisig_hex = 1011;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Reference in a new issue