diff --git a/README.md b/README.md index 9c315254b1..6a89a17dfa 100644 --- a/README.md +++ b/README.md @@ -74,4 +74,4 @@ To bring Haveno to life, we need resources. If you have the possibility, please ![Qr code](https://raw.githubusercontent.com/haveno-dex/haveno/master/media/qrhaveno.png) -If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address. +If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address. \ No newline at end of file diff --git a/build.gradle b/build.gradle index d09dfbcc91..4b5887d3c7 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ configure(subprojects) { grpcVersion = '1.25.0' gsonVersion = '2.8.5' guavaVersion = '28.2-jre' - moneroJavaVersion = '0.5.3' + moneroJavaVersion = '0.5.4' httpclient5Version = '5.0' guiceVersion = '4.2.2' hamcrestVersion = '1.3' diff --git a/cli/src/main/java/bisq/cli/TradeFormat.java b/cli/src/main/java/bisq/cli/TradeFormat.java index f076c58d46..97167a3546 100644 --- a/cli/src/main/java/bisq/cli/TradeFormat.java +++ b/cli/src/main/java/bisq/cli/TradeFormat.java @@ -161,7 +161,7 @@ public class TradeFormat { private static final Function amountFormat = (t) -> t.getOffer().getBaseCurrencyCode().equals("XMR") - ? formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong())) + ? formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong())) : formatCryptoCurrencyOfferVolume(t.getOffer().getVolume()); private static final BiFunction makerTakerMinerTxFeeFormat = (t, isTaker) -> { @@ -173,13 +173,13 @@ public class TradeFormat { }; private static final BiFunction makerTakerFeeFormat = (t, isTaker) -> { - return formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTakerFeeAsLong())); + return formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTakerFeeAsLong())); }; private static final Function tradeCostFormat = (t) -> t.getOffer().getBaseCurrencyCode().equals("XMR") ? formatOfferVolume(t.getOffer().getVolume()) - : formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong())); + : formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong())); private static final BiFunction bsqReceiveAddress = (t, showBsqBuyerAddress) -> { if (showBsqBuyerAddress) { diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index 69c123bd6a..ac242d9e80 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -302,7 +302,7 @@ public class AccountAgeWitnessService { private Optional findTradePeerWitness(Trade trade) { if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers - TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer(); + TradingPeer tradingPeer = trade.getTradingPeer(); return (tradingPeer == null || tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) ? @@ -426,7 +426,7 @@ public class AccountAgeWitnessService { long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value; var factor = signedBuyFactor(accountAgeCategory); if (factor > 0) { - limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor); + limit = MathUtils.roundDoubleToLong(maxTradeLimit.value * factor); } log.debug("limit={}, factor={}, accountAgeWitnessHash={}", @@ -726,8 +726,8 @@ public class AccountAgeWitnessService { public Optional traderSignAndPublishPeersAccountAgeWitness(Trade trade) { AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null); Coin tradeAmount = trade.getTradeAmount(); - checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring"); - PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey(); + checkNotNull(trade.getTradingPeer().getPubKeyRing(), "Peer must have a keyring"); + PublicKey peersPubKey = trade.getTradingPeer().getPubKeyRing().getSignaturePubKey(); checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", trade.toString()); checkNotNull(tradeAmount, "Trade amount must not be null"); @@ -767,15 +767,11 @@ public class AccountAgeWitnessService { boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) || filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) || filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) || - filterManager.isPaymentMethodBanned( - PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) || - filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload()) || - filterManager.arePeersPaymentAccountDataBanned( - dispute.getContract().getSellerPaymentAccountPayload()) || - filterManager.isWitnessSignerPubKeyBanned( - Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) || - filterManager.isWitnessSignerPubKeyBanned( - Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes())); + filterManager.isPaymentMethodBanned(PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) || + filterManager.arePeersPaymentAccountDataBanned(dispute.getBuyerPaymentAccountPayload()) || + filterManager.arePeersPaymentAccountDataBanned(dispute.getSellerPaymentAccountPayload()) || + filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) || + filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes())); return !isFiltered; } @@ -797,8 +793,8 @@ public class AccountAgeWitnessService { PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing(); PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing(); - PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload(); - PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload(); + PaymentAccountPayload buyerPaymentAccountPaload = dispute.getBuyerPaymentAccountPayload(); + PaymentAccountPayload sellerPaymentAccountPaload = dispute.getSellerPaymentAccountPayload(); TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing) .map(witness -> new TraderDataItem( @@ -913,8 +909,7 @@ public class AccountAgeWitnessService { public boolean isSignWitnessTrade(Trade trade) { checkNotNull(trade, "trade must not be null"); checkNotNull(trade.getOffer(), "offer must not be null"); - Contract contract = checkNotNull(trade.getContract()); - PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); + PaymentAccountPayload sellerPaymentAccountPayload = trade.getSeller().getPaymentAccountPayload(); AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload); getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness); diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java index 628f0a8186..da92b96b73 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java @@ -131,14 +131,14 @@ public class AccountAgeWitnessUtils { // Log to find why accounts sometimes don't get signed as expected // TODO: Demote to debug or remove once account signing is working ok checkNotNull(trade.getContract()); - checkNotNull(trade.getContract().getBuyerPaymentAccountPayload()); + checkNotNull(trade.getBuyer().getPaymentAccountPayload()); boolean checkingSignTrade = true; boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing()); AccountAgeWitness witness = myWitness; if (witness == null) { witness = isBuyer ? - accountAgeWitnessService.getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) : - accountAgeWitnessService.getMyWitness(trade.getContract().getSellerPaymentAccountPayload()); + accountAgeWitnessService.getMyWitness(trade.getBuyer().getPaymentAccountPayload()) : + accountAgeWitnessService.getMyWitness(trade.getSeller().getPaymentAccountPayload()); checkingSignTrade = false; } boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) && @@ -157,9 +157,9 @@ public class AccountAgeWitnessUtils { "\nisSignWitnessTrade: {}", trade.getId(), isBuyer, - getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(), + getWitnessDebugLog(trade.getBuyer().getPaymentAccountPayload(), trade.getContract().getBuyerPubKeyRing()), - getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(), + getWitnessDebugLog(trade.getSeller().getPaymentAccountPayload(), trade.getContract().getSellerPubKeyRing()), checkingSignTrade, // Following cases added to use same logic as in seller signing check accountAgeWitnessService.accountIsSigner(witness), diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 21942a60ff..9d93994faa 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -109,7 +109,6 @@ class CoreTradesService { tradeManager.onTakeOffer(offer.getAmount(), takeOfferModel.getTxFeeFromFeeService(), takeOfferModel.getTakerFee(), - offer.getPrice().getValue(), takeOfferModel.getFundsNeededForTrade(), offer, paymentAccountId, diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java index 8ba70a02e2..c5c3c60c43 100644 --- a/core/src/main/java/bisq/core/api/model/TradeInfo.java +++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java @@ -108,8 +108,8 @@ public class TradeInfo implements Payload { contract.isBuyerMakerAndSellerTaker(), contract.getMakerAccountId(), contract.getTakerAccountId(), - toPaymentAccountPayloadInfo(contract.getMakerPaymentAccountPayload()), - toPaymentAccountPayloadInfo(contract.getTakerPaymentAccountPayload()), + toPaymentAccountPayloadInfo(trade.getMaker().getPaymentAccountPayload()), + toPaymentAccountPayloadInfo(trade.getTaker().getPaymentAccountPayload()), contract.getMakerPayoutAddressString(), contract.getTakerPayoutAddressString(), contract.getLockTime()); @@ -127,8 +127,8 @@ public class TradeInfo implements Payload { .withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong()) .withTakerFeeTxId(trade.getTakerFeeTxId()) - .withMakerDepositTxId(trade.getMakerDepositTxId()) - .withTakerDepositTxId(trade.getTakerDepositTxId()) + .withMakerDepositTxId(trade.getMaker().getDepositTxHash()) + .withTakerDepositTxId(trade.getTaker().getDepositTxHash()) .withPayoutTxId(trade.getPayoutTxId()) .withTradeAmountAsLong(trade.getTradeAmountAsLong()) .withTradePrice(trade.getTradePrice().getValue()) diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java index a5cb49acd4..ba7492b20e 100644 --- a/core/src/main/java/bisq/core/app/WalletAppSetup.java +++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java @@ -249,10 +249,10 @@ public class WalletAppSetup { .filter(trade -> trade.getOffer() != null) .forEach(trade -> { String details = null; - if (txId.equals(trade.getMakerDepositTxId())) { + if (txId.equals(trade.getMaker().getDepositTxHash())) { details = Res.get("popup.warning.trade.txRejected.deposit"); // TODO (woodser): txRejected.maker_deposit, txRejected.taker_deposit } - if (txId.equals(trade.getTakerDepositTxId())) { + if (txId.equals(trade.getTaker().getDepositTxHash())) { details = Res.get("popup.warning.trade.txRejected.deposit"); } if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) { diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java index d9247f0822..6c6dc9a81b 100644 --- a/core/src/main/java/bisq/core/btc/Balances.java +++ b/core/src/main/java/bisq/core/btc/Balances.java @@ -17,7 +17,9 @@ package bisq.core.btc; +import bisq.common.UserThread; import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.support.dispute.Dispute; @@ -26,30 +28,21 @@ import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; - -import bisq.common.UserThread; - -import org.bitcoinj.core.Coin; - -import javax.inject.Inject; - +import bisq.core.util.ParsingUtils; +import bisq.network.p2p.P2PService; +import java.math.BigInteger; +import java.util.List; +import java.util.stream.Collectors; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; - import javafx.collections.ListChangeListener; - -import java.math.BigInteger; - -import java.util.List; - +import javax.inject.Inject; import lombok.Getter; import lombok.extern.slf4j.Slf4j; - - - -import monero.wallet.model.MoneroAccount; +import monero.wallet.model.MoneroOutputQuery; import monero.wallet.model.MoneroOutputWallet; import monero.wallet.model.MoneroWalletListener; +import org.bitcoinj.core.Coin; @Slf4j public class Balances { @@ -98,29 +91,45 @@ public class Balances { // Need to delay a bit to get the balances correct UserThread.execute(() -> { updateAvailableBalance(); - updateReservedBalance(); updateLockedBalance(); + updateReservedBalance(); }); } - // TODO (woodser): currently reserved balance = reserved for trade (excluding multisig) and locked balance = txs with < 10 confirmations + // TODO (woodser): balances being set as Coin from BigInteger.longValue(), which can lose precision. should be in centineros for consistency with the rest of the application private void updateAvailableBalance() { - availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValue())); + availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValueExact())); } - - private void updateReservedBalance() { - BigInteger sum = new BigInteger("0"); - List accounts = xmrWalletService.getWallet().getAccounts(); - for (MoneroAccount account : accounts) { - if (account.getIndex() != 0) sum = sum.add(account.getBalance()); - } - reservedBalance.set(Coin.valueOf(sum.longValue())); - } - + private void updateLockedBalance() { BigInteger balance = xmrWalletService.getWallet().getBalance(0); BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0); - lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValue())); + lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValueExact())); + } + + private void updateReservedBalance() { + + // add frozen input amounts + Coin sum = Coin.valueOf(0); + List frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false)); + for (MoneroOutputWallet frozenOutput : frozenOutputs) sum = sum.add(Coin.valueOf(frozenOutput.getAmount().longValueExact())); + + // add multisig deposit amounts + List openTrades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); + for (Trade trade : openTrades) { + if (trade.getContract() == null) continue; + Long reservedAmt; + OfferPayload offerPayload = trade.getContract().getOfferPayload(); + if (trade.getArbitratorNodeAddress().equals(P2PService.getMyNodeAddress())) { // TODO (woodser): this only works if node address does not change + reservedAmt = offerPayload.getAmount() + offerPayload.getBuyerSecurityDeposit() + offerPayload.getSellerSecurityDeposit(); // arbitrator reserved balance is sum of amounts sent to multisig + } else { + reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit(); + } + sum = sum.add(Coin.valueOf(ParsingUtils.centinerosToAtomicUnits(reservedAmt).longValueExact())); + } + + // set reserved balance + reservedBalance.set(sum); } } diff --git a/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java b/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java index 2c8247453d..3cf2f0081e 100644 --- a/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java +++ b/core/src/main/java/bisq/core/btc/listeners/XmrBalanceListener.java @@ -20,17 +20,17 @@ package bisq.core.btc.listeners; import java.math.BigInteger; public class XmrBalanceListener { - private Integer accountIndex; + private Integer subaddressIndex; public XmrBalanceListener() { } public XmrBalanceListener(Integer accountIndex) { - this.accountIndex = accountIndex; + this.subaddressIndex = accountIndex; } - public Integer getAccountIndex() { - return accountIndex; + public Integer getSubaddressIndex() { + return subaddressIndex; } public void onBalanceChanged(BigInteger balance) { diff --git a/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java b/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java index aaba5fec67..812d91fd30 100644 --- a/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java +++ b/core/src/main/java/bisq/core/btc/model/XmrAddressEntry.java @@ -60,7 +60,7 @@ public final class XmrAddressEntry implements PersistablePayload { @Getter private final Context context; @Getter - private final int accountIndex; + private final int subaddressIndex; @Getter private final String addressString; @@ -71,12 +71,12 @@ public final class XmrAddressEntry implements PersistablePayload { // Constructor, initialization /////////////////////////////////////////////////////////////////////////////////////////// - public XmrAddressEntry(int accountIndex, String address, Context context) { - this(accountIndex, address, context, null, null); + public XmrAddressEntry(int subaddressIndex, String address, Context context) { + this(subaddressIndex, address, context, null, null); } - public XmrAddressEntry(int accountIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) { - this.accountIndex = accountIndex; + public XmrAddressEntry(int subaddressIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) { + this.subaddressIndex = subaddressIndex; this.addressString = address; this.offerId = offerId; this.context = context; @@ -89,7 +89,7 @@ public final class XmrAddressEntry implements PersistablePayload { /////////////////////////////////////////////////////////////////////////////////////////// public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) { - return new XmrAddressEntry(proto.getAccountIndex(), + return new XmrAddressEntry(proto.getSubaddressIndex(), ProtoUtil.stringOrNullFromProto(proto.getAddressString()), ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()), ProtoUtil.stringOrNullFromProto(proto.getOfferId()), @@ -99,7 +99,7 @@ public final class XmrAddressEntry implements PersistablePayload { @Override public protobuf.XmrAddressEntry toProtoMessage() { protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder() - .setAccountIndex(accountIndex) + .setSubaddressIndex(subaddressIndex) .setAddressString(addressString) .setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name())) .setCoinLockedInMultiSig(coinLockedInMultiSig); @@ -143,7 +143,7 @@ public final class XmrAddressEntry implements PersistablePayload { return "XmrAddressEntry{" + "offerId='" + getOfferId() + '\'' + ", context=" + context + - ", accountIndex=" + getAccountIndex() + + ", subaddressIndex=" + getSubaddressIndex() + ", address=" + getAddressString() + '}'; } diff --git a/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java b/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java index 59c0212f41..114259fa3b 100644 --- a/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java +++ b/core/src/main/java/bisq/core/btc/model/XmrAddressEntryList.java @@ -38,7 +38,6 @@ import lombok.extern.slf4j.Slf4j; import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroAccount; import monero.wallet.model.MoneroOutputWallet; import monero.wallet.model.MoneroWalletListener; @@ -131,10 +130,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted // }); // // toBeRemoved.forEach(entrySet::remove); - } else { - // As long the old arbitration domain is not removed from the code base we still support it here. - MoneroAccount account = wallet.createAccount(); - entrySet.add(new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), XmrAddressEntry.Context.ARBITRATOR)); } // In case we restore from seed words and have balance we need to add the relevant addresses to our list. @@ -174,7 +169,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted if (entryWithSameOfferIdAndContextAlreadyExist) { log.error("We have an address entry with the same offer ID and context. We do not add the new one. " + "addressEntry={}, entrySet={}", addressEntry, entrySet); - if (true) throw new RuntimeException("why?"); return; } @@ -185,7 +179,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted public void swapToAvailable(XmrAddressEntry addressEntry) { boolean setChangedByRemove = entrySet.remove(addressEntry); - boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), + boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getSubaddressIndex(), addressEntry.getAddressString(), XmrAddressEntry.Context.AVAILABLE)); if (setChangedByRemove || setChangedByAdd) { requestPersistence(); @@ -196,7 +190,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted XmrAddressEntry.Context context, String offerId) { boolean setChangedByRemove = entrySet.remove(addressEntry); - final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), context, offerId, null); + final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getSubaddressIndex(), addressEntry.getAddressString(), context, offerId, null); boolean setChangedByAdd = entrySet.add(newAddressEntry); if (setChangedByRemove || setChangedByAdd) requestPersistence(); @@ -213,6 +207,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted // Private /////////////////////////////////////////////////////////////////////////////////////////// + // TODO (woodser): this should be removed since only using account 0 private void maybeAddNewAddressEntry(MoneroOutputWallet output) { if (output.getAccountIndex() == 0) return; String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex()); diff --git a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java index 97cb0f7464..4d0e8ea198 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletConfig.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletConfig.java @@ -89,6 +89,8 @@ import static com.google.common.base.Preconditions.checkState; import monero.common.MoneroUtils; +import monero.daemon.MoneroDaemon; +import monero.daemon.MoneroDaemonRpc; import monero.daemon.model.MoneroNetworkType; import monero.wallet.MoneroWallet; import monero.wallet.MoneroWalletRpc; @@ -137,6 +139,7 @@ public class WalletConfig extends AbstractIdleService { protected final String filePrefix; protected volatile BlockChain vChain; protected volatile SPVBlockStore vStore; + protected volatile MoneroDaemon vXmrDaemon; protected volatile MoneroWallet vXmrWallet; protected volatile Wallet vBtcWallet; protected volatile Wallet vBsqWallet; @@ -345,6 +348,9 @@ public class WalletConfig extends AbstractIdleService { try { File chainFile = new File(directory, filePrefix + ".spvchain"); boolean chainFileExists = chainFile.exists(); + + // XMR daemon + vXmrDaemon = new MoneroDaemonRpc(MONERO_DAEMON_URI, MONERO_DAEMON_USERNAME, MONERO_DAEMON_PASSWORD); // XMR wallet String xmrPrefix = "_XMR"; @@ -361,7 +367,8 @@ public class WalletConfig extends AbstractIdleService { // vXmrWallet.rescanBlockchain(); vXmrWallet.sync(); vXmrWallet.save(); - System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance()); + System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance(0)); + System.out.println("Loaded wallet unlocked balance: " + vXmrWallet.getUnlockedBalance(0)); String btcPrefix = "_BTC"; vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet"); @@ -626,10 +633,16 @@ public class WalletConfig extends AbstractIdleService { return vBtcWallet; } + + public MoneroDaemon getXmrDaemon() { + checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); + return vXmrDaemon; + } + public MoneroWallet getXmrWallet() { - checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); - return vXmrWallet; - } + checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); + return vXmrWallet; + } public Wallet bsqWallet() { checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java index d4a00453ae..3454882c1c 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java @@ -105,7 +105,7 @@ import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; - +import monero.daemon.MoneroDaemon; import monero.wallet.MoneroWallet; // Setup wallets and use WalletConfig for BitcoinJ wiring. @@ -490,6 +490,10 @@ public class WalletsSetup { return walletConfig.btcWallet(); } + public MoneroDaemon getXmrDaemon() { + return walletConfig.getXmrDaemon(); + } + public MoneroWallet getXmrWallet() { return walletConfig.getXmrWallet(); } diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java index 87f701d114..2df2f90eb5 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -146,8 +146,8 @@ public class TradeWalletService { return xmrWallet.createTx(new MoneroTxConfig() .setAccountIndex(0) .setDestinations( - new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)), - new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value))) + new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)), + new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer))) .setRelay(broadcastTx)); } diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java index f182032d3c..6d23da97fa 100644 --- a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java @@ -1,5 +1,6 @@ package bisq.core.btc.wallet; +import bisq.common.UserThread; import bisq.core.btc.exceptions.AddressEntryException; import bisq.core.btc.listeners.XmrBalanceListener; import bisq.core.btc.model.XmrAddressEntry; @@ -36,11 +37,10 @@ import lombok.Getter; import monero.common.MoneroUtils; +import monero.daemon.MoneroDaemon; import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroAccount; import monero.wallet.model.MoneroOutputWallet; import monero.wallet.model.MoneroSubaddress; -import monero.wallet.model.MoneroTransfer; import monero.wallet.model.MoneroTxConfig; import monero.wallet.model.MoneroTxQuery; import monero.wallet.model.MoneroTxWallet; @@ -57,6 +57,8 @@ public class XmrWalletService { protected final CopyOnWriteArraySet walletListeners = new CopyOnWriteArraySet<>(); private Map multisigWallets; + @Getter + private MoneroDaemon daemon; @Getter private MoneroWallet wallet; @@ -69,48 +71,48 @@ public class XmrWalletService { this.multisigWallets = new HashMap(); walletsSetup.addSetupCompletedHandler(() -> { - wallet = walletsSetup.getXmrWallet(); - wallet.addListener(new MoneroWalletListener() { - @Override - public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { } + daemon = walletsSetup.getXmrDaemon(); + wallet = walletsSetup.getXmrWallet(); + wallet.addListener(new MoneroWalletListener() { + @Override + public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { } - @Override - public void onNewBlock(long height) { } + @Override + public void onNewBlock(long height) { } - @Override - public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { - notifyBalanceListeners(); - } - }); + @Override + public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { + notifyBalanceListeners(); + } + }); }); } // TODO (woodser): wallet has single password which is passed here? // TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse - public MoneroWallet getOrCreateMultisigWallet(String tradeId) { - String path = "xmr_multisig_trade_" + tradeId; - MoneroWallet multisigWallet = null; - if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); - else if (MoneroUtils.walletExists(new File(walletsSetup.getWalletConfig().directory(), path).getPath())) { // TODO: use monero-wallet-rpc to determine existence? - multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig() - .setPath(path) - .setPassword("abctesting123")); - } else { + + public MoneroWallet createMultisigWallet(String tradeId) { + if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); + String path = "xmr_multisig_trade_" + tradeId; + MoneroWallet multisigWallet = null; multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig() .setPath(path) .setPassword("abctesting123")); - } - multisigWallets.put(tradeId, multisigWallet); - multisigWallet.startSyncing(5000l); - return multisigWallet; + multisigWallets.put(tradeId, multisigWallet); + multisigWallet.startSyncing(5000l); + return multisigWallet; } - - public XmrAddressEntry getArbitratorAddressEntry() { - XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR; - Optional addressEntry = getAddressEntryListAsImmutableList().stream() - .filter(e -> context == e.getContext()) - .findAny(); - return getOrCreateAddressEntry(context, addressEntry); + + public MoneroWallet getMultisigWallet(String tradeId) { + if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); + String path = "xmr_multisig_trade_" + tradeId; + MoneroWallet multisigWallet = null; + multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig() + .setPath(path) + .setPassword("abctesting123")); + multisigWallets.put(tradeId, multisigWallet); + multisigWallet.startSyncing(5000l); + return multisigWallet; } public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) { @@ -121,17 +123,10 @@ public class XmrWalletService { } public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) { - if (context == XmrAddressEntry.Context.TRADE_PAYOUT) { - XmrAddressEntry entry = new XmrAddressEntry(0, wallet.createSubaddress(0).getAddress(), context, offerId, null); - System.out.println("Adding address entry: " + entry.getAccountIndex() + ", " + entry.getAddressString()); + MoneroSubaddress subaddress = wallet.createSubaddress(0); + XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null); addressEntryList.addAddressEntry(entry); return entry; - } else { - MoneroAccount account = wallet.createAccount(); - XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null); - addressEntryList.addAddressEntry(entry); - return entry; - } } public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) { @@ -142,37 +137,19 @@ public class XmrWalletService { if (addressEntry.isPresent()) { return addressEntry.get(); } else { - // We try to use available and not yet used entries // TODO (woodser): "available" entries is not applicable in xmr which uses account 0 for main wallet and subsequent accounts for reserved trades, refactor address association for xmr? + // We try to use available and not yet used entries Optional emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream() .filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext()) - .filter(e -> isAccountUnused(e.getAccountIndex())) + .filter(e -> isSubaddressUnused(e.getSubaddressIndex())) .findAny(); if (emptyAvailableAddressEntry.isPresent()) { return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId); } else { - MoneroAccount account = wallet.createAccount(); - XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null); - addressEntryList.addAddressEntry(entry); - return entry; + return getNewAddressEntry(offerId, context); } } } - private XmrAddressEntry getOrCreateAddressEntry(XmrAddressEntry.Context context, Optional addressEntry) { - if (addressEntry.isPresent()) { - return addressEntry.get(); - } else { - if (context == XmrAddressEntry.Context.ARBITRATOR) { - MoneroSubaddress subaddress = wallet.createSubaddress(0); - XmrAddressEntry entry = new XmrAddressEntry(0, subaddress.getAddress(), context); - addressEntryList.addAddressEntry(entry); - return entry; - } else { - throw new RuntimeException("XmrWalletService.getOrCreateAddressEntry(context, addressEntry) not implemented for non-arbitrator context"); // TODO (woodser): this method used with non-arbitrator context? - } - } - } - public Optional getAddressEntry(String offerId, XmrAddressEntry.Context context) { return getAddressEntryListAsImmutableList().stream() .filter(e -> offerId.equals(e.getOfferId())) @@ -238,7 +215,7 @@ public class XmrWalletService { public List getFundedAvailableAddressEntries() { return getAvailableAddressEntries().stream() - .filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive()) + .filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive()) .collect(Collectors.toList()); } @@ -246,26 +223,26 @@ public class XmrWalletService { return addressEntryList.getAddressEntriesAsListImmutable(); } - public boolean isAccountUnused(int accountIndex) { - return accountIndex != 0 && getBalanceForAccount(accountIndex).value == 0; + public boolean isSubaddressUnused(int subaddressIndex) { + return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0; //return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds } - public Coin getBalanceForAccount(int accountIndex) { + public Coin getBalanceForSubaddress(int subaddressIndex) { + + // get subaddress balance + BigInteger balance = wallet.getBalance(0, subaddressIndex); - // get wallet balance - BigInteger balance = wallet.getBalance(accountIndex); +// // balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack? +// for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) { +// for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) { +// if (transfer.getAccountIndex() == subaddressIndex) { +// balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount()); +// } +// } +// } - // balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack? - for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) { - for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) { - if (transfer.getAccountIndex() == accountIndex) { - balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount()); - } - } - } - - System.out.println("Returning balance for account " + accountIndex + ": " + balance.longValueExact()); + System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact()); return Coin.valueOf(balance.longValueExact()); } @@ -283,7 +260,7 @@ public class XmrWalletService { Stream availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream()); Stream available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream()); available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream()); - return available.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive()); + return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive()); } public void addBalanceListener(XmrBalanceListener listener) { @@ -353,7 +330,7 @@ public class XmrWalletService { MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig() .setAccountIndex(fromAccountIndex) .setAddress(toAddress) - .setAmount(ParsingUtils.satoshisToXmrAtomicUnits(receiverAmount.value)) + .setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount)) .setRelay(true)); callback.onSuccess(tx); printTxs("sendFunds", tx); @@ -387,17 +364,23 @@ public class XmrWalletService { private void notifyBalanceListeners() { for (XmrBalanceListener balanceListener : balanceListeners) { Coin balance; - if (balanceListener.getAccountIndex() != null && balanceListener.getAccountIndex() != 0) { - balance = getBalanceForAccount(balanceListener.getAccountIndex()); + if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) { + balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex()); } else { balance = getAvailableConfirmedBalance(); } - balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value)); + UserThread.execute(new Runnable() { + @Override public void run() { + balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value)); + } + }); } } /** * Wraps a MoneroWalletListener to notify the Haveno application. + * + * TODO (woodser): this is no longer necessary since not syncing to thread? */ public class HavenoWalletListener extends MoneroWalletListener { @@ -409,27 +392,47 @@ public class XmrWalletService { @Override public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { - listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); + UserThread.execute(new Runnable() { + @Override public void run() { + listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); + } + }); } @Override public void onNewBlock(long height) { - listener.onNewBlock(height); + UserThread.execute(new Runnable() { + @Override public void run() { + listener.onNewBlock(height); + } + }); } - + @Override public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { - listener.onBalancesChanged(newBalance, newUnlockedBalance); + UserThread.execute(new Runnable() { + @Override public void run() { + listener.onBalancesChanged(newBalance, newUnlockedBalance); + } + }); } @Override public void onOutputReceived(MoneroOutputWallet output) { - listener.onOutputReceived(output); + UserThread.execute(new Runnable() { + @Override public void run() { + listener.onOutputReceived(output); + } + }); } @Override public void onOutputSpent(MoneroOutputWallet output) { - listener.onOutputSpent(output); + UserThread.execute(new Runnable() { + @Override public void run() { + listener.onOutputSpent(output); + } + }); } } } diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/AvailabilityResult.java index e4ad982163..4ba827aebe 100644 --- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java +++ b/core/src/main/java/bisq/core/offer/AvailabilityResult.java @@ -30,7 +30,8 @@ public enum AvailabilityResult { @SuppressWarnings("unused") NO_REFUND_AGENTS("cannot take offer because no refund agents are available"), UNCONF_TX_LIMIT_HIT("cannot take offer because you have too many unconfirmed transactions at this moment"), MAKER_DENIED_API_USER("cannot take offer because maker is api user"), - PRICE_CHECK_FAILED("cannot take offer because trade price check failed"); + PRICE_CHECK_FAILED("cannot take offer because trade price check failed"), + MAKER_DENIED_TAKER("cannot take offer because maker denied taker"); private final String description; diff --git a/core/src/main/java/bisq/core/offer/CreateOfferService.java b/core/src/main/java/bisq/core/offer/CreateOfferService.java index 733eee1a7f..9d4c6de82e 100644 --- a/core/src/main/java/bisq/core/offer/CreateOfferService.java +++ b/core/src/main/java/bisq/core/offer/CreateOfferService.java @@ -23,10 +23,14 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.monetary.Price; +import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.coin.CoinUtil; @@ -64,6 +68,8 @@ public class CreateOfferService { private final PubKeyRing pubKeyRing; private final User user; private final BtcWalletService btcWalletService; + private final TradeStatisticsManager tradeStatisticsManager; + private final MediatorManager mediatorManager; /////////////////////////////////////////////////////////////////////////////////////////// @@ -77,7 +83,9 @@ public class CreateOfferService { P2PService p2PService, PubKeyRing pubKeyRing, User user, - BtcWalletService btcWalletService) { + BtcWalletService btcWalletService, + TradeStatisticsManager tradeStatisticsManager, + MediatorManager mediatorManager) { this.offerUtil = offerUtil; this.txFeeEstimationService = txFeeEstimationService; this.priceFeedService = priceFeedService; @@ -85,6 +93,8 @@ public class CreateOfferService { this.pubKeyRing = pubKeyRing; this.user = user; this.btcWalletService = btcWalletService; + this.tradeStatisticsManager = tradeStatisticsManager; + this.mediatorManager = mediatorManager; } @@ -181,6 +191,9 @@ public class CreateOfferService { paymentAccount, currencyCode, makerFeeAsCoin); + + // select signing arbitrator + Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators OfferPayload offerPayload = new OfferPayload(offerId, creationTime, @@ -194,8 +207,6 @@ public class CreateOfferService { minAmountAsLong, baseCurrencyCode, counterCurrencyCode, - arbitratorNodeAddresses, - mediatorNodeAddresses, paymentAccount.getPaymentMethod().getId(), paymentAccount.getId(), null, @@ -219,7 +230,9 @@ public class CreateOfferService { isPrivateOffer, hashOfChallenge, extraDataMap, - Version.TRADE_PROTOCOL_VERSION); + Version.TRADE_PROTOCOL_VERSION, + arbitrator.getNodeAddress(), + null); Offer offer = new Offer(offerPayload); offer.setPriceFeedService(priceFeedService); return offer; diff --git a/core/src/main/java/bisq/core/offer/Offer.java b/core/src/main/java/bisq/core/offer/Offer.java index c79afcf99d..ce1a51062d 100644 --- a/core/src/main/java/bisq/core/offer/Offer.java +++ b/core/src/main/java/bisq/core/offer/Offer.java @@ -82,7 +82,7 @@ public class Offer implements NetworkPayload, PersistablePayload { public enum State { UNKNOWN, - OFFER_FEE_PAID, + OFFER_FEE_RESERVED, AVAILABLE, NOT_AVAILABLE, REMOVED, diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java index bfd37f2af1..9d42736e41 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -22,6 +22,8 @@ import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.trade.TradeUtils; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -80,7 +82,8 @@ public class OfferFilter { IS_NODE_ADDRESS_BANNED, REQUIRE_UPDATE_TO_NEW_VERSION, IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, - IS_MY_INSUFFICIENT_TRADE_LIMIT; + IS_MY_INSUFFICIENT_TRADE_LIMIT, + SIGNATURE_NOT_VALIDATED; @Getter private final boolean isValid; @@ -128,6 +131,9 @@ public class OfferFilter { if (isMyInsufficientTradeLimit(offer)) { return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; } + if (!hasValidSignature(offer)) { + return Result.SIGNATURE_NOT_VALIDATED; // TODO (woodser): handle this wherever IS_MY_INSUFFICIENT_TRADE_LIMIT is handled + } return Result.VALID; } @@ -206,4 +212,14 @@ public class OfferFilter { myInsufficientTradeLimitCache.put(offerId, result); return result; } + + public boolean hasValidSignature(Offer offer) { + + // get arbitrator + Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()); // TODO (woodser): does this return null if arbitrator goes offline? + if (arbitrator == null) return false; // TODO (woodser): if arbitrator is null, get arbirator's pub key ring from store, otherwise cannot validate and offer is not seen by takers when arbitrator goes offline + + // validate arbitrator signature + return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator); + } } diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/OfferPayload.java index 1db517a1b3..6be94c7979 100644 --- a/core/src/main/java/bisq/core/offer/OfferPayload.java +++ b/core/src/main/java/bisq/core/offer/OfferPayload.java @@ -36,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -120,12 +119,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay private final String baseCurrencyCode; private final String counterCurrencyCode; - @Deprecated - // Not used anymore but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash) - private final List arbitratorNodeAddresses; - @Deprecated - // Not used anymore but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash) - private final List mediatorNodeAddresses; private final String paymentMethodId; private final String makerPaymentAccountId; // Mutable property. Has to be set before offer is save in P2P network as it changes the objects hash! @@ -174,6 +167,13 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay @Nullable private final Map extraDataMap; private final int protocolVersion; + + // address and signature of signing arbitrator + @Setter + private NodeAddress arbitratorNodeAddress; + @Nullable + @Setter + private String arbitratorSignature; /////////////////////////////////////////////////////////////////////////////////////////// @@ -192,8 +192,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay long minAmount, String baseCurrencyCode, String counterCurrencyCode, - List arbitratorNodeAddresses, - List mediatorNodeAddresses, String paymentMethodId, String makerPaymentAccountId, @Nullable String offerFeePaymentTxId, @@ -217,7 +215,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay boolean isPrivateOffer, @Nullable String hashOfChallenge, @Nullable Map extraDataMap, - int protocolVersion) { + int protocolVersion, + NodeAddress arbitratorSigner, + @Nullable String arbitratorSignature) { this.id = id; this.date = date; this.ownerNodeAddress = ownerNodeAddress; @@ -230,8 +230,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay this.minAmount = minAmount; this.baseCurrencyCode = baseCurrencyCode; this.counterCurrencyCode = counterCurrencyCode; - this.arbitratorNodeAddresses = arbitratorNodeAddresses; - this.mediatorNodeAddresses = mediatorNodeAddresses; this.paymentMethodId = paymentMethodId; this.makerPaymentAccountId = makerPaymentAccountId; this.offerFeePaymentTxId = offerFeePaymentTxId; @@ -256,6 +254,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay this.hashOfChallenge = hashOfChallenge; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.protocolVersion = protocolVersion; + this.arbitratorNodeAddress = arbitratorSigner; + this.arbitratorSignature = arbitratorSignature; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -277,12 +277,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay .setMinAmount(minAmount) .setBaseCurrencyCode(baseCurrencyCode) .setCounterCurrencyCode(counterCurrencyCode) - .addAllArbitratorNodeAddresses(arbitratorNodeAddresses.stream() - .map(NodeAddress::toProtoMessage) - .collect(Collectors.toList())) - .addAllMediatorNodeAddresses(mediatorNodeAddresses.stream() - .map(NodeAddress::toProtoMessage) - .collect(Collectors.toList())) .setPaymentMethodId(paymentMethodId) .setMakerPaymentAccountId(makerPaymentAccountId) .setVersionNr(versionNr) @@ -310,6 +304,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes); Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge); Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData); + + builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()); + Optional.ofNullable(arbitratorSignature).ifPresent(builder::setArbitratorSignature); return protobuf.StoragePayload.newBuilder().setOfferPayload(builder).build(); } @@ -336,12 +333,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay proto.getMinAmount(), proto.getBaseCurrencyCode(), proto.getCounterCurrencyCode(), - proto.getArbitratorNodeAddressesList().stream() - .map(NodeAddress::fromProto) - .collect(Collectors.toList()), - proto.getMediatorNodeAddressesList().stream() - .map(NodeAddress::fromProto) - .collect(Collectors.toList()), proto.getPaymentMethodId(), proto.getMakerPaymentAccountId(), proto.getOfferFeePaymentTxId(), @@ -365,7 +356,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay proto.getIsPrivateOffer(), hashOfChallenge, extraDataMapMap, - proto.getProtocolVersion()); + proto.getProtocolVersion(), + NodeAddress.fromProto(proto.getArbitratorNodeAddress()), + ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature())); } @@ -431,6 +424,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay ",\n hashOfChallenge='" + hashOfChallenge + '\'' + ",\n extraDataMap=" + extraDataMap + ",\n protocolVersion=" + protocolVersion + + ",\n arbitratorSigner=" + arbitratorNodeAddress + + ",\n arbitratorSignature=" + arbitratorSignature + "\n}"; } } diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java index 4c2d06c308..5d5c63596f 100644 --- a/core/src/main/java/bisq/core/offer/OpenOffer.java +++ b/core/src/main/java/bisq/core/offer/OpenOffer.java @@ -24,8 +24,9 @@ import bisq.network.p2p.NodeAddress; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.proto.ProtoUtil; - +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Optional; import lombok.EqualsAndHashCode; @@ -58,16 +59,9 @@ public final class OpenOffer implements Tradable { @Setter @Nullable private NodeAddress arbitratorNodeAddress; - @Getter @Setter - @Nullable - private NodeAddress mediatorNodeAddress; - - // Added v1.2.0 @Getter - @Setter - @Nullable - private NodeAddress refundAgentNodeAddress; + private List frozenKeyImages = new ArrayList<>(); // Added in v1.5.3. // If market price reaches that trigger price the offer gets deactivated @@ -86,6 +80,13 @@ public final class OpenOffer implements Tradable { this.triggerPrice = triggerPrice; state = State.AVAILABLE; } + + public OpenOffer(Offer offer, long triggerPrice, List frozenKeyImages) { + this.offer = offer; + this.triggerPrice = triggerPrice; + state = State.AVAILABLE; + this.frozenKeyImages = frozenKeyImages; + } /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER @@ -94,14 +95,10 @@ public final class OpenOffer implements Tradable { private OpenOffer(Offer offer, State state, @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, - @Nullable NodeAddress refundAgentNodeAddress, long triggerPrice) { this.offer = offer; this.state = state; this.arbitratorNodeAddress = arbitratorNodeAddress; - this.mediatorNodeAddress = mediatorNodeAddress; - this.refundAgentNodeAddress = refundAgentNodeAddress; this.triggerPrice = triggerPrice; if (this.state == State.RESERVED) @@ -113,22 +110,21 @@ public final class OpenOffer implements Tradable { protobuf.OpenOffer.Builder builder = protobuf.OpenOffer.newBuilder() .setOffer(offer.toProtoMessage()) .setTriggerPrice(triggerPrice) - .setState(protobuf.OpenOffer.State.valueOf(state.name())); + .setState(protobuf.OpenOffer.State.valueOf(state.name())) + .addAllFrozenKeyImages(frozenKeyImages); Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage())); - Optional.ofNullable(mediatorNodeAddress).ifPresent(nodeAddress -> builder.setMediatorNodeAddress(nodeAddress.toProtoMessage())); - Optional.ofNullable(refundAgentNodeAddress).ifPresent(nodeAddress -> builder.setRefundAgentNodeAddress(nodeAddress.toProtoMessage())); return protobuf.Tradable.newBuilder().setOpenOffer(builder).build(); } public static Tradable fromProto(protobuf.OpenOffer proto) { - return new OpenOffer(Offer.fromProto(proto.getOffer()), + OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()), ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()), proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, - proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, - proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, proto.getTriggerPrice()); + openOffer.setFrozenKeyImages(proto.getFrozenKeyImagesList()); + return openOffer; } @@ -192,8 +188,6 @@ public final class OpenOffer implements Tradable { ",\n offer=" + offer + ",\n state=" + state + ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + - ",\n mediatorNodeAddress=" + mediatorNodeAddress + - ",\n refundAgentNodeAddress=" + refundAgentNodeAddress + ",\n triggerPrice=" + triggerPrice + "\n}"; } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 6b8a3b5b73..8d74961d50 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -29,18 +29,23 @@ import bisq.core.locale.Res; import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityResponse; +import bisq.core.offer.messages.SignOfferRequest; +import bisq.core.offer.messages.SignOfferResponse; import bisq.core.offer.placeoffer.PlaceOfferModel; import bisq.core.offer.placeoffer.PlaceOfferProtocol; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.trade.TradableList; +import bisq.core.trade.TradeUtils; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; +import bisq.core.util.ParsingUtils; import bisq.core.util.Validator; import bisq.network.p2p.AckMessage; @@ -53,7 +58,6 @@ import bisq.network.p2p.P2PService; import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.peers.Broadcaster; import bisq.network.p2p.peers.PeerManager; - import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Capabilities; @@ -61,26 +65,28 @@ import bisq.common.app.Capability; import bisq.common.app.Version; import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.Sig; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.util.Tuple2; - +import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; import javax.inject.Inject; import javafx.collections.FXCollections; import javafx.collections.ObservableList; - +import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -88,7 +94,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import lombok.Getter; - +import monero.daemon.model.MoneroOutput; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -119,13 +125,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private final TradeStatisticsManager tradeStatisticsManager; private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; - private final RefundAgentManager refundAgentManager; private final DaoFacade daoFacade; private final FilterManager filterManager; private final Broadcaster broadcaster; private final PersistenceManager> persistenceManager; private final Map offersToBeEdited = new HashMap<>(); private final TradableList openOffers = new TradableList<>(); + private final SignedOfferList signedOffers = new SignedOfferList(); + private final PersistenceManager signedOfferPersistenceManager; + private final Map placeOfferProtocols = new HashMap(); private boolean stopped; private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer; @Getter @@ -157,7 +165,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe DaoFacade daoFacade, FilterManager filterManager, Broadcaster broadcaster, - PersistenceManager> persistenceManager) { + PersistenceManager> persistenceManager, + PersistenceManager signedOfferPersistenceManager) { this.coreContext = coreContext; this.createOfferService = createOfferService; this.keyRing = keyRing; @@ -174,21 +183,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe this.tradeStatisticsManager = tradeStatisticsManager; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; - this.refundAgentManager = refundAgentManager; this.daoFacade = daoFacade; this.filterManager = filterManager; this.broadcaster = broadcaster; this.persistenceManager = persistenceManager; + this.signedOfferPersistenceManager = signedOfferPersistenceManager; this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE); + this.signedOfferPersistenceManager.initialize(signedOffers, "SignedOffers", PersistenceManager.Source.PRIVATE); // arbitrator stores reserve tx for signed offers } @Override public void readPersisted(Runnable completeHandler) { + + // read open offers persistenceManager.readPersisted(persisted -> { openOffers.setAll(persisted.getList()); openOffers.forEach(openOffer -> openOffer.getOffer().setPriceFeedService(priceFeedService)); - completeHandler.run(); + + // read signed offers + signedOfferPersistenceManager.readPersisted(signedOfferPersisted -> { + signedOffers.setAll(signedOfferPersisted.getList()); + completeHandler.run(); + }, + completeHandler); }, completeHandler); } @@ -286,7 +304,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // We get an encrypted message but don't do the signature check as we don't know the peer yet. // A basic sig check is in done also at decryption time NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope(); - if (networkEnvelope instanceof OfferAvailabilityRequest) { + if (networkEnvelope instanceof SignOfferRequest) { + handleSignOfferRequest((SignOfferRequest) networkEnvelope, peerNodeAddress); + } if (networkEnvelope instanceof SignOfferResponse) { + handleSignOfferResponse((SignOfferResponse) networkEnvelope, peerNodeAddress); + } else if (networkEnvelope instanceof OfferAvailabilityRequest) { handleOfferAvailabilityRequest((OfferAvailabilityRequest) networkEnvelope, peerNodeAddress); } else if (networkEnvelope instanceof AckMessage) { AckMessage ackMessage = (AckMessage) networkEnvelope; @@ -380,24 +402,36 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe offer.getAmount(), buyerSecurityDeposit, createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit)); + + if (placeOfferProtocols.containsKey(offer.getId())) { + log.warn("We already have a place offer protocol for offer " + offer.getId() + ", ignoring"); + throw new RuntimeException("We already have a place offer protocol for offer " + offer.getId() + ", ignoring"); + } PlaceOfferModel model = new PlaceOfferModel(offer, reservedFundsForOffer, useSavingsWallet, + p2PService, btcWalletService, xmrWalletService, tradeWalletService, bsqWalletService, offerBookService, arbitratorManager, + mediatorManager, tradeStatisticsManager, daoFacade, user, + keyRing, filterManager); PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( model, transaction -> { - OpenOffer openOffer = new OpenOffer(offer, triggerPrice); + + // save frozen key images with open offer + List frozenKeyImages = new ArrayList(); + for (MoneroOutput output : model.getReserveTx().getInputs()) frozenKeyImages.add(output.getKeyImage().getHex()); + OpenOffer openOffer = new OpenOffer(offer, triggerPrice, frozenKeyImages); openOffers.add(openOffer); requestPersistence(); resultHandler.handleResult(transaction); @@ -410,7 +444,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe }, errorMessageHandler ); - placeOfferProtocol.placeOffer(); + + placeOfferProtocols.put(offer.getId(), placeOfferProtocol); + placeOfferProtocol.placeOffer(); // TODO (woodser): if error placing offer (e.g. bad signature), remove protocol and unfreeze trade funds } // Remove from offerbook @@ -590,7 +626,128 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe public Optional getOpenOfferById(String offerId) { return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst(); } + + public Optional getSignedOfferById(String offerId) { + return signedOffers.stream().filter(e -> e.getOfferId().equals(offerId)).findFirst(); + } + /////////////////////////////////////////////////////////////////////////////////////////// + // Arbitrator Signs Offer + /////////////////////////////////////////////////////////////////////////////////////////// + + private void handleSignOfferRequest(SignOfferRequest request, NodeAddress peer) { + log.info("Received SignOfferRequest from {} with offerId {} and uid {}", + peer, request.getOfferId(), request.getUid()); + + String errorMessage = null; + try { + + // verify this node is an arbitrator + Mediator thisArbitrator = user.getRegisteredMediator(); + NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress(); + if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) { + errorMessage = "Cannot sign offer because we are not a registered arbitrator"; + log.info(errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); + return; + } + + // verify arbitrator is signer of offer payload + if (!request.getOfferPayload().getArbitratorNodeAddress().equals(thisAddress)) { + errorMessage = "Cannot sign offer because offer payload is for a different arbitrator"; + log.info(errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); + return; + } + + // verify offer not seen before + Optional openOfferOptional = getOpenOfferById(request.offerId); + if (openOfferOptional.isPresent()) { + errorMessage = "We already got a request to sign offer id " + request.offerId; + log.info(errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); + return; + } + + // verify reserve tx not signed before + + // verify maker's reserve tx (double spend, trade fee, trade amount, mining fee) + 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(), + request.getPayoutAddress(), + depositAmount, + tradeFee, + request.getReserveTxHash(), + request.getReserveTxHex(), + request.getReserveTxKey(), + true); + + // arbitrator signs offer to certify they have valid reserve tx + String offerPayloadAsJson = Utilities.objectToJson(request.getOfferPayload()); + String signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), offerPayloadAsJson); + OfferPayload signedOfferPayload = request.getOfferPayload(); + signedOfferPayload.setArbitratorSignature(signature); + + // create record of signed offer + SignedOffer signedOffer = new SignedOffer(signedOfferPayload.getId(), request.getReserveTxHash(), request.getReserveTxHex(), signature); // TODO (woodser): no need for signature to be part of SignedOffer? + signedOffers.add(signedOffer); + requestPersistence(); + + // send ack + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), true, errorMessage); + + // send response with signature + SignOfferResponse response = new SignOfferResponse(request.getOfferId(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + signedOfferPayload); + p2PService.sendEncryptedDirectMessage(peer, + request.getPubKeyRing(), + response, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", + response.getClass().getSimpleName(), + response.getOfferId(), + response.getUid()); + } + + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", + response.getClass().getSimpleName(), + response.getUid(), + peer, + errorMessage); + } + }); + } catch (Exception e) { + e.printStackTrace(); + errorMessage = "Exception at handleSignOfferRequest " + e.getMessage(); + log.error(errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); + } + } + + private void handleSignOfferResponse(SignOfferResponse response, NodeAddress peer) { + log.info("Received SignOfferResponse from {} with offerId {} and uid {}", + peer, response.getOfferId(), response.getUid()); + + // get previously created protocol + PlaceOfferProtocol protocol = placeOfferProtocols.get(response.getOfferId()); + if (protocol == null) { + log.warn("No place offer protocol created for offer " + response.getOfferId()); + return; + } + + // handle response + protocol.handleSignOfferResponse(response, peer); + } /////////////////////////////////////////////////////////////////////////////////////////// // OfferPayload Availability @@ -606,7 +763,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (!p2PService.isBootstrapped()) { errorMessage = "We got a handleOfferAvailabilityRequest but we have not bootstrapped yet."; log.info(errorMessage); - sendAckMessage(request, peer, false, errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); return; } @@ -614,14 +771,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (!btcWalletService.isChainHeightSyncedWithinTolerance()) { errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced."; log.info(errorMessage); - sendAckMessage(request, peer, false, errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); return; } if (stopped) { errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call."; log.debug(errorMessage); - sendAckMessage(request, peer, false, errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); return; } @@ -631,50 +788,60 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } catch (Throwable t) { errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString(); log.warn(errorMessage); - sendAckMessage(request, peer, false, errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); return; } try { Optional openOfferOptional = getOpenOfferById(request.offerId); AvailabilityResult availabilityResult; + String makerSignature = null; NodeAddress arbitratorNodeAddress = null; - NodeAddress mediatorNodeAddress = null; - NodeAddress refundAgentNodeAddress = null; if (openOfferOptional.isPresent()) { OpenOffer openOffer = openOfferOptional.get(); if (!apiUserDeniedByOffer(request)) { - if (openOffer.getState() == OpenOffer.State.AVAILABLE) { - Offer offer = openOffer.getOffer(); - if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { - arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress(); - openOffer.setArbitratorNodeAddress(arbitratorNodeAddress); + if (!takerDeniedByMaker(request)) { + if (openOffer.getState() == OpenOffer.State.AVAILABLE) { + Offer offer = openOffer.getOffer(); + if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { + + // use signing arbitrator if available, otherwise use least used arbitrator + boolean isSignerOnline = true; + arbitratorNodeAddress = isSignerOnline ? offer.getOfferPayload().getArbitratorNodeAddress() : DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress(); + openOffer.setArbitratorNodeAddress(arbitratorNodeAddress); + + // maker signs taker's request // TODO (woodser): should maker signature include selected arbitrator? + String tradeRequestAsJson = Utilities.objectToJson(request.getTradeRequest()); + makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson); - try { - // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference - // in trade price between the peers. Also here poor connectivity might cause market price API connection - // losses and therefore an outdated market price. - offer.checkTradePriceTolerance(request.getTakersTradePrice()); - availabilityResult = AvailabilityResult.AVAILABLE; - } catch (TradePriceOutOfToleranceException e) { - log.warn("Trade price check failed because takers price is outside out tolerance."); - availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; - } catch (MarketPriceNotAvailableException e) { - log.warn(e.getMessage()); - availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; - } catch (Throwable e) { - log.warn("Trade price check failed. " + e.getMessage()); - if (coreContext.isApiUser()) - // Give api user something more than 'unknown_failure'. - availabilityResult = AvailabilityResult.PRICE_CHECK_FAILED; - else - availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + try { + // Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference + // in trade price between the peers. Also here poor connectivity might cause market price API connection + // losses and therefore an outdated market price. + offer.checkTradePriceTolerance(request.getTakersTradePrice()); + availabilityResult = AvailabilityResult.AVAILABLE; + } catch (TradePriceOutOfToleranceException e) { + log.warn("Trade price check failed because takers price is outside out tolerance."); + availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE; + } catch (MarketPriceNotAvailableException e) { + log.warn(e.getMessage()); + availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE; + } catch (Throwable e) { + log.warn("Trade price check failed. " + e.getMessage()); + if (coreContext.isApiUser()) + // Give api user something more than 'unknown_failure'. + availabilityResult = AvailabilityResult.PRICE_CHECK_FAILED; + else + availabilityResult = AvailabilityResult.UNKNOWN_FAILURE; + } + } else { + availabilityResult = AvailabilityResult.USER_IGNORED; } } else { - availabilityResult = AvailabilityResult.USER_IGNORED; + availabilityResult = AvailabilityResult.OFFER_TAKEN; } } else { - availabilityResult = AvailabilityResult.OFFER_TAKEN; + availabilityResult = AvailabilityResult.MAKER_DENIED_TAKER; } } else { availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER; @@ -689,12 +856,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.warn(errorMessage); availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT; } - + OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, availabilityResult, - arbitratorNodeAddress, - mediatorNodeAddress, - refundAgentNodeAddress); + makerSignature, + arbitratorNodeAddress); log.info("Send {} with offerId {} and uid {} to peer {}", offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(), offerAvailabilityResponse.getUid(), peer); @@ -725,53 +891,57 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe log.error(errorMessage); t.printStackTrace(); } finally { - sendAckMessage(request, peer, result, errorMessage); + sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage); } } private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) { return preferences.isDenyApiTaker() && request.isTakerApiUser(); } + + private boolean takerDeniedByMaker(OfferAvailabilityRequest request) { + if (request.getTradeRequest() == null) return true; + return false; // TODO (woodser): implement taker verification here, doing work of ApplyFilter and VerifyPeersAccountAgeWitness + } - private void sendAckMessage(OfferAvailabilityRequest message, + private void sendAckMessage(Class reqClass, NodeAddress sender, + PubKeyRing senderPubKeyRing, + String offerId, + String uid, boolean result, String errorMessage) { - String offerId = message.getOfferId(); - String sourceUid = message.getUid(); + String sourceUid = uid; AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(), AckMessageSourceType.OFFER_MESSAGE, - message.getClass().getSimpleName(), + reqClass.getSimpleName(), sourceUid, offerId, result, errorMessage); - final NodeAddress takersNodeAddress = sender; - PubKeyRing takersPubKeyRing = message.getPubKeyRing(); - log.info("Send AckMessage for OfferAvailabilityRequest to peer {} with offerId {} and sourceUid {}", - takersNodeAddress, offerId, ackMessage.getSourceUid()); + log.info("Send AckMessage for {} to peer {} with offerId {} and sourceUid {}", + reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid()); p2PService.sendEncryptedDirectMessage( - takersNodeAddress, - takersPubKeyRing, + sender, + senderPubKeyRing, ackMessage, new SendDirectMessageListener() { @Override public void onArrived() { - log.info("AckMessage for OfferAvailabilityRequest arrived at takersNodeAddress {}. offerId={}, sourceUid={}", - takersNodeAddress, offerId, ackMessage.getSourceUid()); + log.info("AckMessage for {} arrived at sender {}. offerId={}, sourceUid={}", + reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid()); } @Override public void onFault(String errorMessage) { - log.error("AckMessage for OfferAvailabilityRequest failed. AckMessage={}, takersNodeAddress={}, errorMessage={}", - ackMessage, takersNodeAddress, errorMessage); + log.error("AckMessage for {} failed. AckMessage={}, sender={}, errorMessage={}", + reqClass.getSimpleName(), ackMessage, sender, errorMessage); } } ); } - /////////////////////////////////////////////////////////////////////////////////////////// // Update persisted offer if a new capability is required after a software update /////////////////////////////////////////////////////////////////////////////////////////// @@ -838,8 +1008,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe originalOfferPayload.getMinAmount(), originalOfferPayload.getBaseCurrencyCode(), originalOfferPayload.getCounterCurrencyCode(), - originalOfferPayload.getArbitratorNodeAddresses(), - originalOfferPayload.getMediatorNodeAddresses(), originalOfferPayload.getPaymentMethodId(), originalOfferPayload.getMakerPaymentAccountId(), originalOfferPayload.getOfferFeePaymentTxId(), @@ -863,7 +1031,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe originalOfferPayload.isPrivateOffer(), originalOfferPayload.getHashOfChallenge(), updatedExtraDataMap, - protocolVersion); + protocolVersion, + originalOfferPayload.getArbitratorNodeAddress(), + originalOfferPayload.getArbitratorSignature()); // Save states from original data to use for the updated Offer.State originalOfferState = originalOffer.getState(); @@ -1024,6 +1194,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private void requestPersistence() { persistenceManager.requestPersistence(); + signedOfferPersistenceManager.requestPersistence(); } diff --git a/core/src/main/java/bisq/core/offer/SignedOffer.java b/core/src/main/java/bisq/core/offer/SignedOffer.java new file mode 100644 index 0000000000..aa9cd7924c --- /dev/null +++ b/core/src/main/java/bisq/core/offer/SignedOffer.java @@ -0,0 +1,79 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.offer; + +import bisq.common.proto.persistable.PersistablePayload; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@EqualsAndHashCode +@Slf4j +public final class SignedOffer implements PersistablePayload { + + @Getter + private final String offerId; + @Getter + private final String reserveTxHash; + @Getter + private final String reserveTxHex; + @Getter + private final String arbitratorSignature; + + public SignedOffer(String offerId, String reserveTxHash, String reserveTxHex, String arbitratorSignature) { + this.offerId = offerId; + this.reserveTxHash = reserveTxHash; + this.reserveTxHex = reserveTxHex; + this.arbitratorSignature = arbitratorSignature; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.SignedOffer toProtoMessage() { + protobuf.SignedOffer.Builder builder = protobuf.SignedOffer.newBuilder() + .setOfferId(offerId) + .setReserveTxHash(reserveTxHash) + .setReserveTxHex(reserveTxHex) + .setArbitratorSignature(arbitratorSignature); + + return builder.build(); + } + + public static SignedOffer fromProto(protobuf.SignedOffer proto) { + return new SignedOffer(proto.getOfferId(), proto.getReserveTxHash(), proto.getReserveTxHex(), proto.getArbitratorSignature()); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public String toString() { + return "SignedOffer{" + + ",\n offerId=" + offerId + + ",\n reserveTxHash=" + reserveTxHash + + ",\n reserveTxHex=" + reserveTxHex + + ",\n arbitratorSignature=" + arbitratorSignature + + "\n}"; + } +} + diff --git a/core/src/main/java/bisq/core/offer/SignedOfferList.java b/core/src/main/java/bisq/core/offer/SignedOfferList.java new file mode 100644 index 0000000000..4976587bff --- /dev/null +++ b/core/src/main/java/bisq/core/offer/SignedOfferList.java @@ -0,0 +1,74 @@ +package bisq.core.offer; + +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +import bisq.common.proto.ProtoUtil; +import bisq.common.proto.persistable.PersistableListAsObservable; + +import com.google.protobuf.Message; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class SignedOfferList extends PersistableListAsObservable { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public SignedOfferList() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + protected SignedOfferList(Collection collection) { + super(collection); + } + + @Override + public Message toProtoMessage() { + return protobuf.PersistableEnvelope.newBuilder() + .setSignedOfferList(protobuf.SignedOfferList.newBuilder() + .addAllSignedOffer(ProtoUtil.collectionToProto(getList(), protobuf.SignedOffer.class))) + .build(); + } + + public static SignedOfferList fromProto(protobuf.SignedOfferList proto) { + List list = proto.getSignedOfferList().stream() + .map(signedOffer -> { + return SignedOffer.fromProto(signedOffer); + }) + .collect(Collectors.toList()); + + return new SignedOfferList(list); + } + + @Override + public String toString() { + return "SignedOfferList{" + + ",\n list=" + getList() + + "\n}"; + } +} diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index c1559cec8d..ffed475112 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -17,9 +17,12 @@ package bisq.core.offer.availability; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; +import bisq.core.offer.OfferUtil; import bisq.core.offer.messages.OfferAvailabilityResponse; import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.User; @@ -40,6 +43,8 @@ public class OfferAvailabilityModel implements Model { @Getter private final PubKeyRing pubKeyRing; // takers PubKey (my pubkey) @Getter + private final XmrWalletService xmrWalletService; + @Getter private final P2PService p2PService; @Getter final private User user; @@ -49,22 +54,20 @@ public class OfferAvailabilityModel implements Model { private final TradeStatisticsManager tradeStatisticsManager; private NodeAddress peerNodeAddress; // maker private OfferAvailabilityResponse message; + @Getter + private String paymentAccountId; + @Getter + private OfferUtil offerUtil; + @Getter + @Setter + private InitTradeRequest tradeRequest; @Nullable @Setter @Getter - private NodeAddress selectedArbitrator; - - // Added in v1.1.6 - @Nullable + private String makerSignature; @Setter @Getter - private NodeAddress selectedMediator; - - // Added in v1.2.0 - @Nullable - @Setter - @Getter - private NodeAddress selectedRefundAgent; + private NodeAddress arbitratorNodeAddress; // Added in v1.5.5 @Getter @@ -72,18 +75,24 @@ public class OfferAvailabilityModel implements Model { public OfferAvailabilityModel(Offer offer, PubKeyRing pubKeyRing, + XmrWalletService xmrWalletService, P2PService p2PService, User user, MediatorManager mediatorManager, TradeStatisticsManager tradeStatisticsManager, - boolean isTakerApiUser) { + boolean isTakerApiUser, + String paymentAccountId, + OfferUtil offerUtil) { this.offer = offer; this.pubKeyRing = pubKeyRing; + this.xmrWalletService = xmrWalletService; this.p2PService = p2PService; this.user = user; this.mediatorManager = mediatorManager; this.tradeStatisticsManager = tradeStatisticsManager; this.isTakerApiUser = isTakerApiUser; + this.paymentAccountId = paymentAccountId; + this.offerUtil = offerUtil; } public NodeAddress getPeerNodeAddress() { diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index 20bc844792..cf39489752 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -19,18 +19,16 @@ package bisq.core.offer.availability.tasks; import bisq.core.offer.AvailabilityResult; import bisq.core.offer.Offer; -import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.availability.OfferAvailabilityModel; import bisq.core.offer.messages.OfferAvailabilityResponse; - -import bisq.network.p2p.NodeAddress; - +import bisq.core.trade.TradeUtils; import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; @Slf4j public class ProcessOfferAvailabilityResponse extends Task { @@ -47,26 +45,27 @@ public class ProcessOfferAvailabilityResponse extends Task { protected void run() { try { runInterceptHook(); - + + // collect fields + Offer offer = model.getOffer(); + User user = model.getUser(); + P2PService p2PService = model.getP2PService(); + XmrWalletService walletService = model.getXmrWalletService(); + OfferUtil offerUtil = model.getOfferUtil(); + String paymentAccountId = model.getPaymentAccountId(); + String paymentMethodId = user.getPaymentAccount(paymentAccountId).getPaymentAccountPayload().getPaymentMethodId(); + String payoutAddress = walletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); // reserve new payout address + + // taker signs offer using offer id as nonce to avoid challenge protocol + byte[] sig = Sig.sign(model.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8)); + + // send InitTradeRequest to maker to sign + InitTradeRequest tradeRequest = new InitTradeRequest( + offer.getId(), + P2PService.getMyNodeAddress(), + p2PService.getKeyRing().getPubKeyRing(), + offer.getAmount().value, + offer.getPrice().getValue(), + offerUtil.getTakerFee(true, offer.getAmount()).value, + user.getAccountId(), + paymentAccountId, + paymentMethodId, + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + sig, + new Date().getTime(), + offer.getMakerNodeAddress(), + P2PService.getMyNodeAddress(), + null, // maker provides node address of arbitrator on response + null, // reserve tx not sent from taker to maker + null, + null, + payoutAddress, + null); + + // save trade request to later send to arbitrator + model.setTradeRequest(tradeRequest); + OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(), - model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser()); + model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser(), tradeRequest); log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getOfferId(), message.getUid(), model.getPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java index 6d9d14eaf6..7d357771ae 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityRequest.java @@ -22,7 +22,8 @@ import bisq.network.p2p.SupportedCapabilitiesMessage; import bisq.common.app.Capabilities; import bisq.common.app.Version; import bisq.common.crypto.PubKeyRing; - +import bisq.core.proto.CoreProtoResolver; +import bisq.core.trade.messages.InitTradeRequest; import java.util.Optional; import java.util.UUID; @@ -43,18 +44,21 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp @Nullable private final Capabilities supportedCapabilities; private final boolean isTakerApiUser; + private final InitTradeRequest tradeRequest; public OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice, - boolean isTakerApiUser) { + boolean isTakerApiUser, + InitTradeRequest tradeRequest) { this(offerId, pubKeyRing, takersTradePrice, isTakerApiUser, Capabilities.app, Version.getP2PMessageVersion(), - UUID.randomUUID().toString()); + UUID.randomUUID().toString(), + tradeRequest); } @@ -68,13 +72,24 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp boolean isTakerApiUser, @Nullable Capabilities supportedCapabilities, int messageVersion, - @Nullable String uid) { + @Nullable String uid, + InitTradeRequest tradeRequest) { super(messageVersion, offerId, uid); this.pubKeyRing = pubKeyRing; this.takersTradePrice = takersTradePrice; this.isTakerApiUser = isTakerApiUser; this.supportedCapabilities = supportedCapabilities; + this.tradeRequest = tradeRequest; } + +// @Override +// public protobuf.Offer toProtoMessage() { +// return protobuf.Offer.newBuilder().setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()).build(); +// } +// +// public static Offer fromProto(protobuf.Offer proto) { +// return new Offer(OfferPayload.fromProto(proto.getOfferPayload())); +// } @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { @@ -82,7 +97,8 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp .setOfferId(offerId) .setPubKeyRing(pubKeyRing.toProtoMessage()) .setTakersTradePrice(takersTradePrice) - .setIsTakerApiUser(isTakerApiUser); + .setIsTakerApiUser(isTakerApiUser) + .setTradeRequest(tradeRequest.toProtoNetworkEnvelope().getInitTradeRequest()); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); @@ -92,13 +108,14 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp .build(); } - public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityRequest proto, int messageVersion) { + public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) { return new OfferAvailabilityRequest(proto.getOfferId(), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getTakersTradePrice(), proto.getIsTakerApiUser(), Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, - proto.getUid().isEmpty() ? null : proto.getUid()); + proto.getUid().isEmpty() ? null : proto.getUid(), + InitTradeRequest.fromProto(proto.getTradeRequest(), coreProtoResolver, messageVersion)); } } diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java index 41b5aa0fcc..0d49401f1b 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java @@ -44,28 +44,21 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup @Nullable private final Capabilities supportedCapabilities; - private final NodeAddress arbitrator; - // Was introduced in v 1.1.6. Might be null if msg received from node with old version @Nullable - private final NodeAddress mediator; - - // Added v1.2.0 - @Nullable - private final NodeAddress refundAgent; + private final String makerSignature; + private final NodeAddress arbitratorNodeAddress; public OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult, - NodeAddress arbitrator, - NodeAddress mediator, - NodeAddress refundAgent) { + String makerSignature, + NodeAddress arbitratorNodeAddress) { this(offerId, availabilityResult, Capabilities.app, Version.getP2PMessageVersion(), UUID.randomUUID().toString(), - arbitrator, - mediator, - refundAgent); + makerSignature, + arbitratorNodeAddress); } @@ -78,28 +71,25 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup @Nullable Capabilities supportedCapabilities, int messageVersion, @Nullable String uid, - NodeAddress arbitrator, - @Nullable NodeAddress mediator, - @Nullable NodeAddress refundAgent) { + String makerSignature, + NodeAddress arbitratorNodeAddress) { super(messageVersion, offerId, uid); this.availabilityResult = availabilityResult; this.supportedCapabilities = supportedCapabilities; - this.arbitrator = arbitrator; - this.mediator = mediator; - this.refundAgent = refundAgent; + this.makerSignature = makerSignature; + this.arbitratorNodeAddress = arbitratorNodeAddress; } @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder() .setOfferId(offerId) - .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())); + .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())) + .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); - Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage())); - Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage())); - Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage())); + Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); return getNetworkEnvelopeBuilder() .setOfferAvailabilityResponse(builder) @@ -112,8 +102,7 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, proto.getUid().isEmpty() ? null : proto.getUid(), - proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null, - proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null, - proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null); + proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(), + NodeAddress.fromProto(proto.getArbitratorNodeAddress())); } } diff --git a/core/src/main/java/bisq/core/offer/messages/SignOfferRequest.java b/core/src/main/java/bisq/core/offer/messages/SignOfferRequest.java new file mode 100644 index 0000000000..2fff876f55 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/messages/SignOfferRequest.java @@ -0,0 +1,115 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.offer.messages; + +import bisq.common.crypto.PubKeyRing; +import bisq.core.offer.OfferPayload; +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class SignOfferRequest extends OfferMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final String senderAccountId; + private final OfferPayload offerPayload; + private final long currentDate; + private final String reserveTxHash; + private final String reserveTxHex; + private final String reserveTxKey; + private final String payoutAddress; + + public SignOfferRequest(String offerId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String senderAccountId, + OfferPayload offerPayload, + String uid, + int messageVersion, + long currentDate, + String reserveTxHash, + String reserveTxHex, + String reserveTxKey, + String payoutAddress) { + super(messageVersion, offerId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.senderAccountId = senderAccountId; + this.offerPayload = offerPayload; + this.currentDate = currentDate; + this.reserveTxHash = reserveTxHash; + this.reserveTxHex = reserveTxHex; + this.reserveTxKey = reserveTxKey; + this.payoutAddress = payoutAddress; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.SignOfferRequest.Builder builder = protobuf.SignOfferRequest.newBuilder() + .setOfferId(offerId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setSenderAccountId(senderAccountId) + .setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()) + .setUid(uid) + .setCurrentDate(currentDate) + .setReserveTxHash(reserveTxHash) + .setReserveTxHex(reserveTxHex) + .setReserveTxKey(reserveTxKey) + .setPayoutAddress(payoutAddress); + + return getNetworkEnvelopeBuilder().setSignOfferRequest(builder).build(); + } + + public static SignOfferRequest fromProto(protobuf.SignOfferRequest proto, + int messageVersion) { + return new SignOfferRequest(proto.getOfferId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getSenderAccountId(), + OfferPayload.fromProto(proto.getOfferPayload()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + proto.getReserveTxHash(), + proto.getReserveTxHex(), + proto.getReserveTxKey(), + proto.getPayoutAddress()); + } + + @Override + public String toString() { + return "SignOfferRequest {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n reserveTxHash='" + reserveTxHash + + ",\n reserveTxHex='" + reserveTxHex + + ",\n reserveTxKey='" + reserveTxKey + + ",\n payoutAddress='" + payoutAddress + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/offer/messages/SignOfferResponse.java b/core/src/main/java/bisq/core/offer/messages/SignOfferResponse.java new file mode 100644 index 0000000000..9229c0cbba --- /dev/null +++ b/core/src/main/java/bisq/core/offer/messages/SignOfferResponse.java @@ -0,0 +1,67 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.offer.messages; + +import bisq.core.offer.OfferPayload; +import bisq.network.p2p.DirectMessage; +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class SignOfferResponse extends OfferMessage implements DirectMessage { + private final OfferPayload signedOfferPayload; + + public SignOfferResponse(String offerId, + String uid, + int messageVersion, + OfferPayload signedOfferPayload) { + super(messageVersion, offerId, uid); + this.signedOfferPayload = signedOfferPayload; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.SignOfferResponse.Builder builder = protobuf.SignOfferResponse.newBuilder() + .setOfferId(offerId) + .setUid(uid) + .setSignedOfferPayload(signedOfferPayload.toProtoMessage().getOfferPayload()); + + return getNetworkEnvelopeBuilder().setSignOfferResponse(builder).build(); + } + + public static SignOfferResponse fromProto(protobuf.SignOfferResponse proto, + int messageVersion) { + return new SignOfferResponse(proto.getOfferId(), + proto.getUid(), + messageVersion, + OfferPayload.fromProto(proto.getSignedOfferPayload())); + } + + @Override + public String toString() { + return "SignOfferResponse {" + + ",\n arbitratorSignature='" + signedOfferPayload.getArbitratorSignature() + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java index f2683978c1..c7ebe7ac4a 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java @@ -25,12 +25,16 @@ import bisq.core.dao.DaoFacade; import bisq.core.filter.FilterManager; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; +import bisq.core.offer.messages.SignOfferResponse; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.User; - +import bisq.common.crypto.KeyRing; import bisq.common.taskrunner.Model; +import bisq.network.p2p.P2PService; + import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; @@ -49,15 +53,18 @@ public class PlaceOfferModel implements Model { private final Offer offer; private final Coin reservedFundsForOffer; private final boolean useSavingsWallet; + private final P2PService p2PService; private final BtcWalletService walletService; private final XmrWalletService xmrWalletService; private final TradeWalletService tradeWalletService; private final BsqWalletService bsqWalletService; private final OfferBookService offerBookService; private final ArbitratorManager arbitratorManager; + private final MediatorManager mediatorManager; private final TradeStatisticsManager tradeStatisticsManager; private final DaoFacade daoFacade; private final User user; + private final KeyRing keyRing; @Getter private final FilterManager filterManager; @@ -67,33 +74,41 @@ public class PlaceOfferModel implements Model { @Setter private Transaction transaction; @Setter - private MoneroTxWallet xmrTransaction; + private MoneroTxWallet reserveTx; + @Setter + private SignOfferResponse signOfferResponse; public PlaceOfferModel(Offer offer, Coin reservedFundsForOffer, boolean useSavingsWallet, + P2PService p2PService, BtcWalletService walletService, XmrWalletService xmrWalletService, TradeWalletService tradeWalletService, BsqWalletService bsqWalletService, OfferBookService offerBookService, ArbitratorManager arbitratorManager, + MediatorManager mediatorManager, TradeStatisticsManager tradeStatisticsManager, DaoFacade daoFacade, User user, + KeyRing keyRing, FilterManager filterManager) { this.offer = offer; this.reservedFundsForOffer = reservedFundsForOffer; this.useSavingsWallet = useSavingsWallet; + this.p2PService = p2PService; this.walletService = walletService; this.xmrWalletService = xmrWalletService; this.tradeWalletService = tradeWalletService; this.bsqWalletService = bsqWalletService; this.offerBookService = offerBookService; this.arbitratorManager = arbitratorManager; + this.mediatorManager = mediatorManager; this.tradeStatisticsManager = tradeStatisticsManager; this.daoFacade = daoFacade; this.user = user; + this.keyRing = keyRing; this.filterManager = filterManager; } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java index cf1437bfd9..4fadf2a9f9 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java @@ -17,12 +17,14 @@ package bisq.core.offer.placeoffer; +import bisq.core.offer.messages.SignOfferResponse; import bisq.core.offer.placeoffer.tasks.AddToOfferBook; -import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions; +import bisq.core.offer.placeoffer.tasks.MakerReservesTradeFunds; +import bisq.core.offer.placeoffer.tasks.MakerSendsSignOfferRequest; +import bisq.core.offer.placeoffer.tasks.MakerProcessesSignOfferResponse; import bisq.core.offer.placeoffer.tasks.ValidateOffer; import bisq.core.trade.handlers.TransactionResultHandler; -import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx; - +import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.taskrunner.TaskRunner; @@ -55,34 +57,60 @@ public class PlaceOfferProtocol { /////////////////////////////////////////////////////////////////////////////////////////// public void placeOffer() { - log.debug("model.offer.id" + model.getOffer().getId()); + log.debug("placeOffer() " + model.getOffer().getId()); TaskRunner taskRunner = new TaskRunner<>(model, () -> { - log.debug("sequence at handleRequestTakeOfferMessage completed"); - resultHandler.handleResult(model.getTransaction()); + log.debug("sequence at placeOffer completed"); }, (errorMessage) -> { log.error(errorMessage); - - if (model.isOfferAddedToOfferBook()) { - model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(), - () -> { - model.setOfferAddedToOfferBook(false); - log.debug("OfferPayload removed from offer book."); - }, - log::error); - } model.getOffer().setErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage); } ); taskRunner.addTasks( ValidateOffer.class, - CheckNumberOfUnconfirmedTransactions.class, - MakerCreateFeeTx.class, - AddToOfferBook.class + MakerReservesTradeFunds.class, + MakerSendsSignOfferRequest.class ); taskRunner.run(); } + + // TODO (woodser): switch to fluent + public void handleSignOfferResponse(SignOfferResponse response, NodeAddress sender) { + log.debug("handleSignOfferResponse() " + model.getOffer().getId()); + model.setSignOfferResponse(response); + + if (!model.getOffer().getOfferPayload().getArbitratorNodeAddress().equals(sender)) { + log.warn("Ignoring sign offer response from different sender"); + return; + } + + TaskRunner taskRunner = new TaskRunner<>(model, + () -> { + log.debug("sequence at handleSignOfferResponse completed"); + resultHandler.handleResult(model.getTransaction()); // TODO (woodser): XMR transaction instead + }, + (errorMessage) -> { + log.error(errorMessage); + if (model.isOfferAddedToOfferBook()) { + model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(), + () -> { + model.setOfferAddedToOfferBook(false); + log.debug("OfferPayload removed from offer book."); + }, + log::error); + } + model.getOffer().setErrorMessage(errorMessage); + errorMessageHandler.handleErrorMessage(errorMessage); + } + ); + taskRunner.addTasks( + MakerProcessesSignOfferResponse.class, + AddToOfferBook.class + ); + + taskRunner.run(); + } } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java index 16def612ba..cb2c6c91df 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java @@ -17,6 +17,7 @@ package bisq.core.offer.placeoffer.tasks; +import bisq.core.offer.Offer; import bisq.core.offer.placeoffer.PlaceOfferModel; import bisq.common.taskrunner.Task; @@ -32,7 +33,7 @@ public class AddToOfferBook extends Task { protected void run() { try { runInterceptHook(); - model.getOfferBookService().addOffer(model.getOffer(), + model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), () -> { model.setOfferAddedToOfferBook(true); complete(); diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java deleted file mode 100644 index 31e8d30003..0000000000 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java +++ /dev/null @@ -1,20 +0,0 @@ -package bisq.core.offer.placeoffer.tasks; - -import bisq.core.locale.Res; -import bisq.core.offer.placeoffer.PlaceOfferModel; - -import bisq.common.taskrunner.Task; -import bisq.common.taskrunner.TaskRunner; - -public class CheckNumberOfUnconfirmedTransactions extends Task { - public CheckNumberOfUnconfirmedTransactions(TaskRunner taskHandler, PlaceOfferModel model) { - super(taskHandler, model); - } - - @Override - protected void run() { - if (model.getWalletService().isUnconfirmedTransactionsLimitHit() || model.getBsqWalletService().isUnconfirmedTransactionsLimitHit()) - failed(Res.get("shared.unconfirmedTransactionsLimitReached")); - complete(); - } -} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java index df69713431..849319690d 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java @@ -90,7 +90,7 @@ public class CreateMakerFeeTx extends Task { model.setTransaction(transaction); walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); - model.getOffer().setState(Offer.State.OFFER_FEE_PAID); + model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED); complete(); } else { @@ -137,7 +137,7 @@ public class CreateMakerFeeTx extends Task { walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); log.debug("Successfully sent tx with id " + transaction.getTxId().toString()); - model.getOffer().setState(Offer.State.OFFER_FEE_PAID); + model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED); complete(); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java new file mode 100644 index 0000000000..4c985647cc --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java @@ -0,0 +1,58 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.offer.placeoffer.tasks; + +import bisq.core.offer.Offer; +import bisq.core.offer.placeoffer.PlaceOfferModel; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.trade.TradeUtils; + +import static com.google.common.base.Preconditions.checkNotNull; + +import bisq.common.taskrunner.Task; +import bisq.common.taskrunner.TaskRunner; + +public class MakerProcessesSignOfferResponse extends Task { + public MakerProcessesSignOfferResponse(TaskRunner taskHandler, PlaceOfferModel model) { + super(taskHandler, model); + } + + @Override + protected void run() { + Offer offer = model.getOffer(); + try { + runInterceptHook(); + + // validate arbitrator signature + Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(arbitratorNodeAddress) must not be null"); + if (!TradeUtils.isArbitratorSignatureValid(model.getSignOfferResponse().getSignedOfferPayload(), arbitrator)) { + throw new RuntimeException("Offer payload has invalid arbitrator signature"); + } + + // set arbitrator signature for maker's offer + model.getOffer().getOfferPayload().setArbitratorSignature(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature()); + + complete(); + } catch (Exception e) { + offer.setErrorMessage("An error occurred.\n" + + "Error message:\n" + + e.getMessage()); + failed(e); + } + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java new file mode 100644 index 0000000000..140775a798 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java @@ -0,0 +1,76 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.offer.placeoffer.tasks; + +import bisq.common.taskrunner.Task; +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 { + + public MakerReservesTradeFunds(TaskRunner taskHandler, PlaceOfferModel model) { + super(taskHandler, model); + } + + @Override + protected void run() { + + Offer offer = model.getOffer(); + + try { + runInterceptHook(); + + // 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.createReserveTx(model.getXmrWalletService(), offer.getId(), makerFee, returnAddress, depositAmount); + + // freeze reserved outputs + // TODO (woodser): synchronize to handle potential race condition where concurrent trades freeze each other's outputs + List frozenKeyImages = new ArrayList(); + MoneroWallet wallet = model.getXmrWalletService().getWallet(); + for (MoneroOutput input : reserveTx.getInputs()) { + frozenKeyImages.add(input.getKeyImage().getHex()); + wallet.freezeOutput(input.getKeyImage().getHex()); + } + + // save offer state + // TODO (woodser): persist + model.setReserveTx(reserveTx); + offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): rename this to reserve tx id + offer.setState(Offer.State.OFFER_FEE_RESERVED); + complete(); + } catch (Throwable t) { + offer.setErrorMessage("An error occurred.\n" + + "Error message:\n" + + t.getMessage()); + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java new file mode 100644 index 0000000000..29bde2629d --- /dev/null +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java @@ -0,0 +1,93 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.offer.placeoffer.tasks; + +import bisq.common.app.Version; +import bisq.common.taskrunner.Task; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.offer.Offer; +import bisq.core.offer.messages.SignOfferRequest; +import bisq.core.offer.placeoffer.PlaceOfferModel; +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.network.p2p.P2PService; +import bisq.network.p2p.SendDirectMessageListener; +import java.util.Date; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class MakerSendsSignOfferRequest extends Task { + private static final Logger log = LoggerFactory.getLogger(MakerSendsSignOfferRequest.class); + + @SuppressWarnings({"unused"}) + public MakerSendsSignOfferRequest(TaskRunner taskHandler, PlaceOfferModel model) { + super(taskHandler, model); + } + + @Override + protected void run() { + + Offer offer = model.getOffer(); + + try { + runInterceptHook(); + + // create request for arbitrator to sign offer + String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); + SignOfferRequest request = new SignOfferRequest( + model.getOffer().getId(), + P2PService.getMyNodeAddress(), + model.getKeyRing().getPubKeyRing(), + model.getUser().getAccountId(), + offer.getOfferPayload(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + model.getReserveTx().getHash(), + model.getReserveTx().getFullHex(), + model.getReserveTx().getKey(), + returnAddress); + + // get signing arbitrator + Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); + + // send request + model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: arbitrator={}; offerId={}; uid={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } catch (Throwable t) { + offer.setErrorMessage("An error occurred.\n" + + "Error message:\n" + + t.getMessage()); + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/payment/payload/PaymentAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/PaymentAccountPayload.java index 7fa67beb03..6c78dc3c7c 100644 --- a/core/src/main/java/bisq/core/payment/payload/PaymentAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/PaymentAccountPayload.java @@ -19,6 +19,7 @@ package bisq.core.payment.payload; import bisq.common.consensus.UsedForTradeContractJson; import bisq.common.crypto.CryptoUtils; +import bisq.common.crypto.Hash; import bisq.common.proto.network.NetworkPayload; import bisq.common.util.JsonExclude; import bisq.common.util.Utilities; @@ -46,7 +47,7 @@ import static com.google.common.base.Preconditions.checkArgument; @ToString @Slf4j public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTradeContractJson { - + // Keys for excludeFromJsonDataMap public static final String SALT = "salt"; public static final String HOLDER_NAME = "holderName"; @@ -117,6 +118,10 @@ public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTr public abstract String getPaymentDetails(); public abstract String getPaymentDetailsForTradePopup(); + + public byte[] getHash() { + return Hash.getRipemd160hash(this.toProtoMessage().toByteArray()); + } public byte[] getSalt() { checkArgument(excludeFromJsonDataMap.containsKey(SALT), "Salt must have been set in excludeFromJsonDataMap."); diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java index bd7796f3ac..e613bd2476 100644 --- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java @@ -39,6 +39,8 @@ import bisq.core.network.p2p.inventory.messages.GetInventoryResponse; import bisq.core.offer.OfferPayload; import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityResponse; +import bisq.core.offer.messages.SignOfferRequest; +import bisq.core.offer.messages.SignOfferResponse; import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage; @@ -53,19 +55,22 @@ import bisq.core.support.messages.ChatMessage; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; +import bisq.core.trade.messages.DepositRequest; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InputsForDepositTxRequest; import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; 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.PeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.messages.RefreshTradeStateRequest; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TraderSignedWitnessMessage; import bisq.core.trade.messages.UpdateMultisigRequest; import bisq.core.trade.messages.UpdateMultisigResponse; @@ -133,8 +138,13 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo case PONG: return Pong.fromProto(proto.getPong(), messageVersion); + case SIGN_OFFER_REQUEST: + return SignOfferRequest.fromProto(proto.getSignOfferRequest(), messageVersion); + case SIGN_OFFER_RESPONSE: + return SignOfferResponse.fromProto(proto.getSignOfferResponse(), messageVersion); + case OFFER_AVAILABILITY_REQUEST: - return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), messageVersion); + return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), this, messageVersion); case OFFER_AVAILABILITY_RESPONSE: return OfferAvailabilityResponse.fromProto(proto.getOfferAvailabilityResponse(), messageVersion); case REFRESH_OFFER_MESSAGE: @@ -157,16 +167,22 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion); case INIT_TRADE_REQUEST: return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion); - case INIT_MULTISIG_MESSAGE: - return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion); + case INIT_MULTISIG_REQUEST: + return InitMultisigRequest.fromProto(proto.getInitMultisigRequest(), this, messageVersion); + case SIGN_CONTRACT_REQUEST: + return SignContractRequest.fromProto(proto.getSignContractRequest(), this, messageVersion); + case SIGN_CONTRACT_RESPONSE: + return SignContractResponse.fromProto(proto.getSignContractResponse(), this, messageVersion); + case DEPOSIT_REQUEST: + return DepositRequest.fromProto(proto.getDepositRequest(), this, messageVersion); + case DEPOSIT_RESPONSE: + return DepositResponse.fromProto(proto.getDepositResponse(), this, messageVersion); + case PAYMENT_ACCOUNT_PAYLOAD_REQUEST: + return PaymentAccountPayloadRequest.fromProto(proto.getPaymentAccountPayloadRequest(), this, messageVersion); case UPDATE_MULTISIG_REQUEST: return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion); case UPDATE_MULTISIG_RESPONSE: return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion); - case MAKER_READY_TO_FUND_MULTISIG_REQUEST: - return MakerReadyToFundMultisigRequest.fromProto(proto.getMakerReadyToFundMultisigRequest(), this, messageVersion); - case MAKER_READY_TO_FUND_MULTISIG_RESPONSE: - return MakerReadyToFundMultisigResponse.fromProto(proto.getMakerReadyToFundMultisigResponse(), this, messageVersion); case INPUTS_FOR_DEPOSIT_TX_REQUEST: return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion); case INPUTS_FOR_DEPOSIT_TX_RESPONSE: @@ -265,6 +281,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo } } + @Override public NetworkPayload fromProto(protobuf.StorageEntryWrapper proto) { if (proto != null) { switch (proto.getMessageCase()) { @@ -282,6 +299,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo } } + @Override public NetworkPayload fromProto(protobuf.StoragePayload proto) { if (proto != null) { switch (proto.getMessageCase()) { diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java index 8f18bffc81..297cbe7ff0 100644 --- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java @@ -34,6 +34,7 @@ import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore; import bisq.core.dao.state.model.governance.BallotList; import bisq.core.dao.state.storage.DaoStateStore; import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputList; +import bisq.core.offer.SignedOfferList; import bisq.core.payment.PaymentAccountList; import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.ArbitrationDisputeList; @@ -85,6 +86,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) { if (proto != null) { switch (proto.getMessageCase()) { + case SIGNED_OFFER_LIST: + return SignedOfferList.fromProto(proto.getSignedOfferList()); case SEQUENCE_NUMBER_MAP: return SequenceNumberMap.fromProto(proto.getSequenceNumberMap()); case PEER_LIST: diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 81527f431a..f89f785785 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -18,6 +18,7 @@ package bisq.core.support.dispute; import bisq.core.locale.Res; +import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; import bisq.core.support.SupportType; import bisq.core.support.messages.ChatMessage; @@ -128,9 +129,6 @@ public final class Dispute implements NetworkPayload, PersistablePayload { @Nullable private String delayedPayoutTxId; - // Added for XMR integration - private boolean isOpener; - // Added at v1.4.0 @Setter @Nullable @@ -144,6 +142,13 @@ public final class Dispute implements NetworkPayload, PersistablePayload { @Nullable @Setter private Map extraDataMap; + + // Added for XMR integration + private boolean isOpener; + @Nullable + private PaymentAccountPayload makerPaymentAccountPayload; + @Nullable + private PaymentAccountPayload takerPaymentAccountPayload; // We do not persist uid, it is only used by dispute agents to guarantee an uid. @Setter @@ -178,6 +183,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload { String contractAsJson, @Nullable String makerContractSignature, @Nullable String takerContractSignature, + @Nullable PaymentAccountPayload makerPaymentAccountPayload, + @Nullable PaymentAccountPayload takerPaymentAccountPayload, PubKeyRing agentPubKeyRing, boolean isSupportTicket, SupportType supportType) { @@ -199,6 +206,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload { this.contractAsJson = contractAsJson; this.makerContractSignature = makerContractSignature; this.takerContractSignature = takerContractSignature; + this.makerPaymentAccountPayload = makerPaymentAccountPayload; + this.takerPaymentAccountPayload = takerPaymentAccountPayload; this.agentPubKeyRing = agentPubKeyRing; this.isSupportTicket = isSupportTicket; this.supportType = supportType; @@ -246,6 +255,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload { Optional.ofNullable(disputePayoutTxId).ifPresent(builder::setDisputePayoutTxId); Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature); Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature); + Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())); + Optional.ofNullable(takerPaymentAccountPayload).ifPresent(e -> builder.setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage())); Optional.ofNullable(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage())); Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType))); Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult)); @@ -274,6 +285,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload { proto.getContractAsJson(), ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()), ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()), + proto.hasMakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()) : null, + proto.hasTakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()) : null, PubKeyRing.fromProto(proto.getAgentPubKeyRing()), proto.getIsSupportTicket(), SupportType.fromProto(proto.getSupportType())); @@ -448,6 +461,16 @@ public final class Dispute implements NetworkPayload, PersistablePayload { return Res.get("support.sellerTaker"); } } + + @Nullable + public PaymentAccountPayload getBuyerPaymentAccountPayload() { + return contract.isBuyerMakerAndSellerTaker() ? makerPaymentAccountPayload : takerPaymentAccountPayload; + } + + @Nullable + public PaymentAccountPayload getSellerPaymentAccountPayload() { + return contract.isBuyerMakerAndSellerTaker() ? takerPaymentAccountPayload : makerPaymentAccountPayload; + } @Override public String toString() { diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 61a15c7ea0..2810cf59b4 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -244,6 +244,7 @@ public abstract class DisputeManager> extends Sup // API /////////////////////////////////////////////////////////////////////////////////////////// + @Override public void onAllServicesInitialized() { super.onAllServicesInitialized(); disputeListService.onAllServicesInitialized(); @@ -329,7 +330,7 @@ public abstract class DisputeManager> extends Sup if (isAgent(dispute)) { // update arbitrator's multisig wallet - MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId()); + MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId()); multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex())); System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:"); System.out.println(multisigWallet.getTxs()); @@ -364,12 +365,14 @@ public abstract class DisputeManager> extends Sup addMediationResultMessage(dispute); try { + TradeDataValidation.validatePaymentAccountPayloads(dispute); TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); //TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed? TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config); } catch (TradeDataValidation.AddressException | - TradeDataValidation.NodeAddressException e) { + TradeDataValidation.NodeAddressException | + TradeDataValidation.InvalidPaymentAccountPayloadException e) { log.error(e.toString()); validationExceptions.add(e); } @@ -581,6 +584,8 @@ public abstract class DisputeManager> extends Sup disputeFromOpener.getContractAsJson(), disputeFromOpener.getMakerContractSignature(), disputeFromOpener.getTakerContractSignature(), + disputeFromOpener.getMakerPaymentAccountPayload(), + disputeFromOpener.getTakerPaymentAccountPayload(), disputeFromOpener.getAgentPubKeyRing(), disputeFromOpener.isSupportTicket(), disputeFromOpener.getSupportType()); diff --git a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java index 3bcbf50aa9..dc64297b77 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java @@ -24,7 +24,6 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; -import bisq.core.trade.Contract; import bisq.core.user.DontShowAgainLookup; import bisq.common.crypto.Hash; @@ -90,8 +89,8 @@ public class MultipleHolderNameDetection { public static PaymentAccountPayload getPaymentAccountPayload(Dispute dispute) { return isBuyer(dispute) ? - dispute.getContract().getBuyerPaymentAccountPayload() : - dispute.getContract().getSellerPaymentAccountPayload(); + dispute.getBuyerPaymentAccountPayload() : + dispute.getSellerPaymentAccountPayload(); } public static String getAddress(Dispute dispute) { @@ -202,10 +201,9 @@ public class MultipleHolderNameDetection { Map> allDisputesByTraderMap = new HashMap<>(); disputeManager.getDisputesAsObservableList().stream() .filter(dispute -> { - Contract contract = dispute.getContract(); PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ? - contract.getBuyerPaymentAccountPayload() : - contract.getSellerPaymentAccountPayload(); + dispute.getBuyerPaymentAccountPayload() : + dispute.getSellerPaymentAccountPayload(); return paymentAccountPayload instanceof PayloadWithHolderName; }) .forEach(dispute -> { diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java index be3aa1fcbc..90bd8a8ab2 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -240,7 +240,7 @@ public final class ArbitrationManager extends DisputeManager disputeOptional = findOwnDispute(tradeId); if (!disputeOptional.isPresent()) { log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId); @@ -596,8 +596,8 @@ public final class ArbitrationManager extends DisputeManager disputeOptional = findOwnDispute(tradeId); if (!disputeOptional.isPresent()) throw new RuntimeException("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId); Dispute dispute = disputeOptional.get(); @@ -665,11 +665,11 @@ public final class ArbitrationManager extends DisputeManager // If we have not got yet the peers signature we sign and send to the peer our signature. // Otherwise we sign and complete with the peers signature the payout tx. - if (processModel.getTradingPeer().getMediatedPayoutTxSignature() == null) { + if (trade.getTradingPeer().getMediatedPayoutTxSignature() == null) { tradeProtocol.onAcceptMediationResult(() -> { if (trade.getPayoutTx() != null) { tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.MEDIATION_CLOSED); diff --git a/core/src/main/java/bisq/core/trade/ArbitratorTrade.java b/core/src/main/java/bisq/core/trade/ArbitratorTrade.java index 13a9d6abd7..79e51805ce 100644 --- a/core/src/main/java/bisq/core/trade/ArbitratorTrade.java +++ b/core/src/main/java/bisq/core/trade/ArbitratorTrade.java @@ -20,19 +20,18 @@ import lombok.extern.slf4j.Slf4j; */ @Slf4j public class ArbitratorTrade extends Trade { - + public ArbitratorTrade(Offer offer, Coin tradeAmount, - Coin txFee, Coin takerFee, long tradePrice, - NodeAddress makerNodeAddress, - NodeAddress takerNodeAddress, - NodeAddress arbitratorNodeAddress, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { - super(offer, tradeAmount, txFee, takerFee, tradePrice, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, xmrWalletService, processModel, uid); + String uid, + NodeAddress makerNodeAddress, + NodeAddress takerNodeAddress, + NodeAddress arbitratorNodeAddress) { + super(offer, tradeAmount, takerFee, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress); } @Override @@ -64,15 +63,14 @@ public class ArbitratorTrade extends Trade { return fromProto(new ArbitratorTrade( Offer.fromProto(proto.getOffer()), Coin.valueOf(proto.getTradeAmountAsLong()), - Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), proto.getTradePrice(), - proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, - proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, - proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, xmrWalletService, processModel, - uid), + uid, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null), proto, coreProtoResolver); } diff --git a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java index 38b0540053..add4a9bb28 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java @@ -42,23 +42,25 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { /////////////////////////////////////////////////////////////////////////////////////////// public BuyerAsMakerTrade(Offer offer, - Coin txFee, + Coin tradeAmount, Coin takeOfferFee, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, + long tradePrice, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + NodeAddress makerNodeAddress, + NodeAddress takerNodeAddress, + NodeAddress arbitratorNodeAddress) { super(offer, - txFee, + tradeAmount, takeOfferFee, - takerNodeAddress, - makerNodeAddress, - arbitratorNodeAddress, + tradePrice, xmrWalletService, processModel, - uid); + uid, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -84,14 +86,15 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade { } BuyerAsMakerTrade trade = new BuyerAsMakerTrade( Offer.fromProto(proto.getOffer()), - Coin.valueOf(proto.getTxFeeAsLong()), + Coin.valueOf(proto.getTradeAmountAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), - proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, - proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, - proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, + proto.getTradePrice(), xmrWalletService, processModel, - uid); + uid, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null); trade.setTradeAmountAsLong(proto.getTradeAmountAsLong()); trade.setTradePrice(proto.getTradePrice()); diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java index 6007437e9f..91ec972828 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java @@ -43,26 +43,24 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { public BuyerAsTakerTrade(Offer offer, Coin tradeAmount, - Coin txFee, Coin takerFee, long tradePrice, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { super(offer, tradeAmount, - txFee, takerFee, tradePrice, - makerNodeAddress, - takerNodeAddress, - arbitratorNodeAddress, xmrWalletService, processModel, - uid); + uid, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress); } @@ -90,15 +88,14 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { return fromProto(new BuyerAsTakerTrade( Offer.fromProto(proto.getOffer()), Coin.valueOf(proto.getTradeAmountAsLong()), - Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), proto.getTradePrice(), - proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, - proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, - proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, xmrWalletService, processModel, - uid), + uid, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null), proto, coreProtoResolver); } diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java index 4b6313e32a..f2b483ece2 100644 --- a/core/src/main/java/bisq/core/trade/BuyerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerTrade.java @@ -35,46 +35,24 @@ import static com.google.common.base.Preconditions.checkNotNull; public abstract class BuyerTrade extends Trade { BuyerTrade(Offer offer, Coin tradeAmount, - Coin txFee, Coin takerFee, long tradePrice, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { super(offer, tradeAmount, - txFee, takerFee, tradePrice, - takerNodeAddress, - makerNodeAddress, - arbitratorNodeAddress, xmrWalletService, processModel, - uid); - } - - BuyerTrade(Offer offer, - Coin txFee, - Coin takerFee, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, - XmrWalletService xmrWalletService, - ProcessModel processModel, - String uid) { - super(offer, - txFee, - takerFee, + uid, takerNodeAddress, makerNodeAddress, - arbitratorNodeAddress, - xmrWalletService, - processModel, - uid); + arbitratorNodeAddress); } @Override diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/Contract.java index 7b145ff6b6..af5f874de4 100644 --- a/core/src/main/java/bisq/core/trade/Contract.java +++ b/core/src/main/java/bisq/core/trade/Contract.java @@ -21,13 +21,12 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.OfferPayload; -import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.proto.CoreProtoResolver; import bisq.core.util.VolumeUtil; import bisq.network.p2p.NodeAddress; - +import com.google.protobuf.ByteString; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.network.NetworkPayload; import bisq.common.util.JsonExclude; @@ -56,14 +55,18 @@ public final class Contract implements NetworkPayload { private final boolean isBuyerMakerAndSellerTaker; private final String makerAccountId; private final String takerAccountId; - private final PaymentAccountPayload makerPaymentAccountPayload; - private final PaymentAccountPayload takerPaymentAccountPayload; + private final String makerPaymentMethodId; + private final String takerPaymentMethodId; + private final byte[] makerPaymentAccountPayloadHash; + private final byte[] takerPaymentAccountPayloadHash; @JsonExclude private final PubKeyRing makerPubKeyRing; @JsonExclude private final PubKeyRing takerPubKeyRing; private final String makerPayoutAddressString; private final String takerPayoutAddressString; + private final String makerDepositTxHash; + private final String takerDepositTxHash; // Added in v1.2.0 private long lockTime; @@ -77,13 +80,17 @@ public final class Contract implements NetworkPayload { boolean isBuyerMakerAndSellerTaker, String makerAccountId, String takerAccountId, - PaymentAccountPayload makerPaymentAccountPayload, - PaymentAccountPayload takerPaymentAccountPayload, + String makerPaymentMethodId, + String takerPaymentMethodId, + byte[] makerPaymentAccountPayloadHash, + byte[] takerPaymentAccountPayloadHash, PubKeyRing makerPubKeyRing, PubKeyRing takerPubKeyRing, String makerPayoutAddressString, String takerPayoutAddressString, - long lockTime) { + long lockTime, + String makerDepositTxHash, + String takerDepositTxHash) { this.offerPayload = offerPayload; this.tradeAmount = tradeAmount; this.tradePrice = tradePrice; @@ -93,16 +100,18 @@ public final class Contract implements NetworkPayload { this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker; this.makerAccountId = makerAccountId; this.takerAccountId = takerAccountId; - this.makerPaymentAccountPayload = makerPaymentAccountPayload; - this.takerPaymentAccountPayload = takerPaymentAccountPayload; + this.makerPaymentMethodId = makerPaymentMethodId; + this.takerPaymentMethodId = takerPaymentMethodId; + this.makerPaymentAccountPayloadHash = makerPaymentAccountPayloadHash; + this.takerPaymentAccountPayloadHash = takerPaymentAccountPayloadHash; this.makerPubKeyRing = makerPubKeyRing; this.takerPubKeyRing = takerPubKeyRing; this.makerPayoutAddressString = makerPayoutAddressString; this.takerPayoutAddressString = takerPayoutAddressString; this.lockTime = lockTime; + this.makerDepositTxHash = makerDepositTxHash; + this.takerDepositTxHash = takerDepositTxHash; - String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId(); - String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId(); // For SEPA offers we accept also SEPA_INSTANT takers // Otherwise both ids need to be the same boolean result = (makerPaymentMethodId.equals(PaymentMethod.SEPA_ID) && takerPaymentMethodId.equals(PaymentMethod.SEPA_INSTANT_ID)) || @@ -127,13 +136,17 @@ public final class Contract implements NetworkPayload { proto.getIsBuyerMakerAndSellerTaker(), proto.getMakerAccountId(), proto.getTakerAccountId(), - coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()), - coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()), + proto.getMakerPaymentMethodId(), + proto.getTakerPaymentMethodId(), + proto.getMakerPaymentAccountPayloadHash().toByteArray(), + proto.getTakerPaymentAccountPayloadHash().toByteArray(), PubKeyRing.fromProto(proto.getMakerPubKeyRing()), PubKeyRing.fromProto(proto.getTakerPubKeyRing()), proto.getMakerPayoutAddressString(), proto.getTakerPayoutAddressString(), - proto.getLockTime()); + proto.getLockTime(), + proto.getMakerDepositTxHash(), + proto.getTakerDepositTxHash()); } @Override @@ -148,13 +161,17 @@ public final class Contract implements NetworkPayload { .setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker) .setMakerAccountId(makerAccountId) .setTakerAccountId(takerAccountId) - .setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage()) - .setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage()) + .setMakerPaymentMethodId(makerPaymentMethodId) + .setTakerPaymentMethodId(takerPaymentMethodId) + .setMakerPaymentAccountPayloadHash(ByteString.copyFrom(makerPaymentAccountPayloadHash)) + .setTakerPaymentAccountPayloadHash(ByteString.copyFrom(takerPaymentAccountPayloadHash)) .setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()) .setTakerPubKeyRing(takerPubKeyRing.toProtoMessage()) .setMakerPayoutAddressString(makerPayoutAddressString) .setTakerPayoutAddressString(takerPayoutAddressString) .setLockTime(lockTime) + .setMakerDepositTxHash(makerDepositTxHash) + .setTakerDepositTxHash(takerDepositTxHash) .build(); } @@ -179,16 +196,16 @@ public final class Contract implements NetworkPayload { return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing; } - public PaymentAccountPayload getBuyerPaymentAccountPayload() { - return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload; + public byte[] getBuyerPaymentAccountPayloadHash() { + return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayloadHash : takerPaymentAccountPayloadHash; } - public PaymentAccountPayload getSellerPaymentAccountPayload() { - return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayload : makerPaymentAccountPayload; + public byte[] getSellerPaymentAccountPayloadHash() { + return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayloadHash : makerPaymentAccountPayloadHash; } - + public String getPaymentMethodId() { - return makerPaymentAccountPayload.getPaymentMethodId(); + return makerPaymentMethodId; } public Coin getTradeAmount() { @@ -270,13 +287,17 @@ public final class Contract implements NetworkPayload { ",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker + ",\n makerAccountId='" + makerAccountId + '\'' + ",\n takerAccountId='" + takerAccountId + '\'' + - ",\n makerPaymentAccountPayload=" + makerPaymentAccountPayload + - ",\n takerPaymentAccountPayload=" + takerPaymentAccountPayload + + ",\n makerPaymentMethodId='" + makerPaymentMethodId + '\'' + + ",\n takerPaymentMethodId='" + takerPaymentMethodId + '\'' + + ",\n makerPaymentAccountPayloadHash=" + makerPaymentAccountPayloadHash + + ",\n takerPaymentAccountPayloadHash=" + takerPaymentAccountPayloadHash + ",\n makerPubKeyRing=" + makerPubKeyRing + ",\n takerPubKeyRing=" + takerPubKeyRing + ",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' + ",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' + ",\n lockTime=" + lockTime + + ",\n makerDepositTxHash='" + makerDepositTxHash + '\'' + + ",\n takerDepositTxHash='" + takerDepositTxHash + '\'' + "\n}"; } } diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java index e0e83c24be..3bc35f88bd 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java @@ -42,23 +42,25 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade /////////////////////////////////////////////////////////////////////////////////////////// public SellerAsMakerTrade(Offer offer, - Coin txFee, + Coin tradeAmount, Coin takerFee, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, + long tradePrice, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { super(offer, - txFee, + tradeAmount, takerFee, - makerNodeAddress, - takerNodeAddress, - arbitratorNodeAddress, + tradePrice, xmrWalletService, processModel, - uid); + uid, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress); } @@ -85,14 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade } SellerAsMakerTrade trade = new SellerAsMakerTrade( Offer.fromProto(proto.getOffer()), - Coin.valueOf(proto.getTxFeeAsLong()), + Coin.valueOf(proto.getTradeAmountAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), - proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, - proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, - proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, + proto.getTradePrice(), xmrWalletService, processModel, - uid); + uid, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null); trade.setTradeAmountAsLong(proto.getTradeAmountAsLong()); trade.setTradePrice(proto.getTradePrice()); diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java index 85cf04e2d1..b4d1c0dc14 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java @@ -43,26 +43,24 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade public SellerAsTakerTrade(Offer offer, Coin tradeAmount, - Coin txFee, Coin takerFee, long tradePrice, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { super(offer, tradeAmount, - txFee, takerFee, tradePrice, - makerNodeAddress, - takerNodeAddress, - arbitratorNodeAddress, xmrWalletService, processModel, - uid); + uid, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress); } @@ -90,15 +88,14 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade return fromProto(new SellerAsTakerTrade( Offer.fromProto(proto.getOffer()), Coin.valueOf(proto.getTradeAmountAsLong()), - Coin.valueOf(proto.getTxFeeAsLong()), Coin.valueOf(proto.getTakerFeeAsLong()), proto.getTradePrice(), - proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, - proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, - proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, xmrWalletService, processModel, - uid), + uid, + proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null, + proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null, + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null), proto, coreProtoResolver); } diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java index e54c4b952b..2b88bef700 100644 --- a/core/src/main/java/bisq/core/trade/SellerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerTrade.java @@ -36,46 +36,24 @@ import static com.google.common.base.Preconditions.checkNotNull; public abstract class SellerTrade extends Trade { SellerTrade(Offer offer, Coin tradeAmount, - Coin txFee, Coin takerFee, long tradePrice, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { super(offer, tradeAmount, - txFee, takerFee, tradePrice, - makerNodeAddress, - takerNodeAddress, - arbitratorNodeAddress, xmrWalletService, processModel, - uid); - } - - SellerTrade(Offer offer, - Coin txFee, - Coin takeOfferFee, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, - XmrWalletService xmrWalletService, - ProcessModel processModel, - String uid) { - super(offer, - txFee, - takeOfferFee, + uid, makerNodeAddress, takerNodeAddress, - arbitratorNodeAddress, - xmrWalletService, - processModel, - uid); + arbitratorNodeAddress); } @Override diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 8e5e9bf963..34c1c8759d 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -22,6 +22,7 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; +import bisq.core.offer.OfferPayload.Direction; import bisq.core.payment.payload.PaymentMethod; import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; @@ -31,10 +32,11 @@ import bisq.core.support.messages.ChatMessage; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.ProcessModelServiceProvider; -import bisq.core.trade.protocol.TradeMessageListener; +import bisq.core.trade.protocol.TradeListener; +import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.util.VolumeUtil; - +import bisq.network.p2p.AckMessage; import bisq.network.p2p.NodeAddress; import bisq.common.crypto.PubKeyRing; @@ -80,9 +82,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import monero.common.MoneroError; import monero.daemon.MoneroDaemon; -import monero.daemon.MoneroDaemonRpc; import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroOutputWallet; import monero.wallet.model.MoneroTxWallet; +import monero.wallet.model.MoneroWalletListener; /** * Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are @@ -345,14 +348,6 @@ public abstract class Trade implements Tradable, Model { @Nullable @Getter @Setter - private String takerContractSignature; - @Nullable - @Getter - @Setter - private String makerContractSignature; - @Nullable - @Getter - @Setter private NodeAddress arbitratorNodeAddress; @Nullable @Setter @@ -364,14 +359,6 @@ public abstract class Trade implements Tradable, Model { @Nullable @Getter @Setter - private NodeAddress mediatorNodeAddress; - @Nullable - @Getter - @Setter - private PubKeyRing mediatorPubKeyRing; - @Nullable - @Getter - @Setter private String takerPaymentAccountId; @Nullable private String errorMessage; @@ -460,7 +447,7 @@ public abstract class Trade implements Tradable, Model { // Added in XMR integration - private transient List tradeMessageListeners; // notified on fully validated trade messages + private transient List tradeListeners; // notified on fully validated trade messages @Getter @Setter private NodeAddress makerNodeAddress; @@ -473,18 +460,11 @@ public abstract class Trade implements Tradable, Model { @Getter @Setter private PubKeyRing takerPubKeyRing; - @Nullable + transient MoneroWalletListener depositTxListener; + transient Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked + transient Boolean takerDepositLocked; transient private MoneroTxWallet makerDepositTx; - @Nullable transient private MoneroTxWallet takerDepositTx; - @Nullable - @Getter - @Setter - private String makerDepositTxId; - @Nullable - @Getter - @Setter - private String takerDepositTxId; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization @@ -492,28 +472,34 @@ public abstract class Trade implements Tradable, Model { // maker protected Trade(Offer offer, - Coin txFee, - Coin takerFee, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, + Coin tradeAmount, + Coin takerFee, // TODO (woodser): makerFee, takerFee, but not given one during construction + long tradePrice, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { this.offer = offer; - this.txFee = txFee; + this.tradeAmount = tradeAmount; + this.txFee = Coin.valueOf(0); // TODO (woodser): remove this field this.takerFee = takerFee; - this.makerNodeAddress = makerNodeAddress; - this.takerNodeAddress = takerNodeAddress; - this.arbitratorNodeAddress = arbitratorNodeAddress; + this.tradePrice = tradePrice; this.xmrWalletService = xmrWalletService; this.processModel = processModel; this.uid = uid; - txFeeAsLong = txFee.value; - takerFeeAsLong = takerFee.value; - takeOfferDate = new Date().getTime(); - tradeMessageListeners = new ArrayList(); + this.txFeeAsLong = txFee.value; + this.takerFeeAsLong = takerFee.value; + this.takeOfferDate = new Date().getTime(); + this.tradeListeners = new ArrayList(); + + this.makerNodeAddress = makerNodeAddress; + this.takerNodeAddress = takerNodeAddress; + this.arbitratorNodeAddress = arbitratorNodeAddress; + + setTradeAmount(tradeAmount); } @@ -525,28 +511,28 @@ public abstract class Trade implements Tradable, Model { Coin txFee, Coin takerFee, long tradePrice, - @Nullable NodeAddress makerNodeAddress, - @Nullable NodeAddress takerNodeAddress, - @Nullable NodeAddress arbitratorNodeAddress, - @Nullable NodeAddress mediatorNodeAddress, + @Nullable NodeAddress mediatorNodeAddress, // TODO (woodser): remove mediator, refund agent from trade @Nullable NodeAddress refundAgentNodeAddress, XmrWalletService xmrWalletService, ProcessModel processModel, - String uid) { + String uid, + @Nullable NodeAddress makerNodeAddress, + @Nullable NodeAddress takerNodeAddress, + @Nullable NodeAddress arbitratorNodeAddress) { this(offer, - txFee, + tradeAmount, takerFee, - makerNodeAddress, - takerNodeAddress, - arbitratorNodeAddress, + tradePrice, xmrWalletService, processModel, - uid); - this.tradePrice = tradePrice; - setTradeAmount(tradeAmount); + uid, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress); } + // TODO: remove these constructors // arbitrator @SuppressWarnings("NullableProblems") protected Trade(Offer offer, @@ -562,16 +548,16 @@ public abstract class Trade implements Tradable, Model { String uid) { this(offer, - txFee, + tradeAmount, takerFee, - makerNodeAddress, - takerNodeAddress, - arbitratorNodeAddress, + tradePrice, xmrWalletService, processModel, - uid); + uid, + makerNodeAddress, + takerNodeAddress, + arbitratorNodeAddress); - this.tradePrice = tradePrice; setTradeAmount(tradeAmount); } @@ -599,22 +585,16 @@ public abstract class Trade implements Tradable, Model { .setUid(uid); Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId); - Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId); - Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId); Optional.ofNullable(payoutTxId).ifPresent(builder::setPayoutTxId); Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage())); Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson); Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash))); - Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature); - Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature); Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())); - Optional.ofNullable(mediatorNodeAddress).ifPresent(e -> builder.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())); Optional.ofNullable(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())); Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey))); Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId); Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage); Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage())); - Optional.ofNullable(mediatorPubKeyRing).ifPresent(e -> builder.setMediatorPubKeyRing(mediatorPubKeyRing.toProtoMessage())); Optional.ofNullable(refundAgentPubKeyRing).ifPresent(e -> builder.setRefundAgentPubKeyRing(refundAgentPubKeyRing.toProtoMessage())); Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId)); Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState))); @@ -625,7 +605,7 @@ public abstract class Trade implements Tradable, Model { Optional.ofNullable(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage())); Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())); Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.toProtoMessage())); - Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(takerPubKeyRing.toProtoMessage())); + Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())); return builder.build(); } @@ -635,22 +615,16 @@ public abstract class Trade implements Tradable, Model { trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState())); trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState())); trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId())); - trade.setMakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId())); - trade.setTakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId())); trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId())); trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null); trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson())); trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash())); - trade.setTakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature())); - trade.setMakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature())); trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null); - trade.setMediatorNodeAddress(proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null); trade.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null); trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey())); trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId())); trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage())); trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null); - trade.setMediatorPubKeyRing(proto.hasMediatorPubKeyRing() ? PubKeyRing.fromProto(proto.getMediatorPubKeyRing()) : null); trade.setRefundAgentPubKeyRing(proto.hasRefundAgentPubKeyRing() ? PubKeyRing.fromProto(proto.getRefundAgentPubKeyRing()) : null); trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId()); trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState())); @@ -688,12 +662,6 @@ public abstract class Trade implements Tradable, Model { arbitratorPubKeyRing = arbitrator.getPubKeyRing(); }); - serviceProvider.getMediatorManager().getDisputeAgentByNodeAddress(mediatorNodeAddress) - .ifPresent(mediator -> mediatorPubKeyRing = mediator.getPubKeyRing()); - - serviceProvider.getRefundAgentManager().getDisputeAgentByNodeAddress(refundAgentNodeAddress) - .ifPresent(refundAgent -> refundAgentPubKeyRing = refundAgent.getPubKeyRing()); - isInitialized = true; } @@ -732,42 +700,86 @@ public abstract class Trade implements Tradable, Model { void updateDepositTxFromWallet() { if (getMakerDepositTx() != null && getTakerDepositTx() != null) { System.out.println(processModel.getProvider().getXmrWalletService()); - MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId()); - applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId())); + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(getId()); + applyDepositTxs(multisigWallet.getTx(getMakerDepositTx().getHash()), multisigWallet.getTx(getTakerDepositTx().getHash())); } } public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) { this.makerDepositTx = makerDepositTx; this.takerDepositTx = takerDepositTx; - makerDepositTxId = makerDepositTx.getHash(); - takerDepositTxId = takerDepositTx.getHash(); - //setupConfirmationListener(); // TODO (woodser): listening disabled here, using SetupDepositTxsListener in buyer and seller if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) { setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations } } + + public void setupDepositTxsListener() { + + // ignore if already listening + if (depositTxListener != null) { + log.warn("Trade {} already listening for deposit txs", getId()); + return; + } + + // create listener for deposit transactions + MoneroWallet multisigWallet = processModel.getXmrWalletService().getMultisigWallet(getId()); + depositTxListener = processModel.getXmrWalletService().new HavenoWalletListener(new MoneroWalletListener() { // TODO (woodser): separate into own class file + @Override + public void onOutputReceived(MoneroOutputWallet output) { + + // ignore if no longer listening + if (depositTxListener == null) return; + + // TODO (woodser): remove this + if (output.getTx().isConfirmed() && (processModel.getMaker().getDepositTxHash().equals(output.getTx().getHash()) || processModel.getTaker().getDepositTxHash().equals(output.getTx().getHash()))) { + System.out.println("Deposit output for tx " + output.getTx().getHash() + " is confirmed at height " + output.getTx().getHeight()); + } + + // update locked state + if (output.getTx().getHash().equals(processModel.getMaker().getDepositTxHash())) makerDepositLocked = output.getTx().isLocked(); + else if (output.getTx().getHash().equals(processModel.getTaker().getDepositTxHash())) takerDepositLocked = output.getTx().isLocked(); + + // deposit txs seen when both locked states seen + if (makerDepositLocked != null && takerDepositLocked != null) { + setState(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK); + } + + // confirm trade and update ui when both deposits unlock + if (Boolean.FALSE.equals(makerDepositLocked) && Boolean.FALSE.equals(takerDepositLocked)) { + System.out.println("Multisig deposit txs unlocked!"); + applyDepositTxs(multisigWallet.getTx(processModel.getMaker().getDepositTxHash()), multisigWallet.getTx(processModel.getTaker().getDepositTxHash())); + multisigWallet.removeListener(depositTxListener); // remove listener when notified + depositTxListener = null; // prevent re-applying trade state in subsequent requests + } + } + }); + + // register wallet listener + multisigWallet.addListener(depositTxListener); + } @Nullable public MoneroTxWallet getTakerDepositTx() { - try { - if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null; - return takerDepositTx; - } catch (MoneroError e) { - log.error("Wallet is missing taker deposit tx " + takerDepositTxId); - return null; - } + String depositTxHash = getProcessModel().getTaker().getDepositTxHash(); + try { + if (takerDepositTx == null) takerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash); + return takerDepositTx; + } catch (MoneroError e) { + log.error("Wallet is missing taker deposit tx " + depositTxHash); + return null; + } } @Nullable public MoneroTxWallet getMakerDepositTx() { - try { - if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null; - return makerDepositTx; - } catch (MoneroError e) { - log.error("Wallet is missing maker deposit tx " + makerDepositTxId); - return null; - } + String depositTxHash = getProcessModel().getMaker().getDepositTxHash(); + try { + if (makerDepositTx == null) makerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash); + return makerDepositTx; + } catch (MoneroError e) { + log.error("Wallet is missing maker deposit tx " + depositTxHash); + return null; + } } public void applyDelayedPayoutTx(Transaction delayedPayoutTx) { @@ -845,20 +857,27 @@ public abstract class Trade implements Tradable, Model { // Listeners /////////////////////////////////////////////////////////////////////////////////////////// - public void addTradeMessageListener(TradeMessageListener listener) { - tradeMessageListeners.add(listener); + public void addListener(TradeListener listener) { + tradeListeners.add(listener); } - public void removeTradeMessageListener(TradeMessageListener listener) { - if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered"); + public void removeListener(TradeListener listener) { + if (!tradeListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered"); } - // notified from TradeProtocol of verified messages + // notified from TradeProtocol of verified trade messages public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { - for (TradeMessageListener listener : new ArrayList(tradeMessageListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception + for (TradeListener listener : new ArrayList(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception listener.onVerifiedTradeMessage(message, sender); } } + + // notified from TradeProtocol of ack messages + public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { + for (TradeListener listener : new ArrayList(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception + listener.onAckMessage(ackMessage, sender); + } + } /////////////////////////////////////////////////////////////////////////////////////////// // Setters @@ -879,7 +898,7 @@ public abstract class Trade implements Tradable, Model { log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state); } if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) { - String message = "We got a state change to a previous phase.\n" + + String message = "We got a state change to a previous phase (id=" + getShortId() + ").\n" + "Old state is: " + this.state + ". New state is: " + state; log.warn(message); } @@ -935,6 +954,56 @@ public abstract class Trade implements Tradable, Model { /////////////////////////////////////////////////////////////////////////////////////////// // Getter /////////////////////////////////////////////////////////////////////////////////////////// + + public TradingPeer getSelf() { + if (this instanceof MakerTrade) return processModel.getMaker(); + if (this instanceof TakerTrade) return processModel.getTaker(); + if (this instanceof ArbitratorTrade) return processModel.getArbitrator(); + throw new RuntimeException("Trade is not maker, taker, or arbitrator"); + } + + public TradingPeer getMaker() { + return processModel.getMaker(); + } + + public TradingPeer getTaker() { + return processModel.getTaker(); + } + + public TradingPeer getBuyer() { + return offer.getDirection() == Direction.BUY ? processModel.getMaker() : processModel.getTaker(); + } + + public TradingPeer getSeller() { + return offer.getDirection() == Direction.BUY ? processModel.getTaker() : processModel.getMaker(); + } + + /** + * Get the taker if maker, maker if taker, null if arbitrator. + * + * @return the trade peer + */ + public TradingPeer getTradingPeer() { + if (this instanceof MakerTrade) return processModel.getTaker(); + else if (this instanceof TakerTrade) return processModel.getMaker(); + else if (this instanceof ArbitratorTrade) return null; + else throw new RuntimeException("Unknown trade type: " + getClass().getName()); + } + + /** + * Get the peer with the given address which can be self. + * + * TODO (woodser): this naming convention is confusing + * + * @param address is the address of the peer to get + * @return the trade peer + */ + public TradingPeer getTradingPeer(NodeAddress address) { + if (address.equals(getMakerNodeAddress())) return processModel.getMaker(); + if (address.equals(getTakerNodeAddress())) return processModel.getTaker(); + if (address.equals(getArbitratorNodeAddress())) return processModel.getArbitrator(); + throw new RuntimeException("No protocol participant has node address: " + address); + } public Date getTakeOfferDate() { return new Date(takeOfferDate); @@ -985,7 +1054,7 @@ public abstract class Trade implements Tradable, Model { if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) { final long tradeTime = getTakeOfferDate().getTime(); long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight()); - MoneroDaemon daemonRpc = new MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123"); // TODO (woodser): move to common config + MoneroDaemon daemonRpc = xmrWalletService.getDaemon(); long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp(); // if (depositTx.getConfidence().getDepthInBlocks() > 0) { @@ -1158,8 +1227,8 @@ public abstract class Trade implements Tradable, Model { public boolean isTxChainInvalid() { return offer.getOfferFeePaymentTxId() == null || getTakerFeeTxId() == null || - getMakerDepositTxId() == null || - getTakerDepositTxId() == null || + processModel.getMaker().getDepositTxHash() == null || + processModel.getMaker().getDepositTxHash() == null || getDelayedPayoutTxBytes() == null; } @@ -1241,7 +1310,6 @@ public abstract class Trade implements Tradable, Model { ",\n takeOfferDate=" + takeOfferDate + ",\n processModel=" + processModel + ",\n takerFeeTxId='" + takerFeeTxId + '\'' + - ",\n takerDepositTxId='" + takerDepositTxId + '\'' + ",\n payoutTxId='" + payoutTxId + '\'' + ",\n tradeAmountAsLong=" + tradeAmountAsLong + ",\n tradePrice=" + tradePrice + @@ -1251,11 +1319,7 @@ public abstract class Trade implements Tradable, Model { ",\n contract=" + contract + ",\n contractAsJson='" + contractAsJson + '\'' + ",\n contractHash=" + Utilities.bytesAsHexString(contractHash) + - ",\n takerContractSignature='" + takerContractSignature + '\'' + - ",\n makerContractSignature='" + makerContractSignature + '\'' + ",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) + - ",\n mediatorNodeAddress=" + mediatorNodeAddress + - ",\n mediatorPubKeyRing=" + mediatorPubKeyRing + ",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' + ",\n errorMessage='" + errorMessage + '\'' + ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' + diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index 829c5e2c01..5d4d9883b8 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -36,7 +36,7 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; - +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -54,6 +54,11 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j public class TradeDataValidation { + + public static void validatePaymentAccountPayloads(Dispute dispute) throws InvalidPaymentAccountPayloadException { + if (!Arrays.equals(dispute.getMakerPaymentAccountPayload().getHash(), dispute.getContract().getMakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of maker's payment account payload does not match contract"); + if (!Arrays.equals(dispute.getTakerPaymentAccountPayload().getHash(), dispute.getContract().getTakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of taker's payment account payload does not match contract"); + } public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade) throws AddressException { @@ -396,6 +401,12 @@ public class TradeDataValidation { this.dispute = dispute; } } + + public static class InvalidPaymentAccountPayloadException extends ValidationException { + InvalidPaymentAccountPayloadException(@Nullable Dispute dispute, String msg) { + super(dispute, msg); + } + } public static class AddressException extends ValidationException { AddressException(@Nullable Dispute dispute, String msg) { diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index e3ac16e511..08a233850f 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -17,7 +17,6 @@ package bisq.core.trade; -import bisq.core.btc.exceptions.AddressEntryException; import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.XmrWalletService; @@ -25,21 +24,27 @@ import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.SignedOffer; import bisq.core.offer.availability.OfferAvailabilityModel; +import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.DepositRequest; +import bisq.core.trade.messages.DepositResponse; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.UpdateMultisigRequest; import bisq.core.trade.protocol.ArbitratorProtocol; import bisq.core.trade.protocol.MakerProtocol; @@ -48,11 +53,12 @@ import bisq.core.trade.protocol.ProcessModelServiceProvider; import bisq.core.trade.protocol.TakerProtocol; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.protocol.TradeProtocolFactory; +import bisq.core.trade.protocol.TraderProtocol; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.User; import bisq.core.util.Validator; - +import bisq.core.util.coin.CoinUtil; import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.DecryptedDirectMessageListener; import bisq.network.p2p.DecryptedMessageWithPubKey; @@ -70,15 +76,11 @@ import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistedDataHost; -import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; import javax.inject.Inject; import javax.inject.Named; -import com.google.common.util.concurrent.FutureCallback; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -106,8 +108,6 @@ import org.slf4j.LoggerFactory; import lombok.Getter; import lombok.Setter; -import org.jetbrains.annotations.NotNull; - import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; @@ -132,6 +132,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi private final P2PService p2PService; private final PriceFeedService priceFeedService; private final TradeStatisticsManager tradeStatisticsManager; + private final OfferUtil offerUtil; private final TradeUtil tradeUtil; @Getter private final ArbitratorManager arbitratorManager; @@ -171,6 +172,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi P2PService p2PService, PriceFeedService priceFeedService, TradeStatisticsManager tradeStatisticsManager, + OfferUtil offerUtil, TradeUtil tradeUtil, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -191,6 +193,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi this.p2PService = p2PService; this.priceFeedService = priceFeedService; this.tradeStatisticsManager = tradeStatisticsManager; + this.offerUtil = offerUtil; this.tradeUtil = tradeUtil; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; @@ -238,15 +241,19 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi //handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests } else if (networkEnvelope instanceof InitTradeRequest) { handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer); - } else if (networkEnvelope instanceof InitMultisigMessage) { - handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer); - } else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) { - handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer); - } else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) { - handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer); - } else if (networkEnvelope instanceof DepositTxMessage) { - handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer); - } else if (networkEnvelope instanceof UpdateMultisigRequest) { + } else if (networkEnvelope instanceof InitMultisigRequest) { + handleInitMultisigRequest((InitMultisigRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof SignContractRequest) { + handleSignContractRequest((SignContractRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof SignContractResponse) { + handleSignContractResponse((SignContractResponse) networkEnvelope, peer); + } else if (networkEnvelope instanceof DepositRequest) { + handleDepositRequest((DepositRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof DepositResponse) { + handleDepositResponse((DepositResponse) networkEnvelope, peer); + } else if (networkEnvelope instanceof PaymentAccountPayloadRequest) { + handlePaymentAccountPayloadRequest((PaymentAccountPayloadRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof UpdateMultisigRequest) { handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer); } } @@ -327,65 +334,96 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi persistenceManager.requestPersistence(); } - private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) { - log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid()); + private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender) { + log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", sender, request.getTradeId(), request.getUid()); try { - Validator.nonEmptyStringOf(initTradeRequest.getTradeId()); + Validator.nonEmptyStringOf(request.getTradeId()); } catch (Throwable t) { - log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString()); + log.warn("Invalid InitTradeRequest message " + request.toString()); return; } - System.out.println("RECEIVED INIT REQUEST INFO"); - System.out.println("Sender peer node address: " + initTradeRequest.getSenderNodeAddress()); - System.out.println("Maker node address: " + initTradeRequest.getMakerNodeAddress()); - System.out.println("Taker node adddress: " + initTradeRequest.getTakerNodeAddress()); - System.out.println("Arbitrator node address: " + initTradeRequest.getArbitratorNodeAddress()); - // handle request as arbitrator - boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress()); + boolean isArbitrator = request.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress()); if (isArbitrator) { + + // verify this node is registered arbitrator + Mediator thisArbitrator = user.getRegisteredMediator(); + NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress(); + if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because we are not an arbitrator", sender, request.getTradeId()); + return; + } // get offer associated with trade Offer offer = null; for (Offer anOffer : offerBookService.getOffers()) { - if (anOffer.getId().equals(initTradeRequest.getTradeId())) { - offer = anOffer; - } + if (anOffer.getId().equals(request.getTradeId())) { + offer = anOffer; + } + } + if (offer == null) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because no offer is on the books", sender, request.getTradeId()); + return; + } + + // verify arbitrator is payload signer unless they are offline + // TODO (woodser): handle if payload signer differs from current arbitrator (verify signer is offline) + + // verify maker is offer owner + // TODO (woodser): maker address might change if they disconnect and reconnect, should allow maker address to differ if pubKeyRing is same ? + if (!offer.getOwnerNodeAddress().equals(request.getMakerNodeAddress())) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because maker is not offer owner", sender, request.getTradeId()); + return; } - if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling Trade trade; Optional tradeOptional = getTradeById(offer.getId()); - if (!tradeOptional.isPresent()) { - trade = new ArbitratorTrade(offer, - Coin.valueOf(initTradeRequest.getTradeAmount()), - Coin.valueOf(initTradeRequest.getTxFee()), - Coin.valueOf(initTradeRequest.getTradeFee()), - initTradeRequest.getTradePrice(), - initTradeRequest.getMakerNodeAddress(), - initTradeRequest.getTakerNodeAddress(), - initTradeRequest.getArbitratorNodeAddress(), - xmrWalletService, - getNewProcessModel(offer), - UUID.randomUUID().toString()); - initTradeAndProtocol(trade, getTradeProtocol(trade)); - tradableList.add(trade); + if (tradeOptional.isPresent()) { + trade = tradeOptional.get(); + + // verify request is from maker + if (!sender.equals(request.getMakerNodeAddress())) { + log.warn("Trade is already taken"); // TODO (woodser): need to respond with bad ack + return; + } } else { - trade = tradeOptional.get(); + + // verify request is from taker + if (!sender.equals(request.getTakerNodeAddress())) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from taker when trade is not initialized", sender, request.getTradeId()); + return; + } + + // compute expected taker fee + Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(true), Coin.valueOf(offer.getOfferPayload().getAmount())); + Coin takerFee = CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee(true)); + + // create arbitrator trade + trade = new ArbitratorTrade(offer, + Coin.valueOf(offer.getOfferPayload().getAmount()), + takerFee, + offer.getOfferPayload().getPrice(), + xmrWalletService, + getNewProcessModel(offer), + UUID.randomUUID().toString(), + request.getMakerNodeAddress(), + request.getTakerNodeAddress(), + request.getArbitratorNodeAddress()); + + // set reserve tx hash + Optional signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId()); + if (!signedOfferOptional.isPresent()) return; + SignedOffer signedOffer = signedOfferOptional.get(); + trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash()); + initTradeAndProtocol(trade, getTradeProtocol(trade)); + tradableList.add(trade); } - // TODO (woodser): do this for arbitrator? -// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); -// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); -// if (prev != null) { -// log.error("We had already an entry with uid {}", trade.getUid()); -// } - - ((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> { + ((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { if (takeOfferRequestErrorMessageHandler != null) - takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? // TODO (woodser): ensure failed trade removed }); requestPersistence(); @@ -394,7 +432,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi // handle request as maker else { - Optional openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId()); + Optional openOfferOptional = openOfferManager.getOpenOfferById(request.getTradeId()); if (!openOfferOptional.isPresent()) { return; } @@ -406,102 +444,181 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi Offer offer = openOffer.getOffer(); openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? + + // verify request is from signing arbitrator when they're online, else from selected arbitrator + if (!sender.equals(offer.getOfferPayload().getArbitratorNodeAddress())) { + boolean isSignerOnline = true; // TODO (woodser): determine if signer is online and test + if (isSignerOnline) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signing arbitrator when online", sender, request.getTradeId()); + return; + } else if (!sender.equals(openOffer.getArbitratorNodeAddress())) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from selected arbitrator when signing arbitrator is offline", sender, request.getTradeId()); + return; + } + } Trade trade; if (offer.isBuyOffer()) trade = new BuyerAsMakerTrade(offer, - Coin.valueOf(initTradeRequest.getTxFee()), - Coin.valueOf(initTradeRequest.getTradeFee()), - initTradeRequest.getMakerNodeAddress(), - initTradeRequest.getTakerNodeAddress(), - initTradeRequest.getArbitratorNodeAddress(), + Coin.valueOf(offer.getOfferPayload().getAmount()), + Coin.valueOf(offer.getOfferPayload().getMakerFee()), // TODO (woodser): this is maker fee, but Trade calls it taker fee, why not have both? + offer.getOfferPayload().getPrice(), xmrWalletService, getNewProcessModel(offer), - UUID.randomUUID().toString()); + UUID.randomUUID().toString(), + request.getMakerNodeAddress(), + request.getTakerNodeAddress(), + request.getArbitratorNodeAddress()); else trade = new SellerAsMakerTrade(offer, - Coin.valueOf(initTradeRequest.getTxFee()), - Coin.valueOf(initTradeRequest.getTradeFee()), - initTradeRequest.getMakerNodeAddress(), - initTradeRequest.getTakerNodeAddress(), - openOffer.getArbitratorNodeAddress(), + Coin.valueOf(offer.getOfferPayload().getAmount()), + Coin.valueOf(offer.getOfferPayload().getMakerFee()), + offer.getOfferPayload().getPrice(), xmrWalletService, getNewProcessModel(offer), - UUID.randomUUID().toString()); - - TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); - TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol); - if (prev != null) { - log.error("We had already an entry with uid {}", trade.getUid()); - } + UUID.randomUUID().toString(), + request.getMakerNodeAddress(), + request.getTakerNodeAddress(), + request.getArbitratorNodeAddress()); + //System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender); + //trade.setTradingPeerNodeAddress(sender); + // TODO (woodser): what if maker's address changes while offer open, or taker's address changes after multisig deposit available? need to verify and update + trade.setArbitratorPubKeyRing(user.getAcceptedMediatorByAddress(sender).getPubKeyRing()); + trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing()); + initTradeAndProtocol(trade, getTradeProtocol(trade)); + trade.getProcessModel().setReserveTxHash(offer.getOfferFeePaymentTxId()); // TODO (woodser): initialize in initTradeAndProtocol ? + trade.getProcessModel().setFrozenKeyImages(openOffer.getFrozenKeyImages()); tradableList.add(trade); - initTradeAndProtocol(trade, tradeProtocol); - ((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> { - if (takeOfferRequestErrorMessageHandler != null) + ((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } }); requestPersistence(); } } - private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) { - log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer) { + log.info("Received InitMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); try { Validator.nonEmptyStringOf(request.getTradeId()); } catch (Throwable t) { - log.warn("Invalid InitTradeRequest message " + request.toString()); + log.warn("Invalid InitMultisigRequest " + request.toString()); return; } Optional tradeOptional = getTradeById(request.getTradeId()); if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling Trade trade = tradeOptional.get(); - ((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> { - if (takeOfferRequestErrorMessageHandler != null) - takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + getTradeProtocol(trade).handleInitMultisigRequest(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } }); } + + private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) { + log.info("Received SignContractRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); - private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) { - log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid()); + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid SignContractRequest message " + request.toString()); + return; + } - try { - Validator.nonEmptyStringOf(response.getTradeId()); - } catch (Throwable t) { - log.warn("Invalid InitTradeRequest message " + response.toString()); - return; - } - - Optional tradeOptional = getTradeById(response.getTradeId()); - if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling - Trade trade = tradeOptional.get(); - ((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> { - if (takeOfferRequestErrorMessageHandler != null) - takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); - }); + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + getTradeProtocol(trade).handleSignContractRequest(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } + }); } + + private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) { + log.info("Received SignContractResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); - private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) { - log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid()); + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid SignContractResponse message " + request.toString()); + return; + } - try { - Validator.nonEmptyStringOf(multisigMessage.getTradeId()); - } catch (Throwable t) { - log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString()); - return; - } + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + ((TraderProtocol) getTradeProtocol(trade)).handleSignContractResponse(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } + }); + } + + private void handleDepositRequest(DepositRequest request, NodeAddress peer) { + log.info("Received DepositRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); - Optional tradeOptional = getTradeById(multisigMessage.getTradeId()); - if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling - Trade trade = tradeOptional.get(); - getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> { - if (takeOfferRequestErrorMessageHandler != null) - takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); - }); + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid DepositRequest message " + request.toString()); + return; + } + + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + ((ArbitratorProtocol) getTradeProtocol(trade)).handleDepositRequest(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } + }); + } + + private void handleDepositResponse(DepositResponse response, NodeAddress peer) { + log.info("Received DepositResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid()); + + try { + Validator.nonEmptyStringOf(response.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid DepositResponse message " + response.toString()); + return; + } + + Optional tradeOptional = getTradeById(response.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + ((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } + }); + } + + private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) { + log.info("Received PaymentAccountPayloadRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid PaymentAccountPayloadRequest message " + request.toString()); + return; + } + + Optional tradeOptional = getTradeById(request.getTradeId()); + if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling + Trade trade = tradeOptional.get(); + ((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer, errorMessage -> { + if (takeOfferRequestErrorMessageHandler != null) { + takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); + } + }); } private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) { @@ -523,41 +640,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi }); } - private void handleDepositTxMessage(DepositTxMessage request, NodeAddress peer) { - log.info("Received DepositTxMessage from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); - - try { - Validator.nonEmptyStringOf(request.getTradeId()); - } catch (Throwable t) { - log.warn("Invalid InitTradeRequest message " + request.toString()); - return; - } - - Optional tradeOptional = getTradeById(request.getTradeId()); - if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling - Trade trade = tradeOptional.get(); - getTradeProtocol(trade).handleDepositTxMessage(request, peer, errorMessage -> { - if (takeOfferRequestErrorMessageHandler != null) - takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); - }); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Take offer /////////////////////////////////////////////////////////////////////////////////////////// public void checkOfferAvailability(Offer offer, boolean isTakerApiUser, + String paymentAccountId, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler); + offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId), resultHandler, errorMessageHandler); } // First we check if offer is still available then we create the trade with the protocol public void onTakeOffer(Coin amount, Coin txFee, Coin takerFee, - long tradePrice, Coin fundsNeededForTrade, Offer offer, String paymentAccountId, @@ -567,8 +665,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi ErrorMessageHandler errorMessageHandler) { checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId())); - - OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser); + + OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId); offer.checkOfferAvailability(model, () -> { if (offer.getState() == Offer.State.AVAILABLE) { @@ -576,30 +674,33 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi if (offer.isBuyOffer()) { trade = new SellerAsTakerTrade(offer, amount, - txFee, takerFee, - tradePrice, - model.getPeerNodeAddress(), - P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address? - model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront + model.getTradeRequest().getTradePrice(), xmrWalletService, getNewProcessModel(offer), - UUID.randomUUID().toString()); + UUID.randomUUID().toString(), + model.getPeerNodeAddress(), + P2PService.getMyNodeAddress(), + offer.getOfferPayload().getArbitratorNodeAddress()); } else { trade = new BuyerAsTakerTrade(offer, amount, - txFee, takerFee, - tradePrice, - model.getPeerNodeAddress(), - P2PService.getMyNodeAddress(), - model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront + model.getTradeRequest().getTradePrice(), xmrWalletService, getNewProcessModel(offer), - UUID.randomUUID().toString()); + UUID.randomUUID().toString(), + model.getPeerNodeAddress(), + P2PService.getMyNodeAddress(), + offer.getOfferPayload().getArbitratorNodeAddress()); } + + trade.getProcessModel().setTradeMessage(model.getTradeRequest()); + trade.getProcessModel().setMakerSignature(model.getMakerSignature()); + trade.getProcessModel().setArbitratorNodeAddress(model.getArbitratorNodeAddress()); trade.getProcessModel().setUseSavingsWallet(useSavingsWallet); trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value); + trade.setTakerPubKeyRing(model.getPubKeyRing()); trade.setTakerPaymentAccountId(paymentAccountId); TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade); @@ -627,15 +728,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi processModelServiceProvider.getKeyRing().getPubKeyRing()); } - private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) { + private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser, String paymentAccountId) { return new OfferAvailabilityModel( offer, keyRing.getPubKeyRing(), + xmrWalletService, p2PService, user, mediatorManager, tradeStatisticsManager, - isTakerApiUser); + isTakerApiUser, + paymentAccountId, + offerUtil); } @@ -643,6 +747,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi // Complete trade /////////////////////////////////////////////////////////////////////////////////////////// + // TODO (woodser): remove this function public void onWithdrawRequest(String toAddress, Coin amount, Coin fee, @@ -651,35 +756,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi @Nullable String memo, ResultHandler resultHandler, FaultHandler faultHandler) { - int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(), - XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex(); - FutureCallback callback = new FutureCallback() { - @Override - public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) { - if (transaction != null) { - log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash()); - onTradeCompleted(trade); - trade.setState(Trade.State.WITHDRAW_COMPLETED); - getTradeProtocol(trade).onWithdrawCompleted(); - requestPersistence(); - resultHandler.handleResult(); - } - } - - @Override - public void onFailure(@NotNull Throwable t) { - t.printStackTrace(); - log.error(t.getMessage()); - faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t); - } - }; - try { - xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback); - } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) { - e.printStackTrace(); - log.error(e.getMessage()); - faultHandler.handleFault("An exception occurred at requestWithdraw.", e); - } + throw new RuntimeException("Withdraw trade funds after payout to Haveno wallet not supported"); } // If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades diff --git a/core/src/main/java/bisq/core/trade/TradeUtils.java b/core/src/main/java/bisq/core/trade/TradeUtils.java index 2f4da66098..446a2f8d14 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtils.java +++ b/core/src/main/java/bisq/core/trade/TradeUtils.java @@ -17,14 +17,254 @@ package bisq.core.trade; +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 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 java.math.BigInteger; import java.util.Objects; +import monero.daemon.MoneroDaemon; +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. + * + * TODO (woodser): combine with TradeUtil.java ? + */ public class TradeUtils { + + /** + * Address to collect Haveno trade fees. TODO (woodser): move to config constants + */ + public static String FEE_ADDRESS = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; + + /** + * Check if the arbitrator signature for an offer is valid. + * + * @param arbitrator is the possible original arbitrator + * @param signedOfferPayload is a signed offer payload + * @return true if the arbitrator's signature is valid for the offer + */ + public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Mediator arbitrator) { + + // remove arbitrator signature from signed payload + String signature = signedOfferPayload.getArbitratorSignature(); + signedOfferPayload.setArbitratorSignature(null); + + // get unsigned offer payload as json string + String unsignedOfferAsJson = Utilities.objectToJson(signedOfferPayload); + + // verify arbitrator signature + boolean isValid = true; + try { + isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), // TODO (woodser): assign isValid + unsignedOfferAsJson, + signature); + } catch (Exception e) { + isValid = false; + } + + // replace signature + signedOfferPayload.setArbitratorSignature(signature); + + // return result + return isValid; + } + + /** + * Check if the maker signature for a trade request is valid. + * + * @param request is the trade request to check + * @return true if the maker's signature is valid for the trade request + */ + public static boolean isMakerSignatureValid(InitTradeRequest request, String signature, PubKeyRing makerPubKeyRing) { + + // re-create trade request with signed fields + InitTradeRequest signedRequest = new InitTradeRequest( + request.getTradeId(), + request.getSenderNodeAddress(), + request.getPubKeyRing(), + request.getTradeAmount(), + request.getTradePrice(), + request.getTradeFee(), + request.getAccountId(), + request.getPaymentAccountId(), + request.getPaymentMethodId(), + request.getUid(), + request.getMessageVersion(), + request.getAccountAgeWitnessSignatureOfOfferId(), + request.getCurrentDate(), + request.getMakerNodeAddress(), + request.getTakerNodeAddress(), + null, + null, + null, + null, + request.getPayoutAddress(), + null + ); + + // get trade request as string + String tradeRequestAsJson = Utilities.objectToJson(signedRequest); + + // verify maker signature + try { + return Sig.verify(makerPubKeyRing.getSignaturePubKey(), + tradeRequestAsJson, + signature); + } catch (Exception e) { + return false; + } + } + + /** + * Create a transaction to reserve a trade. 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 createReserveTx(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? + + 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. + * + * @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 isReserveTx indicates if the tx is a reserve tx, which requires fee padding + */ + public static void processTradeTx(MoneroDaemon daemon, MoneroWallet wallet, String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, boolean isReserveTx) { + + // 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)); + } else if (tx.isRelayed()) { + throw new RuntimeException("Reserve tx must not be relayed"); + } + + // 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 (isReserveTx) 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()); + } + + /** + * 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 if both are AVAILABLE, otherwise null static Tuple2 getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService, diff --git a/core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java b/core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java index 5e3e40810b..f15c690b24 100644 --- a/core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java +++ b/core/src/main/java/bisq/core/trade/closed/CleanupMailboxMessages.java @@ -123,7 +123,7 @@ public class CleanupMailboxMessages { private boolean isPubKeyValid(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) { // We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer // Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already. - PubKeyRing peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing(); + PubKeyRing peersPubKeyRing = trade.getTradingPeer().getPubKeyRing(); boolean isValid = true; if (peersPubKeyRing != null && !decryptedMessageWithPubKey.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) { diff --git a/core/src/main/java/bisq/core/trade/messages/DepositRequest.java b/core/src/main/java/bisq/core/trade/messages/DepositRequest.java new file mode 100644 index 0000000000..5f3e89d940 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/DepositRequest.java @@ -0,0 +1,103 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; +import com.google.protobuf.ByteString; +import bisq.common.crypto.PubKeyRing; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class DepositRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + private final String contractSignature; + private final String depositTxHex; + private final String depositTxKey; + + public DepositRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + String contractSignature, + String depositTxHex, + String depositTxKey) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.contractSignature = contractSignature; + this.depositTxHex = depositTxHex; + this.depositTxKey = depositTxKey; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.DepositRequest.Builder builder = protobuf.DepositRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid) + .setContractSignature(contractSignature) + .setDepositTxHex(depositTxHex) + .setDepositTxKey(depositTxKey); + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setDepositRequest(builder).build(); + } + + public static DepositRequest fromProto(protobuf.DepositRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new DepositRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + proto.getContractSignature(), + proto.getDepositTxHex(), + proto.getDepositTxKey()); + } + + @Override + public String toString() { + return "DepositRequest {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n contractSignature=" + contractSignature + + ",\n depositTxHex='" + depositTxHex + + ",\n depositTxKey='" + depositTxKey + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java b/core/src/main/java/bisq/core/trade/messages/DepositResponse.java similarity index 73% rename from core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java rename to core/src/main/java/bisq/core/trade/messages/DepositResponse.java index 0a2b63d1f2..bd30c798ac 100644 --- a/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigRequest.java +++ b/core/src/main/java/bisq/core/trade/messages/DepositResponse.java @@ -21,7 +21,7 @@ import bisq.core.proto.CoreProtoResolver; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; - +import com.google.protobuf.ByteString; import bisq.common.crypto.PubKeyRing; import lombok.EqualsAndHashCode; @@ -29,18 +29,21 @@ import lombok.Value; @EqualsAndHashCode(callSuper = true) @Value -public final class MakerReadyToFundMultisigRequest extends TradeMessage implements DirectMessage { +public final class DepositResponse extends TradeMessage implements DirectMessage { private final NodeAddress senderNodeAddress; private final PubKeyRing pubKeyRing; + private final long currentDate; - public MakerReadyToFundMultisigRequest(String tradeId, + public DepositResponse(String tradeId, NodeAddress senderNodeAddress, PubKeyRing pubKeyRing, String uid, - int messageVersion) { + int messageVersion, + long currentDate) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; } @@ -50,30 +53,33 @@ public final class MakerReadyToFundMultisigRequest extends TradeMessage implemen @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { - protobuf.MakerReadyToFundMultisigRequest.Builder builder = protobuf.MakerReadyToFundMultisigRequest.newBuilder() + protobuf.DepositResponse.Builder builder = protobuf.DepositResponse.newBuilder() .setTradeId(tradeId) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setPubKeyRing(pubKeyRing.toProtoMessage()) .setUid(uid); + builder.setCurrentDate(currentDate); - return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigRequest(builder).build(); + return getNetworkEnvelopeBuilder().setDepositResponse(builder).build(); } - public static MakerReadyToFundMultisigRequest fromProto(protobuf.MakerReadyToFundMultisigRequest proto, + public static DepositResponse fromProto(protobuf.DepositResponse proto, CoreProtoResolver coreProtoResolver, int messageVersion) { - return new MakerReadyToFundMultisigRequest(proto.getTradeId(), + return new DepositResponse(proto.getTradeId(), NodeAddress.fromProto(proto.getSenderNodeAddress()), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getUid(), - messageVersion); + messageVersion, + proto.getCurrentDate()); } @Override public String toString() { - return "MakerReadyToFundMultisigRequest{" + + return "DepositResponse {" + "\n senderNodeAddress=" + senderNodeAddress + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java b/core/src/main/java/bisq/core/trade/messages/InitMultisigRequest.java similarity index 88% rename from core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java rename to core/src/main/java/bisq/core/trade/messages/InitMultisigRequest.java index a1c3a12300..4bec8b8ccc 100644 --- a/core/src/main/java/bisq/core/trade/messages/InitMultisigMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/InitMultisigRequest.java @@ -34,7 +34,7 @@ import javax.annotation.Nullable; @EqualsAndHashCode(callSuper = true) @Value -public final class InitMultisigMessage extends TradeMessage implements DirectMessage { +public final class InitMultisigRequest extends TradeMessage implements DirectMessage { private final NodeAddress senderNodeAddress; private final PubKeyRing pubKeyRing; private final long currentDate; @@ -43,7 +43,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes @Nullable private final String madeMultisigHex; - public InitMultisigMessage(String tradeId, + public InitMultisigRequest(String tradeId, NodeAddress senderNodeAddress, PubKeyRing pubKeyRing, String uid, @@ -66,7 +66,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { - protobuf.InitMultisigMessage.Builder builder = protobuf.InitMultisigMessage.newBuilder() + protobuf.InitMultisigRequest.Builder builder = protobuf.InitMultisigRequest.newBuilder() .setTradeId(tradeId) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setPubKeyRing(pubKeyRing.toProtoMessage()) @@ -77,13 +77,13 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes builder.setCurrentDate(currentDate); - return getNetworkEnvelopeBuilder().setInitMultisigMessage(builder).build(); + return getNetworkEnvelopeBuilder().setInitMultisigRequest(builder).build(); } - public static InitMultisigMessage fromProto(protobuf.InitMultisigMessage proto, + public static InitMultisigRequest fromProto(protobuf.InitMultisigRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) { - return new InitMultisigMessage(proto.getTradeId(), + return new InitMultisigRequest(proto.getTradeId(), NodeAddress.fromProto(proto.getSenderNodeAddress()), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getUid(), @@ -95,7 +95,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes @Override public String toString() { - return "MultisigMessage {" + + return "InitMultisigRequest {" + "\n senderNodeAddress=" + senderNodeAddress + ",\n pubKeyRing=" + pubKeyRing + ",\n currentDate=" + currentDate + diff --git a/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java b/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java index c3bce4cc77..cc7b253057 100644 --- a/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java +++ b/core/src/main/java/bisq/core/trade/messages/InitTradeRequest.java @@ -17,7 +17,6 @@ package bisq.core.trade.messages; -import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; import bisq.network.p2p.DirectMessage; @@ -42,59 +41,73 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag private final NodeAddress senderNodeAddress; private final long tradeAmount; private final long tradePrice; - private final long txFee; private final long tradeFee; - private final String payoutAddressString; - private final PaymentAccountPayload paymentAccountPayload; - private final PubKeyRing pubKeyRing; private final String accountId; - @Nullable - private final String tradeFeeTxId; - private final NodeAddress arbitratorNodeAddress; + private final String paymentAccountId; + private final String paymentMethodId; + private final PubKeyRing pubKeyRing; // added in v 0.6. can be null if we trade with an older peer @Nullable private final byte[] accountAgeWitnessSignatureOfOfferId; private final long currentDate; - // added for XMR integration - private final NodeAddress takerNodeAddress; + // XMR integration private final NodeAddress makerNodeAddress; + private final NodeAddress takerNodeAddress; + @Nullable + private final NodeAddress arbitratorNodeAddress; + @Nullable + private final String reserveTxHash; + @Nullable + private final String reserveTxHex; + @Nullable + private final String reserveTxKey; + @Nullable + private final String payoutAddress; + @Nullable + private final String makerSignature; public InitTradeRequest(String tradeId, NodeAddress senderNodeAddress, PubKeyRing pubKeyRing, long tradeAmount, long tradePrice, - long txFee, long tradeFee, - String payoutAddressString, - PaymentAccountPayload paymentAccountPayload, String accountId, - String tradeFeeTxId, + String paymentAccountId, + String paymentMethodId, String uid, int messageVersion, @Nullable byte[] accountAgeWitnessSignatureOfOfferId, long currentDate, - NodeAddress takerNodeAddress, NodeAddress makerNodeAddress, - NodeAddress arbitratorNodeAddress) { + NodeAddress takerNodeAddress, + NodeAddress arbitratorNodeAddress, + @Nullable String reserveTxHash, + @Nullable String reserveTxHex, + @Nullable String reserveTxKey, + @Nullable String payoutAddress, + @Nullable String makerSignature) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.pubKeyRing = pubKeyRing; - this.paymentAccountPayload = paymentAccountPayload; this.tradeAmount = tradeAmount; this.tradePrice = tradePrice; - this.txFee = txFee; this.tradeFee = tradeFee; - this.payoutAddressString = payoutAddressString; this.accountId = accountId; - this.tradeFeeTxId = tradeFeeTxId; + this.paymentAccountId = paymentAccountId; + this.paymentMethodId = paymentMethodId; this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId; this.currentDate = currentDate; - this.takerNodeAddress = takerNodeAddress; this.makerNodeAddress = makerNodeAddress; + this.takerNodeAddress = takerNodeAddress; this.arbitratorNodeAddress = arbitratorNodeAddress; + this.reserveTxHash = reserveTxHash; + this.reserveTxHex = reserveTxHex; + this.reserveTxKey = reserveTxKey; + this.payoutAddress = payoutAddress; + this.makerSignature = makerSignature; } @@ -109,19 +122,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setTakerNodeAddress(takerNodeAddress.toProtoMessage()) .setMakerNodeAddress(makerNodeAddress.toProtoMessage()) - .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()) .setTradeAmount(tradeAmount) .setTradePrice(tradePrice) - .setTxFee(txFee) .setTradeFee(tradeFee) - .setPayoutAddressString(payoutAddressString) .setPubKeyRing(pubKeyRing.toProtoMessage()) - .setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage()) + .setPaymentAccountId(paymentAccountId) + .setPaymentMethodId(paymentMethodId) .setAccountId(accountId) .setUid(uid); - Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId)); + Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())); + Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); + Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex)); + Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey)); + Optional.ofNullable(payoutAddress).ifPresent(e -> builder.setPayoutAddress(payoutAddress)); Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e))); + Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); builder.setCurrentDate(currentDate); return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build(); @@ -135,19 +151,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getTradeAmount(), proto.getTradePrice(), - proto.getTxFee(), proto.getTradeFee(), - proto.getPayoutAddressString(), - coreProtoResolver.fromProto(proto.getPaymentAccountPayload()), proto.getAccountId(), - ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()), + proto.getPaymentAccountId(), + proto.getPaymentMethodId(), proto.getUid(), messageVersion, ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()), proto.getCurrentDate(), - NodeAddress.fromProto(proto.getTakerNodeAddress()), NodeAddress.fromProto(proto.getMakerNodeAddress()), - NodeAddress.fromProto(proto.getArbitratorNodeAddress())); + NodeAddress.fromProto(proto.getTakerNodeAddress()), + proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, + ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()), + ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()), + ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()), + ProtoUtil.stringOrNullFromProto(proto.getPayoutAddress()), + ProtoUtil.stringOrNullFromProto(proto.getMakerSignature())); } @Override @@ -156,16 +175,19 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag "\n senderNodeAddress=" + senderNodeAddress + ",\n tradeAmount=" + tradeAmount + ",\n tradePrice=" + tradePrice + - ",\n txFee=" + txFee + - ",\n takerFee=" + tradeFee + - ",\n payoutAddressString='" + payoutAddressString + '\'' + + ",\n tradeFee=" + tradeFee + ",\n pubKeyRing=" + pubKeyRing + - ",\n paymentAccountPayload=" + paymentAccountPayload + - ",\n paymentAccountPayload='" + accountId + '\'' + - ",\n takerFeeTxId='" + tradeFeeTxId + '\'' + + ",\n accountId='" + accountId + '\'' + + ",\n paymentAccountId=" + paymentAccountId + + ",\n paymentMethodId=" + paymentMethodId + ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + ",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) + ",\n currentDate=" + currentDate + + ",\n reserveTxHash=" + reserveTxHash + + ",\n reserveTxHex=" + reserveTxHex + + ",\n reserveTxKey=" + reserveTxKey + + ",\n payoutAddress=" + payoutAddress + + ",\n makerSignature=" + makerSignature + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java b/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java deleted file mode 100644 index a4bc7c4fd5..0000000000 --- a/core/src/main/java/bisq/core/trade/messages/MakerReadyToFundMultisigResponse.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.messages; - -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.proto.CoreProtoResolver; - -import bisq.network.p2p.DirectMessage; - -import java.util.Optional; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Value; - -import javax.annotation.Nullable; - -@EqualsAndHashCode(callSuper = true) -@Value -public class MakerReadyToFundMultisigResponse extends TradeMessage implements DirectMessage { - @Getter - private final boolean isMakerReadyToFundMultisig; - @Getter - @Nullable - private final String makerContractAsJson; - @Getter - @Nullable - private final String makerContractSignature; - @Getter - @Nullable - private final String makerPayoutAddressString; - @Getter - @Nullable - private final PaymentAccountPayload makerPaymentAccountPayload; - @Getter - @Nullable - private final String makerAccountId; - @Getter - private final long currentDate; - - public MakerReadyToFundMultisigResponse(String tradeId, - boolean isMakerReadyToFundMultisig, - String uid, - int messageVersion, - String makerContractAsJson, - String makerContractSignature, - String makerPayoutAddressString, - PaymentAccountPayload makerPaymentAccountPayload, - String makerAccountId, - long currentDate) { - super(messageVersion, tradeId, uid); - this.isMakerReadyToFundMultisig = isMakerReadyToFundMultisig; - this.makerContractAsJson = makerContractAsJson; - this.makerContractSignature = makerContractSignature; - this.makerPayoutAddressString = makerPayoutAddressString; - this.makerPaymentAccountPayload = makerPaymentAccountPayload; - this.makerAccountId = makerAccountId; - this.currentDate = currentDate; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { - protobuf.MakerReadyToFundMultisigResponse.Builder builder = protobuf.MakerReadyToFundMultisigResponse.newBuilder() - .setTradeId(tradeId) - .setIsMakerReadyToFundMultisig(isMakerReadyToFundMultisig) - .setCurrentDate(currentDate); - - Optional.ofNullable(makerContractAsJson).ifPresent(e -> builder.setMakerContractAsJson(makerContractAsJson)); - Optional.ofNullable(makerContractSignature).ifPresent(e -> builder.setMakerContractSignature(makerContractSignature)); - Optional.ofNullable(makerPayoutAddressString).ifPresent(e -> builder.setMakerPayoutAddressString(makerPayoutAddressString)); - Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())); - Optional.ofNullable(makerAccountId).ifPresent(e -> builder.setMakerAccountId(makerAccountId)); - - return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigResponse(builder).build(); - } - - public static MakerReadyToFundMultisigResponse fromProto(protobuf.MakerReadyToFundMultisigResponse proto, - CoreProtoResolver coreProtoResolver, - int messageVersion) { - return new MakerReadyToFundMultisigResponse(proto.getTradeId(), - proto.getIsMakerReadyToFundMultisig(), - proto.getUid(), - messageVersion, - proto.getMakerContractAsJson(), - proto.getMakerContractSignature(), - proto.getMakerPayoutAddressString(), - coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()), - proto.getMakerAccountId(), - proto.getCurrentDate()); - } - - @Override - public String toString() { - return "MakerReadyToFundMultisigResponse{" + - "\n isMakerReadyToFundMultisig=" + isMakerReadyToFundMultisig + - "\n} " + super.toString(); - } -} diff --git a/core/src/main/java/bisq/core/trade/messages/PaymentAccountPayloadRequest.java b/core/src/main/java/bisq/core/trade/messages/PaymentAccountPayloadRequest.java new file mode 100644 index 0000000000..faa9502711 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/PaymentAccountPayloadRequest.java @@ -0,0 +1,92 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; +import com.google.protobuf.ByteString; +import bisq.common.crypto.PubKeyRing; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class PaymentAccountPayloadRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + private final PaymentAccountPayload paymentAccountPayload; + + public PaymentAccountPayloadRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + PaymentAccountPayload paymentAccountPayload) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.paymentAccountPayload = paymentAccountPayload; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.PaymentAccountPayloadRequest.Builder builder = protobuf.PaymentAccountPayloadRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid) + .setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage()); + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setPaymentAccountPayloadRequest(builder).build(); + } + + public static PaymentAccountPayloadRequest fromProto(protobuf.PaymentAccountPayloadRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new PaymentAccountPayloadRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + coreProtoResolver.fromProto(proto.getPaymentAccountPayload())); + } + + @Override + public String toString() { + return "PaymentAccountPayloadRequest {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n paymentAccountPayload=" + paymentAccountPayload + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/SignContractRequest.java b/core/src/main/java/bisq/core/trade/messages/SignContractRequest.java new file mode 100644 index 0000000000..985528cc8f --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/SignContractRequest.java @@ -0,0 +1,110 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; +import com.google.protobuf.ByteString; +import bisq.common.crypto.PubKeyRing; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class SignContractRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + private final String accountId; + private final byte[] paymentAccountPayloadHash; + private final String payoutAddress; + private final String depositTxHash; + + public SignContractRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + String accountId, + byte[] paymentAccountPayloadHash, + String payoutAddress, + String depositTxHash) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.accountId = accountId; + this.paymentAccountPayloadHash = paymentAccountPayloadHash; + this.payoutAddress = payoutAddress; + this.depositTxHash = depositTxHash; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.SignContractRequest.Builder builder = protobuf.SignContractRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid) + .setAccountId(accountId) + .setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash)) + .setPayoutAddress(payoutAddress) + .setDepositTxHash(depositTxHash); + + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setSignContractRequest(builder).build(); + } + + public static SignContractRequest fromProto(protobuf.SignContractRequest proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new SignContractRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + proto.getAccountId(), + proto.getPaymentAccountPayloadHash().toByteArray(), + proto.getPayoutAddress(), + proto.getDepositTxHash()); + } + + @Override + public String toString() { + return "SignContractRequest {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n accountId=" + accountId + + ",\n paymentAccountPayloadHash='" + paymentAccountPayloadHash + + ",\n payoutAddress='" + payoutAddress + + ",\n depositTxHash='" + depositTxHash + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java b/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java new file mode 100644 index 0000000000..800ba3cf68 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java @@ -0,0 +1,98 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; + +import bisq.common.crypto.PubKeyRing; +import bisq.common.proto.ProtoUtil; + +import java.util.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +import javax.annotation.Nullable; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class SignContractResponse extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + private final long currentDate; + private final String contractSignature; + + public SignContractResponse(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + int messageVersion, + long currentDate, + String contractSignature) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + this.currentDate = currentDate; + this.contractSignature = contractSignature; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.SignContractResponse.Builder builder = protobuf.SignContractResponse.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid); + + Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(contractSignature)); + + builder.setCurrentDate(currentDate); + + return getNetworkEnvelopeBuilder().setSignContractResponse(builder).build(); + } + + public static SignContractResponse fromProto(protobuf.SignContractResponse proto, + CoreProtoResolver coreProtoResolver, + int messageVersion) { + return new SignContractResponse(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion, + proto.getCurrentDate(), + ProtoUtil.stringOrNullFromProto(proto.getContractSignature())); + } + + @Override + public String toString() { + return "SignContractResponse {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + ",\n currentDate=" + currentDate + + ",\n contractSignature='" + contractSignature + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java index f20d7c9221..0c0c422b7a 100644 --- a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java +++ b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigRequest.java @@ -89,7 +89,7 @@ public final class UpdateMultisigRequest extends TradeMessage implements DirectM @Override public String toString() { - return "MultisigMessage {" + + return "UpdateMultisigRequest {" + "\n senderNodeAddress=" + senderNodeAddress + ",\n pubKeyRing=" + pubKeyRing + ",\n currentDate=" + currentDate + diff --git a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java index 3acfcc5c69..d22027f256 100644 --- a/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java +++ b/core/src/main/java/bisq/core/trade/messages/UpdateMultisigResponse.java @@ -89,7 +89,7 @@ public final class UpdateMultisigResponse extends TradeMessage implements Direct @Override public String toString() { - return "MultisigMessage {" + + return "UpdateMultisigResponse {" + "\n senderNodeAddress=" + senderNodeAddress + ",\n pubKeyRing=" + pubKeyRing + ",\n currentDate=" + currentDate + diff --git a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java index 5bda7e48dc..934b2c3d65 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -2,10 +2,19 @@ package bisq.core.trade.protocol; import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.DepositRequest; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeRequestToMakerIfFromTaker; +import bisq.core.trade.protocol.tasks.ProcessDepositRequest; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; +import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; +import bisq.core.trade.protocol.tasks.ArbitratorSendsInitMultisigRequestsIfFundsReserved; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; - +import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; +import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; @@ -15,73 +24,91 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class ArbitratorProtocol extends DisputeProtocol { - private final ArbitratorTrade arbitratorTrade; - public ArbitratorProtocol(ArbitratorTrade trade) { super(trade); - this.arbitratorTrade = trade; } /////////////////////////////////////////////////////////////////////////////////////////// // Incoming messages /////////////////////////////////////////////////////////////////////////////////////////// - // TODO: new implementation for MakerProtocol -// private void handle(InitTradeRequest message, NodeAddress peer) { -// expect(phase(Trade.Phase.INIT) -// .with(message) -// .from(peer)) -// .setup(tasks(ProcessInitTradeRequest.class, -// ApplyFilter.class, -// VerifyPeersAccountAgeWitness.class, -// MakerVerifyTakerFeePayment.class, -// MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig -// MakerSendsReadyToFundMultisigResponse.class) -// .withTimeout(30)) -// .executeTasks(); -// } - public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler - expect(phase(Trade.Phase.INIT) - .with(message) - .from(peer)) - .setup(tasks( - //ApplyFilter.class, - ProcessInitTradeRequest.class)) - .executeTasks(); + processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set + //processModel.setTempTradingPeerNodeAddress(peer); + expect(phase(Trade.Phase.INIT) + .with(message) + .from(peer)) + .setup(tasks( + ApplyFilter.class, + ProcessInitTradeRequest.class, + ArbitratorProcessesReserveTx.class, + ArbitratorSendsInitTradeRequestToMakerIfFromTaker.class, + ArbitratorSendsInitMultisigRequestsIfFundsReserved.class)) + .executeTasks(); } - + @Override - public void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) { - throw new RuntimeException("Not implemented"); + public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("ArbitratorProtocol.handleInitMultisigRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT) + .with(request) + .from(sender)) + .setup(tasks( + ProcessInitMultisigRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("ArbitratorProtocol.handleSignContractRequest()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed + expect(anyPhase(Trade.Phase.INIT) + .with(message) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessSignContractRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) + .executeTasks(); + } + + public void handleDepositRequest(DepositRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("ArbitratorProtocol.handleDepositRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT) + .with(request) + .from(sender)) + .setup(tasks( + ProcessDepositRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) + .executeTasks(); } - -// @Override -// public void handleTakeOfferRequest(InputsForDepositTxRequest message, -// NodeAddress peer, -// ErrorMessageHandler errorMessageHandler) { -// expect(phase(Trade.Phase.INIT) -// .with(message) -// .from(peer)) -// .setup(tasks( -// MakerProcessesInputsForDepositTxRequest.class, -// ApplyFilter.class, -// VerifyPeersAccountAgeWitness.class, -// getVerifyPeersFeePaymentClass(), -// MakerSetsLockTime.class, -// MakerCreateAndSignContract.class, -// BuyerAsMakerCreatesAndSignsDepositTx.class, -// BuyerSetupDepositTxListener.class, -// BuyerAsMakerSendsInputsForDepositTxResponse.class). -// using(new TradeTaskRunner(trade, -// () -> handleTaskRunnerSuccess(message), -// errorMessage -> { -// errorMessageHandler.handleErrorMessage(errorMessage); -// handleTaskRunnerFault(message, errorMessage); -// })) -// .withTimeout(30)) -// .executeTasks(); -// } /////////////////////////////////////////////////////////////////////////////////////////// // Message dispatcher diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index f50bd8e40e..c73036fb6e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -20,27 +20,28 @@ package bisq.core.trade.protocol; import bisq.core.trade.BuyerAsMakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.PayoutTxPublishedMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; +import bisq.core.trade.protocol.tasks.ProcessDepositResponse; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; +import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; +import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse; import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest; -import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse; -import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener; -import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.util.Validator; @@ -73,6 +74,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol // Incoming messages Take offer process /////////////////////////////////////////////////////////////////////////////////////////// + @Override protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) { expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED) .with(message) @@ -138,74 +140,130 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol .from(peer)) .setup(tasks( ProcessInitTradeRequest.class, - ApplyFilter.class, - VerifyPeersAccountAgeWitness.class, - MakerVerifyTakerFeePayment.class, - MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig - MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx - MakerSendsReadyToFundMultisigResponse.class). + //ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here + //VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee + //MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this + MakerRemovesOpenOffer.class). using(new TradeTaskRunner(trade, () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); + handleTaskRunnerSuccess(peer, message); }, errorMessage -> { errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); + handleTaskRunnerFault(peer, message, errorMessage); })) .withTimeout(30)) .executeTasks(); } - + @Override - public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, - NodeAddress sender, - ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - processModel.setTempTradingPeerNodeAddress(sender); - - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed + expect(anyPhase(Trade.Phase.INIT) + .with(request) + .from(sender)) + .setup(tasks( + ProcessInitMultisigRequest.class, + SendSignContractRequestAfterMultisig.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT) .with(message) .from(sender)) .setup(tasks( - MakerSendsReadyToFundMultisigResponse.class). - using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) + // TODO (woodser): validate request + ProcessSignContractRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) .executeTasks(); } @Override - public void handleDepositTxMessage(DepositTxMessage message, - NodeAddress sender, - ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - processModel.setTempTradingPeerNodeAddress(sender); - - // TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed + expect(anyPhase(Trade.Phase.INIT) .with(message) .from(sender)) .setup(tasks( - MakerVerifyTakerDepositTx.class, - MakerCreateAndSignContract.class, - MakerCreateAndPublishDepositTx.class, - MakerSetupDepositTxsListener.class). - using(new TradeTaskRunner(trade, - () -> handleTaskRunnerSuccess(message), - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - }))) + // TODO (woodser): validate request + ProcessSignContractResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleDepositResponse()"); + Validator.checkTradeId(processModel.getOfferId(), response); + processModel.setTradeMessage(response); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(response) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessDepositResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, response); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, response, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(request) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessPaymentAccountPayloadRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) .executeTasks(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 6eecc420ff..8dec9ae9d7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -22,15 +22,22 @@ import bisq.core.offer.Offer; import bisq.core.trade.BuyerAsTakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.PayoutTxPublishedMessage; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage; +import bisq.core.trade.protocol.tasks.ProcessDepositResponse; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; +import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; +import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx; @@ -41,43 +48,24 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.FundMultisig; -import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages; -import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests; -import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest; -import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; +import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds; +import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; import bisq.core.util.Validator; - import bisq.network.p2p.NodeAddress; -import bisq.common.Timer; -import bisq.common.UserThread; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; -import java.math.BigInteger; - import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; - - -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroTxWallet; -import monero.wallet.model.MoneroWalletListener; - // TODO (woodser): remove unused request handling @Slf4j public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol { - private ResultHandler takeOfferListener; - private Timer initDepositTimer; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -87,7 +75,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol super(trade); Offer offer = checkNotNull(trade.getOffer()); - processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); // TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase? } @@ -107,8 +95,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol .from(trade.getTradingPeerNodeAddress())) .setup(tasks( ApplyFilter.class, - TakerVerifyMakerFeePayment.class, - TakerSendInitTradeRequests.class) + TakerReservesTradeFunds.class, + TakerSendsInitTradeRequestToArbitrator.class) .withTimeout(30)) .executeTasks(); } @@ -125,7 +113,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol .setup(tasks(TakerProcessesInputsForDepositTxResponse.class, ApplyFilter.class, VerifyPeersAccountAgeWitness.class, - TakerVerifyAndSignContract.class, + //TakerVerifyAndSignContract.class, TakerPublishFeeTx.class, BuyerAsTakerSignsDepositTx.class, BuyerSetupDepositTxListener.class, @@ -134,6 +122,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol .executeTasks(); } + @Override protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) { expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED) .with(message) @@ -196,188 +185,123 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol } /////////////////////////////////////////////////////////////////////////////////////////// - // MakerProtocol + // TakerProtocol /////////////////////////////////////////////////////////////////////////////////////////// // TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance - - /////////////////////////////////////////////////////////////////////////////////////////// - // Incoming message handling - /////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { - System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()"); - System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig()); - processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this - if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling - processModel.setTradeMessage(message); - if (message.isMakerReadyToFundMultisig()) { - createAndFundMultisig(message, takeOfferListener); - } else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests - reserveTrade(message, takeOfferListener); - } - } - - private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) { - System.out.println("BuyerAsTakerProtocol.reserveTrade()"); - - // define wallet listener which initiates multisig deposit when trade fee tx unlocked - // TODO (woodser): this needs run for reserved trades when client is opened - // TODO (woodser): test initiating multisig when maker offline - MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet(); - MoneroWalletListener fundMultisigListener = new MoneroWalletListener() { - public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { - - // get updated offer fee tx - MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId()); - - // check if tx is unlocked - if (Boolean.FALSE.equals(feeTx.isLocked())) { - System.out.println("TRADE FEE TX IS UNLOCKED!!!"); - - // stop listening to wallet - wallet.removeListener(this); - - // periodically request multisig deposit until successful - Runnable requestMultisigDeposit = new Runnable() { - @Override - public void run() { - if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler); - else initDepositTimer.stop(); - } - }; - UserThread.execute(requestMultisigDeposit); - initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60); - } - } - }; - - // run pipeline to publish trade fee tx - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - TakerCreateFeeTx.class, - TakerVerifyMakerFeePayment.class, - //TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx - TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade? - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later? - wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - - private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) { - System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()"); - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - TakerVerifyMakerFeePayment.class, - TakerSendReadyToFundMultisigRequest.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - - private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) { - System.out.println("TakerProtocolBase.createAndFundMultisig()"); - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - TakerVerifyMakerFeePayment.class, - TakerVerifyAndSignContract.class, - TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - - @Override - public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { - System.out.println("TakerProtocolBase.handleMultisigMessage()"); - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) - .with(message) + public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT) + .with(request) .from(sender)) .setup(tasks( - ProcessInitMultisigMessage.class) + ProcessInitMultisigRequest.class, + SendSignContractRequestAfterMultisig.class) .using(new TradeTaskRunner(trade, () -> { System.out.println("handle multisig pipeline completed successfully!"); - handleTaskRunnerSuccess(message); - if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) { - processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time - fundMultisig(message, takeOfferListener); - } + handleTaskRunnerSuccess(sender, request); }, errorMessage -> { System.out.println("error in handle multisig pipeline!!!: " + errorMessage); errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - takeOfferListener.handleResult(); - }))) + handleTaskRunnerFault(sender, request, errorMessage); + })) + .withTimeout(30)) .executeTasks(); } - - private void fundMultisig(InitMultisigMessage message, ResultHandler handler) { - System.out.println("TakerProtocolBase.fundMultisig()"); - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - FundMultisig.class). // will receive MultisigMessage in response - using(new TradeTaskRunner(trade, - () -> { - System.out.println("MULTISIG WALLET FUNDED!!!!"); - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - + @Override - public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { - System.out.println("TakerProtocolBase.handleDepositTxMessage()"); - processModel.setTradeMessage(message); - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) - .with(message) - .from(sender)) - .setup(tasks( - TakerProcessesMakerDepositTxMessage.class, - TakerSetupDepositTxsListener.class). - using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); + public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handleSignContractRequest()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT) + .with(message) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessSignContractRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handleSignContractResponse()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT) + .with(message) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessSignContractResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handleDepositResponse()"); + Validator.checkTradeId(processModel.getOfferId(), response); + processModel.setTradeMessage(response); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(response) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessDepositResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, response); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, response, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(request) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessPaymentAccountPayloadRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) + .executeTasks(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java index f7fc3ceb3b..01b8895e5f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -24,12 +24,12 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.PayoutTxPublishedMessage; 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.BuyerSetupDepositTxListener; import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener; import bisq.network.p2p.NodeAddress; @@ -57,16 +57,15 @@ public abstract class BuyerProtocol extends DisputeProtocol { @Override protected void onInitialized() { super.onInitialized(); - // We get called the constructor with any possible state and phase. As we don't want to log an error for such - // cases we use the alternative 'given' method instead of 'expect'. - given(phase(Trade.Phase.TAKER_FEE_PUBLISHED) + + given(phase(Trade.Phase.DEPOSIT_PUBLISHED) .with(BuyerEvent.STARTUP)) - .setup(tasks(BuyerSetupDepositTxListener.class)) + .setup(tasks(SetupDepositTxsListener.class)) .executeTasks(); given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED) .with(BuyerEvent.STARTUP)) - .setup(tasks(BuyerSetupPayoutTxListener.class)) + .setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup? .executeTasks(); given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED) @@ -166,10 +165,10 @@ public abstract class BuyerProtocol extends DisputeProtocol { BuyerProcessPayoutTxPublishedMessage.class) .using(new TradeTaskRunner(trade, () -> { - handleTaskRunnerSuccess(message); + handleTaskRunnerSuccess(peer, message); }, errorMessage -> { - handleTaskRunnerFault(message, errorMessage); + handleTaskRunnerFault(peer, message, errorMessage); }))) .executeTasks(); } diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java index d8e0e71721..00cdc4b8d5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java @@ -65,7 +65,7 @@ public abstract class DisputeProtocol extends TradeProtocol { Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED) .with(event) - .preCondition(trade.getProcessModel().getTradingPeer().getMediatedPayoutTxSignature() == null, + .preCondition(trade.getTradingPeer().getMediatedPayoutTxSignature() == null, () -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer.")) .preCondition(trade.getPayoutTx() == null, () -> errorMessageHandler.handleErrorMessage("Payout tx is already published."))) diff --git a/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java b/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java index 2d79f9ed7e..a1bde56df7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/FluentProtocol.java @@ -98,7 +98,7 @@ public class FluentProtocol { NodeAddress peer = condition.getPeer(); if (peer != null) { - tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); + tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); // TODO (woodser): node has multiple peers (arbitrator and maker or taker), but fluent protocol assumes only one tradeProtocol.processModel.getTradeManager().requestPersistence(); } @@ -108,7 +108,7 @@ public class FluentProtocol { tradeProtocol.processModel.getTradeManager().requestPersistence(); } - TradeTaskRunner taskRunner = setup.getTaskRunner(message, condition.getEvent()); + TradeTaskRunner taskRunner = setup.getTaskRunner(peer, message, condition.getEvent()); taskRunner.addTasks(setup.getTasks()); taskRunner.run(); return this; @@ -366,12 +366,12 @@ public class FluentProtocol { return this; } - public TradeTaskRunner getTaskRunner(@Nullable TradeMessage message, @Nullable Event event) { + public TradeTaskRunner getTaskRunner(NodeAddress sender, @Nullable TradeMessage message, @Nullable Event event) { if (taskRunner == null) { if (message != null) { taskRunner = new TradeTaskRunner(trade, - () -> tradeProtocol.handleTaskRunnerSuccess(message), - errorMessage -> tradeProtocol.handleTaskRunnerFault(message, errorMessage)); + () -> tradeProtocol.handleTaskRunnerSuccess(sender, message), + errorMessage -> tradeProtocol.handleTaskRunnerFault(sender, message, errorMessage)); } else if (event != null) { taskRunner = new TradeTaskRunner(trade, () -> tradeProtocol.handleTaskRunnerSuccess(event), diff --git a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java index c56750ca17..6e683b861a 100644 --- a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java @@ -19,13 +19,11 @@ package bisq.core.trade.protocol; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; -public interface MakerProtocol { +public interface MakerProtocol extends TraderProtocol { void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler); - void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler); } diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index 5ea0900694..fecf1e72f3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -22,10 +22,12 @@ import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.btc.wallet.XmrWalletService; import bisq.core.dao.DaoFacade; import bisq.core.filter.FilterManager; import bisq.core.network.MessageState; import bisq.core.offer.Offer; +import bisq.core.offer.OfferPayload.Direction; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentAccountPayload; @@ -60,7 +62,7 @@ import org.bitcoinj.core.Transaction; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; - +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -90,14 +92,6 @@ public class ProcessModel implements Model, PersistablePayload { transient private ProcessModelServiceProvider provider; transient private TradeManager tradeManager; transient private Offer offer; - @Setter - transient private Trade trade; - - // Transient/Mutable - @Getter - transient private MoneroTxWallet takeOfferFeeTx; - @Setter - transient private TradeMessage tradeMessage; // Added in v1.2.0 @Setter @@ -114,7 +108,7 @@ public class ProcessModel implements Model, PersistablePayload { transient private ObjectProperty depositTxMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED); @Setter @Getter - transient private Transaction depositTx; + transient private Transaction depositTx; // TODO (woodser): remove and rename depositTxBtc with depositTx // Persistable Immutable (private setter only used by PB method) private TradingPeer maker = new TradingPeer(); @@ -153,7 +147,7 @@ public class ProcessModel implements Model, PersistablePayload { // After successful verified we copy that over to the trade.tradingPeerAddress @Nullable @Setter - private NodeAddress tempTradingPeerNodeAddress; + private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely // Added in v.1.1.6 @Nullable @@ -165,10 +159,33 @@ public class ProcessModel implements Model, PersistablePayload { private long sellerPayoutAmountFromMediation; // Added for XMR integration + @Getter + transient private MoneroTxWallet takeOfferFeeTx; // TODO (woodser): remove + @Setter + transient private TradeMessage tradeMessage; + @Getter + @Setter + private String makerSignature; + @Getter + @Setter + private NodeAddress arbitratorNodeAddress; @Nullable @Getter @Setter - private String preparedMultisigHex; + transient private MoneroTxWallet reserveTx; + @Setter + @Getter + private String reserveTxHash; + @Setter + @Getter + private List frozenKeyImages = new ArrayList<>(); + @Getter + @Setter + transient private MoneroTxWallet depositTxXmr; + @Nullable + @Getter + @Setter + private String preparedMultisigHex; // TODO (woodser): ProcessModel shares many fields with TradingPeer; switch to trade getMaker(), getTaker(), getArbitrator(), getSelf(), with common TradingPeer object? @Nullable @Getter @Setter @@ -180,19 +197,12 @@ public class ProcessModel implements Model, PersistablePayload { @Nullable @Getter @Setter - private boolean makerReadyToFundMultisig; + private boolean makerReadyToFundMultisig; // TODO (woodser): remove @Getter @Setter private boolean multisigDepositInitiated; @Nullable - @Setter - private String makerPreparedDepositTxId; - @Nullable - @Setter - private String takerPreparedDepositTxId; - @Nullable - transient private MoneroTxWallet buyerSignedPayoutTx; - + transient private MoneroTxWallet buyerSignedPayoutTx; // TODO (woodser): remove // We want to indicate the user the state of the message delivery of the @@ -239,18 +249,20 @@ public class ProcessModel implements Model, PersistablePayload { .setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong) .setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name()) .setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation) - .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation); + .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation) + .addAllFrozenKeyImages(frozenKeyImages); Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage())); Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage())); Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage())); Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId); + Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature))); - Optional.ofNullable(makerPreparedDepositTxId).ifPresent(e -> builder.setMakerPreparedDepositTxId(makerPreparedDepositTxId)); - Optional.ofNullable(takerPreparedDepositTxId).ifPresent(e -> builder.setTakerPreparedDepositTxId(takerPreparedDepositTxId)); Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class))); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey))); Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage())); + Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); + Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())); Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete)); @@ -272,6 +284,8 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation()); // nullable + processModel.setReserveTxHash(proto.getReserveTxHash()); + processModel.setFrozenKeyImages(proto.getFrozenKeyImagesList()); processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId())); processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature())); List rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ? @@ -282,13 +296,13 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey())); processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null); processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); + processModel.setMakerSignature(proto.getMakerSignature()); + processModel.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null); processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete()); processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig()); processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated()); - processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId()); - processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId()); String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState()); MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString); @@ -349,22 +363,7 @@ public class ProcessModel implements Model, PersistablePayload { tradeManager.requestPersistence(); } } - - public void setTradingPeer(TradingPeer peer) { - if (trade == null) throw new RuntimeException("Cannot set trading peer because trade is null"); - else if (trade instanceof MakerTrade) taker = peer; - else if (trade instanceof TakerTrade) maker = peer; - else throw new RuntimeException("Must be maker or taker to set trading peer"); - } - - public TradingPeer getTradingPeer() { - if (trade == null) throw new RuntimeException("Cannot get trading peer because trade is null"); - else if (trade instanceof MakerTrade) return taker; - else if (trade instanceof TakerTrade) return maker; - else if (trade instanceof ArbitratorTrade) return null; - else throw new RuntimeException("Unknown trade type: " + trade.getClass().getName()); - } - + void setDepositTxSentAckMessage(AckMessage ackMessage) { MessageState messageState = ackMessage.isSuccess() ? MessageState.ACKNOWLEDGED : @@ -387,6 +386,10 @@ public class ProcessModel implements Model, PersistablePayload { /////////////////////////////////////////////////////////////////////////////////////////// // Delegates /////////////////////////////////////////////////////////////////////////////////////////// + + public XmrWalletService getXmrWalletService() { + return provider.getXmrWalletService(); + } public BtcWalletService getBtcWalletService() { return provider.getBtcWalletService(); diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index 874a7276fe..d9fbc8f57e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -21,21 +21,23 @@ package bisq.core.trade.protocol; import bisq.core.trade.SellerAsMakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositTxMessage; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.ProcessDepositResponse; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; +import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; +import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; -import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest; -import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse; -import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener; -import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; @@ -43,7 +45,6 @@ import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage; import bisq.core.util.Validator; - import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; @@ -127,8 +128,10 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc } /////////////////////////////////////////////////////////////////////////////////////////// - // MakerProtocol TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance + // MakerProtocol /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance @Override public void handleInitTradeRequest(InitTradeRequest message, @@ -139,74 +142,130 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc .from(peer)) .setup(tasks( ProcessInitTradeRequest.class, - ApplyFilter.class, - VerifyPeersAccountAgeWitness.class, - MakerVerifyTakerFeePayment.class, - MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig - MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx - MakerSendsReadyToFundMultisigResponse.class). + //ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here + //VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee + //MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this + MakerRemovesOpenOffer.class). using(new TradeTaskRunner(trade, () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); + handleTaskRunnerSuccess(peer, message); }, errorMessage -> { errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); + handleTaskRunnerFault(peer, message, errorMessage); })) .withTimeout(30)) .executeTasks(); } - + @Override - public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, - NodeAddress sender, - ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - processModel.setTempTradingPeerNodeAddress(sender); - - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed + expect(anyPhase(Trade.Phase.INIT) + .with(request) + .from(sender)) + .setup(tasks( + ProcessInitMultisigRequest.class, + SendSignContractRequestAfterMultisig.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT) .with(message) .from(sender)) .setup(tasks( - MakerSendsReadyToFundMultisigResponse.class). - using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) + // TODO (woodser): validate request + ProcessSignContractRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) .executeTasks(); } @Override - public void handleDepositTxMessage(DepositTxMessage message, - NodeAddress sender, - ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - processModel.setTempTradingPeerNodeAddress(sender); - - // TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) + public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed + expect(anyPhase(Trade.Phase.INIT) .with(message) .from(sender)) .setup(tasks( - MakerVerifyTakerDepositTx.class, - MakerCreateAndSignContract.class, - MakerCreateAndPublishDepositTx.class, - MakerSetupDepositTxsListener.class). - using(new TradeTaskRunner(trade, - () -> handleTaskRunnerSuccess(message), - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - }))) + // TODO (woodser): validate request + ProcessSignContractResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handleDepositResponse()"); + Validator.checkTradeId(processModel.getOfferId(), response); + processModel.setTradeMessage(response); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(response) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessDepositResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, response); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, response, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(request) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessPaymentAccountPayloadRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) .executeTasks(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index de2ee5162c..cdf64dfc8b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -22,56 +22,45 @@ import bisq.core.offer.Offer; import bisq.core.trade.SellerAsTakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.DepositResponse; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InputsForDepositTxResponse; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage; +import bisq.core.trade.protocol.tasks.ProcessDepositResponse; +import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; +import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; +import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; +import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.taker.FundMultisig; -import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages; -import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests; -import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest; -import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; +import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds; +import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; -import bisq.common.Timer; -import bisq.common.UserThread; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; -import java.math.BigInteger; - import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; - - -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroTxWallet; -import monero.wallet.model.MoneroWalletListener; - // TODO (woodser): remove unused request handling @Slf4j public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol { - private ResultHandler takeOfferListener; - private Timer initDepositTimer; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -80,7 +69,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc public SellerAsTakerProtocol(SellerAsTakerTrade trade) { super(trade); Offer offer = checkNotNull(trade.getOffer()); - processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); } @@ -97,8 +86,8 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc .from(trade.getTradingPeerNodeAddress())) .setup(tasks( ApplyFilter.class, - TakerVerifyMakerFeePayment.class, - TakerSendInitTradeRequests.class) + TakerReservesTradeFunds.class, + TakerSendsInitTradeRequestToArbitrator.class) .withTimeout(30)) .executeTasks(); } @@ -116,7 +105,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc TakerProcessesInputsForDepositTxResponse.class, ApplyFilter.class, VerifyPeersAccountAgeWitness.class, - TakerVerifyAndSignContract.class, + //TakerVerifyAndSignContract.class, TakerPublishFeeTx.class, SellerAsTakerSignsDepositTx.class, SellerCreatesDelayedPayoutTx.class, @@ -176,182 +165,118 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc // TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance - /////////////////////////////////////////////////////////////////////////////////////////// - // Incoming message handling - /////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { - System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()"); - System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig()); - processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this - if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling - processModel.setTradeMessage(message); - if (message.isMakerReadyToFundMultisig()) { - createAndFundMultisig(message, takeOfferListener); - } else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests - reserveTrade(message, takeOfferListener); - } - } - - private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) { - System.out.println("BuyerAsTakerProtocol.reserveTrade()"); - - // define wallet listener which initiates multisig deposit when trade fee tx unlocked - // TODO (woodser): this needs run for reserved trades when client is opened - // TODO (woodser): test initiating multisig when maker offline - MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet(); - MoneroWalletListener fundMultisigListener = new MoneroWalletListener() { - public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { - - // get updated offer fee tx - MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId()); - - // check if tx is unlocked - if (Boolean.FALSE.equals(feeTx.isLocked())) { - System.out.println("TRADE FEE TX IS UNLOCKED!!!"); - - // stop listening to wallet - wallet.removeListener(this); - - // periodically request multisig deposit until successful - Runnable requestMultisigDeposit = new Runnable() { - @Override - public void run() { - if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler); - else initDepositTimer.stop(); - } - }; - UserThread.execute(requestMultisigDeposit); - initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60); - } - } - }; - - // run pipeline to publish trade fee tx - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - TakerCreateFeeTx.class, - TakerVerifyMakerFeePayment.class, - //TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx - TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade? - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later? - wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - - private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) { - System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()"); - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - TakerVerifyMakerFeePayment.class, - TakerSendReadyToFundMultisigRequest.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - - private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) { - System.out.println("TakerProtocolBase.createAndFundMultisig()"); - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - TakerVerifyMakerFeePayment.class, - TakerVerifyAndSignContract.class, - TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - - @Override - public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { - System.out.println("TakerProtocolBase.handleMultisigMessage()"); - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED) - .with(message) + public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT) + .with(request) .from(sender)) .setup(tasks( - ProcessInitMultisigMessage.class) + ProcessInitMultisigRequest.class, + SendSignContractRequestAfterMultisig.class) .using(new TradeTaskRunner(trade, () -> { System.out.println("handle multisig pipeline completed successfully!"); - handleTaskRunnerSuccess(message); - if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) { - processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time - fundMultisig(message, takeOfferListener); - } + handleTaskRunnerSuccess(sender, request); }, errorMessage -> { System.out.println("error in handle multisig pipeline!!!: " + errorMessage); errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - takeOfferListener.handleResult(); - }))) + handleTaskRunnerFault(sender, request, errorMessage); + })) + .withTimeout(30)) .executeTasks(); } - - private void fundMultisig(InitMultisigMessage message, ResultHandler handler) { - System.out.println("TakerProtocolBase.fundMultisig()"); - expect(new FluentProtocol.Condition(trade)) - .setup(tasks( - FundMultisig.class). // will receive MultisigMessage in response - using(new TradeTaskRunner(trade, - () -> { - System.out.println("MULTISIG WALLET FUNDED!!!!"); - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); - } - + @Override - public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { - System.out.println("TakerProtocolBase.handleDepositTxMessage()"); - processModel.setTradeMessage(message); - expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) - .with(message) - .from(sender)) - .setup(tasks( - TakerProcessesMakerDepositTxMessage.class, - TakerSetupDepositTxsListener.class). - using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message); - }, - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - })) - .withTimeout(30)) - .executeTasks(); + public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handleSignContractRequest()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT) + .with(message) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessSignContractRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + })) + .withTimeout(30)) + .executeTasks(); + } + + @Override + public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handleSignContractResponse()"); + Validator.checkTradeId(processModel.getOfferId(), message); + processModel.setTradeMessage(message); + expect(anyPhase(Trade.Phase.INIT) + .with(message) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessSignContractResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, message); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, message, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handleDepositResponse()"); + Validator.checkTradeId(processModel.getOfferId(), response); + processModel.setTradeMessage(response); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(response) + .from(sender)) + .setup(tasks( + // TODO (woodser): validate request + ProcessDepositResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, response); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, response, errorMessage); + }))) + .executeTasks(); + } + + @Override + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { + System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()"); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED) + .with(request) + .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() + .setup(tasks( + // TODO (woodser): validate request + ProcessPaymentAccountPayloadRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + errorMessageHandler.handleErrorMessage(errorMessage); + handleTaskRunnerFault(sender, request, errorMessage); + }))) + .executeTasks(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java index 64b16177a1..7c95fbe0c8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -21,7 +21,9 @@ import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; 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; @@ -44,6 +46,16 @@ public abstract class SellerProtocol extends DisputeProtocol { public SellerProtocol(SellerTrade trade) { super(trade); } + + @Override + protected void onInitialized() { + super.onInitialized(); + + given(phase(Trade.Phase.DEPOSIT_PUBLISHED) + .with(BuyerEvent.STARTUP)) + .setup(tasks(SetupDepositTxsListener.class)) + .executeTasks(); + } /////////////////////////////////////////////////////////////////////////////////////////// @@ -78,7 +90,7 @@ public abstract class SellerProtocol extends DisputeProtocol { log.warn("We received a CounterCurrencyTransferStartedMessage 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(message, true, null); + sendAckMessage(peer, message, true, null); removeMailboxMessageAfterProcessing(message); })) .setup(tasks( diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java index e506f281cb..4c98f0ca58 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TakerProtocol.java @@ -17,20 +17,11 @@ package bisq.core.trade.protocol; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; -import bisq.network.p2p.NodeAddress; - -import bisq.common.handlers.ErrorMessageHandler; - -public interface TakerProtocol { +public interface TakerProtocol extends TraderProtocol { void onTakeOffer(); enum TakerEvent implements FluentProtocol.Event { TAKE_OFFER } - - // TODO (woodser): update after rebase - //Ã¥void takeAvailableOffer(ResultHandler handler); - void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler); } \ No newline at end of file diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java b/core/src/main/java/bisq/core/trade/protocol/TradeListener.java similarity index 50% rename from core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java rename to core/src/main/java/bisq/core/trade/protocol/TradeListener.java index 53134ecf9a..9b88017b07 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeMessageListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeListener.java @@ -1,12 +1,13 @@ package bisq.core.trade.protocol; import bisq.core.trade.messages.TradeMessage; - +import bisq.network.p2p.AckMessage; import bisq.network.p2p.NodeAddress; /** - * Receives notifications of decrypted, verified trade messages. + * Receives notifications of decrypted, verified trade and ack messages. */ -public class TradeMessageListener { +public class TradeListener { public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { } + public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { } } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 6b4bd79465..913441b180 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -22,11 +22,10 @@ import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.InitMultisigRequest; +import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.UpdateMultisigRequest; -import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage; import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest; import bisq.core.util.Validator; @@ -70,7 +69,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D public TradeProtocol(Trade trade) { this.trade = trade; this.processModel = trade.getProcessModel(); - this.processModel.setTrade(trade); // TODO (woodser): added to explicitly set trade circular loop, keep? } @@ -128,12 +126,14 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D if (networkEnvelope instanceof TradeMessage) { onTradeMessage((TradeMessage) networkEnvelope, peer); + // notify trade listeners // TODO (woodser): better way to register message notifications for trade? if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) { trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer); } } else if (networkEnvelope instanceof AckMessage) { onAckMessage((AckMessage) networkEnvelope, peer); + trade.onAckMessage((AckMessage) networkEnvelope, peer); // notify trade listeners } } @@ -205,28 +205,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D /////////////////////////////////////////////////////////////////////////////////////////// protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer); - - public void handleMultisigMessage(InitMultisigMessage message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { - Validator.checkTradeId(processModel.getOfferId(), message); - processModel.setTradeMessage(message); - - TradeTaskRunner taskRunner = new TradeTaskRunner(trade, - () -> { - stopTimeout(); - handleTaskRunnerSuccess(message, "handleMultisigMessage"); - }, - errorMessage -> { - errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); - }); - taskRunner.addTasks( - ProcessInitMultisigMessage.class - ); - startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode - taskRunner.run(); - } - - public abstract void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler); + public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler); + public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler); // TODO (woodser): update to use fluent for consistency public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { @@ -236,11 +216,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D TradeTaskRunner taskRunner = new TradeTaskRunner(trade, () -> { stopTimeout(); - handleTaskRunnerSuccess(message, "handleUpdateMultisigRequest"); + handleTaskRunnerSuccess(peer, message, "handleUpdateMultisigRequest"); }, errorMessage -> { errorMessageHandler.handleErrorMessage(errorMessage); - handleTaskRunnerFault(message, errorMessage); + handleTaskRunnerFault(peer, message, errorMessage); }); taskRunner.addTasks( ProcessUpdateMultisigRequest.class @@ -262,6 +242,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D if (!result.isValid()) { log.warn(result.getInfo()); handleTaskRunnerFault(null, + null, result.name(), result.getInfo()); } @@ -292,9 +273,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // ACK msg /////////////////////////////////////////////////////////////////////////////////////////// + // TODO (woodser): support notifications of ack messages private void onAckMessage(AckMessage ackMessage, NodeAddress peer) { // We handle the ack for CounterCurrencyTransferStartedMessage 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())) { processModel.setPaymentStartedAckMessage(ackMessage); } else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) { @@ -310,15 +293,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } } - protected void sendAckMessage(TradeMessage message, boolean result, @Nullable String errorMessage) { + protected void sendAckMessage(NodeAddress peer, TradeMessage message, boolean result, @Nullable String errorMessage) { + + // TODO (woodser): remove trade.getTradingPeerNodeAddress() and processModel.getTempTradingPeerNodeAddress() if everything should be maker, taker, or arbitrator - // If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet. - // We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case. - NodeAddress peer = trade.getTradingPeerNodeAddress() != null ? - trade.getTradingPeerNodeAddress() : - processModel.getTempTradingPeerNodeAddress(); - - // get destination pub key ring + // get peer's pub key ring PubKeyRing peersPubKeyRing = getPeersPubKeyRing(peer); if (peersPubKeyRing == null) { log.error("We cannot send the ACK message as peersPubKeyRing is null"); @@ -392,20 +371,20 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // Task runner /////////////////////////////////////////////////////////////////////////////////////////// - protected void handleTaskRunnerSuccess(TradeMessage message) { - handleTaskRunnerSuccess(message, message.getClass().getSimpleName()); + protected void handleTaskRunnerSuccess(NodeAddress sender, TradeMessage message) { + handleTaskRunnerSuccess(sender, message, message.getClass().getSimpleName()); } protected void handleTaskRunnerSuccess(FluentProtocol.Event event) { - handleTaskRunnerSuccess(null, event.name()); + handleTaskRunnerSuccess(null, null, event.name()); } - protected void handleTaskRunnerFault(TradeMessage message, String errorMessage) { - handleTaskRunnerFault(message, message.getClass().getSimpleName(), errorMessage); + protected void handleTaskRunnerFault(NodeAddress sender, TradeMessage message, String errorMessage) { + handleTaskRunnerFault(sender, message, message.getClass().getSimpleName(), errorMessage); } protected void handleTaskRunnerFault(FluentProtocol.Event event, String errorMessage) { - handleTaskRunnerFault(null, event.name(), errorMessage); + handleTaskRunnerFault(null, null, event.name(), errorMessage); } @@ -446,10 +425,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void handleTaskRunnerSuccess(@Nullable TradeMessage message, String source) { + private void handleTaskRunnerSuccess(NodeAddress sender, @Nullable TradeMessage message, String source) { log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId()); if (message != null) { - sendAckMessage(message, true, null); + sendAckMessage(sender, message, true, null); // Once a taskRunner is completed we remove the mailbox message. To not remove it directly at the task // adds some resilience in case of minor errors, so after a restart the mailbox message can be applied @@ -458,11 +437,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } } - void handleTaskRunnerFault(@Nullable TradeMessage message, String source, String errorMessage) { + void handleTaskRunnerFault(NodeAddress ackReceiver, @Nullable TradeMessage message, String source, String errorMessage) { log.error("Task runner failed with error {}. Triggered from {}", errorMessage, source); if (message != null) { - sendAckMessage(message, false, errorMessage); + sendAckMessage(ackReceiver, message, false, errorMessage); } cleanup(); } diff --git a/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java new file mode 100644 index 0000000000..236eb3d139 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java @@ -0,0 +1,32 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol; + + +import bisq.core.trade.messages.DepositResponse; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.SignContractResponse; +import bisq.network.p2p.NodeAddress; + +import bisq.common.handlers.ErrorMessageHandler; + +public interface TraderProtocol { + public void handleSignContractResponse(SignContractResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler); + public void handleDepositResponse(DepositResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler); + public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler); +} diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java index e2df05570b..a470b163c0 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java @@ -58,6 +58,12 @@ public final class TradingPeer implements PersistablePayload { @Nullable private String accountId; @Nullable + private String paymentAccountId; + @Nullable + private String paymentMethodId; + @Nullable + private byte[] paymentAccountPayloadHash; + @Nullable private PaymentAccountPayload paymentAccountPayload; @Nullable private String payoutAddressString; @@ -90,10 +96,24 @@ public final class TradingPeer implements PersistablePayload { // Added for XMR integration @Nullable + private String reserveTxHash; + @Nullable + private String reserveTxHex; + @Nullable + private String reserveTxKey; + @Nullable private String preparedMultisigHex; + @Nullable private String madeMultisigHex; + @Nullable private String signedPayoutTxHex; - + @Nullable + private String depositTxHash; + @Nullable + private String depositTxHex; + @Nullable + private String depositTxKey; + public TradingPeer() { } @@ -102,6 +122,9 @@ public final class TradingPeer implements PersistablePayload { final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder() .setChangeOutputValue(changeOutputValue); Optional.ofNullable(accountId).ifPresent(builder::setAccountId); + Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId); + Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId); + Optional.ofNullable(paymentAccountPayloadHash).ifPresent(e -> builder.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash))); Optional.ofNullable(paymentAccountPayload).ifPresent(e -> builder.setPaymentAccountPayload((protobuf.PaymentAccountPayload) e.toProtoMessage())); Optional.ofNullable(payoutAddressString).ifPresent(builder::setPayoutAddressString); Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson); @@ -115,9 +138,15 @@ public final class TradingPeer implements PersistablePayload { Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e))); Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); + Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); + Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex)); + 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(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash)); + Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex)); + Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey)); builder.setCurrentDate(currentDate); return builder.build(); @@ -130,6 +159,9 @@ public final class TradingPeer implements PersistablePayload { TradingPeer tradingPeer = new TradingPeer(); tradingPeer.setChangeOutputValue(proto.getChangeOutputValue()); tradingPeer.setAccountId(ProtoUtil.stringOrNullFromProto(proto.getAccountId())); + tradingPeer.setPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getPaymentAccountId())); + tradingPeer.setPaymentMethodId(ProtoUtil.stringOrNullFromProto(proto.getPaymentMethodId())); + tradingPeer.setPaymentAccountPayloadHash(proto.getPaymentAccountPayloadHash().toByteArray()); tradingPeer.setPaymentAccountPayload(proto.hasPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getPaymentAccountPayload()) : null); tradingPeer.setPayoutAddressString(ProtoUtil.stringOrNullFromProto(proto.getPayoutAddressString())); tradingPeer.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson())); @@ -148,9 +180,15 @@ public final class TradingPeer implements PersistablePayload { tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature())); tradingPeer.setCurrentDate(proto.getCurrentDate()); tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); + tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash())); + tradingPeer.setReserveTxHex(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex())); + tradingPeer.setReserveTxKey(ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey())); 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())); return tradingPeer; } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java index f3a2d34159..371c05d7df 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ApplyFilter.java @@ -19,8 +19,9 @@ package bisq.core.trade.protocol.tasks; import bisq.core.filter.FilterManager; import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.Trade; - +import bisq.core.trade.messages.InitTradeRequest; import bisq.network.p2p.NodeAddress; import bisq.common.taskrunner.TaskRunner; @@ -43,9 +44,7 @@ public class ApplyFilter extends TradeTask { runInterceptHook(); NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress()); - @Nullable - PaymentAccountPayload paymentAccountPayload = processModel.getTradingPeer().getPaymentAccountPayload(); - + FilterManager filterManager = processModel.getFilterManager(); if (filterManager.isNodeAddressBanned(nodeAddress)) { failed("Other trader is banned by their node address.\n" + @@ -59,9 +58,6 @@ public class ApplyFilter extends TradeTask { } else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) { failed("Payment method is banned.\n" + "Payment method=" + trade.getOffer().getPaymentMethod().getId()); - } else if (paymentAccountPayload != null && filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload)) { - failed("Other trader is banned by their trading account data.\n" + - "paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails()); } else if (filterManager.requireUpdateToNewVersionForTrading()) { failed("Your version of Bisq is not compatible for trading anymore. " + "Please update to the latest Bisq version at https://bisq.network/downloads."); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java new file mode 100644 index 0000000000..d545fda21d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java @@ -0,0 +1,82 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.common.taskrunner.TaskRunner; +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.InitTradeRequest; +import bisq.core.trade.protocol.TradingPeer; +import bisq.core.util.ParsingUtils; +import java.math.BigInteger; +import lombok.extern.slf4j.Slf4j; + +/** + * Arbitrator verifies reserve tx from maker or taker. + * + * The maker reserve tx is only verified here if this arbitrator is not + * the original offer signer and thus does not have the original reserve tx. + */ +@Slf4j +public class ArbitratorProcessesReserveTx extends TradeTask { + @SuppressWarnings({"unused"}) + public ArbitratorProcessesReserveTx(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + Offer offer = trade.getOffer(); + InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); + boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTakerNodeAddress()); + boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferPayload.Direction.SELL : offer.getDirection() == OfferPayload.Direction.BUY; + + // TODO (woodser): if signer online, should never be called by maker + + // 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(), + request.getPayoutAddress(), + depositAmount, + tradeFee, + request.getReserveTxHash(), + request.getReserveTxHex(), + request.getReserveTxKey(), + true); + + // save reserve tx to model + TradingPeer trader = isFromTaker ? processModel.getTaker() : processModel.getMaker(); + trader.setReserveTxHash(request.getReserveTxHash()); + trader.setReserveTxHex(request.getReserveTxHex()); + trader.setReserveTxKey(request.getReserveTxKey()); + + // persist trade + processModel.getTradeManager().requestPersistence(); + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java similarity index 56% rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java index 79f3d333ee..76b0c74d4a 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitMultisigMessages.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java @@ -15,34 +15,35 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.protocol.tasks.taker; +package bisq.core.trade.protocol.tasks; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InitMultisigMessage; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.network.p2p.SendDirectMessageListener; +import static com.google.common.base.Preconditions.checkNotNull; import bisq.common.app.Version; +import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; - +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitMultisigRequest; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.network.p2p.SendDirectMessageListener; +import com.google.common.base.Charsets; import java.util.Date; import java.util.UUID; - import lombok.extern.slf4j.Slf4j; - - - import monero.wallet.MoneroWallet; +/** + * Arbitrator sends InitMultisigRequest to maker and taker if both reserve txs received. + */ @Slf4j -public class TakerSendInitMultisigMessages extends TradeTask { - +public class ArbitratorSendsInitMultisigRequestsIfFundsReserved extends TradeTask { + + private boolean takerAck; private boolean makerAck; - private boolean arbitratorAck; - + @SuppressWarnings({"unused"}) - public TakerSendInitMultisigMessages(TaskRunner taskHandler, Trade trade) { + public ArbitratorSendsInitMultisigRequestsIfFundsReserved(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -50,18 +51,23 @@ public class TakerSendInitMultisigMessages extends TradeTask { protected void run() { try { runInterceptHook(); - + + // skip if arbitrator does not have maker reserve tx + if (processModel.getMaker().getReserveTxHash() == null) { + log.info("Arbitrator does not have maker reserve tx for offerId {}, waiting to receive before initializing multisig wallet", processModel.getOffer().getId()); + complete(); + return; + } + // create wallet for multisig - // TODO (woodser): assert that wallet does not already exist - MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId()); - + MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId()); + // prepare multisig String preparedHex = multisigWallet.prepareMultisig(); processModel.setPreparedMultisigHex(preparedHex); - System.out.println("Prepared multisig hex: " + preparedHex); - // create message to initialize trade - InitMultisigMessage message = new InitMultisigMessage( + // create message to initialize multisig + InitMultisigRequest request = new InitMultisigRequest( processModel.getOffer().getId(), processModel.getMyNodeAddress(), processModel.getPubKeyRing(), @@ -71,55 +77,55 @@ public class TakerSendInitMultisigMessages extends TradeTask { preparedHex, null); - // send request to arbitrator - log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getArbitratorNodeAddress(), - trade.getArbitratorPubKeyRing(), - message, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); - arbitratorAck = true; - checkComplete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - // send request to maker - log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getMakerNodeAddress()); + log.info("Send {} with offerId {} and uid {} to maker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress()); processModel.getP2PService().sendEncryptedDirectMessage( trade.getMakerNodeAddress(), trade.getMakerPubKeyRing(), - message, + request, new SendDirectMessageListener() { @Override public void onArrived() { - log.info("{} arrived at peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + log.info("{} arrived at arbitrator: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); makerAck = true; checkComplete(); } @Override public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + + // send request to taker + log.info("Send {} with offerId {} and uid {} to taker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getTakerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getTakerNodeAddress(), + trade.getTakerPubKeyRing(), + request, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); + takerAck = true; + checkComplete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getTakerNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); failed(); } } ); } catch (Throwable t) { - failed(t); + failed(t); } } - + private void checkComplete() { - if (makerAck && arbitratorAck) complete(); + if (makerAck && takerAck) complete(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java new file mode 100644 index 0000000000..2703dec66e --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java @@ -0,0 +1,125 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.app.Version; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.protocol.TradeListener; +import bisq.network.p2p.AckMessage; +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; +import com.google.common.base.Charsets; +import java.util.Date; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +/** + * Arbitrator sends InitTradeRequest to maker after receiving InitTradeRequest + * from taker and verifying taker reserve tx. + */ +@Slf4j +public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask { + @SuppressWarnings({"unused"}) + public ArbitratorSendsInitTradeRequestToMakerIfFromTaker(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // collect fields for request + String offerId = processModel.getOffer().getId(); + InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); + + // arbitrator signs offer id as nonce to avoid challenge protocol + byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8)); + + // save pub keys + processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model + trade.setArbitratorPubKeyRing(processModel.getPubKeyRing()); + trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing()); + trade.setTakerPubKeyRing(request.getPubKeyRing()); + + // create request to initialize trade with maker + InitTradeRequest makerRequest = new InitTradeRequest( + offerId, + request.getSenderNodeAddress(), + request.getPubKeyRing(), + trade.getTradeAmount().value, + trade.getTradePrice().getValue(), + trade.getTakerFee().getValue(), + request.getAccountId(), + request.getPaymentAccountId(), + request.getPaymentMethodId(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + sig, + new Date().getTime(), + trade.getMakerNodeAddress(), + trade.getTakerNodeAddress(), + trade.getArbitratorNodeAddress(), + null, + null, // do not include taker's reserve tx + null, + null, + null); + + // listen for maker to ack InitTradeRequest + TradeListener listener = new TradeListener() { + @Override + public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { + if (sender.equals(trade.getMakerNodeAddress()) && ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName())) { + trade.removeListener(this); + if (ackMessage.isSuccess()) complete(); + else failed("Received unsuccessful ack for InitTradeRequest from maker"); // TODO (woodser): maker should not do this, penalize them by broadcasting reserve tx? + } + } + }; + trade.addListener(listener); + + // send request to maker + log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), // TODO (woodser): maker's address might be different from original owner address if they disconnect and reconnect, need to validate and update address when requests received + trade.getMakerPubKeyRing(), + makerRequest, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at maker: offerId={}; uid={}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid()); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage); + trade.removeListener(listener); + failed(); + } + } + ); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositRequest.java new file mode 100644 index 0000000000..082a65a981 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositRequest.java @@ -0,0 +1,156 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; +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; +import bisq.core.util.ParsingUtils; +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.MoneroDaemon; +import monero.wallet.MoneroWallet; + +@Slf4j +public class ProcessDepositRequest extends TradeTask { + + @SuppressWarnings({"unused"}) + public ProcessDepositRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // get contract and signature + String contractAsJson = trade.getContractAsJson(); + DepositRequest request = (DepositRequest) processModel.getTradeMessage(); // TODO (woodser): verify response + String signature = request.getContractSignature(); + + // get peer info + // TODO (woodser): make these utilities / refactor model + // TODO (woodser): verify request + PubKeyRing peerPubKeyRing; + TradingPeer peer = trade.getTradingPeer(request.getSenderNodeAddress()); + if (peer == processModel.getArbitrator()) peerPubKeyRing = trade.getArbitratorPubKeyRing(); + else if (peer == processModel.getMaker()) peerPubKeyRing = trade.getMakerPubKeyRing(); + else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing(); + else throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator"); + + // verify signature + if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid"); + + // set peer's signature + peer.setContractSignature(signature); + + // collect expected values of deposit tx + Offer offer = trade.getOffer(); + boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTakerNodeAddress()); + boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferPayload.Direction.SELL : offer.getDirection() == OfferPayload.Direction.BUY; + BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit())); + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId()); // TODO (woodser): only get, do not create + String depositAddress = multisigWallet.getPrimaryAddress(); + BigInteger tradeFee; + TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress()); + if (trader == processModel.getMaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getOffer().getMakerFee()); + else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee()); + else throw new RuntimeException("DepositRequest is not from maker or taker"); + + // flush reserve tx from pool + MoneroDaemon daemon = trade.getXmrWalletService().getDaemon(); + daemon.flushTxPool(trader.getReserveTxHash()); + + // process and verify deposit tx which submits to the pool + TradeUtils.processTradeTx( + daemon, + trade.getXmrWalletService().getWallet(), + depositAddress, + depositAmount, + tradeFee, + trader.getDepositTxHash(), + request.getDepositTxHex(), + request.getDepositTxKey(), + false); + + // sychronize to send only one response + synchronized(processModel) { + + // set deposit info + trader.setDepositTxHex(request.getDepositTxHex()); + trader.setDepositTxKey(request.getDepositTxKey()); + + // relay deposit txs when both available + // TODO (woodser): add small delay so tx has head start against double spend attempts? + if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) { + + // relay txs + daemon.relayTxByHash(processModel.getMaker().getDepositTxHash()); + daemon.relayTxByHash(processModel.getTaker().getDepositTxHash()); + + // create deposit response + DepositResponse response = new DepositResponse( + trade.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime()); + + // send deposit response to maker and taker + sendDepositResponse(trade.getMakerNodeAddress(), trade.getMakerPubKeyRing(), response); + sendDepositResponse(trade.getTakerNodeAddress(), trade.getTakerPubKeyRing(), response); + } + } + + // TODO (woodser): request persistence? + complete(); + } catch (Throwable t) { + failed(t); + } + } + + private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) { + processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), nodeAddress, trade.getId()); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), nodeAddress, trade.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java new file mode 100644 index 0000000000..735ccdb420 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java @@ -0,0 +1,78 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.network.p2p.SendDirectMessageListener; +import java.util.Date; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ProcessDepositResponse extends TradeTask { + + @SuppressWarnings({"unused"}) + public ProcessDepositResponse(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // arbitrator has broadcast deposit txs + trade.setState(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); // TODO (woodser): maker and taker? + + // set payment account payload + trade.getSelf().setPaymentAccountPayload(processModel.getPaymentAccountPayload(trade)); + + // create request with payment account payload + PaymentAccountPayloadRequest request = new PaymentAccountPayloadRequest( + trade.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + trade.getSelf().getPaymentAccountPayload()); + + // send payment account payload 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()); + } + @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(); + } + }); + + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java similarity index 80% rename from core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java index 284c434e9a..392378cb26 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitMultisigRequest.java @@ -21,7 +21,7 @@ import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.MakerTrade; import bisq.core.trade.TakerTrade; import bisq.core.trade.Trade; -import bisq.core.trade.messages.InitMultisigMessage; +import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.protocol.TradingPeer; import bisq.network.p2p.NodeAddress; @@ -46,7 +46,7 @@ import monero.wallet.MoneroWallet; import monero.wallet.model.MoneroMultisigInitResult; @Slf4j -public class ProcessInitMultisigMessage extends TradeTask { +public class ProcessInitMultisigRequest extends TradeTask { private boolean ack1 = false; private boolean ack2 = false; @@ -54,7 +54,7 @@ public class ProcessInitMultisigMessage extends TradeTask { MoneroWallet multisigWallet; @SuppressWarnings({"unused"}) - public ProcessInitMultisigMessage(TaskRunner taskHandler, Trade trade) { + public ProcessInitMultisigRequest(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -63,12 +63,12 @@ public class ProcessInitMultisigMessage extends TradeTask { try { runInterceptHook(); log.debug("current trade state " + trade.getState()); - InitMultisigMessage message = (InitMultisigMessage) processModel.getTradeMessage(); - checkNotNull(message); - checkTradeId(processModel.getOfferId(), message); + InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage(); + checkNotNull(request); + checkTradeId(processModel.getOfferId(), request); System.out.println("PROCESS MULTISIG MESSAGE"); - System.out.println(message); + System.out.println(request); // System.out.println("PROCESS MULTISIG MESSAGE TRADE"); // System.out.println(trade); @@ -81,26 +81,26 @@ public class ProcessInitMultisigMessage extends TradeTask { // get peer multisig participant TradingPeer multisigParticipant; - if (message.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker(); - else if (message.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker(); - else if (message.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator(); + if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker(); + else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker(); + else if (request.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator(); else throw new RuntimeException("Invalid sender to process init trade message: " + trade.getClass().getName()); // reconcile peer's established multisig hex with message - if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(message.getPreparedMultisigHex()); - else if (!multisigParticipant.getPreparedMultisigHex().equals(message.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + message.getPreparedMultisigHex()); - if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(message.getMadeMultisigHex()); - else if (!multisigParticipant.getMadeMultisigHex().equals(message.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages"); - - // get or create multisig wallet // TODO (woodser): ensure multisig wallet is created for first time - multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId()); - + if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.getPreparedMultisigHex()); + else if (!multisigParticipant.getPreparedMultisigHex().equals(request.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + request.getPreparedMultisigHex()); + if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(request.getMadeMultisigHex()); + else if (!multisigParticipant.getMadeMultisigHex().equals(request.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages"); + // prepare multisig if applicable boolean updateParticipants = false; if (processModel.getPreparedMultisigHex() == null) { System.out.println("Preparing multisig wallet!"); + multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId()); processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig()); updateParticipants = true; + } else { + multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId()); } // make multisig if applicable @@ -145,38 +145,38 @@ public class ProcessInitMultisigMessage extends TradeTask { } if (peer1Address == null) throw new RuntimeException("Peer1 address is null"); - if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring"); + if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring is null"); if (peer2Address == null) throw new RuntimeException("Peer2 address is null"); - if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring"); + if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring null"); // send to peer 1 - sendMultisigMessage(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() { + sendInitMultisigRequest(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() { @Override public void onArrived() { - log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer1Address, message.getTradeId(), message.getUid()); + log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer1Address, request.getTradeId(), request.getUid()); ack1 = true; if (ack1 && ack2) completeAux(); } @Override public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer1Address, errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer1Address, errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); failed(); } }); // send to peer 2 - sendMultisigMessage(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() { + sendInitMultisigRequest(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() { @Override public void onArrived() { - log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer2Address, message.getTradeId(), message.getUid()); + log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer2Address, request.getTradeId(), request.getUid()); ack2 = true; if (ack1 && ack2) completeAux(); } @Override public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer2Address, errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer2Address, errorMessage); + appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); failed(); } }); @@ -204,10 +204,10 @@ public class ProcessInitMultisigMessage extends TradeTask { return peers; } - private void sendMultisigMessage(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) { + private void sendInitMultisigRequest(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) { // create multisig message with current multisig hex - InitMultisigMessage message = new InitMultisigMessage( + InitMultisigRequest request = new InitMultisigRequest( processModel.getOffer().getId(), processModel.getMyNodeAddress(), processModel.getPubKeyRing(), @@ -217,8 +217,8 @@ public class ProcessInitMultisigMessage extends TradeTask { processModel.getPreparedMultisigHex(), processModel.getMadeMultisigHex()); - log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), recipient); - processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, message, listener); + log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient); + processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener); } private void completeAux() { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java index e8f8e8cbd2..0944484320 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java @@ -19,18 +19,15 @@ package bisq.core.trade.protocol.tasks; import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.offer.Offer; -import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.TradeUtils; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.protocol.TradingPeer; import bisq.core.user.User; -import bisq.network.p2p.NodeAddress; - import bisq.common.taskrunner.TaskRunner; - import org.bitcoinj.core.Coin; import com.google.common.base.Charsets; @@ -49,82 +46,83 @@ public class ProcessInitTradeRequest extends TradeTask { super(taskHandler, trade); } + // TODO (woodser): synchronize access to setting trade state in case of concurrent requests @Override protected void run() { try { runInterceptHook(); - log.debug("current trade state " + trade.getState()); + Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); checkNotNull(request); checkTradeId(processModel.getOfferId(), request); System.out.println("PROCESS INIT TRADE REQUEST"); System.out.println(request); - - User user = checkNotNull(processModel.getUser(), "User must not be null"); - - // handle maker trade + + // handle request as arbitrator TradingPeer multisigParticipant; - if (trade instanceof MakerTrade) { - - NodeAddress arbitratorNodeAddress = checkNotNull(request.getArbitratorNodeAddress(), "payDepositRequest.getMediatorNodeAddress() must not be null"); - Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(arbitratorNodeAddress), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // TODO (woodser): switch to arbitrator? - - multisigParticipant = processModel.getTaker(); - trade.setTakerNodeAddress(request.getTakerNodeAddress()); - trade.setTakerPubKeyRing(request.getPubKeyRing()); - trade.setArbitratorNodeAddress(request.getArbitratorNodeAddress()); - trade.setArbitratorPubKeyRing(mediator.getPubKeyRing()); + if (trade instanceof ArbitratorTrade) { + + // handle request from taker + if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) { + multisigParticipant = processModel.getTaker(); + if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); + if (trade.getTakerPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest"); + trade.setTakerPubKeyRing(request.getPubKeyRing()); + if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature + } + + // handle request from maker + else if (request.getSenderNodeAddress().equals(request.getMakerNodeAddress())) { + multisigParticipant = processModel.getMaker(); + if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling, uninitialize trade for other takers + if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing()); + else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling + trade.setMakerPubKeyRing(request.getPubKeyRing()); + } else { + throw new RuntimeException("Sender is not trade's maker or taker"); + } } - - // handle arbitrator trade - else if (trade instanceof ArbitratorTrade) { - // TODO (woodser): synchronize access to setting trade state in case of concurrent requests - if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) { - multisigParticipant = processModel.getMaker(); - if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling - if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing()); - else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling - } else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) { + + // handle maker trade + else if (trade instanceof MakerTrade) { multisigParticipant = processModel.getTaker(); - if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling - if (trade.getTakerPubKeyRing() == null) trade.setTakerPubKeyRing(request.getPubKeyRing()); - else if (!trade.getTakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling - } else { - throw new RuntimeException("Sender is not trade's maker or taker"); - } - } else { - throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName()); + trade.setTakerNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring + trade.setTakerPubKeyRing(request.getPubKeyRing()); + } + + // handle invalid trade type + else { + throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName()); } - multisigParticipant.setPaymentAccountPayload(checkNotNull(request.getPaymentAccountPayload())); - multisigParticipant.setPayoutAddressString(nonEmptyStringOf(request.getPayoutAddressString())); + // set trading peer info + if (multisigParticipant.getPaymentAccountId() == null) multisigParticipant.setPaymentAccountId(request.getPaymentAccountId()); + else if (multisigParticipant.getPaymentAccountId() != request.getPaymentAccountId()) throw new RuntimeException("Payment account id is different from previous"); multisigParticipant.setPubKeyRing(checkNotNull(request.getPubKeyRing())); - multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId())); - //trade.setTakerFeeTxId(nonEmptyStringOf(request.getTradeFeeTxId())); // TODO (woodser): no trade fee tx yet if creating multisig first - - // Taker has to sign offerId (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed) + multisigParticipant.setPaymentMethodId(nonEmptyStringOf(request.getPaymentMethodId())); multisigParticipant.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8)); multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId()); multisigParticipant.setCurrentDate(request.getCurrentDate()); - Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); + // check trade price try { - long takersTradePrice = request.getTradePrice(); - offer.checkTradePriceTolerance(takersTradePrice); - trade.setTradePrice(takersTradePrice); + long tradePrice = request.getTradePrice(); + offer.checkTradePriceTolerance(tradePrice); + trade.setTradePrice(tradePrice); } catch (TradePriceOutOfToleranceException e) { failed(e.getMessage()); } catch (Throwable e2) { failed(e2); } + // check trade amount checkArgument(request.getTradeAmount() > 0); trade.setTradeAmount(Coin.valueOf(request.getTradeAmount())); + // persist trade processModel.getTradeManager().requestPersistence(); - complete(); } catch (Throwable t) { failed(t); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPaymentAccountPayloadRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPaymentAccountPayloadRequest.java new file mode 100644 index 0000000000..b3e904ff23 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPaymentAccountPayloadRequest.java @@ -0,0 +1,89 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import static com.google.common.base.Preconditions.checkNotNull; + +import bisq.common.UserThread; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.btc.model.XmrAddressEntry; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.MakerTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import java.util.Arrays; +import lombok.extern.slf4j.Slf4j; +import monero.wallet.MoneroWallet; +import monero.wallet.model.MoneroTxWallet; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; + +@Slf4j +public class ProcessPaymentAccountPayloadRequest extends TradeTask { + + private Subscription tradeStateSubscription; + + @SuppressWarnings({"unused"}) + public ProcessPaymentAccountPayloadRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + if (trade.getTradingPeer().getPaymentAccountPayload() != null) throw new RuntimeException("Peer's payment account payload has already been set"); + + // get peer's payment account payload + PaymentAccountPayloadRequest request = (PaymentAccountPayloadRequest) processModel.getTradeMessage(); // TODO (woodser): verify request + PaymentAccountPayload paymentAccountPayload = request.getPaymentAccountPayload(); + + // verify hash of payment account payload + byte[] peerPaymentAccountPayloadHash = trade instanceof MakerTrade ? trade.getContract().getTakerPaymentAccountPayloadHash() : trade.getContract().getMakerPaymentAccountPayloadHash(); + if (!Arrays.equals(paymentAccountPayload.getHash(), peerPaymentAccountPayloadHash)) throw new RuntimeException("Hash of peer's payment account payload does not match contract"); + + // set payment account payload + trade.getTradingPeer().setPaymentAccountPayload(paymentAccountPayload); + + // subscribe to trade state to notify ui when deposit txs seen in network + tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { + if (trade.isDepositPublished()) applyPublishedDepositTxs(); + }); + if (trade.isDepositPublished()) applyPublishedDepositTxs(); // deposit txs might be seen before subcription + + // persist and complete + processModel.getTradeManager().requestPersistence(); + complete(); + } catch (Throwable t) { + failed(t); + } + } + + private void applyPublishedDepositTxs() { + MoneroWallet multisigWallet = processModel.getXmrWalletService().getMultisigWallet(trade.getId()); + MoneroTxWallet makerDepositTx = checkNotNull(multisigWallet.getTx(processModel.getMaker().getDepositTxHash())); + MoneroTxWallet takerDepositTx = checkNotNull(multisigWallet.getTx(processModel.getTaker().getDepositTxHash())); + trade.applyDepositTxs(makerDepositTx, takerDepositTx); + UserThread.execute(this::unSubscribe); // remove trade state subscription at callback + } + + private void unSubscribe() { + if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe(); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java new file mode 100644 index 0000000000..b1aef60958 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java @@ -0,0 +1,133 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; +import bisq.common.util.Utilities; +import bisq.core.trade.ArbitratorTrade; +import bisq.core.trade.Contract; +import bisq.core.trade.Trade; +import bisq.core.trade.TradeUtils; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.trade.messages.SignContractResponse; +import bisq.core.trade.protocol.TradingPeer; +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; +import java.util.Date; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ProcessSignContractRequest extends TradeTask { + + private boolean ack1 = false; + private boolean ack2 = false; + + @SuppressWarnings({"unused"}) + public ProcessSignContractRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // extract fields from request + // TODO (woodser): verify request and from maker or taker + SignContractRequest request = (SignContractRequest) processModel.getTradeMessage(); + TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress()); + trader.setDepositTxHash(request.getDepositTxHash()); + trader.setAccountId(request.getAccountId()); + trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); + trader.setPayoutAddressString(request.getPayoutAddress()); + + // return contract signature when ready + // TODO (woodser): synchronize contract creation; both requests received at the same time + // TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade + if (processModel.getMaker().getDepositTxHash() != null && processModel.getTaker().getDepositTxHash() != null) { // TODO (woodser): synchronize on process model before setting hash so response only sent once + + // create and sign contract + Contract contract = TradeUtils.createContract(trade); + String contractAsJson = Utilities.objectToJson(contract); + String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); + + // save contract and signature + trade.setContract(contract); + trade.setContractAsJson(contractAsJson); + trade.getSelf().setContractSignature(signature); + + // create response with contract signature + SignContractResponse response = new SignContractResponse( + trade.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + signature); + + // get response recipients. only arbitrator sends response to both peers + NodeAddress recipient1 = trade instanceof ArbitratorTrade ? trade.getMakerNodeAddress() : trade.getTradingPeerNodeAddress(); + PubKeyRing recipient1PubKey = trade instanceof ArbitratorTrade ? trade.getMakerPubKeyRing() : trade.getTradingPeerPubKeyRing(); + NodeAddress recipient2 = trade instanceof ArbitratorTrade ? trade.getTakerNodeAddress() : null; + PubKeyRing recipient2PubKey = trade instanceof ArbitratorTrade ? trade.getTakerPubKeyRing() : null; + + // send response to recipient 1 + processModel.getP2PService().sendEncryptedDirectMessage(recipient1, recipient1PubKey, response, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient1, trade.getId()); + ack1 = true; + if (ack1 && (recipient2 == null || ack2)) complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient1, trade.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + + // send response to recipient 2 if applicable + if (recipient2 != null) { + processModel.getP2PService().sendEncryptedDirectMessage(recipient2, recipient2PubKey, response, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient2, trade.getId()); + ack2 = true; + if (ack1 && ack2) complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient2, trade.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } + } + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java new file mode 100644 index 0000000000..bac6a00977 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java @@ -0,0 +1,115 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.Sig; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositRequest; +import bisq.core.trade.messages.SignContractResponse; +import bisq.core.trade.protocol.TradingPeer; +import bisq.network.p2p.SendDirectMessageListener; +import common.utils.GenUtils; +import java.util.Date; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ProcessSignContractResponse extends TradeTask { + + @SuppressWarnings({"unused"}) + public ProcessSignContractResponse(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // wait until contract is available from peer's sign contract request + // TODO (woodser): this will loop if peer disappears; use proper notification + while (trade.getContract() == null) { + GenUtils.waitFor(250); + } + + // get contract and signature + String contractAsJson = trade.getContractAsJson(); + SignContractResponse response = (SignContractResponse) processModel.getTradeMessage(); // TODO (woodser): verify response + String signature = response.getContractSignature(); + + // get peer info + // TODO (woodser): make these utilities / refactor model + PubKeyRing peerPubKeyRing; + TradingPeer peer = trade.getTradingPeer(response.getSenderNodeAddress()); + if (peer == processModel.getArbitrator()) peerPubKeyRing = trade.getArbitratorPubKeyRing(); + else if (peer == processModel.getMaker()) peerPubKeyRing = trade.getMakerPubKeyRing(); + else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing(); + else throw new RuntimeException(response.getClass().getSimpleName() + " is not from maker, taker, or arbitrator"); + + // verify signature + // TODO (woodser): transfer contract for convenient comparison? + if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid"); + + // set peer's signature + peer.setContractSignature(signature); + + // send deposit request when all contract signatures received + if (processModel.getArbitrator().getContractSignature() != null && processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) { + + // start listening for deposit txs + trade.setupDepositTxsListener(); + + // create request for arbitrator to deposit funds to multisig + DepositRequest request = new DepositRequest( + trade.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + trade.getSelf().getContractSignature(), + processModel.getDepositTxXmr().getFullHex(), + processModel.getDepositTxXmr().getKey()); + + // 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()); + } + @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(); + } + }); + } + + // persist and complete + processModel.getTradeManager().requestPersistence(); + complete(); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java index 67a15568a1..19491cdc53 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessUpdateMultisigRequest.java @@ -55,7 +55,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask { UpdateMultisigRequest request = (UpdateMultisigRequest) processModel.getTradeMessage(); checkNotNull(request); checkTradeId(processModel.getOfferId(), request); - MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId()); + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId()); System.out.println("PROCESS UPDATE MULTISIG REQUEST"); System.out.println(request); @@ -85,13 +85,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask { new Date().getTime(), updatedMultisigHex); - System.out.println("SENDING MESSAGE!!!!!!!"); - System.out.println(response); - log.info("Send {} with offerId {} and uid {} to peer {}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid(), trade.getTradingPeerNodeAddress()); - System.out.println("GONNA BE BAD IF EITHER OF THESE ARE NULL"); - System.out.println(trade.getTradingPeerNodeAddress()); - System.out.println(trade.getTradingPeerPubKeyRing()); processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), response, new SendDirectMessageListener() { @Override public void onArrived() { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java b/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java index 62b06e122a..cded6382f8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/PublishTradeStatistics.java @@ -54,7 +54,7 @@ public class PublishTradeStatistics extends TradeTask { extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get()); } - NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress()); + NodeAddress mediatorNodeAddress = checkNotNull(trade.getArbitratorNodeAddress()); // The first 4 chars are sufficient to identify a mediator. // For testing with regtest/localhost we use the full address as its localhost and would result in // same values for multiple mediators. @@ -62,15 +62,15 @@ public class PublishTradeStatistics extends TradeTask { String address = networkNode instanceof TorNetworkNode ? mediatorNodeAddress.getFullAddress().substring(0, 4) : mediatorNodeAddress.getFullAddress(); - extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address); + extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address); Offer offer = checkNotNull(trade.getOffer()); TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(), trade.getTradePrice(), trade.getTradeAmount(), trade.getDate(), - trade.getMakerDepositTxId(), - trade.getTakerDepositTxId(), + trade.getMaker().getDepositTxHash(), + trade.getTaker().getDepositTxHash(), extraDataMap); processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java index b4eb80cf3d..4d39136554 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SendMailboxMessageTask.java @@ -57,7 +57,7 @@ public abstract class SendMailboxMessageTask extends TradeTask { processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + trade.getTradingPeer().getPubKeyRing(), message, new SendMailboxMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java new file mode 100644 index 0000000000..59d791d1ba --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java @@ -0,0 +1,135 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.common.app.Version; +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.TakerTrade; +import bisq.core.trade.Trade; +import bisq.core.trade.TradeUtils; +import bisq.core.trade.messages.SignContractRequest; +import bisq.core.util.ParsingUtils; +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; + +// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest +@Slf4j +public class SendSignContractRequestAfterMultisig extends TradeTask { + + private boolean ack1 = false; + private boolean ack2 = false; + + @SuppressWarnings({"unused"}) + public SendSignContractRequestAfterMultisig(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // skip if multisig wallet not complete + if (!processModel.isMultisigSetupComplete()) return; + + // skip if deposit tx already created + if (processModel.getDepositTxXmr() != null) return; + + // thaw reserved outputs + MoneroWallet wallet = trade.getXmrWalletService().getWallet(); + for (String frozenKeyImage : processModel.getFrozenKeyImages()) { + wallet.thawOutput(frozenKeyImage); + } + + // 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()); + MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId()); + String multisigAddress = multisigWallet.getPrimaryAddress(); + MoneroTxWallet depositTx = TradeUtils.createDepositTx(trade.getXmrWalletService(), tradeFee, multisigAddress, depositAmount); + + // freeze deposit outputs + // TODO (woodser): save frozen key images and unfreeze if trade fails before sent 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) complete(); + } + @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) complete(); + } + @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); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java index 6eb96385a7..bf22336a91 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupDepositTxsListener.java @@ -17,82 +17,26 @@ package bisq.core.trade.protocol.tasks; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.Trade.State; - import bisq.common.taskrunner.TaskRunner; - +import bisq.core.trade.Trade; import lombok.extern.slf4j.Slf4j; - - -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroWalletListener; - @Slf4j -public abstract class SetupDepositTxsListener extends TradeTask { - // Use instance fields to not get eaten up by the GC - private MoneroWalletListener depositTxListener; - private Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked - private Boolean takerDepositLocked; +public class SetupDepositTxsListener extends TradeTask { - @SuppressWarnings({ "unused" }) - public SetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - // fetch relevant trade info - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); - System.out.println("Maker prepared deposit tx id: " + processModel.getMakerPreparedDepositTxId()); - System.out.println("Taker prepared deposit tx id: " + processModel.getTakerPreparedDepositTxId()); - - // register listener with multisig wallet - depositTxListener = walletService.new HavenoWalletListener(new MoneroWalletListener() { // TODO (woodser): separate into own class file - @Override - public void onOutputReceived(MoneroOutputWallet output) { - - // ignore if no longer listening - if (depositTxListener == null) return; - - // TODO (woodser): remove this - if (output.getTx().isConfirmed() && (processModel.getMakerPreparedDepositTxId().equals(output.getTx().getHash()) || processModel.getTakerPreparedDepositTxId().equals(output.getTx().getHash()))) { - System.out.println("Deposit output for tx " + output.getTx().getHash() + " is confirmed at height " + output.getTx().getHeight()); - } - - // update locked state - if (output.getTx().getHash().equals(processModel.getMakerPreparedDepositTxId())) makerDepositLocked = output.getTx().isLocked(); - else if (output.getTx().getHash().equals(processModel.getTakerPreparedDepositTxId())) takerDepositLocked = output.getTx().isLocked(); - - // deposit txs seen when both locked states seen - if (makerDepositLocked != null && takerDepositLocked != null) { - trade.setState(getSeenState()); - } - - // confirm trade and update ui when both deposits unlock - if (Boolean.FALSE.equals(makerDepositLocked) && Boolean.FALSE.equals(takerDepositLocked)) { - System.out.println("MULTISIG DEPOSIT TXS UNLOCKED!!!"); - trade.applyDepositTxs(multisigWallet.getTx(processModel.getMakerPreparedDepositTxId()), multisigWallet.getTx(processModel.getTakerPreparedDepositTxId())); - multisigWallet.removeListener(depositTxListener); // remove listener when notified - depositTxListener = null; // prevent re-applying trade state in subsequent requests - } - } - }); - multisigWallet.addListener(depositTxListener); - - // complete immediately - complete(); - } catch (Throwable t) { - failed(t); + @SuppressWarnings({ "unused" }) + public SetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); } - } - protected abstract State getSeenState(); + @Override + protected void run() { + try { + runInterceptHook(); + trade.setupDepositTxsListener(); + complete(); + } catch (Throwable t) { + failed(t); + } + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java index a970807410..fd18c53cb2 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java @@ -22,7 +22,7 @@ import bisq.core.trade.Trade; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.UpdateMultisigRequest; import bisq.core.trade.messages.UpdateMultisigResponse; -import bisq.core.trade.protocol.TradeMessageListener; +import bisq.core.trade.protocol.TradeListener; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; @@ -43,7 +43,7 @@ import monero.wallet.MoneroWallet; @Slf4j public class UpdateMultisigWithTradingPeer extends TradeTask { - private TradeMessageListener updateMultisigResponseListener; + private TradeListener updateMultisigResponseListener; @SuppressWarnings({"unused"}) public UpdateMultisigWithTradingPeer(TaskRunner taskHandler, Trade trade) { @@ -57,7 +57,7 @@ public class UpdateMultisigWithTradingPeer extends TradeTask { // fetch relevant trade info XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // skip if multisig wallet does not need updated if (!multisigWallet.isMultisigImportNeeded()) { @@ -67,7 +67,7 @@ public class UpdateMultisigWithTradingPeer extends TradeTask { } // register listener to receive updated multisig response - updateMultisigResponseListener = new TradeMessageListener() { + updateMultisigResponseListener = new TradeListener() { @Override public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { if (!(message instanceof UpdateMultisigResponse)) return; @@ -81,11 +81,11 @@ public class UpdateMultisigWithTradingPeer extends TradeTask { multisigWallet.sync(); multisigWallet.save(); System.out.println("Num outputs signed with imported multisig hex: " + numOutputsSigned); - trade.removeTradeMessageListener(updateMultisigResponseListener); + trade.removeListener(updateMultisigResponseListener); complete(); } }; - trade.addTradeMessageListener(updateMultisigResponseListener); + trade.addListener(updateMultisigResponseListener); // get updated multisig hex multisigWallet.sync(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java b/core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java index 68ea2d9eeb..40079226df 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/VerifyPeersAccountAgeWitness.java @@ -53,7 +53,7 @@ public class VerifyPeersAccountAgeWitness extends TradeTask { } AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = trade.getTradingPeer(); PaymentAccountPayload peersPaymentAccountPayload = checkNotNull(tradingPeer.getPaymentAccountPayload(), "Peers peersPaymentAccountPayload must not be null"); PubKeyRing peersPubKeyRing = checkNotNull(tradingPeer.getPubKeyRing(), "peersPubKeyRing must not be null"); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java index e5bf316976..da93bbdc51 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerCreateAndSignPayoutTx.java @@ -69,32 +69,19 @@ public class BuyerCreateAndSignPayoutTx extends TradeTask { // gather relevant trade info XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); - String sellerPayoutAddress = processModel.getTradingPeer().getPayoutAddressString(); + 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.getTakerPreparedDepositTxId() : processModel.getMakerPreparedDepositTxId()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs trade.getDepositTxId() necessary or avoidable? - BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getMakerPreparedDepositTxId() : processModel.getTakerPreparedDepositTxId()).getIncomingAmount(); - BigInteger tradeAmount = ParsingUtils.satoshisToXmrAtomicUnits(trade.getTradeAmount().value); + 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); - System.out.println("sellerPayoutAddress: " + sellerPayoutAddress); - System.out.println("buyerPayoutAddress: " + buyerPayoutAddress); - System.out.println("Multisig balance: " + multisigWallet.getBalance()); - System.out.println("Multisig unlocked balance: " + multisigWallet.getUnlockedBalance()); - System.out.println("Multisig txs"); - System.out.println(multisigWallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true))); - - //System.out.println("Testing buyer payout amount: " + buyerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5))); - //System.out.println("Testing seller payout amount: " + sellerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5))); - //System.out.println("Testing payout amount: " + (buyerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5))).add(sellerPayoutAmount.multiply(BigInteger.valueOf(3)).divide(BigInteger.valueOf(5)))); - // create transaction to get fee estimate if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Multisig import is still needed!!!"); - - System.out.println("Creating feeEstimateTx!"); MoneroTxWallet feeEstimateTx = multisigWallet.createTx(new MoneroTxConfig() .setAccountIndex(0) .addDestination(buyerPayoutAddress, buyerPayoutAmount.multiply(BigInteger.valueOf(4)).divide(BigInteger.valueOf(5))) // reduce payment amount to compute fee of similar tx @@ -102,10 +89,6 @@ public class BuyerCreateAndSignPayoutTx extends TradeTask { .setRelay(false) ); - System.out.println("Created fee estimate tx!"); - System.out.println(feeEstimateTx); - //BigInteger estimatedFee = feeEstimateTx.getFee(); - // attempt to create payout tx by increasing estimated fee until successful MoneroTxWallet payoutTx = null; int numAttempts = 0; @@ -119,15 +102,14 @@ public class BuyerCreateAndSignPayoutTx extends TradeTask { .addDestination(new MoneroDestination(sellerPayoutAddress, sellerPayoutAmount.subtract(feeEstimate.divide(BigInteger.valueOf(2))))) // TODO (woodser): support addDestination(addr, amt) without new .setRelay(false)); } catch (MoneroError e) { - e.printStackTrace(); - System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING..."); + //e.printStackTrace(); + //System.out.println("FAILED TO CREATE PAYOUT TX, ITERATING..."); } } if (payoutTx == null) throw new RuntimeException("Failed to generate payout tx"); System.out.println("PAYOUT TX GENERATED ON ATTEMPT " + numAttempts); System.out.println(payoutTx); - processModel.setBuyerSignedPayoutTx(payoutTx); complete(); } catch (Throwable t) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java index 30824a14d7..7c56c9de14 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerFinalizesDelayedPayoutTx.java @@ -37,10 +37,10 @@ public class BuyerFinalizesDelayedPayoutTx extends TradeTask { checkArgument(Arrays.equals(buyerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] sellerMultiSigPubKey = trade.getTradingPeer().getMultiSigPubKey(); byte[] buyerSignature = processModel.getDelayedPayoutTxSignature(); - byte[] sellerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature(); + byte[] sellerSignature = trade.getTradingPeer().getDelayedPayoutTxSignature(); Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeUnconnectedDelayedPayoutTx( preparedDelayedPayoutTx, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java index 19cdb957f7..3cf0ee338b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java @@ -46,7 +46,7 @@ public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask { byte[] delayedPayoutTxAsBytes = checkNotNull(request.getDelayedPayoutTx()); Transaction preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes); processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx); - processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(request.getDelayedPayoutTxSellerSignature())); + trade.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(request.getDelayedPayoutTxSellerSignature())); // When we receive that message the taker has published the taker fee, so we apply it to the trade. // The takerFeeTx was sent in the first message. It should be part of DelayedPayoutTxSignatureRequest diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java index 3bf5fa0342..3ae8fea6b8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java @@ -58,7 +58,7 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask { if (trade.getPayoutTx() == null) { XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); + MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); List txHashes = multisigWallet.submitMultisigTxHex(message.getSignedMultisigTxHex()); trade.setPayoutTx(multisigWallet.getTx(txHashes.get(0))); XmrWalletService.printTxs("payoutTx received from peer", trade.getPayoutTx()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java index 5b9702798b..b198ed8ab4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java @@ -59,7 +59,7 @@ public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + trade.getTradingPeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java index cab7fc896b..e1bd0b8bff 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java @@ -60,7 +60,7 @@ public class BuyerSignsDelayedPayoutTx extends TradeTask { checkArgument(Arrays.equals(buyerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] sellerMultiSigPubKey = trade.getTradingPeer().getMultiSigPubKey(); byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx( preparedDelayedPayoutTx, preparedDepositTx, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java index 5f259b5574..96f93c8ca6 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesPreparedDelayedPayoutTx.java @@ -64,7 +64,7 @@ public class BuyerVerifiesPreparedDelayedPayoutTx extends TradeTask { private boolean isDepositTxNonMalleable() { var buyerInputs = checkNotNull(processModel.getRawTransactionInputs()); - var sellerInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs()); + var sellerInputs = checkNotNull(trade.getTradingPeer().getRawTransactionInputs()); return buyerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH) && sellerInputs.stream().allMatch(processModel.getTradeWalletService()::isP2WH); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java index 5932f735d0..7171d2a091 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java @@ -56,7 +56,7 @@ public class BuyerAsMakerCreatesAndSignsDepositTx extends TradeTask { BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = trade.getTradingPeer(); Offer offer = checkNotNull(trade.getOffer()); Coin makerInputAmount = offer.getBuyerSecurityDeposit(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java index 30a9703ef5..f020c6d3bd 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java @@ -46,7 +46,7 @@ public class BuyerAsTakerSendsDepositTxMessage extends TradeTask { // message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); // processModel.getP2PService().sendEncryptedDirectMessage( // peersNodeAddress, -// processModel.getTradingPeer().getPubKeyRing(), +// trade.getTradingPeer().getPubKeyRing(), // message, // new SendDirectMessageListener() { // @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java index 26cc545652..77daa2635f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java @@ -57,7 +57,7 @@ public class BuyerAsTakerSignsDepositTx extends TradeTask { // buyerMultiSigAddressEntry.setCoinLockedInMultiSig(buyerInput.subtract(trade.getTxFee().multiply(2))); // walletService.saveAddressEntryList(); // -// TradingPeer tradingPeer = processModel.getTradingPeer(); +// TradingPeer tradingPeer = trade.getTradingPeer(); // byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); // checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()), // "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java deleted file mode 100644 index b1ef6243ac..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndPublishDepositTx.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.maker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.Validator; - -import bisq.network.p2p.SendDirectMessageListener; - -import bisq.common.UserThread; -import bisq.common.taskrunner.TaskRunner; - -import org.fxmisc.easybind.EasyBind; -import org.fxmisc.easybind.Subscription; - -import java.math.BigInteger; - -import java.util.List; -import java.util.UUID; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkNotNull; - - - -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroTxConfig; -import monero.wallet.model.MoneroTxWallet; - -@Slf4j -public class MakerCreateAndPublishDepositTx extends TradeTask { - private Subscription tradeStateSubscription; - - @SuppressWarnings({"unused"}) - public MakerCreateAndPublishDepositTx(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - System.out.println("MakerCreateAndPublishDepositTx"); - log.debug("current trade state " + trade.getState()); - DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); - Validator.checkTradeId(processModel.getOfferId(), message); - checkNotNull(message); - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - if (trade.getMakerDepositTxId() != null) throw new RuntimeException("Maker deposit tx already set for trade " + trade.getId() + ", this should not happen"); // TODO: ignore and nack bad requests to not show on client - - System.out.println(message); - - // decide who goes first - boolean takerGoesFirst = true; // TODO (woodser): based on rep? - - // send deposit tx after taker - if (takerGoesFirst) { - - // verify taker's deposit tx - // TODO (woodser): taker needs to prove tx to address, cannot claim tx id, verify tx id seen in pool - // TODO (woodser): need to wait for tx to be seen by multisig wallet, might not be in wallet at first - if (message.getDepositTxId() == null) throw new RuntimeException("Taker must provide deposit tx id"); - processModel.setTakerPreparedDepositTxId(message.getDepositTxId()); - - // collect parameters for transfer to multisig - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet wallet = walletService.getWallet(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); - String multisigAddress = multisigWallet.getPrimaryAddress(); - - // send deposit tx - XmrAddressEntry addressEntry = walletService.getAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.RESERVED_FOR_TRADE).get(); - int accountIndex = addressEntry.getAccountIndex(); - if (!wallet.getBalance(accountIndex).equals(wallet.getUnlockedBalance(accountIndex)) || wallet.getBalance(accountIndex).equals(BigInteger.valueOf(0))) { - throw new RuntimeException("Reserved trade account balance expected to be fully available"); - } - System.out.println("Sweeping unlocked balance in account " + accountIndex + ": " + wallet.getUnlockedBalance(accountIndex)); - List txs = wallet.sweepUnlocked(new MoneroTxConfig() - .setAccountIndex(accountIndex) - .setAddress(multisigAddress) - .setCanSplit(false) - .setRelay(true)); - if (txs.size() != 1) throw new RuntimeException("Sweeping reserved trade account to multisig expected to create exactly 1 transaction"); - MoneroTxWallet makerDepositTx = txs.get(0); - processModel.setMakerPreparedDepositTxId(makerDepositTx.getHash()); - //trade.setState(Trade.State.SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG); // TODO (wooder): state for MAKER_TRANSFERRED_TO_MULTISIG? - System.out.println("SUCCESSFULLY SWEPT RESERVED TRADE ACCOUNT TO MULTISIG"); - System.out.println(txs.get(0)); - - // apply published transaction which notifies ui - applyPublishedDepositTxs(makerDepositTx, multisigWallet.getTx(processModel.getTakerPreparedDepositTxId())); - - // notify trade state subscription when deposit published - tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { - if (trade.isDepositPublished()) { - swapReservedForTradeEntry(); - UserThread.execute(this::unSubscribe); // hack to remove tradeStateSubscription at callback - } - }); - } - - // send deposit tx before taker - else { - throw new RuntimeException("Maker goes first not implemented"); - } - - // create message to notify taker of maker's deposit tx - DepositTxMessage request = new DepositTxMessage( - UUID.randomUUID().toString(), - processModel.getOffer().getId(), - processModel.getMyNodeAddress(), - null, - trade.getMakerDepositTxId()); - - // notify taker of maker's deposit tx - log.info("Send {} with offerId {} and uid {} to maker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getTakerNodeAddress()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getTakerNodeAddress(), - trade.getTakerPubKeyRing(), - request, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at taker: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); - complete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getTakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending request failed: request=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - - // complete - processModel.getTradeManager().requestPersistence(); - } catch (Throwable t) { - failed(t); - } - } - - private void applyPublishedDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) { - if (trade.getMakerDepositTx() == null && trade.getTakerDepositTx() == null) { - trade.applyDepositTxs(makerDepositTx, takerDepositTx); - XmrWalletService.printTxs("depositTxs received from network", makerDepositTx, takerDepositTx); - trade.setState(Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK); // TODO (woodser): MAKER_PUBLISHED_DEPOSIT_TX - } else { - log.info("We got the deposit tx already set from MakerCreateAndPublishDepositTx. tradeId={}, state={}", trade.getId(), trade.getState()); - } - - swapReservedForTradeEntry(); - - // need delay as it can be called inside the listener handler before listener and tradeStateSubscription are actually set. - UserThread.execute(this::unSubscribe); - } - - private void swapReservedForTradeEntry() { - log.info("swapReservedForTradeEntry"); - processModel.getProvider().getXmrWalletService().swapTradeEntryToAvailableEntry(trade.getId(), XmrAddressEntry.Context.RESERVED_FOR_TRADE); - } - - private void unSubscribe() { - if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe(); - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java deleted file mode 100644 index 3d0391ca1c..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.maker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.trade.BuyerAsMakerTrade; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.network.p2p.NodeAddress; - -import bisq.common.crypto.Hash; -import bisq.common.crypto.Sig; -import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class MakerCreateAndSignContract extends TradeTask { - public MakerCreateAndSignContract(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); - trade.setTakerFeeTxId(message.getTradeFeeTxId()); // TODO (woodser): must verify trade fee tx. set up contract before taker deposits? - //String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId()); - - TradingPeer taker = processModel.getTradingPeer(); - boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade; - NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); - NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress(); - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - String id = processModel.getOffer().getId(); - - // get maker payout address - XmrAddressEntry makerPayoutEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); - checkNotNull(taker.getPayoutAddressString(), "taker.getPayoutAddressString()"); - -// AddressEntry makerAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); -// byte[] makerMultiSigPubKey = makerAddressEntry.getPubKey(); - - Contract contract = new Contract( - processModel.getOffer().getOfferPayload(), - checkNotNull(trade.getTradeAmount()).value, - trade.getTradePrice().getValue(), - //takerFeeTxId, // TODO (woodser): include taker fee tx id? - buyerNodeAddress, - sellerNodeAddress, - trade.getArbitratorNodeAddress(), - isBuyerMakerAndSellerTaker, - processModel.getAccountId(), - checkNotNull(taker.getAccountId()), - checkNotNull(processModel.getPaymentAccountPayload(trade)), - checkNotNull(taker.getPaymentAccountPayload()), - processModel.getPubKeyRing(), - checkNotNull(taker.getPubKeyRing()), - makerPayoutEntry.getAddressString(), - checkNotNull(taker.getPayoutAddressString()), - trade.getLockTime() - ); - String contractAsJson = Utilities.objectToJson(contract); - String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); - - trade.setContract(contract); - trade.setContractAsJson(contractAsJson); - trade.setMakerContractSignature(signature); - - byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson())); - trade.setContractHash(contractHash); - - - processModel.getTradeManager().requestPersistence(); - - complete(); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java deleted file mode 100644 index 7d0368a702..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateFeeTx.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.maker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.TradeWalletService; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.dao.exceptions.DaoDisabledException; -import bisq.core.offer.Offer; -import bisq.core.offer.placeoffer.PlaceOfferModel; - -import bisq.common.UserThread; -import bisq.common.taskrunner.Task; -import bisq.common.taskrunner.TaskRunner; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - - -import monero.wallet.model.MoneroTxWallet; - -public class MakerCreateFeeTx extends Task { - private static final Logger log = LoggerFactory.getLogger(MakerCreateFeeTx.class); - - @SuppressWarnings({"unused"}) - public MakerCreateFeeTx(TaskRunner taskHandler, PlaceOfferModel model) { - super(taskHandler, model); - } - - @Override - protected void run() { - Offer offer = model.getOffer(); - - try { - runInterceptHook(); - - String id = offer.getId(); - XmrWalletService walletService = model.getXmrWalletService(); - - String reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.RESERVED_FOR_TRADE).getAddressString(); - - TradeWalletService tradeWalletService = model.getTradeWalletService(); - String feeReceiver = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode - - if (offer.isCurrencyForMakerFeeBtc()) { - try { - MoneroTxWallet tx = tradeWalletService.createXmrTradingFeeTx( - reservedForTradeAddress, - model.getReservedFundsForOffer(), - offer.getMakerFee(), - offer.getTxFee(), - feeReceiver, - true); - System.out.println("SUCCESS CREATING XMR TRADING FEE TX!"); - System.out.println(tx); - - // we delay one render frame to be sure we don't get called before the method call has - // returned (tradeFeeTx would be null in that case) - UserThread.execute(() -> { - if (!completed) { - offer.setOfferFeePaymentTxId(tx.getHash()); - model.setXmrTransaction(tx); - walletService.swapTradeEntryToAvailableEntry(id, XmrAddressEntry.Context.OFFER_FUNDING); - - model.getOffer().setState(Offer.State.OFFER_FEE_PAID); - - complete(); - } else { - log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); - } - }); - } catch (Exception e) { - System.out.println("FAILURE CREATING XMR TRADING FEE TX!"); - if (!completed) { - failed(e); - } else { - log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); - } - } - -// tradeWalletService.createBtcTradingFeeTx( -// fundingAddress, -// reservedForTradeAddress, -// changeAddress, -// model.getReservedFundsForOffer(), -// model.isUseSavingsWallet(), -// offer.getMakerFee(), -// offer.getTxFee(), -// feeReceiver, -// true, -// new TxBroadcaster.Callback() { -// @Override -// public void onSuccess(Transaction transaction) { -// // we delay one render frame to be sure we don't get called before the method call has -// // returned (tradeFeeTx would be null in that case) -// UserThread.execute(() -> { -// if (!completed) { -// offer.setOfferFeePaymentTxId(transaction.getHashAsString()); -// model.setTransaction(transaction); -// walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); -// -// model.getOffer().setState(Offer.State.OFFER_FEE_PAID); -// -// complete(); -// } else { -// log.warn("We got the onSuccess callback called after the timeout has been triggered a complete()."); -// } -// }); -// } -// -// @Override -// public void onFailure(TxBroadcastException exception) { -// if (!completed) { -// failed(exception); -// } else { -// log.warn("We got the onFailure callback called after the timeout has been triggered a complete()."); -// } -// } -// }); - } - } catch (Throwable t) { - if (t instanceof DaoDisabledException) { - offer.setErrorMessage("You cannot pay the trade fee in BSQ at the moment because the DAO features have been " + - "disabled due technical problems. Please use the BTC fee option until the issues are resolved. " + - "For more information please visit the Bisq Forum."); - } else { - offer.setErrorMessage("An error occurred.\n" + - "Error message:\n" - + t.getMessage()); - } - - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java deleted file mode 100644 index d048ba2ef4..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.maker; - -import bisq.core.exceptions.TradePriceOutOfToleranceException; -import bisq.core.offer.Offer; -import bisq.core.support.dispute.mediation.mediator.Mediator; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.user.User; - -import bisq.network.p2p.NodeAddress; - -import bisq.common.taskrunner.TaskRunner; - -import org.bitcoinj.core.Coin; - -import com.google.common.base.Charsets; - -import lombok.extern.slf4j.Slf4j; - -import static bisq.core.util.Validator.checkTradeId; -import static bisq.core.util.Validator.nonEmptyStringOf; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class MakerProcessesInputsForDepositTxRequest extends TradeTask { - public MakerProcessesInputsForDepositTxRequest(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - InputsForDepositTxRequest request = (InputsForDepositTxRequest) processModel.getTradeMessage(); - checkNotNull(request); - checkTradeId(processModel.getOfferId(), request); - - TradingPeer tradingPeer = processModel.getTradingPeer(); - tradingPeer.setPaymentAccountPayload(checkNotNull(request.getTakerPaymentAccountPayload())); - tradingPeer.setRawTransactionInputs(checkNotNull(request.getRawTransactionInputs())); - checkArgument(request.getRawTransactionInputs().size() > 0); - - tradingPeer.setChangeOutputValue(request.getChangeOutputValue()); - tradingPeer.setChangeOutputAddress(request.getChangeOutputAddress()); - - tradingPeer.setMultiSigPubKey(checkNotNull(request.getTakerMultiSigPubKey())); - tradingPeer.setPayoutAddressString(nonEmptyStringOf(request.getTakerPayoutAddressString())); - tradingPeer.setPubKeyRing(checkNotNull(request.getTakerPubKeyRing())); - - tradingPeer.setAccountId(nonEmptyStringOf(request.getTakerAccountId())); - - // We set the taker fee only in the processModel yet not in the trade as the tx was only created but not - // published yet. Once it was published we move it to trade. The takerFeeTx should be sent in a later - // message but that cannot be changed due backward compatibility issues. It is a left over from the - // old trade protocol. - processModel.setTakeOfferFeeTxId(nonEmptyStringOf(request.getTakerFeeTxId())); - - // Taker has to sign offerId (he cannot manipulate that - so we avoid to have a challenge protocol for - // passing the nonce we want to get signed) - tradingPeer.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8)); - tradingPeer.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId()); - tradingPeer.setCurrentDate(request.getCurrentDate()); - - User user = checkNotNull(processModel.getUser(), "User must not be null"); - - NodeAddress mediatorNodeAddress = checkNotNull(request.getMediatorNodeAddress(), - "InputsForDepositTxRequest.getMediatorNodeAddress() must not be null"); - trade.setMediatorNodeAddress(mediatorNodeAddress); - Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(mediatorNodeAddress), - "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); - trade.setMediatorPubKeyRing(checkNotNull(mediator.getPubKeyRing(), - "mediator.getPubKeyRing() must not be null")); - - Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); - try { - long takersTradePrice = request.getTradePrice(); - offer.checkTradePriceTolerance(takersTradePrice); - trade.setTradePrice(takersTradePrice); - } catch (TradePriceOutOfToleranceException e) { - failed(e.getMessage()); - } catch (Throwable e2) { - failed(e2); - } - - checkArgument(request.getTradeAmount() > 0); - trade.setTradeAmount(Coin.valueOf(request.getTradeAmount())); - - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - - processModel.getTradeManager().requestPersistence(); - - complete(); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java similarity index 81% rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java index 319bb45c93..7825dc67af 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java @@ -17,6 +17,7 @@ package bisq.core.trade.protocol.tasks.maker; +import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.wallet.XmrWalletService; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.Trade; @@ -43,9 +44,9 @@ import static bisq.core.util.Validator.checkTradeId; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j -public class MakerSendsInitTradeRequest extends TradeTask { +public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask { @SuppressWarnings({"unused"}) - public MakerSendsInitTradeRequest(TaskRunner taskHandler, Trade trade) { + public MakerSendsInitTradeRequestIfUnreserved(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -61,16 +62,6 @@ public class MakerSendsInitTradeRequest extends TradeTask { checkNotNull(request); checkTradeId(processModel.getOfferId(), request); -// // create wallet for multisig -// // TODO (woodser): manage in common util, set path, server -// MoneroWallet multisigWallet = MoneroWallet.createWallet(new MoneroWalletConfig() -// .setPassword("abctesting123") -// .setNetworkType(MoneroNetworkType.STAGENET)); -// -// // prepare multisig -// String preparedHex = multisigWallet.prepareMultisig(); -// System.out.println("Prepared multisig hex: " + preparedHex); - // collect fields to send taker prepared multisig response // TODO (woodser): this should happen on response from arbitrator XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); String offerId = processModel.getOffer().getId(); @@ -82,15 +73,16 @@ public class MakerSendsInitTradeRequest extends TradeTask { checkNotNull(user, "User must not be null"); final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null"); - - // Taker has to use offerId as nonce (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed) - // He cannot manipulate the offerId - so we avoid to have a challenge protocol for passing the nonce we want to get signed. + + // maker signs offer id as nonce to avoid challenge protocol final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null"); byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8)); System.out.println("MAKER SENDING ARBITRTATOR SENDER NODE ADDRESS"); System.out.println(processModel.getMyNodeAddress()); - + + if (true) throw new RuntimeException("Not yet implemented"); + // create message to initialize trade InitTradeRequest message = new InitTradeRequest( offerId, @@ -98,30 +90,32 @@ public class MakerSendsInitTradeRequest extends TradeTask { processModel.getPubKeyRing(), trade.getTradeAmount().value, trade.getTradePrice().getValue(), - trade.getTxFee().getValue(), trade.getTakerFee().getValue(), - payoutAddress, - paymentAccountPayload, processModel.getAccountId(), - trade.getTakerFeeTxId(), + paymentAccountPayload.getId(), + paymentAccountPayload.getPaymentMethodId(), UUID.randomUUID().toString(), Version.getP2PMessageVersion(), sig, new Date().getTime(), - trade.getTakerNodeAddress(), trade.getMakerNodeAddress(), - trade.getArbitratorNodeAddress()); + trade.getTakerNodeAddress(), + trade.getArbitratorNodeAddress(), + processModel.getReserveTx().getHash(), // TODO (woodser): need to first create and save reserve tx + processModel.getReserveTx().getFullHex(), + processModel.getReserveTx().getKey(), + processModel.getXmrWalletService().getAddressEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(), + null); log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), - message.getUid(), trade.getMediatorNodeAddress()); + message.getUid(), trade.getArbitratorNodeAddress()); System.out.println("MAKER TRADE INFO"); System.out.println("Trading peer node address: " + trade.getTradingPeerNodeAddress()); System.out.println("Maker node address: " + trade.getMakerNodeAddress()); System.out.println("Taker node adddress: " + trade.getTakerNodeAddress()); - System.out.println("Mediator node address: " + trade.getMediatorNodeAddress()); System.out.println("Arbitrator node address: " + trade.getArbitratorNodeAddress()); // send request to arbitrator diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java index 34380f5bcf..7d6c951c2c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java @@ -78,7 +78,7 @@ public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask { processModel.getAccountId(), makerMultiSigPubKey, trade.getContractAsJson(), - trade.getMakerContractSignature(), + trade.getMaker().getContractSignature(), makerPayoutAddressEntry.getAddressString(), preparedDepositTx, processModel.getRawTransactionInputs(), @@ -95,7 +95,7 @@ public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + trade.getTradingPeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java deleted file mode 100644 index a361e3e7f8..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsReadyToFundMultisigResponse.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.maker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.BuyerAsMakerTrade; -import bisq.core.trade.Contract; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.network.p2p.NodeAddress; -import bisq.network.p2p.SendDirectMessageListener; - -import bisq.common.app.Version; -import bisq.common.crypto.Sig; -import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; - -import java.util.Date; -import java.util.UUID; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkNotNull; - - - -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroTxWallet; - -@Slf4j -public class MakerSendsReadyToFundMultisigResponse extends TradeTask { - @SuppressWarnings({"unused"}) - public MakerSendsReadyToFundMultisigResponse(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - System.out.println("MAKER SENDING READY TO FUND MULTISIG RESPONSE"); - - // determine if maker is ready to fund to-be-created multisig - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet wallet = walletService.getWallet(); - wallet.sync(); - MoneroTxWallet offerFeeTx = wallet.getTx(trade.getOffer().getOfferFeePaymentTxId()); - if (offerFeeTx.isFailed()) throw new RuntimeException("Offer fee tx has failed"); // TODO (woodser): proper error handling - System.out.println("Offer fee num confirmations; " + offerFeeTx.getNumConfirmations()); - System.out.println("Offer fee is locked; " + offerFeeTx.isLocked()); - boolean makerReadyToFundMultisigResponse = !offerFeeTx.isLocked(); - - String contractAsJson = null; - String contractSignature = null; - String payoutAddress = null; - - // TODO (woodser): creating and signing contract here, but should do this in own task handler - if (makerReadyToFundMultisigResponse) { - - TradingPeer taker = processModel.getTradingPeer(); - PaymentAccountPayload makerPaymentAccountPayload = processModel.getPaymentAccountPayload(trade); - checkNotNull(makerPaymentAccountPayload, "makerPaymentAccountPayload must not be null"); - PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(taker.getPaymentAccountPayload()); - boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade; - - NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getMyNodeAddress() : processModel.getTempTradingPeerNodeAddress(); - NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress(); - String id = processModel.getOffer().getId(); - - // get maker payout address - System.out.println("CREATING NEW ADDRESS ENTRY FOR TRADE ID " + id); - XmrAddressEntry makerPayoutEntry = walletService.getNewAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); - checkNotNull(taker.getPayoutAddressString(), "taker.getPayoutAddressString()"); - -// checkArgument(!walletService.getAddressEntry(id, XmrAddressEntry.Context.MULTI_SIG).isPresent(), "addressEntry must not be set here."); -// XmrAddressEntry makerAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.MULTI_SIG); -// byte[] makerMultiSigPubKey = makerAddressEntry.getPubKey(); - - checkNotNull(processModel.getAccountId(), "processModel.getAccountId() must not be null"); - - checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); - Contract contract = new Contract( - processModel.getOffer().getOfferPayload(), - trade.getTradeAmount().value, - trade.getTradePrice().getValue(), - buyerNodeAddress, - sellerNodeAddress, - trade.getArbitratorNodeAddress(), - isBuyerMakerAndSellerTaker, - processModel.getAccountId(), - taker.getAccountId(), - makerPaymentAccountPayload, - takerPaymentAccountPayload, - processModel.getPubKeyRing(), - taker.getPubKeyRing(), - makerPayoutEntry.getAddressString(), - taker.getPayoutAddressString(), - trade.getLockTime() - ); - contractAsJson = Utilities.objectToJson(contract); - contractSignature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); - payoutAddress = makerPayoutEntry.getAddressString(); - - trade.setContract(contract); - trade.setContractAsJson(contractAsJson); - trade.setMakerContractSignature(contractSignature); - System.out.println("Contract as json:"); - System.out.println(contractAsJson); - - processModel.getTradeManager().requestPersistence(); - } - - // create message to indicate if maker is ready to fund to-be-created multisig wallet - System.out.println("BUILDING READY RESPONSE"); - System.out.println("Payout address: " + payoutAddress); - System.out.println("Account id: " + processModel.getAccountId()); - MakerReadyToFundMultisigResponse message = new MakerReadyToFundMultisigResponse( - processModel.getOffer().getId(), - makerReadyToFundMultisigResponse, - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - contractAsJson, - contractSignature, - payoutAddress, - processModel.getPaymentAccountPayload(trade), - processModel.getAccountId(), - new Date().getTime()); - - // send message to taker - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getTakerNodeAddress(), - trade.getTakerPubKeyRing(), - message, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at taker: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); - complete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getTradingPeerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java deleted file mode 100644 index d9a6368849..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxsListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package bisq.core.trade.protocol.tasks.maker; - -import bisq.core.trade.Trade; -import bisq.core.trade.Trade.State; -import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; - -import bisq.common.taskrunner.TaskRunner; - -public class MakerSetupDepositTxsListener extends SetupDepositTxsListener { - - public MakerSetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected State getSeenState() { - return Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK; - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java deleted file mode 100644 index 4a6b975f67..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerVerifyTakerDepositTx.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.maker; - -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.DepositTxMessage; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.Validator; -import common.utils.GenUtils; -import lombok.extern.slf4j.Slf4j; -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroOutputWallet; -import monero.wallet.model.MoneroTxWallet; -import monero.wallet.model.MoneroWalletListener; - -@Slf4j -public class MakerVerifyTakerDepositTx extends TradeTask { - - public MakerVerifyTakerDepositTx(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - // get message - DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); - Validator.checkTradeId(processModel.getOfferId(), message); - checkNotNull(message); - trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - if (trade.getMakerDepositTxId() != null) throw new RuntimeException("Maker deposit tx already set for trade " + trade.getId() + ", this should not happen"); // TODO: ignore and nack bad requests to not show on client - - // verify deposit tx id - if (message.getDepositTxId() == null) throw new RuntimeException("Taker must provide deposit tx id"); - - // get multisig wallet - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); - - // wait until wallet sees taker deposit tx - MoneroTxWallet takerDepositTx = null; - while (takerDepositTx == null) { - try { - takerDepositTx = multisigWallet.getTx(message.getDepositTxId()); - } catch (Exception e) { - - } - GenUtils.waitFor(1000); // TODO (woodser): better way to wait for notification, use listener - } - - // TODO (woodser): verify taker deposit tx - - complete(); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java index c571b5da10..2e36a83a86 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java @@ -39,7 +39,7 @@ public class FinalizeMediatedPayoutTx extends TradeTask { // Transaction depositTx = checkNotNull(trade.getDepositTx()); // String tradeId = trade.getId(); -// TradingPeer tradingPeer = processModel.getTradingPeer(); +// TradingPeer tradingPeer = trade.getTradingPeer(); // BtcWalletService walletService = processModel.getBtcWalletService(); // Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); // Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null"); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java index ba0aad7204..2f57789de3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutSignatureMessage.java @@ -44,7 +44,7 @@ public class ProcessMediatedPayoutSignatureMessage extends TradeTask { Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - processModel.getTradingPeer().setMediatedPayoutTxSignature(checkNotNull(message.getTxSignature())); + trade.getTradingPeer().setMediatedPayoutTxSignature(checkNotNull(message.getTxSignature())); // update to the latest peer address of our peer if the message is correct trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java index cb259a27e4..5410498566 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java @@ -37,7 +37,7 @@ public class SignMediatedPayoutTx extends TradeTask { runInterceptHook(); throw new RuntimeException("SignMediatedPayoutTx not implemented for xmr"); -// TradingPeer tradingPeer = processModel.getTradingPeer(); +// TradingPeer tradingPeer = trade.getTradingPeer(); // if (processModel.getMediatedPayoutTxSignature() != null) { // log.warn("processModel.getTxSignatureFromMediation is already set"); // } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java index 2683e52602..7e3c3ffaa9 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java @@ -49,13 +49,13 @@ public class SellerFinalizesDelayedPayoutTx extends TradeTask { BtcWalletService btcWalletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] buyerMultiSigPubKey = trade.getTradingPeer().getMultiSigPubKey(); byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey(); checkArgument(Arrays.equals(sellerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] buyerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature(); + byte[] buyerSignature = trade.getTradingPeer().getDelayedPayoutTxSignature(); byte[] sellerSignature = processModel.getDelayedPayoutTxSignature(); Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeDelayedPayoutTx( diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 1369b41c12..ee29c5dc30 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -43,8 +43,8 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas Validator.checkTradeId(processModel.getOfferId(), message); checkNotNull(message); - processModel.getTradingPeer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); // TODO (woodser): verify against contract - processModel.getTradingPeer().setSignedPayoutTxHex(message.getBuyerPayoutTxSigned()); + trade.getTradingPeer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); // TODO (woodser): verify against contract + trade.getTradingPeer().setSignedPayoutTxHex(message.getBuyerPayoutTxSigned()); // 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()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java index 33d60965a4..89de38236e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java @@ -42,7 +42,7 @@ public class SellerProcessDelayedPayoutTxSignatureResponse extends TradeTask { checkNotNull(response); checkTradeId(processModel.getOfferId(), response); - processModel.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxBuyerSignature())); + trade.getTradingPeer().setDelayedPayoutTxSignature(checkNotNull(response.getDelayedPayoutTxBuyerSignature())); processModel.getTradeWalletService().sellerAddsBuyerWitnessesToDepositTx( processModel.getDepositTx(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java index 58cfe2da58..8e93931de7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java @@ -60,7 +60,7 @@ public class SellerSendDelayedPayoutTxSignatureRequest extends TradeTask { message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); processModel.getP2PService().sendEncryptedDirectMessage( peersNodeAddress, - processModel.getTradingPeer().getPubKeyRing(), + trade.getTradingPeer().getPubKeyRing(), message, new SendDirectMessageListener() { @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java index 87f8b1b8ec..a1935d70ef 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java @@ -50,10 +50,6 @@ public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask { accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness); } - System.out.println("Trade.getPayoutTx(): " + trade.getPayoutTx()); - System.out.println("trade.getPayoutTx().getTxSet(): " + trade.getPayoutTx().getTxSet()); - System.out.println("trade.getPayoutTx().getTxSet().getMultisigTxHex(): " + trade.getPayoutTx().getTxSet().getMultisigTxHex()); - return new PayoutTxPublishedMessage( id, trade.getPayoutTx().getTxSet().getMultisigTxHex(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java index 214d665631..715406e1f9 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndPublishPayoutTx.java @@ -56,22 +56,22 @@ public class SellerSignAndPublishPayoutTx extends TradeTask { // gather relevant trade info XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); - String buyerSignedPayoutTxHex = processModel.getTradingPeer().getSignedPayoutTxHex(); + MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); + String buyerSignedPayoutTxHex = trade.getTradingPeer().getSignedPayoutTxHex(); Contract contract = trade.getContract(); Offer offer = checkNotNull(trade.getOffer(), "offer must not be null"); - BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getMakerPreparedDepositTxId() : processModel.getTakerPreparedDepositTxId()).getIncomingAmount(); // TODO (woodser): redundancy of processModel.getPreparedDepositTxId() vs trade.getDepositTxId() necessary or avoidable? - BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? processModel.getTakerPreparedDepositTxId() : processModel.getMakerPreparedDepositTxId()).getIncomingAmount(); - BigInteger tradeAmount = ParsingUtils.satoshisToXmrAtomicUnits(trade.getTradeAmount().value); + 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()); System.out.println("SELLER VERIFYING PAYOUT TX"); System.out.println("Trade amount: " + trade.getTradeAmount()); System.out.println("Buyer deposit amount: " + buyerDepositAmount); System.out.println("Seller deposit amount: " + sellerDepositAmount); - BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(offer.getBuyerSecurityDeposit().add(trade.getTradeAmount()).value); + BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(offer.getBuyerSecurityDeposit().add(trade.getTradeAmount())); System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount); - BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(offer.getSellerSecurityDeposit().value); + BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(offer.getSellerSecurityDeposit()); System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount); // parse buyer-signed payout tx @@ -93,7 +93,7 @@ public class SellerSignAndPublishPayoutTx extends TradeTask { 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 // TODO (woodser): ideally change amount is 0, seen with 0 conf payout tx + // verify change address is multisig's primary address // TODO (woodser): ideally change amount is 0, seen with 0 conf payout tx if (!buyerSignedPayoutTx.getChangeAmount().equals(new BigInteger("0")) && !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 @@ -129,7 +129,7 @@ public class SellerSignAndPublishPayoutTx extends TradeTask { // checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null"); // // Offer offer = trade.getOffer(); -// TradingPeer tradingPeer = processModel.getTradingPeer(); +// TradingPeer tradingPeer = trade.getTradingPeer(); // BtcWalletService walletService = processModel.getBtcWalletService(); // String id = processModel.getOffer().getId(); // diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java index adabac92eb..5d01b4f9fe 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java @@ -59,7 +59,7 @@ public class SellerSignsDelayedPayoutTx extends TradeTask { checkArgument(Arrays.equals(sellerMultiSigPubKey, btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); + byte[] buyerMultiSigPubKey = trade.getTradingPeer().getMultiSigPubKey(); byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx( preparedDelayedPayoutTx, diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java index b484324d3f..cdc7d44ce2 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java @@ -55,7 +55,7 @@ public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask { BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = trade.getTradingPeer(); Offer offer = checkNotNull(trade.getOffer()); // params diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java index 796944b28a..1340ca4dfd 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java @@ -40,11 +40,11 @@ public class SellerAsMakerFinalizesDepositTx extends TradeTask { runInterceptHook(); if (true) throw new RuntimeException("SellerAsMakerFinalizesDepositTx not implemented for xmr"); - byte[] takersRawPreparedDepositTx = checkNotNull(processModel.getTradingPeer().getPreparedDepositTx()); + byte[] takersRawPreparedDepositTx = checkNotNull(trade.getTradingPeer().getPreparedDepositTx()); byte[] myRawPreparedDepositTx = checkNotNull(processModel.getPreparedDepositTx()); Transaction takersDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(takersRawPreparedDepositTx); Transaction myDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(myRawPreparedDepositTx); - int numTakersInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs()).size(); + int numTakersInputs = checkNotNull(trade.getTradingPeer().getRawTransactionInputs()).size(); processModel.getTradeWalletService().sellerAsMakerFinalizesDepositTx(myDepositTx, takersDepositTx, numTakersInputs); processModel.setDepositTx(myDepositTx); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java index 5bd25eae51..ad2be2f4c2 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java @@ -40,7 +40,7 @@ public class SellerAsMakerProcessDepositTxMessage extends TradeTask { // Validator.checkTradeId(processModel.getOfferId(), message); // checkNotNull(message); // -// processModel.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses())); +// trade.getTradingPeer().setPreparedDepositTx(checkNotNull(message.getDepositTxWithoutWitnesses())); // trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); // // // When we receive that message the taker has published the taker fee, so we apply it to the trade. diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java index fbf5c42754..0f09c90c00 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java @@ -76,7 +76,7 @@ public class SellerAsTakerSignsDepositTx extends TradeTask { Coin msOutputAmount = offer.getBuyerSecurityDeposit().add(offer.getSellerSecurityDeposit()).add(trade.getTxFee()) .add(checkNotNull(trade.getTradeAmount())); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = trade.getTradingPeer(); Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx( true, @@ -97,7 +97,7 @@ public class SellerAsTakerSignsDepositTx extends TradeTask { } catch (Throwable t) { Contract contract = trade.getContract(); if (contract != null) - contract.printDiff(processModel.getTradingPeer().getContractAsJson()); + contract.printDiff(trade.getTradingPeer().getContractAsJson()); failed(t); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java deleted file mode 100644 index 6e0c3badef..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/FundMultisig.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.util.ParsingUtils; - -import bisq.network.p2p.SendDirectMessageListener; - -import bisq.common.taskrunner.TaskRunner; - -import java.math.BigInteger; - -import java.util.List; -import java.util.UUID; - -import lombok.extern.slf4j.Slf4j; - - - -import monero.wallet.MoneroWallet; -import monero.wallet.model.MoneroDestination; -import monero.wallet.model.MoneroTxConfig; -import monero.wallet.model.MoneroTxWallet; - -@Slf4j -public class FundMultisig extends TradeTask { - @SuppressWarnings({"unused"}) - public FundMultisig(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - if (trade.getMakerDepositTxId() != null) throw new RuntimeException("Maker deposit tx already set"); // TODO (woodser): proper error handling - if (trade.getTakerDepositTxId() != null) throw new RuntimeException("Taker deposit tx already set"); - - // decide who goes first - boolean takerGoesFirst = true; // TODO (woodser): decide who goes first based on rep? - - // taker and maker fund multisig - String takerDepositTxId = null; - if (takerGoesFirst) takerDepositTxId = takerFundMultisig(); - makerFundMultisig(takerDepositTxId); - } catch (Throwable t) { - failed(t); - } - } - - private String takerFundMultisig() { - - // collect parameters for transfer to multisig - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - MoneroWallet wallet = walletService.getWallet(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(processModel.getTrade().getId()); - String multisigAddress = multisigWallet.getPrimaryAddress(); - boolean tradeReserved = trade.getTakerFeeTxId() != null && trade.getState() == Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX; - - // if trade is reserved, fund multisig from reserved account - if (tradeReserved) { - XmrAddressEntry addressEntry = walletService.getAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.RESERVED_FOR_TRADE).get(); - int accountIndex = addressEntry.getAccountIndex(); - if (!wallet.getBalance(accountIndex).equals(wallet.getUnlockedBalance(accountIndex)) || wallet.getBalance(accountIndex).equals(BigInteger.valueOf(0))) { - throw new RuntimeException("Reserved trade account " + accountIndex + " balance expected to be fully available. Balance: " + wallet.getBalance(accountIndex) + ", Unlocked Balance: " + wallet.getUnlockedBalance(accountIndex)); - } - List txs = wallet.sweepUnlocked(new MoneroTxConfig() - .setAccountIndex(accountIndex) - .setAddress(multisigAddress) - .setCanSplit(false) - .setRelay(true)); - if (txs.size() != 1) throw new RuntimeException("Sweeping reserved trade account to multisig expected to create exactly 1 transaction"); - processModel.setTakerPreparedDepositTxId(txs.get(0).getHash()); - trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX); - System.out.println("SUCCESSFULLY SWEPT RESERVED TRADE ACCOUNT TO MULTISIG"); - System.out.println(txs.get(0)); - return txs.get(0).getHash(); - } - - // otherwise fund multisig from account 0 and pay taker fee in one transaction - else { - String tradeFeeAddress = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode - MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig() - .setAccountIndex(0) - .setDestinations( - new MoneroDestination(tradeFeeAddress, ParsingUtils.satoshisToXmrAtomicUnits(trade.getTakerFee().value)), - new MoneroDestination(multisigAddress, ParsingUtils.satoshisToXmrAtomicUnits(processModel.getFundsNeededForTradeAsLong()))) - .setRelay(true)); - System.out.println("SUCCESSFULLY TRANSFERRED FROM ACCOUNT 0 TO MULTISIG AND PAID FEE"); - System.out.println(tx); - trade.setTakerFeeTxId(tx.getHash()); - processModel.setTakerPreparedDepositTxId(tx.getHash()); - trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX); - return tx.getHash(); - } - } - - private void makerFundMultisig(String takerDepositTxId) { - - // create message to initialize trade - DepositTxMessage request = new DepositTxMessage( - UUID.randomUUID().toString(), - processModel.getOffer().getId(), - processModel.getMyNodeAddress(), - trade.getTakerFeeTxId(), - takerDepositTxId); - - // send request to maker - // TODO (woodser): get maker deposit tx id by processing DepositTxMessage or DepositTxRequest/DepositTxResponse - log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getMakerNodeAddress(), - trade.getMakerPubKeyRing(), - request, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at arbitrator: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); - //trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX); // TODO (woodser): Trade.State.MAKER_PUBLISHED_DEPOSIT_TX - if (takerDepositTxId == null) takerFundMultisig(); // send taker funds if not already - complete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getArbitratorNodeAddress(), errorMessage); - appendToErrorMessage("Sending request failed: request=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java index 11218d6fef..c28b68fd71 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java @@ -47,7 +47,7 @@ public class TakerProcessesInputsForDepositTxResponse extends TradeTask { checkTradeId(processModel.getOfferId(), response); checkNotNull(response); - TradingPeer tradingPeer = processModel.getTradingPeer(); + TradingPeer tradingPeer = trade.getTradingPeer(); tradingPeer.setPaymentAccountPayload(checkNotNull(response.getMakerPaymentAccountPayload())); tradingPeer.setAccountId(nonEmptyStringOf(response.getMakerAccountId())); tradingPeer.setMultiSigPubKey(checkNotNull(response.getMakerMultiSigPubKey())); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java deleted file mode 100644 index f3fe115421..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesMakerDepositTxMessage.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.trade.Trade; -import bisq.core.trade.messages.DepositTxMessage; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.common.taskrunner.TaskRunner; - -import lombok.extern.slf4j.Slf4j; - -import static bisq.core.util.Validator.checkTradeId; -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class TakerProcessesMakerDepositTxMessage extends TradeTask { - @SuppressWarnings({"unused"}) - public TakerProcessesMakerDepositTxMessage(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - log.debug("current trade state " + trade.getState()); - DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage(); - checkTradeId(processModel.getOfferId(), message); - checkNotNull(message); - - // TODO (woodser): verify that deposit amount + tx fee = security deposit + trade fee (+ trade amount), or require exact security deposit to multisig? - processModel.setMakerPreparedDepositTxId(message.getDepositTxId()); - complete(); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java new file mode 100644 index 0000000000..8388694dfa --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java @@ -0,0 +1,74 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +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 { + + public TakerReservesTradeFunds(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // 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.createReserveTx(model.getXmrWalletService(), trade.getId(), takerFee, returnAddress, depositAmount); + + // freeze trade funds + // TODO (woodser): synchronize to handle potential race condition where concurrent trades freeze each other's outputs + List frozenKeyImages = new ArrayList(); + MoneroWallet wallet = model.getXmrWalletService().getWallet(); + for (MoneroOutput input : reserveTx.getInputs()) { + frozenKeyImages.add(input.getKeyImage().getHex()); + wallet.freezeOutput(input.getKeyImage().getHex()); + } + + // save process state + // TODO (woodser): persist + processModel.setReserveTx(reserveTx); + processModel.setReserveTxHash(reserveTx.getHash()); + processModel.setFrozenKeyImages(frozenKeyImages); + trade.setTakerFeeTxId(reserveTx.getHash()); + //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" + + t.getMessage()); + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java deleted file mode 100644 index 1e437d4d1b..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInitTradeRequests.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.support.dispute.mediation.mediator.Mediator; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.user.User; - -import bisq.network.p2p.NodeAddress; -import bisq.network.p2p.SendDirectMessageListener; - -import bisq.common.app.Version; -import bisq.common.crypto.Sig; -import bisq.common.taskrunner.TaskRunner; - -import com.google.common.base.Charsets; - -import java.util.Date; -import java.util.List; -import java.util.UUID; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class TakerSendInitTradeRequests extends TradeTask { - - private boolean makerAck = false; - private boolean arbitratorAck = false; - - @SuppressWarnings({"unused"}) - public TakerSendInitTradeRequests(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - // collect fields for requests - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - String offerId = processModel.getOffer().getId(); - String takerPayoutAddress = walletService.getNewAddressEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); - walletService.getWallet().save(); - checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); - //checkNotNull(trade.getTakerFeeTxId(), "TakeOfferFeeTxId must not be null"); - final User user = processModel.getUser(); - checkNotNull(user, "User must not be null"); - - // must have mediator address // TODO (woodser): using mediator instead of arbitrator because it's initially assigned, keep or replace with arbitrator role? - final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); - checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null"); - Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(trade.getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // TODO (woodser): switch to arbitrator? - processModel.getArbitrator().setPubKeyRing(mediator.getPubKeyRing()); - trade.setArbitratorPubKeyRing(processModel.getArbitrator().getPubKeyRing()); - trade.setMakerPubKeyRing(processModel.getTradingPeer().getPubKeyRing()); - - // Taker has to use offerId as nonce (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed) - // He cannot manipulate the offerId - so we avoid to have a challenge protocol for passing the nonce we want to get signed. - final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null"); - byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8)); - - // create message to initialize trade - InitTradeRequest message = new InitTradeRequest( - offerId, - processModel.getMyNodeAddress(), - processModel.getPubKeyRing(), - trade.getTradeAmount().value, - trade.getTradePrice().getValue(), - trade.getTxFee().getValue(), - trade.getTakerFee().getValue(), - takerPayoutAddress, - paymentAccountPayload, - processModel.getAccountId(), - trade.getTakerFeeTxId(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - sig, - new Date().getTime(), - trade.getTakerNodeAddress(), - trade.getMakerNodeAddress(), - trade.getArbitratorNodeAddress()); - - System.out.println("TakerSendsInitTradeRequest sending message:"); - System.out.println(message); - - // send request to arbitrator - log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getArbitratorNodeAddress(), - trade.getArbitratorPubKeyRing(), - message, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); - arbitratorAck = true; - checkComplete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - - // send request to maker - log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getMakerNodeAddress()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getMakerNodeAddress(), - trade.getMakerPubKeyRing(), - message, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); - makerAck = true; - checkComplete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - - } catch (Throwable t) { - failed(t); - } - } - - private void checkComplete() { - if (makerAck && arbitratorAck) complete(); - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java deleted file mode 100644 index 13b72563ed..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.user.User; - -import bisq.network.p2p.NodeAddress; -import bisq.network.p2p.SendDirectMessageListener; - -import bisq.common.app.Version; -import bisq.common.crypto.Sig; -import bisq.common.taskrunner.TaskRunner; - -import org.bitcoinj.core.Coin; - -import com.google.common.base.Charsets; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import lombok.extern.slf4j.Slf4j; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class TakerSendInputsForDepositTxRequest extends TradeTask { - public TakerSendInputsForDepositTxRequest(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); - String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be null"); - User user = checkNotNull(processModel.getUser(), "User must not be null"); - List acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses() == null ? - new ArrayList<>() : - user.getAcceptedArbitratorAddresses(); - List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); - List acceptedRefundAgentAddresses = user.getAcceptedRefundAgentAddresses() == null ? - new ArrayList<>() : - user.getAcceptedRefundAgentAddresses(); - // We don't check for arbitrators as they should vanish soon - checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null"); - // We also don't check for refund agents yet as we don't want to restrict us too much. They are not mandatory. - - BtcWalletService walletService = processModel.getBtcWalletService(); - String id = processModel.getOffer().getId(); - - Optional optionalMultiSigAddressEntry = walletService.getAddressEntry(id, - AddressEntry.Context.MULTI_SIG); - checkArgument(optionalMultiSigAddressEntry.isPresent(), - "MULTI_SIG addressEntry must have been already set here."); - AddressEntry multiSigAddressEntry = optionalMultiSigAddressEntry.get(); - byte[] takerMultiSigPubKey = multiSigAddressEntry.getPubKey(); - processModel.setMyMultiSigPubKey(takerMultiSigPubKey); - - Optional optionalPayoutAddressEntry = walletService.getAddressEntry(id, - AddressEntry.Context.TRADE_PAYOUT); - checkArgument(optionalPayoutAddressEntry.isPresent(), - "TRADE_PAYOUT multiSigAddressEntry must have been already set here."); - AddressEntry payoutAddressEntry = optionalPayoutAddressEntry.get(); - String takerPayoutAddressString = payoutAddressEntry.getAddressString(); - - String offerId = processModel.getOfferId(); - - // Taker has to use offerId as nonce (he cannot manipulate that - so we avoid to have a challenge - // protocol for passing the nonce we want to get signed) - // This is used for verifying the peers account age witness - PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), - "processModel.getPaymentAccountPayload(trade) must not be null"); - byte[] signatureOfNonce = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), - offerId.getBytes(Charsets.UTF_8)); - - InputsForDepositTxRequest request = new InputsForDepositTxRequest( - offerId, - processModel.getMyNodeAddress(), - tradeAmount.value, - trade.getTradePrice().getValue(), - trade.getTxFee().getValue(), - trade.getTakerFee().getValue(), - true, - processModel.getRawTransactionInputs(), - processModel.getChangeOutputValue(), - processModel.getChangeOutputAddress(), - takerMultiSigPubKey, - takerPayoutAddressString, - processModel.getPubKeyRing(), - paymentAccountPayload, - processModel.getAccountId(), - takerFeeTxId, - acceptedArbitratorAddresses, - acceptedMediatorAddresses, - acceptedRefundAgentAddresses, - trade.getArbitratorNodeAddress(), - trade.getMediatorNodeAddress(), - trade.getRefundAgentNodeAddress(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - signatureOfNonce, - new Date().getTime()); - log.info("Send {} with offerId {} and uid {} to peer {}", - request.getClass().getSimpleName(), request.getTradeId(), - request.getUid(), trade.getTradingPeerNodeAddress()); - - processModel.getTradeManager().requestPersistence(); - - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getTradingPeerNodeAddress(), - processModel.getTradingPeer().getPubKeyRing(), - request, - new SendDirectMessageListener() { - public void onArrived() { - log.info("{} arrived at peer: offerId={}; uid={}", - request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); - complete(); - } - - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", - request.getClass().getSimpleName(), request.getUid(), - trade.getTradingPeerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java deleted file mode 100644 index 1fc6bfbb0d..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendReadyToFundMultisigRequest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MakerReadyToFundMultisigRequest; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.network.p2p.SendDirectMessageListener; - -import bisq.common.app.Version; -import bisq.common.taskrunner.TaskRunner; - -import java.util.UUID; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class TakerSendReadyToFundMultisigRequest extends TradeTask { - @SuppressWarnings({"unused"}) - public TakerSendReadyToFundMultisigRequest(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - // create message ask maker if ready - MakerReadyToFundMultisigRequest request = new MakerReadyToFundMultisigRequest( - processModel.getOffer().getId(), - processModel.getMyNodeAddress(), - processModel.getPubKeyRing(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion()); - - // send request to maker - log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getMakerNodeAddress(), - trade.getMakerPubKeyRing(), - request, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at maker: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); - complete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending request failed: request=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java new file mode 100644 index 0000000000..8519a82235 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java @@ -0,0 +1,103 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.protocol.tasks.taker; + +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.protocol.tasks.TradeTask; + +import bisq.network.p2p.SendDirectMessageListener; + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TakerSendsInitTradeRequestToArbitrator extends TradeTask { + + @SuppressWarnings({"unused"}) + public TakerSendsInitTradeRequestToArbitrator(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // get primary arbitrator + Mediator arbitrator = processModel.getUser().getAcceptedMediatorByAddress(trade.getArbitratorNodeAddress()); + if (arbitrator == null) throw new RuntimeException("Cannot get arbitrator instance from node address"); // TODO (woodser): null if arbitrator goes offline or never seen? + + // save pub keys + processModel.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing()); + trade.setArbitratorPubKeyRing(processModel.getArbitrator().getPubKeyRing()); + trade.setMakerPubKeyRing(trade.getTradingPeer().getPubKeyRing()); + + // send trade request to arbitrator + InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); + InitTradeRequest arbitratorRequest = new InitTradeRequest( + makerRequest.getTradeId(), + makerRequest.getSenderNodeAddress(), + makerRequest.getPubKeyRing(), + makerRequest.getTradeAmount(), + makerRequest.getTradePrice(), + makerRequest.getTradeFee(), + makerRequest.getAccountId(), + makerRequest.getPaymentAccountId(), + makerRequest.getPaymentMethodId(), + makerRequest.getUid(), + Version.getP2PMessageVersion(), + makerRequest.getAccountAgeWitnessSignatureOfOfferId(), + makerRequest.getCurrentDate(), + makerRequest.getMakerNodeAddress(), + makerRequest.getTakerNodeAddress(), + trade.getArbitratorNodeAddress(), + processModel.getReserveTx().getHash(), + processModel.getReserveTx().getFullHex(), + processModel.getReserveTx().getKey(), + makerRequest.getPayoutAddress(), + processModel.getMakerSignature()); + + // send request to arbitrator + System.out.println("SENDING INIT TRADE REQUEST TO ARBITRATOR!"); + log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getArbitratorNodeAddress(), + trade.getArbitratorPubKeyRing(), + arbitratorRequest, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}; uid={}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid()); + } + @Override // TODO (woodser): handle case where primary arbitrator is unavailable so use backup arbitrator, distinguish offline from bad ack + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + arbitratorRequest + "\nerrorMessage=" + errorMessage); + failed(); + } + } + ); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java deleted file mode 100644 index 64c0dedfd1..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSetupDepositTxsListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.trade.Trade; -import bisq.core.trade.Trade.State; -import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; - -import bisq.common.taskrunner.TaskRunner; - -public class TakerSetupDepositTxsListener extends SetupDepositTxsListener { - - public TakerSetupDepositTxsListener(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected State getSeenState() { - return Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK; - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java deleted file mode 100644 index 1133365086..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks.taker; - -import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Contract; -import bisq.core.trade.SellerAsTakerTrade; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.MakerReadyToFundMultisigResponse; -import bisq.core.trade.protocol.TradingPeer; -import bisq.core.trade.protocol.tasks.TradeTask; - -import bisq.network.p2p.NodeAddress; - -import bisq.common.crypto.Hash; -import bisq.common.crypto.Sig; -import bisq.common.taskrunner.TaskRunner; -import bisq.common.util.Utilities; - -import org.bitcoinj.core.Coin; - -import lombok.extern.slf4j.Slf4j; - -import static bisq.core.util.Validator.nonEmptyStringOf; -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class TakerVerifyAndSignContract extends TradeTask { - public TakerVerifyAndSignContract(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - - //String takerFeeTxId = checkNotNull(processModel.getTakeOfferFeeTxId())m; // TODO (woodser): take offer fee tx id removed from contract - - // collect maker info from response - // TODO (woodser): this is not right place to collect maker contract info - TradingPeer maker = processModel.getTradingPeer(); - MakerReadyToFundMultisigResponse response = (MakerReadyToFundMultisigResponse) processModel.getTradeMessage(); - maker.setPaymentAccountPayload(response.getMakerPaymentAccountPayload()); - System.out.println("MAKER PAYOUT ADDRESS: " + maker.getPayoutAddressString()); - maker.setPayoutAddressString(response.getMakerPayoutAddressString()); - System.out.println("MAKER ACCOUNT ID: " + maker.getAccountId()); - maker.setPayoutAddressString(response.getMakerPayoutAddressString()); - TradingPeer tradingPeer = processModel.getTradingPeer(); - tradingPeer.setPaymentAccountPayload(checkNotNull(response.getMakerPaymentAccountPayload())); - tradingPeer.setAccountId(nonEmptyStringOf(response.getMakerAccountId())); - tradingPeer.setContractAsJson(nonEmptyStringOf(response.getMakerContractAsJson())); - tradingPeer.setContractSignature(nonEmptyStringOf(response.getMakerContractSignature())); - tradingPeer.setPayoutAddressString(nonEmptyStringOf(response.getMakerPayoutAddressString())); - tradingPeer.setCurrentDate(response.getCurrentDate()); - PaymentAccountPayload makerPaymentAccountPayload = checkNotNull(maker.getPaymentAccountPayload()); - PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade)); - - boolean isBuyerMakerAndSellerTaker = trade instanceof SellerAsTakerTrade; - NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? - processModel.getTempTradingPeerNodeAddress() : - processModel.getMyNodeAddress(); - NodeAddress sellerNodeAddress = isBuyerMakerAndSellerTaker ? - processModel.getMyNodeAddress() : - processModel.getTempTradingPeerNodeAddress(); - - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - String id = processModel.getOffer().getId(); - XmrAddressEntry takerPayoutAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.TRADE_PAYOUT); - String takerPayoutAddressString = takerPayoutAddressEntry.getAddressString(); - - // TODO (woodser): xmr not using pub key ring for multisig address verification, needed? -// AddressEntry takerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); -// byte[] takerMultiSigPubKey = processModel.getMyMultiSigPubKey(); -// checkArgument(Arrays.equals(takerMultiSigPubKey, -// takerMultiSigAddressEntry.getPubKey()), -// "takerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); - - Coin tradeAmount = checkNotNull(trade.getTradeAmount()); - Contract contract = new Contract( - processModel.getOffer().getOfferPayload(), - tradeAmount.value, - trade.getTradePrice().getValue(), - //takerFeeTxId, - buyerNodeAddress, - sellerNodeAddress, - trade.getArbitratorNodeAddress(), // TODO (woodser): updated from mediator, update and use rest of TakerVerifyAndSignContract - isBuyerMakerAndSellerTaker, - maker.getAccountId(), - processModel.getAccountId(), - makerPaymentAccountPayload, - takerPaymentAccountPayload, - maker.getPubKeyRing(), - processModel.getPubKeyRing(), - maker.getPayoutAddressString(), - takerPayoutAddressString, - 0 - ); - String contractAsJson = Utilities.objectToJson(contract); - - if (!contractAsJson.equals(processModel.getTradingPeer().getContractAsJson())) { - contract.printDiff(processModel.getTradingPeer().getContractAsJson()); - failed("Contracts are not matching"); - } - - String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); - trade.setContract(contract); - trade.setContractAsJson(contractAsJson); - - byte[] contractHash = Hash.getSha256Hash(checkNotNull(contractAsJson)); - trade.setContractHash(contractHash); - - trade.setTakerContractSignature(signature); - - processModel.getTradeManager().requestPersistence(); - try { - checkNotNull(maker.getPubKeyRing(), "maker.getPubKeyRing() must nto be null"); - Sig.verify(maker.getPubKeyRing().getSignaturePubKey(), - contractAsJson, - maker.getContractSignature()); - complete(); - } catch (Throwable t) { - failed("Contract signature verification failed. " + t.getMessage()); - } - } catch (Throwable t) { - failed(t); - } - } -} diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java index da74e6e6e9..648627452b 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java @@ -79,15 +79,15 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl extraDataMap.put(OfferPayload.REFERRAL_ID, referralId); } - NodeAddress mediatorNodeAddress = trade.getMediatorNodeAddress(); - if (mediatorNodeAddress != null) { - // The first 4 chars are sufficient to identify a mediator. + NodeAddress arbitratorNodeAddress = trade.getArbitratorNodeAddress(); + if (arbitratorNodeAddress != null) { + // The first 4 chars are sufficient to identify a arbitrator. // For testing with regtest/localhost we use the full address as its localhost and would result in - // same values for multiple mediators. + // same values for multiple arbitrators. String address = isTorNetworkNode ? - mediatorNodeAddress.getFullAddress().substring(0, 4) : - mediatorNodeAddress.getFullAddress(); - extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address); + arbitratorNodeAddress.getFullAddress().substring(0, 4) : + arbitratorNodeAddress.getFullAddress(); + extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address); } Offer offer = trade.getOffer(); @@ -97,13 +97,13 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl trade.getTradePrice(), trade.getTradeAmount(), trade.getDate(), - trade.getMakerDepositTxId(), - trade.getTakerDepositTxId(), + trade.getMaker().getDepositTxHash(), + trade.getTaker().getDepositTxHash(), extraDataMap); } @SuppressWarnings("SpellCheckingInspection") - public static final String MEDIATOR_ADDRESS = "medAddr"; + public static final String ARBITRATOR_ADDRESS = "arbAddr"; @SuppressWarnings("SpellCheckingInspection") public static final String REFUND_AGENT_ADDRESS = "refAddr"; diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java index 2fdf278041..a25367ce75 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsConverter.java @@ -156,7 +156,7 @@ public class TradeStatisticsConverter { private static TradeStatistics3 convertToTradeStatistics3(TradeStatistics2 tradeStatistics2) { Map extraDataMap = tradeStatistics2.getExtraDataMap(); - String mediator = extraDataMap != null ? extraDataMap.get(TradeStatistics2.MEDIATOR_ADDRESS) : null; // TODO (woodser): using mediator as arbitrator + String mediator = extraDataMap != null ? extraDataMap.get(TradeStatistics2.ARBITRATOR_ADDRESS) : null; // TODO (woodser): using mediator as arbitrator long time = tradeStatistics2.getTradeDate().getTime(); // We need to avoid that we duplicate tradeStatistics2 objects in case both traders have not updated yet. // Before v1.4.0 both traders published the trade statistics. If one trader has updated he will check diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java index 6d386437be..42ac3272b6 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java @@ -64,7 +64,7 @@ public class XmrTxProofModel implements AssetTxProofModel { amount = DevEnv.isDevMode() ? XmrTxProofModel.DEV_AMOUNT : // For dev testing we need to add the matching address to the dev tx key and dev view key volume != null ? volume.getValue() * 10000L : 0L; // XMR satoshis have 12 decimal places vs. bitcoin's 8 - PaymentAccountPayload sellersPaymentAccountPayload = checkNotNull(trade.getContract()).getSellerPaymentAccountPayload(); + PaymentAccountPayload sellersPaymentAccountPayload = checkNotNull(trade.getSeller().getPaymentAccountPayload()); recipientAddress = DevEnv.isDevMode() ? XmrTxProofModel.DEV_ADDRESS : // For dev testing we need to add the matching address to the dev tx key and dev view key ((AssetsAccountPayload) sellersPaymentAccountPayload).getAddress(); diff --git a/core/src/main/java/bisq/core/util/ParsingUtils.java b/core/src/main/java/bisq/core/util/ParsingUtils.java index f24eeb47c1..b840eebb84 100644 --- a/core/src/main/java/bisq/core/util/ParsingUtils.java +++ b/core/src/main/java/bisq/core/util/ParsingUtils.java @@ -18,22 +18,30 @@ import lombok.extern.slf4j.Slf4j; public class ParsingUtils { /** - * Temporary multiplier to convert Coin satoshis (denominating XMR centineros) to XMR atomic units. - * - * TODO (woodser): replace bitcoinj/Coin entirely? + * Multiplier to convert centineros (the base XMR unit of Coin) to atomic units. */ - private static BigInteger XMR_SATOSHI_MULTIPLIER = BigInteger.valueOf(10000); // TODO (woodser): make this private and expose satoshisToXmrAtomicUnits() - + private static BigInteger CENTINEROS_AU_MULTIPLIER = BigInteger.valueOf(10000); + /** - * Converts Coin satoshis (the base unit throughout Bisq) to XMR atomic units. + * Convert Coin (denominated in centineros) to atomic units. * - * @param satoshis represents an amount in XMR atomic units scaled to a long - * @return BigInteger is the equivalent amount in XMR atomic units + * @param coin has an amount denominated in centineros + * @return BigInteger the coin amount denominated in atomic units */ - public static BigInteger satoshisToXmrAtomicUnits(long satoshis) { - return BigInteger.valueOf(satoshis).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER); + public static BigInteger coinToAtomicUnits(Coin coin) { + return centinerosToAtomicUnits(coin.value); } + /** + * Convert centineros (the base unit of Coin) to atomic units. + * + * @param centineros denominates an amount of XMR in centineros + * @return BigInteger the amount denominated in atomic units + */ + public static BigInteger centinerosToAtomicUnits(long centineros) { + return BigInteger.valueOf(centineros).multiply(ParsingUtils.CENTINEROS_AU_MULTIPLIER); + } + public static Coin parseToCoin(String input, CoinFormatter coinFormatter) { return parseToCoin(input, coinFormatter.getMonetaryFormat()); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 699d17bc53..2762597f4e 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -393,6 +393,7 @@ offerbook.warning.noMatchingAccount.headline=No matching payment account. offerbook.warning.noMatchingAccount.msg=This offer uses a payment method you haven't set up yet. \n\nWould you like to set up a new payment account now? offerbook.warning.counterpartyTradeRestrictions=This offer cannot be taken due to counterparty trade restrictions +offerbook.warning.signatureNotValidated=This offer cannot be taken because its signature is not validated offerbook.warning.newVersionAnnouncement=With this version of the software, trading peers can verify and sign each others' payment accounts to create a network of trusted payment accounts.\n\n\ After successfully trading with a peer with a verified payment account, your payment account will be signed and trading limits will be lifted after a certain time interval (length of this interval is based on the verification method).\n\n\ diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java index 7bb019cbea..0155ff370c 100644 --- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java @@ -195,6 +195,8 @@ public class AccountAgeWitnessServiceTest { "contractAsJson", null, null, + sellerPaymentAccountPayload, + buyerPaymentAccountPayload, null, true, SupportType.ARBITRATION)); @@ -230,9 +232,10 @@ public class AccountAgeWitnessServiceTest { when(contract.getTradeAmount()).thenReturn(Coin.parseCoin("0.01")); when(contract.getBuyerPubKeyRing()).thenReturn(buyerPubKeyRing); when(contract.getSellerPubKeyRing()).thenReturn(sellerPubKeyRing); - when(contract.getBuyerPaymentAccountPayload()).thenReturn(buyerPaymentAccountPayload); - when(contract.getSellerPaymentAccountPayload()).thenReturn(sellerPaymentAccountPayload); when(contract.getOfferPayload()).thenReturn(mock(OfferPayload.class)); + when(contract.isBuyerMakerAndSellerTaker()).thenReturn(false); + assertEquals(disputes.get(0).getBuyerPaymentAccountPayload(), buyerPaymentAccountPayload); + assertEquals(disputes.get(0).getSellerPaymentAccountPayload(), sellerPaymentAccountPayload); List items = service.getTraderPaymentAccounts(now, getPaymentMethodById(PaymentMethod.SEPA_ID), disputes); assertEquals(2, items.size()); diff --git a/core/src/test/java/bisq/core/offer/OfferMaker.java b/core/src/test/java/bisq/core/offer/OfferMaker.java index aed49ca2d2..8f50b22be0 100644 --- a/core/src/test/java/bisq/core/offer/OfferMaker.java +++ b/core/src/test/java/bisq/core/offer/OfferMaker.java @@ -48,8 +48,6 @@ public class OfferMaker { lookup.valueOf(minAmount, 100000L), lookup.valueOf(baseCurrencyCode, "XMR"), lookup.valueOf(counterCurrencyCode, "USD"), - null, - null, "SEPA", "", null, @@ -73,7 +71,9 @@ public class OfferMaker { false, null, null, - 0)); + 0, + null, + null)); public static final Maker btcUsdOffer = a(Offer); } diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index afeb246cd1..d456e113df 100644 --- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java +++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.*; public class OpenOfferManagerTest { private PersistenceManager> persistenceManager; + private PersistenceManager signedOfferPersistenceManager; private CoreContext coreContext; @Before @@ -34,12 +35,14 @@ public class OpenOfferManagerTest { var corruptedStorageFileHandler = mock(CorruptedStorageFileHandler.class); var storageDir = Files.createTempDirectory("storage").toFile(); persistenceManager = new PersistenceManager<>(storageDir, null, corruptedStorageFileHandler); + signedOfferPersistenceManager = new PersistenceManager<>(storageDir, null, corruptedStorageFileHandler); coreContext = new CoreContext(); } @After public void tearDown() { persistenceManager.shutdown(); + signedOfferPersistenceManager.shutdown(); } @Test @@ -69,7 +72,8 @@ public class OpenOfferManagerTest { null, null, null, - persistenceManager); + persistenceManager, + signedOfferPersistenceManager); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -117,7 +121,8 @@ public class OpenOfferManagerTest { null, null, null, - persistenceManager); + persistenceManager, + signedOfferPersistenceManager); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -158,7 +163,8 @@ public class OpenOfferManagerTest { null, null, null, - persistenceManager); + persistenceManager, + signedOfferPersistenceManager); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java index 5e0ae652c1..926ac0d4c2 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainView.java +++ b/desktop/src/main/java/bisq/desktop/main/MainView.java @@ -327,7 +327,7 @@ public class MainView extends InitializableView secondaryNav.setAlignment(Pos.CENTER); HBox priceAndBalance = new HBox(marketPriceBox.second, getNavigationSeparator(), availableBalanceBox.second, - getNavigationSeparator(), reservedBalanceBox.second, getNavigationSeparator(), lockedBalanceBox.second); + getNavigationSeparator(), lockedBalanceBox.second, getNavigationSeparator(), reservedBalanceBox.second); priceAndBalance.setMaxHeight(41); priceAndBalance.setAlignment(Pos.CENTER); diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java index d85ab457f8..f3f9286f0c 100644 --- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java +++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java @@ -24,9 +24,9 @@ import bisq.desktop.components.TitledGroupBg; import bisq.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; import bisq.core.offer.placeoffer.tasks.AddToOfferBook; +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.SetupDepositTxsListener; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; @@ -43,12 +43,8 @@ import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForD import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage; import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx; -import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract; -import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx; -import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime; -import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx; @@ -67,11 +63,7 @@ import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDeposi import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; -import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; -import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest; -import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener; -import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; import bisq.common.taskrunner.Task; @@ -118,7 +110,7 @@ public class DebugView extends InitializableView { addGroup("PlaceOfferProtocol", FXCollections.observableArrayList(Arrays.asList( ValidateOffer.class, - MakerCreateFeeTx.class, + MakerReservesTradeFunds.class, AddToOfferBook.class) )); @@ -129,12 +121,10 @@ public class DebugView extends InitializableView { TakerVerifyMakerFeePayment.class, TakerCreateFeeTx.class, // TODO (woodser): rename to TakerCreateFeeTx SellerAsTakerCreatesDepositTxInputs.class, - TakerSendInputsForDepositTxRequest.class, TakerProcessesInputsForDepositTxResponse.class, ApplyFilter.class, VerifyPeersAccountAgeWitness.class, - TakerVerifyAndSignContract.class, TakerPublishFeeTx.class, SellerAsTakerSignsDepositTx.class, SellerCreatesDelayedPayoutTx.class, @@ -144,7 +134,6 @@ public class DebugView extends InitializableView { SellerSignsDelayedPayoutTx.class, SellerFinalizesDelayedPayoutTx.class, //SellerSendsDepositTxAndDelayedPayoutTxMessage.class, - TakerProcessesMakerDepositTxMessage.class, SellerPublishesDepositTx.class, SellerPublishesTradeStatistics.class, @@ -162,14 +151,11 @@ public class DebugView extends InitializableView { )); addGroup("BuyerAsMakerProtocol", FXCollections.observableArrayList(Arrays.asList( - MakerProcessesInputsForDepositTxRequest.class, ApplyFilter.class, VerifyPeersAccountAgeWitness.class, MakerVerifyTakerFeePayment.class, MakerSetsLockTime.class, - MakerCreateAndSignContract.class, BuyerAsMakerCreatesAndSignsDepositTx.class, - MakerSetupDepositTxsListener.class, BuyerAsMakerSendsInputsForDepositTxResponse.class, BuyerProcessDelayedPayoutTxSignatureRequest.class, @@ -198,15 +184,12 @@ public class DebugView extends InitializableView { TakerVerifyMakerFeePayment.class, TakerCreateFeeTx.class, BuyerAsTakerCreatesDepositTxInputs.class, - TakerSendInputsForDepositTxRequest.class, TakerProcessesInputsForDepositTxResponse.class, ApplyFilter.class, VerifyPeersAccountAgeWitness.class, - TakerVerifyAndSignContract.class, TakerPublishFeeTx.class, BuyerAsTakerSignsDepositTx.class, - TakerSetupDepositTxsListener.class, BuyerAsTakerSendsDepositTxMessage.class, BuyerProcessDelayedPayoutTxSignatureRequest.class, @@ -227,15 +210,12 @@ public class DebugView extends InitializableView { )); addGroup("SellerAsMakerProtocol", FXCollections.observableArrayList(Arrays.asList( - MakerProcessesInputsForDepositTxRequest.class, ApplyFilter.class, VerifyPeersAccountAgeWitness.class, MakerVerifyTakerFeePayment.class, MakerSetsLockTime.class, - MakerCreateAndSignContract.class, SellerAsMakerCreatesUnsignedDepositTx.class, SellerAsMakerSendsInputsForDepositTxResponse.class, - SetupDepositTxsListener.class, //SellerAsMakerProcessDepositTxMessage.class, MakerRemovesOpenOffer.class, diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java index 7d5eff0061..d6fa8777ff 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalListItem.java @@ -55,7 +55,7 @@ class WithdrawalListItem { // balance balanceLabel = new AutoTooltipLabel(); - balanceListener = new XmrBalanceListener(addressEntry.getAccountIndex()) { + balanceListener = new XmrBalanceListener(addressEntry.getSubaddressIndex()) { @Override public void onBalanceChanged(BigInteger balance) { updateBalance(); @@ -71,7 +71,7 @@ class WithdrawalListItem { } private void updateBalance() { - balance = walletService.getBalanceForAccount(addressEntry.getAccountIndex()); + balance = walletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); if (balance != null) balanceLabel.setText(formatter.formatCoin(this.balance)); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 2fc9f1ea8c..79bebdc06f 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -188,7 +188,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs useMarketBasedPrice.set(preferences.isUsePercentageBasedPrice()); buyerSecurityDeposit.set(Restrictions.getMinBuyerSecurityDepositAsPercent()); - xmrBalanceListener = new XmrBalanceListener(getAddressEntry().getAccountIndex()) { + xmrBalanceListener = new XmrBalanceListener(getAddressEntry().getSubaddressIndex()) { @Override public void onBalanceChanged(BigInteger balance) { updateBalance(); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index e7301a5d85..3de12f6b2a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -629,7 +629,7 @@ public abstract class MutableOfferViewModel ext if (newValue != null) { stopTimeoutTimer(); createOfferRequested = false; - if (offer.getState() == Offer.State.OFFER_FEE_PAID) + if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(newValue + Res.get("createOffer.errorInfo")); else errorMessage.set(newValue); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java index e799bdef3c..e2fea82e33 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/OfferDataModel.java @@ -65,7 +65,7 @@ public abstract class OfferDataModel extends ActivatableDataModel { } protected void updateBalance() { - Coin tradeWalletBalance = xmrWalletService.getBalanceForAccount(addressEntry.getAccountIndex()); + Coin tradeWalletBalance = xmrWalletService.getBalanceForSubaddress(addressEntry.getSubaddressIndex()); if (useSavingsWallet) { Coin savingWalletBalance = xmrWalletService.getSavingWalletBalance(); totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java index b6108d4ef3..77d688935c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java @@ -85,8 +85,8 @@ public class OfferBook { .filter(item -> item.getOffer().getId().equals(offer.getId())) .findAny(); if (candidateWithSameId.isPresent()) { - log.warn("We had an old offer in the list with the same Offer ID. We remove the old one. " + - "old offerBookListItem={}, new offerBookListItem={}", candidateWithSameId.get(), offerBookListItem); + log.warn("We had an old offer in the list with the same Offer ID {}. We remove the old one. " + + "old offerBookListItem={}, new offerBookListItem={}", offer.getId(), candidateWithSameId.get(), offerBookListItem); offerBookListItems.remove(candidateWithSameId.get()); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index 14c78bf5b9..b0ba20c3b6 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -668,6 +668,9 @@ public class OfferBookView extends ActivatableViewAndModel { }, errorMessage -> new Popup().warning(errorMessage).show()); @@ -265,40 +265,11 @@ class TakeOfferDataModel extends OfferDataModel { calculateVolume(); calculateTotalToPay(); - balanceListener = new XmrBalanceListener(addressEntry.getAccountIndex()) { + balanceListener = new XmrBalanceListener(addressEntry.getSubaddressIndex()) { @Override public void onBalanceChanged(BigInteger balance) { - updateBalance(); + updateBalance(); } - -// public void onBalanceChanged(Coin balance, Transaction tx) { -// updateBalance(); -// -// /*if (isMainNet.get()) { -// SettableFuture future = blockchainService.requestFee(tx.getHashAsString()); -// Futures.addCallback(future, new FutureCallback() { -// public void onSuccess(Coin fee) { -// UserThread.execute(() -> setFeeFromFundingTx(fee)); -// } -// -// public void onFailure(@NotNull Throwable throwable) { -// UserThread.execute(() -> new Popup<>() -// .warning("We did not get a response for the request of the mining fee used " + -// "in the funding transaction.\n\n" + -// "Are you sure you used a sufficiently high fee of at least " + -// formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?") -// .actionButtonText("Yes, I used a sufficiently high fee.") -// .onAction(() -> setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx())) -// .closeButtonText("No. Let's cancel that payment.") -// .onClose(() -> setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI)) -// .show()); -// } -// }); -// } else { -// setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx()); -// isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0); -// }*/ -// } }; offer.resetState(); @@ -364,7 +335,6 @@ class TakeOfferDataModel extends OfferDataModel { tradeManager.onTakeOffer(amount.get(), txFeeFromFeeService, getTakerFee(), - tradePrice.getValue(), fundsNeededForTrade, offer, paymentAccount.getId(), diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java index 2cf2935477..4a8004b0c6 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java @@ -379,7 +379,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im switch (state) { case UNKNOWN: break; - case OFFER_FEE_PAID: + case OFFER_FEE_RESERVED: // irrelevant for taker break; case AVAILABLE: diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java b/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java index e0aa093866..2cd98d85c5 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/editor/PeerInfoWithTagEditor.java @@ -251,7 +251,7 @@ public class PeerInfoWithTagEditor extends Overlay { UserThread.runAfter(() -> { PubKeyRing peersPubKeyRing = null; if (trade != null) { - peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing(); + peersPubKeyRing = trade.getTradingPeer().getPubKeyRing(); } else if (offer != null) { peersPubKeyRing = offer.getPubKeyRing(); } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index d28640428c..e46cdaad59 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -148,7 +148,6 @@ public class ContractWindow extends Overlay { if (showAcceptedBanks) rows++; - PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("contractWindow.title")); addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(), Layout.TWICE_FIRST_ROW_DISTANCE).second.setMouseTransparent(false); @@ -178,8 +177,8 @@ public class ContractWindow extends Overlay { contract.getBuyerNodeAddress().getFullAddress() + " / " + contract.getSellerNodeAddress().getFullAddress()); addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("contractWindow.accountAge"), - getAccountAge(contract.getBuyerPaymentAccountPayload(), contract.getBuyerPubKeyRing(), offer.getCurrencyCode()) + " / " + - getAccountAge(contract.getSellerPaymentAccountPayload(), contract.getSellerPubKeyRing(), offer.getCurrencyCode())); + getAccountAge(dispute.getBuyerPaymentAccountPayload(), contract.getBuyerPubKeyRing(), offer.getCurrencyCode()) + " / " + + getAccountAge(dispute.getSellerPaymentAccountPayload(), contract.getSellerPubKeyRing(), offer.getCurrencyCode())); DisputeManager> disputeManager = getDisputeManager(dispute); String nrOfDisputesAsBuyer = disputeManager != null ? disputeManager.getNrOfDisputes(true, contract) : ""; @@ -188,9 +187,9 @@ public class ContractWindow extends Overlay { nrOfDisputesAsBuyer + " / " + nrOfDisputesAsSeller); addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("shared.paymentDetails", Res.get("shared.buyer")), - contract.getBuyerPaymentAccountPayload().getPaymentDetails()).second.setMouseTransparent(false); + dispute.getBuyerPaymentAccountPayload().getPaymentDetails()).second.setMouseTransparent(false); addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("shared.paymentDetails", Res.get("shared.seller")), - sellerPaymentAccountPayload.getPaymentDetails()).second.setMouseTransparent(false); + dispute.getSellerPaymentAccountPayload().getPaymentDetails()).second.setMouseTransparent(false); String title = ""; String agentKeyBaseUserName = ""; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 5400781dfa..0bd38fce58 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -603,9 +603,7 @@ public class DisputeSummaryWindow extends Overlay { if (!dispute.isMediationDispute()) { try { System.out.println(disputeResult); - XmrAddressEntry arbitratorAddressEntry = walletService.getArbitratorAddressEntry(); - MoneroWallet multisigWallet = walletService.getOrCreateMultisigWallet(dispute.getTradeId()); - System.out.println("Arbitrator payout address entry: " + arbitratorAddressEntry.getAddressString()); + MoneroWallet multisigWallet = walletService.getMultisigWallet(dispute.getTradeId()); //dispute.getContract().getArbitratorPubKeyRing(); // TODO: support arbitrator pub key ring in contract? //disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey()); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index fb6a5f681f..857d2657b1 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -180,8 +180,8 @@ public class TradeDetailsWindow extends Overlay { if (contract != null) { rows++; - buyerPaymentAccountPayload = contract.getBuyerPaymentAccountPayload(); - sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); + buyerPaymentAccountPayload = trade.getBuyer().getPaymentAccountPayload(); + sellerPaymentAccountPayload = trade.getSeller().getPaymentAccountPayload(); if (buyerPaymentAccountPayload != null) rows++; @@ -235,11 +235,10 @@ public class TradeDetailsWindow extends Overlay { addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.txFee"), txFee); NodeAddress arbitratorNodeAddress = trade.getArbitratorNodeAddress(); - NodeAddress mediatorNodeAddress = trade.getMediatorNodeAddress(); - if (arbitratorNodeAddress != null && mediatorNodeAddress != null) { + if (arbitratorNodeAddress != null) { addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.agentAddresses"), - arbitratorNodeAddress.getFullAddress() + " / " + mediatorNodeAddress.getFullAddress()); + arbitratorNodeAddress.getFullAddress()); } if (trade.getTradingPeerNodeAddress() != null) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java index f4fec230e6..80d7c2c0cd 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java @@ -433,10 +433,10 @@ public class ClosedTradesView extends ActivatableViewAndModel navigation.navigateTo(MainView.class, SupportView.class, MediationClientView.class); disputeManager = mediationManager; - PubKeyRing mediatorPubKeyRing = trade.getMediatorPubKeyRing(); - checkNotNull(mediatorPubKeyRing, "mediatorPubKeyRing must not be null"); + PubKeyRing arbitratorPubKeyRing = trade.getArbitratorPubKeyRing(); + checkNotNull(arbitratorPubKeyRing, "arbitratorPubKeyRing must not be null"); byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr Dispute dispute = new Dispute(new Date().getTime(), trade.getId(), @@ -561,9 +557,11 @@ public class PendingTradesDataModel extends ActivatableDataModel { depositTxId, payoutTxHashAsString, trade.getContractAsJson(), - trade.getMakerContractSignature(), - trade.getTakerContractSignature(), - mediatorPubKeyRing, + trade.getMaker().getContractSignature(), + trade.getTaker().getContractSignature(), + trade.getMaker().getPaymentAccountPayload(), + trade.getTaker().getPaymentAccountPayload(), + arbitratorPubKeyRing, isSupportTicket, SupportType.MEDIATION); dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId()); @@ -595,8 +593,10 @@ public class PendingTradesDataModel extends ActivatableDataModel { depositTxHashAsString, payoutTxHashAsString, trade.getContractAsJson(), - trade.getMakerContractSignature(), - trade.getTakerContractSignature(), + trade.getMaker().getContractSignature(), + trade.getTaker().getContractSignature(), + trade.getMaker().getPaymentAccountPayload(), + trade.getTaker().getPaymentAccountPayload(), arbitratorPubKeyRing, isSupportTicket, SupportType.ARBITRATION); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 2536b442fc..587bee9d0d 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -190,8 +190,10 @@ public class PendingTradesViewModel extends ActivatableWithDataModel getOptionalHolderName() { Contract contract = trade.getContract(); if (contract != null) { - PaymentAccountPayload paymentAccountPayload = contract.getBuyerPaymentAccountPayload(); + PaymentAccountPayload paymentAccountPayload = trade.getBuyer().getPaymentAccountPayload(); if (paymentAccountPayload instanceof BankAccountPayload) return Optional.of(((BankAccountPayload) paymentAccountPayload).getHolderName()); else if (paymentAccountPayload instanceof SepaAccountPayload) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index 3638b56c5c..4547189e1e 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -453,11 +453,11 @@ public abstract class DisputeView extends ActivatableView { return FilterResult.SELLER_NODE_ADDRESS; } - if (dispute.getContract().getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { + if (dispute.getBuyerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { return FilterResult.BUYER_ACCOUNT_DETAILS; } - if (dispute.getContract().getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { + if (dispute.getSellerPaymentAccountPayload().getPaymentDetails().toLowerCase().contains(filter)) { return FilterResult.SELLER_ACCOUNT_DETAILS; } @@ -750,10 +750,10 @@ public abstract class DisputeView extends ActivatableView { .append(")\n"); String buyerPaymentAccountPayload = Utilities.toTruncatedString( - contract.getBuyerPaymentAccountPayload().getPaymentDetails(). + firstDispute.getBuyerPaymentAccountPayload().getPaymentDetails(). replace("\n", " ").replace(";", "."), 100); String sellerPaymentAccountPayload = Utilities.toTruncatedString( - contract.getSellerPaymentAccountPayload().getPaymentDetails() + firstDispute.getSellerPaymentAccountPayload().getPaymentDetails() .replace("\n", " ").replace(";", "."), 100); String buyerNodeAddress = contract.getBuyerNodeAddress().getFullAddress(); String sellerNodeAddress = contract.getSellerNodeAddress().getFullAddress(); @@ -1226,7 +1226,7 @@ public abstract class DisputeView extends ActivatableView { NodeAddress buyerNodeAddress = contract.getBuyerNodeAddress(); if (buyerNodeAddress != null) { String nrOfDisputes = disputeManager.getNrOfDisputes(true, contract); - long accountAge = accountAgeWitnessService.getAccountAge(contract.getBuyerPaymentAccountPayload(), contract.getBuyerPubKeyRing()); + long accountAge = accountAgeWitnessService.getAccountAge(item.getBuyerPaymentAccountPayload(), contract.getBuyerPubKeyRing()); String age = DisplayUtils.formatAccountAge(accountAge); String postFix = CurrencyUtil.isFiatCurrency(item.getContract().getOfferPayload().getCurrencyCode()) ? " / " + age : ""; return buyerNodeAddress.getHostNameWithoutPostFix() + " (" + nrOfDisputes + postFix + ")"; @@ -1243,7 +1243,7 @@ public abstract class DisputeView extends ActivatableView { NodeAddress sellerNodeAddress = contract.getSellerNodeAddress(); if (sellerNodeAddress != null) { String nrOfDisputes = disputeManager.getNrOfDisputes(false, contract); - long accountAge = accountAgeWitnessService.getAccountAge(contract.getSellerPaymentAccountPayload(), contract.getSellerPubKeyRing()); + long accountAge = accountAgeWitnessService.getAccountAge(item.getSellerPaymentAccountPayload(), contract.getSellerPubKeyRing()); String age = DisplayUtils.formatAccountAge(accountAge); String postFix = CurrencyUtil.isFiatCurrency(item.getContract().getOfferPayload().getCurrencyCode()) ? " / " + age : ""; return sellerNodeAddress.getHostNameWithoutPostFix() + " (" + nrOfDisputes + postFix + ")"; diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 59bb90d5ba..faa52a98a6 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -582,7 +582,7 @@ public class GUIUtil { break; case BUILDING: tooltip.setText(Res.get("confidence.confirmed", confidence.getDepthInBlocks())); - txConfidenceIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0)); + txConfidenceIndicator.setProgress(Math.min(1, confidence.getDepthInBlocks() / 6.0)); break; case DEAD: tooltip.setText(Res.get("confidence.invalid")); @@ -786,9 +786,12 @@ public class GUIUtil { } public static boolean canCreateOrTakeOfferOrShowPopup(User user, Navigation navigation) { + + // TODO (woodser): use refund agents to dispute arbitration? if (!user.hasAcceptedRefundAgents()) { - new Popup().warning(Res.get("popup.warning.noArbitratorsAvailable")).show(); - return false; + log.warn("There are no refund agents available"); // TODO (woodser): refund agents changing from [4444] to [] causing this error + //new Popup().warning(Res.get("popup.warning.noArbitratorsAvailable")).show(); + //return false; } if (!user.hasAcceptedMediators()) { diff --git a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java index 5d2d4c58a2..a5dd9e7503 100644 --- a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java @@ -80,8 +80,6 @@ public class TradesChartsViewModelTest { null, null, null, - null, - null, 0, 0, 0, @@ -97,7 +95,9 @@ public class TradesChartsViewModelTest { false, null, null, - 1 + 1, + null, + null ); @Before diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java index 15e373db12..b0834a75d7 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java @@ -102,7 +102,7 @@ public class CreateOfferViewModelTest { var tradeStats = mock(TradeStatisticsManager.class); when(xmrWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); - when(xmrWalletService.getBalanceForAccount(any(Integer.class))).thenReturn(Coin.valueOf(1000L)); + when(xmrWalletService.getBalanceForSubaddress(any(Integer.class))).thenReturn(Coin.valueOf(1000L)); when(priceFeedService.updateCounterProperty()).thenReturn(new SimpleIntegerProperty()); when(priceFeedService.getMarketPrice(anyString())).thenReturn( new MarketPrice("USD", diff --git a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java index 18ed454534..f337546a36 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java @@ -596,8 +596,6 @@ public class OfferBookViewModelTest { 0, "XMR", tradeCurrencyCode, - null, - null, paymentMethodId, null, null, @@ -621,6 +619,8 @@ public class OfferBookViewModelTest { false, null, null, - 1)); + 1, + null, + null)); } } diff --git a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java index 5d91655bb2..6a02c29854 100644 --- a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java @@ -80,7 +80,7 @@ public class EditOfferDataModelTest { OfferUtil offerUtil = mock(OfferUtil.class); when(xmrWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); - when(xmrWalletService.getBalanceForAccount(any(Integer.class))).thenReturn(Coin.valueOf(1000L)); + when(xmrWalletService.getBalanceForSubaddress(any(Integer.class))).thenReturn(Coin.valueOf(1000L)); when(priceFeedService.updateCounterProperty()).thenReturn(new SimpleIntegerProperty()); when(priceFeedService.getMarketPrice(anyString())).thenReturn( new MarketPrice("USD", diff --git a/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java b/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java index c05e3138a8..4a37c049a1 100644 --- a/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java +++ b/desktop/src/test/java/bisq/desktop/maker/OfferMaker.java @@ -52,8 +52,6 @@ public class OfferMaker { lookup.valueOf(minAmount, 100000L), lookup.valueOf(baseCurrencyCode, "XMR"), lookup.valueOf(counterCurrencyCode, "USD"), - null, - null, "SEPA", "", null, @@ -77,7 +75,9 @@ public class OfferMaker { false, null, null, - 0)); + 0, + null, + null)); public static final Maker btcUsdOffer = a(Offer); public static final Maker btcBCHCOffer = a(Offer).but(with(counterCurrencyCode, "BCHC")); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 8d0aec2650..4475a5d3d8 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -81,15 +81,20 @@ message NetworkEnvelope { GetInventoryRequest get_inventory_request = 52; GetInventoryResponse get_inventory_response = 53; - - InitTradeRequest init_trade_request = 1001; - MakerReadyToFundMultisigRequest maker_ready_to_fund_multisig_request = 1002; - MakerReadyToFundMultisigResponse maker_ready_to_fund_multisig_response = 1003; - InitMultisigMessage init_multisig_message = 1004; - UpdateMultisigRequest update_multisig_request = 1005; - UpdateMultisigResponse update_multisig_response = 1006; - ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1007; - ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1008; + + SignOfferRequest sign_offer_request = 1001; + SignOfferResponse sign_offer_response = 1002; + InitTradeRequest init_trade_request = 1003; + InitMultisigRequest init_multisig_request = 1004; + SignContractRequest sign_contract_request = 1005; + SignContractResponse sign_contract_response = 1006; + 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; } } @@ -161,6 +166,26 @@ message GetInventoryResponse { // offer +message SignOfferRequest { + string offer_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string sender_account_id = 4; + OfferPayload offer_payload = 5; + string uid = 6; + int64 current_date = 7; + string reserve_tx_hash = 8; + string reserve_tx_hex = 9; + string reserve_tx_key = 10; + string payout_address = 11; +} + +message SignOfferResponse { + string offer_id = 1; + string uid = 2; + OfferPayload signed_offer_payload = 3; +} + message OfferAvailabilityRequest { string offer_id = 1; PubKeyRing pub_key_ring = 2; @@ -168,6 +193,7 @@ message OfferAvailabilityRequest { repeated int32 supported_capabilities = 4; string uid = 5; bool is_taker_api_user = 6; + InitTradeRequest trade_request = 7; } message OfferAvailabilityResponse { @@ -175,9 +201,8 @@ message OfferAvailabilityResponse { AvailabilityResult availability_result = 2; repeated int32 supported_capabilities = 3; string uid = 4; - NodeAddress arbitrator = 5; - NodeAddress mediator = 6; - NodeAddress refund_agent = 7; + string maker_signature = 5; + NodeAddress arbitrator_node_address = 6; } message RefreshOfferMessage { @@ -282,40 +307,24 @@ message InitTradeRequest { PubKeyRing pub_key_ring = 3; int64 trade_amount = 4; int64 trade_price = 5; - int64 tx_fee = 6; - int64 trade_fee = 7; - string payout_address_string = 8; - PaymentAccountPayload payment_account_payload = 9; - string account_id = 10; - string trade_fee_tx_id = 11; - string uid = 12; - bytes account_age_witness_signature_of_offer_id = 13; - int64 current_date = 14; - NodeAddress maker_node_address = 15; - NodeAddress taker_node_address = 16; - NodeAddress arbitrator_node_address = 17; + int64 trade_fee = 6; + string account_id = 7; + string payment_account_id = 8; + string payment_method_id = 9; + string uid = 10; + bytes account_age_witness_signature_of_offer_id = 11; + int64 current_date = 12; + NodeAddress maker_node_address = 13; + NodeAddress taker_node_address = 14; + NodeAddress arbitrator_node_address = 15; + string reserve_tx_hash = 16; + string reserve_tx_hex = 17; + string reserve_tx_key = 18; + string payout_address = 19; + string maker_signature = 20; } -message MakerReadyToFundMultisigRequest { - string trade_id = 1; - NodeAddress sender_node_address = 2; - PubKeyRing pub_key_ring = 3; - string uid = 4; -} - -message MakerReadyToFundMultisigResponse { - string trade_id = 1; - string uid = 2; - bool is_maker_ready_to_fund_multisig = 3; - string maker_contract_as_json = 4; - string maker_contract_signature = 5; - string maker_payout_address_string = 6; - PaymentAccountPayload maker_payment_account_payload = 7; - string maker_account_id = 8; - int64 current_date = 9; -} - -message InitMultisigMessage { +message InitMultisigRequest { string trade_id = 1; NodeAddress sender_node_address = 2; PubKeyRing pub_key_ring = 3; @@ -325,6 +334,55 @@ message InitMultisigMessage { string made_multisig_hex = 7; } +message SignContractRequest { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + string account_id = 6; + bytes payment_account_payload_hash = 7; + string payout_address = 8;; + string deposit_tx_hash = 9; +} + +message SignContractResponse { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + string contract_signature = 6; +} + +message DepositRequest { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + string contract_signature = 6; + string deposit_tx_hex = 7; + string deposit_tx_key = 8; +} + +message DepositResponse { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; +} + +message PaymentAccountPayloadRequest { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + int64 current_date = 5; + PaymentAccountPayload payment_account_payload = 6; +} + message UpdateMultisigRequest { string trade_id = 1; NodeAddress sender_node_address = 2; @@ -855,32 +913,33 @@ message OfferPayload { int64 min_amount = 10; string base_currency_code = 11; string counter_currency_code = 12; - repeated NodeAddress arbitrator_node_addresses = 13 [deprecated = true]; // not used anymore but still required as old clients check for nonNull - repeated NodeAddress mediator_node_addresses = 14 [deprecated = true]; // not used anymore but still required as old clients check for nonNull - string payment_method_id = 15; - string maker_payment_account_id = 16; - string offer_fee_payment_tx_id = 17; - string country_code = 18; - repeated string accepted_country_codes = 19; - string bank_id = 20; - repeated string accepted_bank_ids = 21; - string version_nr = 22; - int64 block_height_at_offer_creation = 23; - int64 tx_fee = 24; - int64 maker_fee = 25; - bool is_currency_for_maker_fee_btc = 26; - int64 buyer_security_deposit = 27; - int64 seller_security_deposit = 28; - int64 max_trade_limit = 29; - int64 max_trade_period = 30; - bool use_auto_close = 31; - bool use_re_open_after_auto_close = 32; - int64 lower_close_price = 33; - int64 upper_close_price = 34; - bool is_private_offer = 35; - string hash_of_challenge = 36; - map extra_data = 37; - int32 protocol_version = 38; + string payment_method_id = 13; + string maker_payment_account_id = 14; + string offer_fee_payment_tx_id = 15; + string country_code = 16; + repeated string accepted_country_codes = 17; + string bank_id = 18; + repeated string accepted_bank_ids = 19; + string version_nr = 20; + int64 block_height_at_offer_creation = 21; + int64 tx_fee = 22; + int64 maker_fee = 23; + bool is_currency_for_maker_fee_btc = 24; + int64 buyer_security_deposit = 25; + int64 seller_security_deposit = 26; + int64 max_trade_limit = 27; + int64 max_trade_period = 28; + bool use_auto_close = 29; + bool use_re_open_after_auto_close = 30; + int64 lower_close_price = 31; + int64 upper_close_price = 32; + bool is_private_offer = 33; + string hash_of_challenge = 34; + map extra_data = 35; + int32 protocol_version = 36; + + NodeAddress arbitrator_node_address = 1001; + string arbitrator_signature = 1002; } message AccountAgeWitness { @@ -920,34 +979,36 @@ message Dispute { string trade_id = 1; string id = 2; int32 trader_id = 3; - bool dispute_opener_is_buyer = 4; - bool dispute_opener_is_maker = 5; - int64 opening_date = 6; - PubKeyRing trader_pub_key_ring = 7; - int64 trade_date = 8; - Contract contract = 9; - bytes contract_hash = 10; - bytes deposit_tx_serialized = 11; - bytes payout_tx_serialized = 12; - string deposit_tx_id = 13; - string payout_tx_id = 14; - string contract_as_json = 15; - string maker_contract_signature = 16; - string taker_contract_signature = 17; - PubKeyRing agent_pub_key_ring = 18; - bool is_support_ticket = 19; - repeated ChatMessage chat_message = 20; - bool is_closed = 21; - DisputeResult dispute_result = 22; - string dispute_payout_tx_id = 23; - SupportType support_type = 24; - string mediators_dispute_result = 25; - string delayed_payout_tx_id = 26; - string donation_address_of_delayed_payout_tx = 27; - State state = 28; - int64 trade_period_end = 29; - map extra_data = 30; - bool is_opener = 100; + bool is_opener = 4; + bool dispute_opener_is_buyer = 5; + bool dispute_opener_is_maker = 6; + int64 opening_date = 7; + PubKeyRing trader_pub_key_ring = 8; + int64 trade_date = 9; + Contract contract = 10; + bytes contract_hash = 11; + bytes deposit_tx_serialized = 12; + bytes payout_tx_serialized = 13; + string deposit_tx_id = 14; + string payout_tx_id = 15; + string contract_as_json = 16; + string maker_contract_signature = 17; + string taker_contract_signature = 18; + PaymentAccountPayload maker_payment_account_payload = 19; + PaymentAccountPayload taker_payment_account_payload = 20; + PubKeyRing agent_pub_key_ring = 21; + bool is_support_ticket = 22; + repeated ChatMessage chat_message = 23; + bool is_closed = 24; + DisputeResult dispute_result = 25; + string dispute_payout_tx_id = 26; + SupportType support_type = 27; + string mediators_dispute_result = 28; + string delayed_payout_tx_id = 29; + string donation_address_of_delayed_payout_tx = 30; + State state = 31; + int64 trade_period_end = 32; + map extra_data = 33; } message Attachment { @@ -1010,16 +1071,20 @@ message Contract { bool is_buyer_maker_and_seller_taker = 6; string maker_account_id = 7; string taker_account_id = 8; - PaymentAccountPayload maker_payment_account_payload = 9; - PaymentAccountPayload taker_payment_account_payload = 10; - PubKeyRing maker_pub_key_ring = 11; - PubKeyRing taker_pub_key_ring = 12; - NodeAddress buyer_node_address = 13; - NodeAddress seller_node_address = 14; - string maker_payout_address_string = 15; - string taker_payout_address_string = 16; - NodeAddress arbitrator_node_address = 17; - int64 lock_time = 18; + string maker_payment_method_id = 9; + string taker_payment_method_id = 10; + bytes maker_payment_account_payload_hash = 11; + bytes taker_payment_account_payload_hash = 12; + PubKeyRing maker_pub_key_ring = 13; + PubKeyRing taker_pub_key_ring = 14; + NodeAddress buyer_node_address = 15; + NodeAddress seller_node_address = 16; + string maker_payout_address_string = 17; + string taker_payout_address_string = 18; + NodeAddress arbitrator_node_address = 19; + int64 lock_time = 20; + string maker_deposit_tx_hash = 21; + string taker_deposit_tx_hash = 22; } message RawTransactionInput { @@ -1043,6 +1108,7 @@ enum AvailabilityResult { UNCONF_TX_LIMIT_HIT = 11; MAKER_DENIED_API_USER = 12; PRICE_CHECK_FAILED = 13; + MAKER_DENIED_TAKER = 14; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -1352,7 +1418,8 @@ message PersistableEnvelope { IgnoredMailboxMap ignored_mailbox_map = 33; RemovedPayloadsMap removed_payloads_map = 34; - XmrAddressEntryList xmr_address_entry_list = 100; + XmrAddressEntryList xmr_address_entry_list = 1001; + SignedOfferList signed_offer_list = 1002; } } @@ -1444,7 +1511,7 @@ message XmrAddressEntry { TRADE_PAYOUT = 6; } - int32 account_index = 7; + int32 subaddress_index = 7; string address_string = 8; string offer_id = 9; Context context = 10; @@ -1481,6 +1548,17 @@ message Offer { OfferPayload offer_payload = 1; } +message SignedOfferList { + repeated SignedOffer signed_offer = 1; +} + +message SignedOffer { + string offer_id = 1; + string reserve_tx_hash = 2; + string reserve_tx_hex = 3; + string arbitrator_signature = 4; +} + message OpenOffer { enum State { PB_ERROR = 0; @@ -1494,9 +1572,8 @@ message OpenOffer { Offer offer = 1; State state = 2; NodeAddress arbitrator_node_address = 3; - NodeAddress mediator_node_address = 4; - NodeAddress refund_agent_node_address = 5; - int64 trigger_price = 6; + int64 trigger_price = 4; + repeated string frozen_key_images = 5; } message Tradable { @@ -1507,6 +1584,8 @@ message Tradable { SellerAsMakerTrade seller_as_maker_trade = 4; SellerAsTakerTrade seller_as_taker_trade = 5; ArbitratorTrade arbitrator_trade = 6; + + SignedOffer signed_offer = 1001; } } @@ -1596,34 +1675,30 @@ message Trade { Contract contract = 16; string contract_as_json = 17; bytes contract_hash = 18; - string taker_contract_signature = 19; - string maker_contract_signature = 20; - NodeAddress arbitrator_node_address = 21; - NodeAddress mediator_node_address = 22; - bytes arbitrator_btc_pub_key = 23; - string taker_payment_account_id = 24; - string error_message = 25; - PubKeyRing arbitrator_pub_key_ring = 26; - PubKeyRing mediator_pub_key_ring = 27; - string counter_currency_tx_id = 28; - repeated ChatMessage chat_message = 29; - MediationResultState mediation_result_state = 30; - int64 lock_time = 31; - bytes delayed_payout_tx_bytes = 32; - NodeAddress refund_agent_node_address = 33; - PubKeyRing refund_agent_pub_key_ring = 34; - RefundResultState refund_result_state = 35; - int64 last_refresh_request_date = 36 [deprecated = true]; - string counter_currency_extra_data = 37; - string asset_tx_proof_result = 38; // name of AssetTxProofResult enum - string uid = 39; + NodeAddress arbitrator_node_address = 19; + NodeAddress mediator_node_address = 20; + bytes arbitrator_btc_pub_key = 21; + string taker_payment_account_id = 22; + string error_message = 23; + PubKeyRing arbitrator_pub_key_ring = 24; + PubKeyRing mediator_pub_key_ring = 25; + string counter_currency_tx_id = 26; + repeated ChatMessage chat_message = 27; + MediationResultState mediation_result_state = 28; + int64 lock_time = 29; + bytes delayed_payout_tx_bytes = 30; + NodeAddress refund_agent_node_address = 31; + PubKeyRing refund_agent_pub_key_ring = 32; + RefundResultState refund_result_state = 33; + int64 last_refresh_request_date = 34 [deprecated = true]; + string counter_currency_extra_data = 35; + string asset_tx_proof_result = 36; // name of AssetTxProofResult enum + string uid = 37; - NodeAddress taker_node_address = 100; - PubKeyRing taker_pub_key_ring = 101; - string taker_deposit_tx_id = 102; - NodeAddress maker_node_address = 103; - PubKeyRing maker_pub_key_ring = 104; - string maker_deposit_tx_id = 105; + NodeAddress maker_node_address = 100; // TODO (woodser): move these into TradingPeer + NodeAddress taker_node_address = 101; + PubKeyRing taker_pub_key_ring = 102; + PubKeyRing maker_pub_key_ring = 103; } message BuyerAsMakerTrade { @@ -1668,39 +1743,50 @@ message ProcessModel { int64 buyer_payout_amount_from_mediation = 19; int64 seller_payout_amount_from_mediation = 20; - TradingPeer maker = 1001; - TradingPeer taker = 1002; - TradingPeer arbitrator = 1003; - NodeAddress temp_trading_peer_node_address = 1004; - string prepared_multisig_hex = 1005; - string made_multisig_hex = 1006; - bool multisig_setup_complete = 1007; - bool maker_ready_to_fund_multisig = 1008; - bool multisig_deposit_initiated = 1009; - string taker_prepared_deposit_tx_id = 1010; - string maker_prepared_deposit_tx_id = 1011; + string maker_signature = 1001; + NodeAddress arbitrator_node_address = 1002; + TradingPeer maker = 1003; + TradingPeer taker = 1004; + TradingPeer arbitrator = 1005; + NodeAddress temp_trading_peer_node_address = 1006; + string reserve_tx_hash = 1007; + repeated string frozen_key_images = 1008; + string prepared_multisig_hex = 1009; + string made_multisig_hex = 1010; + bool multisig_setup_complete = 1011; + bool maker_ready_to_fund_multisig = 1012; + bool multisig_deposit_initiated = 1013; } message TradingPeer { string account_id = 1; - PaymentAccountPayload payment_account_payload = 2; - string payout_address_string = 3; - string contract_as_json = 4; - string contract_signature = 5; - bytes signature = 6; // TODO (woodser): remove unused fields? this was buyer-signed payout tx as bytes - PubKeyRing pub_key_ring = 7; - bytes multi_sig_pub_key = 8; - repeated RawTransactionInput raw_transaction_inputs = 9; - int64 change_output_value = 10; - string change_output_address = 11; - bytes account_age_witness_nonce = 12; - bytes account_age_witness_signature = 13; - int64 current_date = 14; - bytes mediated_payout_tx_signature = 15; + string payment_account_id = 2; + string payment_method_id = 3; + bytes payment_account_payload_hash = 4; + PaymentAccountPayload payment_account_payload = 5; + string payout_address_string = 6; + string contract_as_json = 7; + string contract_signature = 8; + bytes signature = 9; // TODO (woodser): remove unused fields? this was buyer-signed payout tx as bytes + PubKeyRing pub_key_ring = 10; + bytes multi_sig_pub_key = 11; + repeated RawTransactionInput raw_transaction_inputs = 12; + int64 change_output_value = 13; + string change_output_address = 14; + bytes account_age_witness_nonce = 15; + bytes account_age_witness_signature = 16; + int64 current_date = 17; + bytes mediated_payout_tx_signature = 18; - string prepared_multisig_hex = 1001; - string made_multisig_hex = 1002; - string signed_payout_tx_hex = 1003; + string reserve_tx_hash = 1001; + string reserve_tx_hex = 1002; + string reserve_tx_key = 1003; + string prepared_multisig_hex = 1004; + string made_multisig_hex = 1005; + string signed_payout_tx_hex = 1006; + string deposit_tx_hash = 1007; + string deposit_tx_hex = 1008; + string deposit_tx_key = 1009; } ///////////////////////////////////////////////////////////////////////////////////////////