listen for published payout tx

fix "Swapping pending OFFER_FUNDING" warning
move payout tx from TradingPeer to Trade
This commit is contained in:
woodser 2022-10-01 09:51:16 -04:00
parent 5fbc41946e
commit dc9c04759f
30 changed files with 231 additions and 279 deletions

View File

@ -568,8 +568,8 @@ public class CoreApi {
coreTradesService.confirmPaymentReceived(tradeId, resultHandler, errorMessageHandler);
}
public void keepFunds(String tradeId) {
coreTradesService.keepFunds(tradeId);
public void closeTrade(String tradeId) {
coreTradesService.closeTrade(tradeId);
}
public void withdrawFunds(String tradeId, String address, String memo) {

View File

@ -155,7 +155,7 @@ class CoreTradesService {
}
}
void keepFunds(String tradeId) {
void closeTrade(String tradeId) {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();

View File

@ -812,6 +812,13 @@ public class XmrWalletService {
return getAddressEntryListAsImmutableList().stream().filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext()).collect(Collectors.toList());
}
public List<XmrAddressEntry> getAddressEntriesForOpenOffer() {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> XmrAddressEntry.Context.OFFER_FUNDING == addressEntry.getContext() ||
XmrAddressEntry.Context.RESERVED_FOR_TRADE == addressEntry.getContext())
.collect(Collectors.toList());
}
public List<XmrAddressEntry> getAddressEntriesForTrade() {
return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() || XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext())

View File

@ -248,7 +248,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private void cleanUpAddressEntries() {
Set<String> openOffersIdSet = openOffers.getList().stream().map(OpenOffer::getId).collect(Collectors.toSet());
btcWalletService.getAddressEntriesForOpenOffer().stream()
xmrWalletService.getAddressEntriesForOpenOffer().stream()
.filter(e -> !openOffersIdSet.contains(e.getOfferId()))
.forEach(e -> {
log.warn("We found an outdated addressEntry for openOffer {} (openOffers does not contain that " +
@ -568,8 +568,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer);
closedTradableManager.add(openOffer);
log.info("onRemoved offerId={}", offer.getId());
xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
log.info("onRemoved offerId={}", offer.getId());
requestPersistence();
}

View File

@ -407,11 +407,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
Contract contract = dispute.getContract();
// verify sender is co-signer and receiver is arbitrator
System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
System.out.println(disputeResult);
System.out.println(disputeResult.getWinner());
System.out.println(contract.getBuyerNodeAddress());
System.out.println(contract.getSellerNodeAddress());
// System.out.println("Any of these null???"); // TODO (woodser): NPE if dispute opener's peer-as-cosigner's ticket is closed first
// System.out.println(disputeResult);
// System.out.println(disputeResult.getWinner());
// System.out.println(contract.getBuyerNodeAddress());
// System.out.println(contract.getSellerNodeAddress());
boolean senderIsWinner = (disputeResult.getWinner() == Winner.BUYER && contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress())) || (disputeResult.getWinner() == Winner.SELLER && contract.getSellerNodeAddress().equals(request.getSenderNodeAddress()));
boolean senderIsCosigner = senderIsWinner || disputeResult.isLoserPublisher();
boolean receiverIsArbitrator = pubKeyRing.equals(dispute.getAgentPubKeyRing());

View File

@ -50,6 +50,7 @@ import bisq.common.util.Utilities;
import com.google.common.base.Preconditions;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import common.utils.GenUtils;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
@ -90,9 +91,12 @@ import monero.common.MoneroError;
import monero.daemon.MoneroDaemon;
import monero.daemon.model.MoneroTx;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroCheckTx;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroMultisigSignResult;
import monero.wallet.model.MoneroTransferQuery;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxSet;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
@ -127,7 +131,7 @@ public abstract class Trade implements Tradable, Model {
// deposit published
ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED),
DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN(Phase.DEPOSITS_PUBLISHED),
DEPOSIT_TXS_SEEN_IN_NETWORK(Phase.DEPOSITS_PUBLISHED),
// deposit confirmed
DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN(Phase.DEPOSITS_CONFIRMED),
@ -157,8 +161,8 @@ public abstract class Trade implements Tradable, Model {
SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
BUYER_SAW_PAYOUT_TX_IN_NETWORK(Phase.PAYOUT_PUBLISHED),
BUYER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED),
PAYOUT_TX_SEEN_IN_NETWORK(Phase.PAYOUT_PUBLISHED),
// trade completed
WITHDRAW_COMPLETED(Phase.WITHDRAWN);
@ -310,9 +314,6 @@ public abstract class Trade implements Tradable, Model {
@Nullable
@Getter
@Setter
private String payoutTxId;
@Getter
@Setter
private long amountAsLong;
@Setter
private long price;
@ -366,9 +367,6 @@ public abstract class Trade implements Tradable, Model {
// Added in v1.2.0
@Nullable
transient private Transaction delayedPayoutTx;
@Nullable
transient private MoneroTxWallet payoutTx;
@Nullable
transient private Coin tradeAmount;
@ -412,12 +410,24 @@ public abstract class Trade implements Tradable, Model {
@Getter
transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty();
// Added in XMR integration
private transient List<TradeListener> tradeListeners; // notified on fully validated trade messages
transient MoneroWalletListener depositTxListener;
transient MoneroWalletListener payoutTxListener;
transient Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked
transient Boolean takerDepositLocked;
@Nullable
transient private MoneroTxWallet payoutTx;
@Getter
@Setter
private String payoutTxId;
@Nullable
@Getter
@Setter
private String payoutTxHex;
@Getter
@Setter
private String payoutTxKey;
private Long startTime; // cache
///////////////////////////////////////////////////////////////////////////////////////////
@ -547,6 +557,8 @@ public abstract class Trade implements Tradable, Model {
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxHex(payoutTxKey));
Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name()));
@ -560,6 +572,8 @@ public abstract class Trade implements Tradable, Model {
trade.setPeriodState(TradePeriodState.fromProto(proto.getPeriodState()));
trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
trade.setPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()));
trade.setPayoutTxKey(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxKey()));
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()));
@ -705,7 +719,7 @@ public abstract class Trade implements Tradable, Model {
BigInteger buyerDepositAmount = multisigWallet.getTx(getBuyer().getDepositTxHash()).getIncomingAmount();
BigInteger tradeAmount = ParsingUtils.coinToAtomicUnits(getAmount());
// parse payout tx
// describe payout tx
MoneroTxSet describedTxSet = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new RuntimeException("Bad payout tx"); // TODO (woodser): test nack
MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
@ -747,8 +761,8 @@ public abstract class Trade implements Tradable, Model {
}
// update trade state
getSelf().setPayoutTxHex(payoutTxHex);
setPayoutTx(describedTxSet.getTxs().get(0));
setPayoutTxHex(payoutTxHex);
// submit payout tx
if (publish) {
@ -807,17 +821,17 @@ public abstract class Trade implements Tradable, Model {
// handle deposit txs seen
if (txs.size() == 2) {
setStatePublished();
setStateDepositsPublished();
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
// check if deposit txs unlocked
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
setStateConfirmed();
setStateDepositsConfirmed();
long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
if (havenoWallet.getHeight() >= unlockHeight) {
setStateUnlocked();
setStateDepositsUnlocked();
return;
}
}
@ -844,7 +858,7 @@ public abstract class Trade implements Tradable, Model {
// skip if deposit txs not seen
if (txs.size() != 2) return;
setStatePublished();
setStateDepositsPublished();
// update deposit txs
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
@ -854,7 +868,7 @@ public abstract class Trade implements Tradable, Model {
// check if deposit txs confirmed and compute unlock height
if (txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed() && unlockHeight == null) {
log.info("Multisig deposits confirmed for trade {}", getId());
setStateConfirmed();
setStateDepositsConfirmed();
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
}
@ -863,7 +877,7 @@ public abstract class Trade implements Tradable, Model {
log.info("Multisig deposits unlocked for trade {}", getId());
xmrWalletService.removeWalletListener(depositTxListener); // remove listener when notified
depositTxListener = null; // prevent re-applying trade state in subsequent requests
setStateUnlocked();
setStateDepositsUnlocked();
}
}
};
@ -872,6 +886,51 @@ public abstract class Trade implements Tradable, Model {
xmrWalletService.addWalletListener(depositTxListener);
}
public void listenForPayoutTx() {
log.info("Listening for payout tx for trade {}", getId());
// check if payout tx already seen
if (getState().ordinal() >= Trade.State.PAYOUT_TX_SEEN_IN_NETWORK.ordinal()) {
log.warn("We had a payout tx already set. tradeId={}, state={}", getId(), getState());
return;
}
// get payout address entry
Optional<XmrAddressEntry> optionalPayoutEntry = xmrWalletService.getAddressEntry(getId(), XmrAddressEntry.Context.TRADE_PAYOUT);
if (!optionalPayoutEntry.isPresent()) throw new RuntimeException("Trade does not have address entry for payout");
XmrAddressEntry payoutEntry = optionalPayoutEntry.get();
// watch for payout tx on loop
new Thread(() -> { // TODO: use thread manager
boolean found = false;
while (!found) {
if (getPayoutTxKey() != null) {
// get txs to payout address
List<MoneroTxWallet> txs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery()
.setTransferQuery(new MoneroTransferQuery()
.setAccountIndex(0)
.setSubaddressIndex(payoutEntry.getSubaddressIndex())
.setIsIncoming(true)));
// check for payout tx
for (MoneroTxWallet tx : txs) {
MoneroCheckTx txCheck = xmrWalletService.getWallet().checkTxKey(tx.getHash(), getPayoutTxKey(), payoutEntry.getAddressString());
if (txCheck.isGood() && txCheck.receivedAmount.compareTo(new BigInteger("0")) > 0) {
found = true;
setPayoutTx(tx);
setStateIfValidTransitionTo(Trade.State.PAYOUT_TX_SEEN_IN_NETWORK);
return;
}
}
}
// wait to loop
GenUtils.waitFor(xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs());
}
}).start();
}
@Nullable
public MoneroTx getTakerDepositTx() {
String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
@ -1045,6 +1104,7 @@ public abstract class Trade implements Tradable, Model {
public void setPayoutTx(MoneroTxWallet payoutTx) {
this.payoutTx = payoutTx;
payoutTxId = payoutTx.getHash();
payoutTxKey = payoutTx.getKey();
}
public void setErrorMessage(String errorMessage) {
@ -1275,7 +1335,6 @@ public abstract class Trade implements Tradable, Model {
}
public boolean isPayoutPublished() {
if (getState() == Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG) return true; // TODO: this is a hack because seller has not seen signed payout tx. replace when payout process refactored
return getState().getPhase().ordinal() >= Phase.PAYOUT_PUBLISHED.ordinal() || isWithdrawn();
}
@ -1397,15 +1456,15 @@ public abstract class Trade implements Tradable, Model {
return tradeVolumeProperty;
}
private void setStatePublished() {
if (!isDepositPublished()) setState(State.DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN);
private void setStateDepositsPublished() {
if (!isDepositPublished()) setState(State.DEPOSIT_TXS_SEEN_IN_NETWORK);
}
private void setStateConfirmed() {
private void setStateDepositsConfirmed() {
if (!isDepositConfirmed()) setState(State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN);
}
private void setStateUnlocked() {
private void setStateDepositsUnlocked() {
if (!isDepositUnlocked()) setState(State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
}

View File

@ -284,7 +284,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null)
.forEach(addressEntry -> {
log.warn("Swapping pending OFFER_FUNDING entries at startup. offerId={}", addressEntry.getOfferId());
log.warn("Swapping pending {} entries at startup. offerId={}", addressEntry.getContext(), addressEntry.getOfferId());
xmrWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), XmrAddressEntry.Context.OFFER_FUNDING);
});
@ -837,6 +837,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
public void onTradeCompleted(Trade trade) {
if (trade.getState() == Trade.State.WITHDRAW_COMPLETED) return;
closedTradableManager.add(trade);
trade.setState(Trade.State.WITHDRAW_COMPLETED);
maybeRemoveTrade(trade);

View File

@ -17,6 +17,9 @@
package bisq.core.trade.protocol;
import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.core.trade.BuyerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentAccountKeyResponse;
@ -25,20 +28,16 @@ import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.FluentProtocol.Condition;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.BuyerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentAccountKeyResponse;
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.BuyerSendPaymentAccountKeyRequestToArbitrator;
import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
import bisq.core.trade.protocol.tasks.BuyerSetupPayoutTxListener;
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentAccountKeyResponse;
import bisq.core.trade.protocol.tasks.BuyerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
@ -70,17 +69,20 @@ public abstract class BuyerProtocol extends DisputeProtocol {
// request key to decrypt seller's payment account payload after first confirmation
sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent.STARTUP, false);
// listen for deposit txs
given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED)
.with(BuyerEvent.STARTUP))
.setup(tasks(SetupDepositTxsListener.class))
.executeTasks();
// listen for payout tx
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
.with(BuyerEvent.STARTUP))
.setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup?
.setup(tasks(SetupPayoutTxListener.class))
.executeTasks();
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
// send payment sent message
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) // TODO: remove payment received phase?
.anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG, Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG)
.with(BuyerEvent.STARTUP))
.setup(tasks(BuyerSendPaymentSentMessage.class))

View File

@ -22,6 +22,7 @@ import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent;
import bisq.core.trade.protocol.FluentProtocol.Condition;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.SellerMaybeSendPayoutTxPublishedMessage;
@ -30,6 +31,7 @@ import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.SellerSendPaymentAccountPayloadKey;
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
@ -60,11 +62,17 @@ public abstract class SellerProtocol extends DisputeProtocol {
sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent.STARTUP);
}
// listen for changes to deposit txs
// listen for deposit txs
given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED)
.with(SellerEvent.STARTUP))
.setup(tasks(SetupDepositTxsListener.class))
.executeTasks();
// listen for payout tx
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
.with(BuyerEvent.STARTUP))
.setup(tasks(SetupPayoutTxListener.class))
.executeTasks();
}
@Override

View File

@ -335,7 +335,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyState(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS, Trade.State.DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN)
expect(anyState(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS, Trade.State.DEPOSIT_TXS_SEEN_IN_NETWORK)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(

View File

@ -124,10 +124,6 @@ public final class TradingPeer implements PersistablePayload {
@Nullable
private String depositTxKey;
@Nullable
transient private MoneroTxWallet payoutTx;
@Nullable
private String payoutTxHex;
@Nullable
private String updatedMultisigHex;
public TradingPeer() {
@ -164,7 +160,6 @@ public final class TradingPeer implements PersistablePayload {
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(exchangedMultisigHex).ifPresent(e -> builder.setExchangedMultisigHex(exchangedMultisigHex));
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));
@ -216,7 +211,6 @@ public final class TradingPeer implements PersistablePayload {
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;
}

View File

@ -65,11 +65,16 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
// create payout tx if we have seller's updated multisig hex
if (trade.getTradingPeer().getUpdatedMultisigHex() != null) {
// create payout tx
log.info("Buyer creating unsigned payout tx");
multisigWallet.importMultisigHex(trade.getTradingPeer().getUpdatedMultisigHex());
MoneroTxWallet payoutTx = trade.createPayoutTx();
trade.getBuyer().setPayoutTx(payoutTx);
trade.getBuyer().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
trade.setPayoutTx(payoutTx);
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
// start listening for published payout tx
trade.listenForPayoutTx();
} else {
if (trade.getSelf().getUpdatedMultisigHex() == null) trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); // only export multisig hex once
}

View File

@ -59,7 +59,7 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
if (trade.getPhase().ordinal() < Trade.Phase.PAYOUT_PUBLISHED.ordinal()) {
// publish payout tx if signed. otherwise verify, sign, and publish payout tx
boolean previouslySigned = trade.getBuyer().getPayoutTxHex() != null;
boolean previouslySigned = trade.getPayoutTxHex() != null;
if (previouslySigned) {
log.info("Buyer publishing signed payout tx from seller");
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
@ -67,14 +67,17 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
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);
trade.setStateIfValidTransitionTo(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.verifyPayoutTx(message.getPayoutTxHex(), true, true);
trade.setState(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
// TODO (woodser): send PayoutTxPublishedMessage to arbitrator and seller
trade.setStateIfValidTransitionTo(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
// TODO (woodser): send PayoutTxPublishedMessage to seller
}
// mark address entries as available
processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
} else {
log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
}
@ -89,7 +92,6 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
}
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {
failed(t);

View File

@ -62,7 +62,7 @@ public class BuyerSendPaymentSentMessage extends SendMailboxMessageTask {
trade.getCounterCurrencyTxId(),
trade.getCounterCurrencyExtraData(),
deterministicId,
trade.getBuyer().getPayoutTxHex(),
trade.getPayoutTxHex(),
trade.getBuyer().getUpdatedMultisigHex(),
trade.getSelf().getPaymentAccountKey()
);

View File

@ -50,13 +50,13 @@ public class BuyerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
@Override
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
checkNotNull(trade.getSelf().getPayoutTxHex(), "Payout tx must not be null");
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
return new PayoutTxPublishedMessage(
tradeId,
processModel.getMyNodeAddress(),
trade.isMaker(),
null, // TODO: send witness data?
trade.getSelf().getPayoutTxHex()
trade.getPayoutTxHex()
);
}

View File

@ -1,49 +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;
import bisq.core.trade.Trade;
import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BuyerSetupPayoutTxListener extends SetupPayoutTxListener {
public BuyerSetupPayoutTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
super.run();
} catch (Throwable t) {
failed(t);
}
}
@Override
protected void setState() {
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_PAYOUT_TX_IN_NETWORK);
processModel.getTradeManager().requestPersistence();
}
}

View File

@ -65,13 +65,13 @@ public class SellerMaybeSendPayoutTxPublishedMessage extends SendMailboxMessageT
@Override
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
checkNotNull(trade.getSelf().getPayoutTxHex(), "Payout tx must not be null");
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
return new PayoutTxPublishedMessage(
tradeId,
processModel.getMyNodeAddress(),
trade.isMaker(),
null, // TODO: send witness data?
trade.getSelf().getPayoutTxHex()
trade.getPayoutTxHex()
);
}

View File

@ -38,17 +38,21 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
runInterceptHook();
// verify, sign, and publish payout tx if given. otherwise create payout tx
if (trade.getBuyer().getPayoutTxHex() != null) {
if (trade.getPayoutTxHex() != null) {
log.info("Seller verifying, signing, and publishing payout tx");
trade.verifyPayoutTx(trade.getBuyer().getPayoutTxHex(), true, true);
trade.verifyPayoutTx(trade.getPayoutTxHex(), true, true);
} else {
// create unsigned payout tx
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());
}
trade.setPayoutTx(payoutTx);
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
// start listening for published payout tx
trade.listenForPayoutTx();
}
complete();
} catch (Throwable t) {
failed(t);

View File

@ -43,7 +43,7 @@ public class SellerProcessPaymentSentMessage extends TradeTask {
checkNotNull(message);
// store buyer info
trade.getBuyer().setPayoutTxHex(message.getPayoutTxHex());
trade.setPayoutTxHex(message.getPayoutTxHex());
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
// decrypt buyer's payment account payload

View File

@ -42,7 +42,7 @@ public class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
try {
runInterceptHook();
if (trade.getSeller().getPayoutTxHex() == null) {
if (trade.getPayoutTxHex() == null) {
log.error("Payout tx is null");
failed("Payout tx is null");
return;
@ -56,12 +56,12 @@ public class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
@Override
protected TradeMailboxMessage getTradeMailboxMessage(String id) {
checkNotNull(trade.getSeller().getPayoutTxHex(), "Payout tx must not be null");
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
return new PaymentReceivedMessage(
id,
processModel.getMyNodeAddress(),
signedWitness,
trade.getSeller().getPayoutTxHex()
trade.getPayoutTxHex()
);
}

View File

@ -17,120 +17,50 @@
package bisq.core.trade.protocol.tasks;
import bisq.core.btc.listeners.AddressConfidenceListener;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.Trade;
import bisq.common.UserThread;
import bisq.common.taskrunner.TaskRunner;
import org.bitcoinj.core.TransactionConfidence;
import bisq.core.trade.Trade;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTransferQuery;
import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
@Slf4j
public abstract class SetupPayoutTxListener extends TradeTask {
// Use instance fields to not get eaten up by the GC
private Subscription tradeStateSubscription;
private AddressConfidenceListener confidenceListener;
public class SetupPayoutTxListener extends TradeTask {
public SetupPayoutTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
private Subscription tradeStateSubscription;
@SuppressWarnings({ "unused" })
public SetupPayoutTxListener(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
protected abstract void setState();
@Override
protected void run() {
try {
runInterceptHook();
System.out.println("NEED TO IMPLEMENT PAYOUT TX LISTENER!"); // TODO (woodser): implement SetupPayoutTxListener
// if (!trade.isPayoutPublished()) {
// BtcWalletService walletService = processModel.getBtcWalletService();
// String id = processModel.getOffer().getId();
// Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
//
// TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
// if (isInNetwork(confidence)) {
// applyConfidence(confidence);
// } else {
// confidenceListener = new AddressConfidenceListener(address) {
// @Override
// public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
// if (isInNetwork(confidence))
// applyConfidence(confidence);
// }
// };
// walletService.addAddressConfidenceListener(confidenceListener);
//
// tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
// if (trade.isPayoutPublished()) {
// processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
//
// // hack to remove tradeStateSubscription at callback
// UserThread.execute(this::unSubscribe);
// }
// });
// }
// }
// we complete immediately, our object stays alive because the balanceListener is stored in the WalletService
// skip if payout already published
if (!trade.isPayoutPublished()) {
// listen for payout tx
trade.listenForPayoutTx();
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
if (trade.isPayoutPublished()) {
// cleanup on trade completion
processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
UserThread.execute(this::unSubscribe); // unsubscribe
}
});
}
complete();
} catch (Throwable t) {
failed(t);
}
}
private void applyPayoutTx(int accountIdx) {
if (trade.getPayoutTx() == null) {
// get txs with transfers to payout subaddress
List<MoneroTxWallet> txs = processModel.getProvider().getXmrWalletService().getWallet().getTxs(new MoneroTxQuery()
.setTransferQuery(new MoneroTransferQuery().setAccountIndex(accountIdx).setSubaddressIndex(0).setIsIncoming(true))); // TODO (woodser): hardcode account 0 as savings wallet, subaddress 0 trade accounts in config
// resolve payout tx if multiple txs sent to payout address
MoneroTxWallet payoutTx;
if (txs.size() > 1) {
throw new RuntimeException("Need to resolve multiple payout txs"); // TODO (woodser)
} else {
payoutTx = txs.get(0);
}
trade.setPayoutTx(payoutTx);
XmrWalletService.printTxs("payoutTx received from network", payoutTx);
setState();
} else {
log.info("We had the payout tx already set. tradeId={}, state={}", trade.getId(), trade.getState());
}
processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
// need delay as it can be called inside the handler before the listener and tradeStateSubscription are actually set.
UserThread.execute(this::unSubscribe);
}
private boolean isInNetwork(TransactionConfidence confidence) {
return confidence != null &&
(confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) ||
confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING));
}
private void unSubscribe() {
if (tradeStateSubscription != null)
tradeStateSubscription.unsubscribe();
if (confidenceListener != null)
processModel.getBtcWalletService().removeAddressConfidenceListener(confidenceListener);
if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe();
}
}

View File

@ -17,17 +17,16 @@
package bisq.core.trade.protocol.tasks.mediation;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SetupMediatedPayoutTxListener extends SetupPayoutTxListener {
public SetupMediatedPayoutTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
public class SetupMediatedPayoutTxListener extends TradeTask {
@SuppressWarnings({ "unused" })
public SetupMediatedPayoutTxListener(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -35,20 +34,10 @@ public class SetupMediatedPayoutTxListener extends SetupPayoutTxListener {
protected void run() {
try {
runInterceptHook();
super.run();
if (true) throw new RuntimeException("Not implemented");
complete();
} catch (Throwable t) {
failed(t);
}
}
@Override
protected void setState() {
trade.setMediationResultState(MediationResultState.PAYOUT_TX_SEEN_IN_NETWORK);
if (trade.getPayoutTx() != null) {
processModel.getTradeManager().closeDisputedTrade(trade.getId(), Trade.DisputeState.MEDIATION_CLOSED);
}
processModel.getTradeManager().requestPersistence();
}
}

View File

@ -176,11 +176,12 @@ class GrpcTradesService extends TradesImplBase {
}
}
// TODO: rename KeepFundsRequest to CloseTradeRequest
@Override
public void keepFunds(KeepFundsRequest req,
StreamObserver<KeepFundsReply> responseObserver) {
try {
coreApi.keepFunds(req.getTradeId());
coreApi.closeTrade(req.getTradeId());
var reply = KeepFundsReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();

View File

@ -30,7 +30,6 @@ import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
import bisq.core.trade.protocol.tasks.BuyerSetupPayoutTxListener;
import bisq.core.trade.protocol.tasks.MakerSetLockTime;
import bisq.core.trade.protocol.tasks.MakerRemoveOpenOffer;
import bisq.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
@ -38,6 +37,7 @@ import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
import bisq.core.trade.protocol.tasks.SellerPublishDepositTx;
import bisq.core.trade.protocol.tasks.SellerPublishTradeStatistics;
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessage;
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
import bisq.core.trade.protocol.tasks.TakerVerifyMakerFeePayment;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.common.taskrunner.Task;
@ -123,7 +123,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
ApplyFilter.class,
BuyerPreparePaymentSentMessage.class,
BuyerSetupPayoutTxListener.class,
SetupPayoutTxListener.class,
BuyerSendPaymentSentMessage.class,
BuyerProcessPaymentReceivedMessage.class
@ -142,7 +142,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
BuyerPreparePaymentSentMessage.class,
BuyerSetupPayoutTxListener.class,
SetupPayoutTxListener.class,
BuyerSendPaymentSentMessage.class,
BuyerProcessPaymentReceivedMessage.class)

View File

@ -200,7 +200,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
trade.getAssetTxProofResult() != null &&
trade.getAssetTxProofResult() != AssetTxProofResult.UNDEFINED;
if (trade.getPayoutTx() != null)
if (trade.getPayoutTxId() != null)
rows++;
boolean showDisputedTx = arbitrationManager.findOwnDispute(trade.getId()).isPresent() &&
arbitrationManager.findOwnDispute(trade.getId()).get().getDisputePayoutTxId() != null;
@ -283,9 +283,9 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), // TODO (woodser): separate UI labels for deposit tx ids
trade.getTakerDepositTx().getHash());
if (trade.getPayoutTx() != null)
if (trade.getPayoutTxId() != null)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.payoutTxId"),
trade.getPayoutTx().getHash());
trade.getPayoutTxId());
if (showDisputedTx)
addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.disputedPayoutTxId"),
arbitrationManager.findOwnDispute(trade.getId()).get().getDisputePayoutTxId());

View File

@ -91,7 +91,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import monero.daemon.model.MoneroTx;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
public class PendingTradesDataModel extends ActivatableDataModel {
@Getter
@ -465,11 +464,10 @@ public class PendingTradesDataModel extends ActivatableDataModel {
byte[] payoutTxSerialized = null;
String payoutTxHashAsString = null;
MoneroTxWallet payoutTx = trade.getPayoutTx();
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
String updatedMultisigHex = multisigWallet.exportMultisigHex();
xmrWalletService.closeMultisigWallet(trade.getId()); // close multisig wallet
if (payoutTx != null) {
if (trade.getPayoutTxId() != null) {
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
// payoutTxHashAsString = payoutTx.getHashAsString();
}

View File

@ -420,7 +420,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
// deposit published
case ARBITRATOR_PUBLISHED_DEPOSIT_TXS:
case DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN:
case DEPOSIT_TXS_SEEN_IN_NETWORK:
case DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN: // TODO: separate step to wait for first confirmation
buyerState.set(BuyerState.STEP1);
sellerState.set(SellerState.STEP1);
@ -472,7 +472,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
// buyer step 4
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:
case PAYOUT_TX_SEEN_IN_NETWORK:
// Alternatively the buyer could fully sign and publish the payout tx
case BUYER_PUBLISHED_PAYOUT_TX:
buyerState.set(BuyerState.STEP4);

View File

@ -203,7 +203,7 @@ public class TradeStepInfo {
footerLabel.setVisible(false);
}
if (trade != null && trade.getPayoutTx() != null) {
if (trade != null && trade.getPayoutTxId() != null) {
button.setDisable(true);
}
}

View File

@ -653,7 +653,7 @@ public abstract class TradeStepView extends AnchorPane {
return;
}
if (trade.getPayoutTx() != null) {
if (trade.getPayoutTxId() != null) {
return;
}

View File

@ -1648,7 +1648,7 @@ message Trade {
STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 10;
SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 11;
ARBITRATOR_PUBLISHED_DEPOSIT_TXS = 12;
DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN = 13;
DEPOSIT_TXS_SEEN_IN_NETWORK = 13;
DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN = 14;
DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN = 15;
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 16;
@ -1668,8 +1668,8 @@ message Trade {
SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 30;
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 31;
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 32;
BUYER_SAW_PAYOUT_TX_IN_NETWORK = 33;
BUYER_PUBLISHED_PAYOUT_TX = 34;
BUYER_PUBLISHED_PAYOUT_TX = 33;
PAYOUT_TX_SEEN_IN_NETWORK = 34;
WITHDRAW_COMPLETED = 35;
}
@ -1712,31 +1712,33 @@ message Trade {
string taker_fee_tx_id = 3;
reserved 4;
string payout_tx_id = 5;
int64 amount_as_long = 6;
int64 tx_fee_as_long = 7;
int64 taker_fee_as_long = 8;
int64 take_offer_date = 9;
int64 price = 10;
State state = 11;
DisputeState dispute_state = 12;
TradePeriodState period_state = 13;
Contract contract = 14;
string contract_as_json = 15;
bytes contract_hash = 16;
NodeAddress arbitrator_node_address = 17;
NodeAddress mediator_node_address = 18;
string payout_tx_hex = 6;
string payout_tx_key = 7;
int64 amount_as_long = 8;
int64 tx_fee_as_long = 9;
int64 taker_fee_as_long = 10;
int64 take_offer_date = 11;
int64 price = 12;
State state = 13;
DisputeState dispute_state = 14;
TradePeriodState period_state = 15;
Contract contract = 16;
string contract_as_json = 17;
bytes contract_hash = 18;
NodeAddress arbitrator_node_address = 19;
NodeAddress mediator_node_address = 20;
string error_message = 21;
string counter_currency_tx_id = 24;
repeated ChatMessage chat_message = 25;
MediationResultState mediation_result_state = 26;
int64 lock_time = 27;
bytes delayed_payout_tx_bytes = 28;
NodeAddress refund_agent_node_address = 29;
RefundResultState refund_result_state = 30;
int64 last_refresh_request_date = 31 [deprecated = true];
string counter_currency_extra_data = 32;
string asset_tx_proof_result = 33; // name of AssetTxProofResult enum
string uid = 34;
string counter_currency_tx_id = 22;
repeated ChatMessage chat_message = 23;
MediationResultState mediation_result_state = 24;
int64 lock_time = 25;
bytes delayed_payout_tx_bytes = 26;
NodeAddress refund_agent_node_address = 27;
RefundResultState refund_result_state = 28;
int64 last_refresh_request_date = 29 [deprecated = true];
string counter_currency_extra_data = 30;
string asset_tx_proof_result = 31; // name of AssetTxProofResult enum
string uid = 32;
}
message BuyerAsMakerTrade {
@ -1819,11 +1821,10 @@ message TradingPeer {
string prepared_multisig_hex = 1005;
string made_multisig_hex = 1006;
string exchanged_multisig_hex = 1007;
string payout_tx_hex = 1008;
string deposit_tx_hash = 1009;
string deposit_tx_hex = 1010;
string deposit_tx_key = 1011;
string updated_multisig_hex = 1012;
string deposit_tx_hash = 1008;
string deposit_tx_hex = 1009;
string deposit_tx_key = 1010;
string updated_multisig_hex = 1011;
}
///////////////////////////////////////////////////////////////////////////////////////////