diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index 3750ffdfb5..0bd4b0b169 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -1243,13 +1243,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // verify maker's reserve tx (double spend, trade fee, trade amount, mining fee) BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.PENALTY_FEE_PCT); BigInteger maxTradeFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.MAKER_FEE_PCT); - BigInteger sendAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount(); + BigInteger sendTradeAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount(); BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit(); - Tuple2 txResult = xmrWalletService.verifyTradeTx( + MoneroTx verifiedTx = xmrWalletService.verifyReserveTx( offer.getId(), penaltyFee, maxTradeFee, - sendAmount, + sendTradeAmount, securityDeposit, request.getPayoutAddress(), request.getReserveTxHash(), @@ -1272,7 +1272,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe request.getReserveTxHash(), request.getReserveTxHex(), request.getReserveTxKeyImages(), - txResult.first.getFee().longValueExact(), + verifiedTx.getFee().longValueExact(), signature); // TODO (woodser): no need for signature to be part of SignedOffer? addSignedOffer(signedOffer); requestPersistence(); diff --git a/core/src/main/java/haveno/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/haveno/core/offer/placeoffer/tasks/CreateMakerFeeTx.java deleted file mode 100644 index 74ac92df18..0000000000 --- a/core/src/main/java/haveno/core/offer/placeoffer/tasks/CreateMakerFeeTx.java +++ /dev/null @@ -1,65 +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 haveno.core.offer.placeoffer.tasks; - -//import haveno.core.util.FeeReceiverSelector; - -import haveno.common.taskrunner.Task; -import haveno.common.taskrunner.TaskRunner; -import haveno.core.offer.Offer; -import haveno.core.offer.placeoffer.PlaceOfferModel; -import haveno.core.xmr.model.AddressEntry; -import haveno.core.xmr.wallet.BtcWalletService; -import haveno.core.xmr.wallet.TradeWalletService; -import org.bitcoinj.core.Address; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CreateMakerFeeTx extends Task { - private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class); - - @SuppressWarnings({"unused"}) - public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) { - super(taskHandler, model); - } - - @Override - protected void run() { - Offer offer = model.getOpenOffer().getOffer(); - - try { - runInterceptHook(); - - String id = offer.getId(); - BtcWalletService walletService = model.getWalletService(); - - Address fundingAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(); - Address reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); - Address changeAddress = walletService.getFreshAddressEntry().getAddress(); - - TradeWalletService tradeWalletService = model.getTradeWalletService(); - throw new RuntimeException("CreateMakerFeeTx not used for XMR"); - } catch (Throwable t) { - offer.setErrorMessage("An error occurred.\n" + - "Error message:\n" - + t.getMessage()); - - failed(t); - } - } -} diff --git a/core/src/main/java/haveno/core/trade/HavenoUtils.java b/core/src/main/java/haveno/core/trade/HavenoUtils.java index 4be303d98f..607d435442 100644 --- a/core/src/main/java/haveno/core/trade/HavenoUtils.java +++ b/core/src/main/java/haveno/core/trade/HavenoUtils.java @@ -429,19 +429,18 @@ public class HavenoUtils { // ----------------------------- OTHER UTILS ------------------------------ - /** - * Get address to collect trade fees. - * - * @return the address which collects trade fees - */ public static String getTradeFeeAddress() { + return xmrWalletService.getBaseAddressEntry().getAddressString(); + } + + public static String getBurnAddress() { switch (Config.baseCurrencyNetwork()) { case XMR_LOCAL: return "Bd37nTGHjL3RvPxc9dypzpWiXQrPzxxG4RsWAasD9CV2iZ1xfFZ7mzTKNDxWBfsqQSUimctAsGtTZ8c8bZJy35BYL9jYj88"; case XMR_STAGENET: - return "5B11hTJdG2XDNwjdKGLRxwSLwDhkbGg7C7UEAZBxjE6FbCeRMjudrpNACmDNtWPiSnNfjDQf39QRjdtdgoL69txv81qc2Mc"; + return "577XbZ8yGfrWJM3aAoCpHVgDCm5higshGVJBb4ZNpTYARp8rLcCdcA1J8QgRfFWTzmJ8QgRfFWTzmJ8QgRfFWTzmCbXF9hd"; case XMR_MAINNET: - throw new RuntimeException("Mainnet fee address not implemented"); + return "46uVWiE1d4kWJM3aAoCpHVgDCm5higshGVJBb4ZNpTYARp8rLcCdcA1J8QgRfFWTzmJ8QgRfFWTzmJ8QgRfFWTzmCag5CXT"; default: throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork()); } diff --git a/core/src/main/java/haveno/core/trade/messages/InitMultisigRequest.java b/core/src/main/java/haveno/core/trade/messages/InitMultisigRequest.java index f8776e21b7..b949a14493 100644 --- a/core/src/main/java/haveno/core/trade/messages/InitMultisigRequest.java +++ b/core/src/main/java/haveno/core/trade/messages/InitMultisigRequest.java @@ -36,6 +36,8 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes private final String madeMultisigHex; @Nullable private final String exchangedMultisigHex; + @Nullable + private final String tradeFeeAddress; public InitMultisigRequest(String tradeId, String uid, @@ -43,12 +45,14 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes long currentDate, String preparedMultisigHex, String madeMultisigHex, - String exchangedMultisigHex) { + String exchangedMultisigHex, + String tradeFeeAddress) { super(messageVersion, tradeId, uid); this.currentDate = currentDate; this.preparedMultisigHex = preparedMultisigHex; this.madeMultisigHex = madeMultisigHex; this.exchangedMultisigHex = exchangedMultisigHex; + this.tradeFeeAddress = tradeFeeAddress; } @@ -65,6 +69,7 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(exchangedMultisigHex).ifPresent(e -> builder.setExchangedMultisigHex(exchangedMultisigHex)); + Optional.ofNullable(tradeFeeAddress).ifPresent(e -> builder.setTradeFeeAddress(tradeFeeAddress)); builder.setCurrentDate(currentDate); @@ -80,16 +85,18 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes proto.getCurrentDate(), ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()), ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()), - ProtoUtil.stringOrNullFromProto(proto.getExchangedMultisigHex())); + ProtoUtil.stringOrNullFromProto(proto.getExchangedMultisigHex()), + ProtoUtil.stringOrNullFromProto(proto.getTradeFeeAddress())); } @Override public String toString() { return "InitMultisigRequest {" + ",\n currentDate=" + currentDate + - ",\n preparedMultisigHex='" + preparedMultisigHex + - ",\n madeMultisigHex='" + madeMultisigHex + - ",\n exchangedMultisigHex='" + exchangedMultisigHex + + ",\n preparedMultisigHex=" + preparedMultisigHex + + ",\n madeMultisigHex=" + madeMultisigHex + + ",\n exchangedMultisigHex=" + exchangedMultisigHex + + ",\n tradeFeeAddress=" + tradeFeeAddress + "\n} " + super.toString(); } } diff --git a/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java b/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java index 8e26e3b833..8e3983ecf7 100644 --- a/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java @@ -151,6 +151,9 @@ public class ProcessModel implements Model, PersistablePayload { @Nullable @Getter @Setter + private String tradeFeeAddress; + @Getter + @Setter private String multisigAddress; @Getter @Setter @@ -212,6 +215,7 @@ public class ProcessModel implements Model, PersistablePayload { Optional.ofNullable(tempTradePeerNodeAddress).ifPresent(e -> builder.setTempTradePeerNodeAddress(tempTradePeerNodeAddress.toProtoMessage())); Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(ByteString.copyFrom(e))); + Optional.ofNullable(tradeFeeAddress).ifPresent(e -> builder.setTradeFeeAddress(tradeFeeAddress)); Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress)); return builder.build(); } @@ -233,6 +237,7 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setTempTradePeerNodeAddress(proto.hasTempTradePeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradePeerNodeAddress()) : null); processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); processModel.setMakerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMakerSignature())); + processModel.setTradeFeeAddress(ProtoUtil.stringOrNullFromProto(proto.getTradeFeeAddress())); processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress())); String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState()); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java index fd2d8385c8..dc287677d2 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java @@ -22,7 +22,6 @@ import common.utils.JsonUtils; import haveno.common.app.Version; import haveno.common.crypto.PubKeyRing; import haveno.common.taskrunner.TaskRunner; -import haveno.common.util.Tuple2; import haveno.core.offer.Offer; import haveno.core.trade.HavenoUtils; import haveno.core.trade.Trade; @@ -80,31 +79,31 @@ public class ArbitratorProcessDepositRequest extends TradeTask { boolean isFromTaker = trader == trade.getTaker(); boolean isFromBuyer = trader == trade.getBuyer(); BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee(); - BigInteger sendAmount = isFromBuyer ? BigInteger.ZERO : trade.getAmount(); + BigInteger sendTradeAmount = isFromBuyer ? BigInteger.ZERO : trade.getAmount(); BigInteger securityDeposit = isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); String depositAddress = processModel.getMultisigAddress(); // verify deposit tx - Tuple2 txResult; + MoneroTx verifiedTx; try { - txResult = trade.getXmrWalletService().verifyTradeTx( - offer.getId(), - null, - tradeFee, - sendAmount, - securityDeposit, - depositAddress, - trader.getDepositTxHash(), - request.getDepositTxHex(), - request.getDepositTxKey(), - null); + verifiedTx = trade.getXmrWalletService().verifyDepositTx( + offer.getId(), + tradeFee, + trade.getProcessModel().getTradeFeeAddress(), + sendTradeAmount, + securityDeposit, + depositAddress, + trader.getDepositTxHash(), + request.getDepositTxHex(), + request.getDepositTxKey(), + null); } catch (Exception e) { throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + trader.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage()); } // set deposit info - trader.setSecurityDeposit(txResult.second); - trader.setDepositTxFee(txResult.first.getFee()); + trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit + trader.setDepositTxFee(verifiedTx.getFee()); trader.setDepositTxHex(request.getDepositTxHex()); trader.setDepositTxKey(request.getDepositTxKey()); if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey()); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java index dec7c5e57a..1e4fdf6bfd 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java @@ -18,7 +18,6 @@ package haveno.core.trade.protocol.tasks; import haveno.common.taskrunner.TaskRunner; -import haveno.common.util.Tuple2; import haveno.core.offer.Offer; import haveno.core.offer.OfferDirection; import haveno.core.trade.HavenoUtils; @@ -59,9 +58,9 @@ public class ArbitratorProcessReserveTx extends TradeTask { BigInteger tradeFee = isFromMaker ? offer.getMaxMakerFee() : trade.getTakerFee(); BigInteger sendAmount = isFromBuyer ? BigInteger.ZERO : isFromMaker ? offer.getAmount() : trade.getAmount(); // maker reserve tx is for offer amount BigInteger securityDeposit = isFromMaker ? isFromBuyer ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit() : isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); - Tuple2 txResult; + MoneroTx verifiedTx; try { - txResult = trade.getXmrWalletService().verifyTradeTx( + verifiedTx = trade.getXmrWalletService().verifyReserveTx( offer.getId(), penaltyFee, tradeFee, @@ -79,10 +78,10 @@ public class ArbitratorProcessReserveTx extends TradeTask { // save reserve tx to model TradePeer trader = isFromMaker ? processModel.getMaker() : processModel.getTaker(); + trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit trader.setReserveTxHash(request.getReserveTxHash()); trader.setReserveTxHex(request.getReserveTxHex()); trader.setReserveTxKey(request.getReserveTxKey()); - trader.setSecurityDeposit(txResult.second); // persist trade processModel.getTradeManager().requestPersistence(); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendInitTradeOrMultisigRequests.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendInitTradeOrMultisigRequests.java index a836d05501..d6d0685ff1 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendInitTradeOrMultisigRequests.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendInitTradeOrMultisigRequests.java @@ -20,6 +20,7 @@ package haveno.core.trade.protocol.tasks; import haveno.common.app.Version; import haveno.common.taskrunner.TaskRunner; +import haveno.core.trade.HavenoUtils; import haveno.core.trade.Trade; import haveno.core.trade.messages.InitMultisigRequest; import haveno.core.trade.messages.InitTradeRequest; @@ -124,6 +125,11 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask { String preparedHex = multisigWallet.prepareMultisig(); trade.getSelf().setPreparedMultisigHex(preparedHex); + // set trade fee address + if (trade.getProcessModel().getTradeFeeAddress() == null) { + trade.getProcessModel().setTradeFeeAddress(HavenoUtils.getTradeFeeAddress()); + } + // create message to initialize multisig InitMultisigRequest initMultisigRequest = new InitMultisigRequest( processModel.getOffer().getId(), @@ -132,7 +138,8 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask { new Date().getTime(), preparedHex, null, - null); + null, + trade.getProcessModel().getTradeFeeAddress()); // send request to maker log.info("Send {} with offerId {} and uid {} to maker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getOfferId(), initMultisigRequest.getUid(), trade.getMaker().getNodeAddress()); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java index 1a37e68cd5..8b4a8027c9 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java @@ -62,16 +62,21 @@ public class ProcessInitMultisigRequest extends TradeTask { checkTradeId(processModel.getOfferId(), request); XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService(); - // get peer multisig participant - TradePeer multisigParticipant = trade.getTradePeer(processModel.getTempTradePeerNodeAddress()); + // get sender + TradePeer sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress()); + + // set trade fee address from arbitrator + if (request.getTradeFeeAddress() != null && sender == trade.getArbitrator()) { + trade.getProcessModel().setTradeFeeAddress(request.getTradeFeeAddress()); + } // reconcile peer's established multisig hex with message - if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.getPreparedMultisigHex()); - else if (request.getPreparedMultisigHex() != null && !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 (request.getMadeMultisigHex() != null && !multisigParticipant.getMadeMultisigHex().equals(request.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages: " + request.getMadeMultisigHex() + " versus " + multisigParticipant.getMadeMultisigHex()); - if (multisigParticipant.getExchangedMultisigHex() == null) multisigParticipant.setExchangedMultisigHex(request.getExchangedMultisigHex()); - else if (request.getExchangedMultisigHex() != null && !multisigParticipant.getExchangedMultisigHex().equals(request.getExchangedMultisigHex())) throw new RuntimeException("Message's exchanged multisig differs from previous messages: " + request.getExchangedMultisigHex() + " versus " + multisigParticipant.getExchangedMultisigHex()); + if (sender.getPreparedMultisigHex() == null) sender.setPreparedMultisigHex(request.getPreparedMultisigHex()); + else if (request.getPreparedMultisigHex() != null && !sender.getPreparedMultisigHex().equals(request.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + sender.getPreparedMultisigHex() + ", message: " + request.getPreparedMultisigHex()); + if (sender.getMadeMultisigHex() == null) sender.setMadeMultisigHex(request.getMadeMultisigHex()); + else if (request.getMadeMultisigHex() != null && !sender.getMadeMultisigHex().equals(request.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages: " + request.getMadeMultisigHex() + " versus " + sender.getMadeMultisigHex()); + if (sender.getExchangedMultisigHex() == null) sender.setExchangedMultisigHex(request.getExchangedMultisigHex()); + else if (request.getExchangedMultisigHex() != null && !sender.getExchangedMultisigHex().equals(request.getExchangedMultisigHex())) throw new RuntimeException("Message's exchanged multisig differs from previous messages: " + request.getExchangedMultisigHex() + " versus " + sender.getExchangedMultisigHex()); // prepare multisig if applicable boolean updateParticipants = false; @@ -210,7 +215,8 @@ public class ProcessInitMultisigRequest extends TradeTask { new Date().getTime(), trade.getSelf().getPreparedMultisigHex(), trade.getSelf().getMadeMultisigHex(), - trade.getSelf().getExchangedMultisigHex()); + trade.getSelf().getExchangedMultisigHex(), + null); log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getOfferId(), request.getUid(), recipient); processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener); diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 7135be02dc..b1780134c1 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -27,7 +27,6 @@ import haveno.common.ThreadUtils; import haveno.common.UserThread; import haveno.common.config.Config; import haveno.common.file.FileUtil; -import haveno.common.util.Tuple2; import haveno.common.util.Utilities; import haveno.core.api.AccountServiceListener; import haveno.core.api.CoreAccountService; @@ -567,19 +566,20 @@ public class XmrWalletService { * * @param penaltyFee penalty fee for breaking protocol * @param tradeFee trade fee - * @param sendAmount amount to send peer + * @param sendTradeAmount trade amount to send peer * @param securityDeposit security deposit amount * @param returnAddress return address for reserved funds * @param reserveExactAmount specifies to reserve the exact input amount * @param preferredSubaddressIndex preferred source subaddress to spend from (optional) * @return the reserve tx */ - public MoneroTxWallet createReserveTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String returnAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) { + public MoneroTxWallet createReserveTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendTradeAmount, BigInteger securityDeposit, String returnAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) { synchronized (WALLET_LOCK) { synchronized (HavenoUtils.getWalletFunctionLock()) { log.info("Creating reserve tx with preferred subaddress index={}, return address={}", preferredSubaddressIndex, returnAddress); long time = System.currentTimeMillis(); - MoneroTxWallet reserveTx = createTradeTx(penaltyFee, tradeFee, sendAmount, securityDeposit, returnAddress, reserveExactAmount, preferredSubaddressIndex); + BigInteger sendAmount = sendTradeAmount.add(securityDeposit).add(tradeFee).subtract(penaltyFee); + MoneroTxWallet reserveTx = createTradeTx(penaltyFee, HavenoUtils.getBurnAddress(), sendAmount, returnAddress, reserveExactAmount, preferredSubaddressIndex); log.info("Done creating reserve tx in {} ms", System.currentTimeMillis() - time); return reserveTx; } @@ -597,27 +597,29 @@ public class XmrWalletService { public MoneroTxWallet createDepositTx(Trade trade, boolean reserveExactAmount, Integer preferredSubaddressIndex) { synchronized (WALLET_LOCK) { synchronized (HavenoUtils.getWalletFunctionLock()) { - String multisigAddress = trade.getProcessModel().getMultisigAddress(); - BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee(); - BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.ZERO : trade.getAmount(); + BigInteger feeAmount = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee(); + String feeAddress = trade.getProcessModel().getTradeFeeAddress(); + BigInteger sendTradeAmount = trade instanceof BuyerTrade ? BigInteger.ZERO : trade.getAmount(); BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); + BigInteger sendAmount = sendTradeAmount.add(securityDeposit); + String multisigAddress = trade.getProcessModel().getMultisigAddress(); long time = System.currentTimeMillis(); log.info("Creating deposit tx for trade {} {} with multisig address={}", trade.getClass().getSimpleName(), trade.getShortId(), multisigAddress); - MoneroTxWallet depositTx = createTradeTx(null, tradeFee, sendAmount, securityDeposit, multisigAddress, reserveExactAmount, preferredSubaddressIndex); + MoneroTxWallet depositTx = createTradeTx(feeAmount, feeAddress, sendAmount, multisigAddress, reserveExactAmount, preferredSubaddressIndex); log.info("Done creating deposit tx for trade {} {} in {} ms", trade.getClass().getSimpleName(), trade.getShortId(), System.currentTimeMillis() - time); return depositTx; } } } - private MoneroTxWallet createTradeTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean reserveExactAmount, Integer preferredSubaddressIndex) { + private MoneroTxWallet createTradeTx(BigInteger feeAmount, String feeAddress, BigInteger sendAmount, String sendAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) { synchronized (WALLET_LOCK) { MoneroWallet wallet = getWallet(); // create a list of subaddresses to attempt spending from in preferred order List subaddressIndices = new ArrayList(); if (reserveExactAmount) { - BigInteger exactInputAmount = tradeFee.add(sendAmount).add(securityDeposit); + BigInteger exactInputAmount = feeAmount.add(sendAmount); List subaddressIndicesWithExactInput = getSubaddressesWithExactInput(exactInputAmount); if (preferredSubaddressIndex != null) subaddressIndicesWithExactInput.remove(preferredSubaddressIndex); Collections.sort(subaddressIndicesWithExactInput); @@ -635,30 +637,27 @@ public class XmrWalletService { // first try preferred subaddressess for (int i = 0; i < subaddressIndices.size(); i++) { try { - return createTradeTxFromSubaddress(penaltyFee, tradeFee, sendAmount, securityDeposit, address, reserveExactAmount, subaddressIndices.get(i)); + return createTradeTxFromSubaddress(feeAmount, feeAddress, sendAmount, sendAddress, subaddressIndices.get(i)); } catch (Exception e) { if (i == subaddressIndices.size() - 1 && reserveExactAmount) throw e; // throw if no subaddress with exact output } } // try any subaddress - return createTradeTxFromSubaddress(penaltyFee, tradeFee, sendAmount, securityDeposit, address, reserveExactAmount, null); + return createTradeTxFromSubaddress(feeAmount, feeAddress, sendAmount, sendAddress, null); } } - private MoneroTxWallet createTradeTxFromSubaddress(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean reserveExactAmount, Integer subaddressIndex) { + private MoneroTxWallet createTradeTxFromSubaddress(BigInteger feeAmount, String feeAddress, BigInteger sendAmount, String sendAddress, Integer subaddressIndex) { // create tx - boolean isDepositTx = penaltyFee == null; - BigInteger feeAmount = isDepositTx ? tradeFee : penaltyFee; - BigInteger transferAmount = isDepositTx ? sendAmount.add(securityDeposit) : sendAmount.add(securityDeposit).add(tradeFee).subtract(penaltyFee); MoneroTxConfig txConfig = new MoneroTxConfig() .setAccountIndex(0) .setSubaddressIndices(subaddressIndex) - .addDestination(address, transferAmount) - .setSubtractFeeFrom(0) // pay fee from transfer amount + .addDestination(sendAddress, sendAmount) + .setSubtractFeeFrom(0) // pay mining fee from send amount .setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY); - if (!BigInteger.valueOf(0).equals(feeAmount)) txConfig.addDestination(HavenoUtils.getTradeFeeAddress(), feeAmount); + if (!BigInteger.valueOf(0).equals(feeAmount)) txConfig.addDestination(feeAddress, feeAmount); MoneroTxWallet tradeTx = createTx(txConfig); // freeze inputs @@ -668,29 +667,37 @@ public class XmrWalletService { return tradeTx; } + public MoneroTx verifyReserveTx(String offerId, BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendTradeAmount, BigInteger securityDeposit, String returnAddress, String txHash, String txHex, String txKey, List keyImages) { + BigInteger sendAmount = sendTradeAmount.add(securityDeposit).add(tradeFee).subtract(penaltyFee); + return verifyTradeTx(offerId, penaltyFee, HavenoUtils.getBurnAddress(), sendAmount, returnAddress, txHash, txHex, txKey, keyImages); + } + + public MoneroTx verifyDepositTx(String offerId, BigInteger feeAmount, String feeAddress, BigInteger sendTradeAmount, BigInteger securityDeposit, String multisigAddress, String txHash, String txHex, String txKey, List keyImages) { + BigInteger sendAmount = sendTradeAmount.add(securityDeposit); + return verifyTradeTx(offerId, feeAmount, feeAddress, sendAmount, multisigAddress, txHash, txHex, txKey, keyImages); + } + /** * Verify a reserve or deposit transaction. * Checks double spends, trade fee, deposit amount and destination, and miner fee. * The transaction is submitted to the pool then flushed without relaying. * * @param offerId id of offer to verify trade tx - * @param penaltyFee penalty fee for breaking protocol - * @param tradeFee trade fee - * @param sendAmount amount to give peer - * @param securityDeposit security deposit amount - * @param address expected destination address for the deposit amount + * @param feeAmount amount sent to fee address + * @param feeAddress fee address + * @param sendAmount amount sent to transfer address + * @param sendAddress transfer address * @param txHash transaction hash * @param txHex transaction hex * @param txKey transaction key * @param keyImages expected key images of inputs, ignored if null - * @return tuple with the verified tx and the actual security deposit + * @return the verified tx */ - public Tuple2 verifyTradeTx(String offerId, BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List keyImages) { + public MoneroTx verifyTradeTx(String offerId, BigInteger feeAmount, String feeAddress, BigInteger sendAmount, String sendAddress, String txHash, String txHex, String txKey, List keyImages) { if (txHash == null) throw new IllegalArgumentException("Cannot verify trade tx with null id"); MoneroDaemonRpc daemon = getDaemon(); MoneroWallet wallet = getWallet(); MoneroTx tx = null; - BigInteger actualSecurityDeposit = null; synchronized (daemon) { try { @@ -723,39 +730,25 @@ public class XmrWalletService { log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff); // verify proof to fee address - MoneroCheckTx feeCheck = wallet.checkTxKey(txHash, txKey, HavenoUtils.getTradeFeeAddress()); - if (!feeCheck.isGood()) throw new RuntimeException("Invalid proof to trade fee address"); - - // verify proof to transfer address - MoneroCheckTx transferCheck = wallet.checkTxKey(txHash, txKey, address); - if (!transferCheck.isGood()) throw new RuntimeException("Invalid proof to transfer address"); - - // verify fee and transfer amounts - BigInteger actualFee = feeCheck.getReceivedAmount(); - BigInteger actualTransferAmount = transferCheck.getReceivedAmount(); - boolean isDepositTx = penaltyFee == null; - if (isDepositTx) { - - // verify trade fee - if (!actualFee.equals(tradeFee)) throw new RuntimeException("Invalid trade fee amount, expected " + tradeFee + " but was " + actualFee); - - // verify multisig deposit amount - BigInteger expectedTransferAmount = sendAmount.add(securityDeposit).subtract(tx.getFee()); - if (!actualTransferAmount.equals(expectedTransferAmount)) throw new RuntimeException("Invalid multisig deposit amount, expected " + expectedTransferAmount + " but was " + actualTransferAmount); - actualSecurityDeposit = actualTransferAmount.subtract(sendAmount); - } else { - - // verify penalty fee - if (!actualFee.equals(penaltyFee)) throw new RuntimeException("Invalid penalty fee amount, expected " + penaltyFee + " but was " + actualFee); - - // verify return amount - BigInteger expectedTransferAmount = sendAmount.add(securityDeposit).add(tradeFee).subtract(penaltyFee).subtract(tx.getFee()); - if (!actualTransferAmount.equals(expectedTransferAmount)) throw new RuntimeException("Invalid return amount, expected " + expectedTransferAmount + " but was " + actualTransferAmount); - actualSecurityDeposit = actualTransferAmount.subtract(sendAmount).subtract(tradeFee); + BigInteger actualFee = BigInteger.ZERO; + if (feeAmount.compareTo(BigInteger.ZERO) > 0) { + MoneroCheckTx feeCheck = wallet.checkTxKey(txHash, txKey, feeAddress); + if (!feeCheck.isGood()) throw new RuntimeException("Invalid proof to trade fee address"); + actualFee = feeCheck.getReceivedAmount(); } - // return the result - return new Tuple2<>(tx, actualSecurityDeposit); + // verify proof to transfer address + MoneroCheckTx transferCheck = wallet.checkTxKey(txHash, txKey, sendAddress); + if (!transferCheck.isGood()) throw new RuntimeException("Invalid proof to transfer address"); + BigInteger actualSendAmount = transferCheck.getReceivedAmount(); + + // verify fee amount + if (!actualFee.equals(feeAmount)) throw new RuntimeException("Invalid fee amount, expected " + feeAmount + " but was " + actualFee); + + // verify send amount + BigInteger expectedSendAmount = sendAmount.subtract(tx.getFee()); + if (!actualSendAmount.equals(expectedSendAmount)) throw new RuntimeException("Invalid send amount, expected " + expectedSendAmount + " but was " + actualSendAmount + " with tx fee " + tx.getFee()); + return tx; } catch (Exception e) { log.warn("Error verifying trade tx with offer id=" + offerId + (tx == null ? "" : ", tx=" + tx) + ": " + e.getMessage()); throw e; @@ -943,14 +936,6 @@ public class XmrWalletService { else return getNewAddressEntry(offerId, context); } - public synchronized XmrAddressEntry getArbitratorAddressEntry() { - XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR; - Optional addressEntry = getAddressEntryListAsImmutableList().stream() - .filter(e -> context == e.getContext()) - .findAny(); - return addressEntry.isPresent() ? addressEntry.get() : getNewAddressEntryAux(null, context); - } - public synchronized Optional getAddressEntry(String offerId, XmrAddressEntry.Context context) { List entries = getAddressEntryListAsImmutableList().stream().filter(e -> offerId.equals(e.getOfferId())).filter(e -> context == e.getContext()).collect(Collectors.toList()); if (entries.size() > 1) throw new RuntimeException("Multiple address entries exist with offer ID " + offerId + " and context " + context + ". That should never happen."); @@ -1008,6 +993,10 @@ public class XmrWalletService { return getAddressEntryListAsImmutableList().stream().filter(addressEntry -> context == addressEntry.getContext()).collect(Collectors.toList()); } + public XmrAddressEntry getBaseAddressEntry() { + return getAddressEntryListAsImmutableList().stream().filter(e -> e.getContext() == XmrAddressEntry.Context.BASE_ADDRESS).findAny().orElse(null); + } + public List getFundedAvailableAddressEntries() { return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList()); } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 97cb969d4c..389d3d6254 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -254,6 +254,7 @@ message InitMultisigRequest { string prepared_multisig_hex = 4; string made_multisig_hex = 5; string exchanged_multisig_hex = 6; + string trade_fee_address = 7; } message SignContractRequest { @@ -1558,6 +1559,7 @@ message ProcessModel { int64 buyer_payout_amount_from_mediation = 16; int64 seller_payout_amount_from_mediation = 17; int64 delete_backups_height = 18; + string trade_fee_address = 19; } message TradePeer {