arbitrator assigns trade fee address

This commit is contained in:
woodser 2024-05-02 07:08:56 -04:00
parent eefcf0191f
commit a5883d7bcd
11 changed files with 125 additions and 177 deletions

View file

@ -1243,13 +1243,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee) // verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.PENALTY_FEE_PCT); BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.PENALTY_FEE_PCT);
BigInteger maxTradeFee = HavenoUtils.multiply(offer.getAmount(), HavenoUtils.MAKER_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(); BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
Tuple2<MoneroTx, BigInteger> txResult = xmrWalletService.verifyTradeTx( MoneroTx verifiedTx = xmrWalletService.verifyReserveTx(
offer.getId(), offer.getId(),
penaltyFee, penaltyFee,
maxTradeFee, maxTradeFee,
sendAmount, sendTradeAmount,
securityDeposit, securityDeposit,
request.getPayoutAddress(), request.getPayoutAddress(),
request.getReserveTxHash(), request.getReserveTxHash(),
@ -1272,7 +1272,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
request.getReserveTxHash(), request.getReserveTxHash(),
request.getReserveTxHex(), request.getReserveTxHex(),
request.getReserveTxKeyImages(), request.getReserveTxKeyImages(),
txResult.first.getFee().longValueExact(), verifiedTx.getFee().longValueExact(),
signature); // TODO (woodser): no need for signature to be part of SignedOffer? signature); // TODO (woodser): no need for signature to be part of SignedOffer?
addSignedOffer(signedOffer); addSignedOffer(signedOffer);
requestPersistence(); requestPersistence();

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class);
@SuppressWarnings({"unused"})
public CreateMakerFeeTx(TaskRunner<PlaceOfferModel> 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);
}
}
}

View file

@ -429,19 +429,18 @@ public class HavenoUtils {
// ----------------------------- OTHER UTILS ------------------------------ // ----------------------------- OTHER UTILS ------------------------------
/**
* Get address to collect trade fees.
*
* @return the address which collects trade fees
*/
public static String getTradeFeeAddress() { public static String getTradeFeeAddress() {
return xmrWalletService.getBaseAddressEntry().getAddressString();
}
public static String getBurnAddress() {
switch (Config.baseCurrencyNetwork()) { switch (Config.baseCurrencyNetwork()) {
case XMR_LOCAL: case XMR_LOCAL:
return "Bd37nTGHjL3RvPxc9dypzpWiXQrPzxxG4RsWAasD9CV2iZ1xfFZ7mzTKNDxWBfsqQSUimctAsGtTZ8c8bZJy35BYL9jYj88"; return "Bd37nTGHjL3RvPxc9dypzpWiXQrPzxxG4RsWAasD9CV2iZ1xfFZ7mzTKNDxWBfsqQSUimctAsGtTZ8c8bZJy35BYL9jYj88";
case XMR_STAGENET: case XMR_STAGENET:
return "5B11hTJdG2XDNwjdKGLRxwSLwDhkbGg7C7UEAZBxjE6FbCeRMjudrpNACmDNtWPiSnNfjDQf39QRjdtdgoL69txv81qc2Mc"; return "577XbZ8yGfrWJM3aAoCpHVgDCm5higshGVJBb4ZNpTYARp8rLcCdcA1J8QgRfFWTzmJ8QgRfFWTzmJ8QgRfFWTzmCbXF9hd";
case XMR_MAINNET: case XMR_MAINNET:
throw new RuntimeException("Mainnet fee address not implemented"); return "46uVWiE1d4kWJM3aAoCpHVgDCm5higshGVJBb4ZNpTYARp8rLcCdcA1J8QgRfFWTzmJ8QgRfFWTzmJ8QgRfFWTzmCag5CXT";
default: default:
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork()); throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
} }

View file

@ -36,6 +36,8 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes
private final String madeMultisigHex; private final String madeMultisigHex;
@Nullable @Nullable
private final String exchangedMultisigHex; private final String exchangedMultisigHex;
@Nullable
private final String tradeFeeAddress;
public InitMultisigRequest(String tradeId, public InitMultisigRequest(String tradeId,
String uid, String uid,
@ -43,12 +45,14 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes
long currentDate, long currentDate,
String preparedMultisigHex, String preparedMultisigHex,
String madeMultisigHex, String madeMultisigHex,
String exchangedMultisigHex) { String exchangedMultisigHex,
String tradeFeeAddress) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.currentDate = currentDate; this.currentDate = currentDate;
this.preparedMultisigHex = preparedMultisigHex; this.preparedMultisigHex = preparedMultisigHex;
this.madeMultisigHex = madeMultisigHex; this.madeMultisigHex = madeMultisigHex;
this.exchangedMultisigHex = exchangedMultisigHex; 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(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(exchangedMultisigHex).ifPresent(e -> builder.setExchangedMultisigHex(exchangedMultisigHex)); Optional.ofNullable(exchangedMultisigHex).ifPresent(e -> builder.setExchangedMultisigHex(exchangedMultisigHex));
Optional.ofNullable(tradeFeeAddress).ifPresent(e -> builder.setTradeFeeAddress(tradeFeeAddress));
builder.setCurrentDate(currentDate); builder.setCurrentDate(currentDate);
@ -80,16 +85,18 @@ public final class InitMultisigRequest extends TradeMessage implements DirectMes
proto.getCurrentDate(), proto.getCurrentDate(),
ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()), ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()),
ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()), ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()),
ProtoUtil.stringOrNullFromProto(proto.getExchangedMultisigHex())); ProtoUtil.stringOrNullFromProto(proto.getExchangedMultisigHex()),
ProtoUtil.stringOrNullFromProto(proto.getTradeFeeAddress()));
} }
@Override @Override
public String toString() { public String toString() {
return "InitMultisigRequest {" + return "InitMultisigRequest {" +
",\n currentDate=" + currentDate + ",\n currentDate=" + currentDate +
",\n preparedMultisigHex='" + preparedMultisigHex + ",\n preparedMultisigHex=" + preparedMultisigHex +
",\n madeMultisigHex='" + madeMultisigHex + ",\n madeMultisigHex=" + madeMultisigHex +
",\n exchangedMultisigHex='" + exchangedMultisigHex + ",\n exchangedMultisigHex=" + exchangedMultisigHex +
",\n tradeFeeAddress=" + tradeFeeAddress +
"\n} " + super.toString(); "\n} " + super.toString();
} }
} }

View file

@ -151,6 +151,9 @@ public class ProcessModel implements Model, PersistablePayload {
@Nullable @Nullable
@Getter @Getter
@Setter @Setter
private String tradeFeeAddress;
@Getter
@Setter
private String multisigAddress; private String multisigAddress;
@Getter @Getter
@Setter @Setter
@ -212,6 +215,7 @@ public class ProcessModel implements Model, PersistablePayload {
Optional.ofNullable(tempTradePeerNodeAddress).ifPresent(e -> builder.setTempTradePeerNodeAddress(tempTradePeerNodeAddress.toProtoMessage())); Optional.ofNullable(tempTradePeerNodeAddress).ifPresent(e -> builder.setTempTradePeerNodeAddress(tempTradePeerNodeAddress.toProtoMessage()));
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(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)); Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress));
return builder.build(); return builder.build();
} }
@ -233,6 +237,7 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setTempTradePeerNodeAddress(proto.hasTempTradePeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradePeerNodeAddress()) : null); processModel.setTempTradePeerNodeAddress(proto.hasTempTradePeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradePeerNodeAddress()) : null);
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
processModel.setMakerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMakerSignature())); processModel.setMakerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMakerSignature()));
processModel.setTradeFeeAddress(ProtoUtil.stringOrNullFromProto(proto.getTradeFeeAddress()));
processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress())); processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress()));
String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState()); String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState());

View file

@ -22,7 +22,6 @@ import common.utils.JsonUtils;
import haveno.common.app.Version; import haveno.common.app.Version;
import haveno.common.crypto.PubKeyRing; import haveno.common.crypto.PubKeyRing;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.common.util.Tuple2;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
@ -80,18 +79,18 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
boolean isFromTaker = trader == trade.getTaker(); boolean isFromTaker = trader == trade.getTaker();
boolean isFromBuyer = trader == trade.getBuyer(); boolean isFromBuyer = trader == trade.getBuyer();
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee(); 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(); BigInteger securityDeposit = isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
String depositAddress = processModel.getMultisigAddress(); String depositAddress = processModel.getMultisigAddress();
// verify deposit tx // verify deposit tx
Tuple2<MoneroTx, BigInteger> txResult; MoneroTx verifiedTx;
try { try {
txResult = trade.getXmrWalletService().verifyTradeTx( verifiedTx = trade.getXmrWalletService().verifyDepositTx(
offer.getId(), offer.getId(),
null,
tradeFee, tradeFee,
sendAmount, trade.getProcessModel().getTradeFeeAddress(),
sendTradeAmount,
securityDeposit, securityDeposit,
depositAddress, depositAddress,
trader.getDepositTxHash(), trader.getDepositTxHash(),
@ -103,8 +102,8 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
} }
// set deposit info // set deposit info
trader.setSecurityDeposit(txResult.second); trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
trader.setDepositTxFee(txResult.first.getFee()); trader.setDepositTxFee(verifiedTx.getFee());
trader.setDepositTxHex(request.getDepositTxHex()); trader.setDepositTxHex(request.getDepositTxHex());
trader.setDepositTxKey(request.getDepositTxKey()); trader.setDepositTxKey(request.getDepositTxKey());
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey()); if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());

View file

@ -18,7 +18,6 @@
package haveno.core.trade.protocol.tasks; package haveno.core.trade.protocol.tasks;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.common.util.Tuple2;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.offer.OfferDirection; import haveno.core.offer.OfferDirection;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
@ -59,9 +58,9 @@ public class ArbitratorProcessReserveTx extends TradeTask {
BigInteger tradeFee = isFromMaker ? offer.getMaxMakerFee() : trade.getTakerFee(); 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 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(); BigInteger securityDeposit = isFromMaker ? isFromBuyer ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit() : isFromBuyer ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
Tuple2<MoneroTx, BigInteger> txResult; MoneroTx verifiedTx;
try { try {
txResult = trade.getXmrWalletService().verifyTradeTx( verifiedTx = trade.getXmrWalletService().verifyReserveTx(
offer.getId(), offer.getId(),
penaltyFee, penaltyFee,
tradeFee, tradeFee,
@ -79,10 +78,10 @@ public class ArbitratorProcessReserveTx extends TradeTask {
// save reserve tx to model // save reserve tx to model
TradePeer trader = isFromMaker ? processModel.getMaker() : processModel.getTaker(); TradePeer trader = isFromMaker ? processModel.getMaker() : processModel.getTaker();
trader.setSecurityDeposit(securityDeposit.subtract(verifiedTx.getFee())); // subtract mining fee from security deposit
trader.setReserveTxHash(request.getReserveTxHash()); trader.setReserveTxHash(request.getReserveTxHash());
trader.setReserveTxHex(request.getReserveTxHex()); trader.setReserveTxHex(request.getReserveTxHex());
trader.setReserveTxKey(request.getReserveTxKey()); trader.setReserveTxKey(request.getReserveTxKey());
trader.setSecurityDeposit(txResult.second);
// persist trade // persist trade
processModel.getTradeManager().requestPersistence(); processModel.getTradeManager().requestPersistence();

View file

@ -20,6 +20,7 @@ package haveno.core.trade.protocol.tasks;
import haveno.common.app.Version; import haveno.common.app.Version;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
import haveno.core.trade.messages.InitMultisigRequest; import haveno.core.trade.messages.InitMultisigRequest;
import haveno.core.trade.messages.InitTradeRequest; import haveno.core.trade.messages.InitTradeRequest;
@ -124,6 +125,11 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
String preparedHex = multisigWallet.prepareMultisig(); String preparedHex = multisigWallet.prepareMultisig();
trade.getSelf().setPreparedMultisigHex(preparedHex); trade.getSelf().setPreparedMultisigHex(preparedHex);
// set trade fee address
if (trade.getProcessModel().getTradeFeeAddress() == null) {
trade.getProcessModel().setTradeFeeAddress(HavenoUtils.getTradeFeeAddress());
}
// create message to initialize multisig // create message to initialize multisig
InitMultisigRequest initMultisigRequest = new InitMultisigRequest( InitMultisigRequest initMultisigRequest = new InitMultisigRequest(
processModel.getOffer().getId(), processModel.getOffer().getId(),
@ -132,7 +138,8 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
new Date().getTime(), new Date().getTime(),
preparedHex, preparedHex,
null, null,
null); null,
trade.getProcessModel().getTradeFeeAddress());
// send request to maker // send request to maker
log.info("Send {} with offerId {} and uid {} to maker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getOfferId(), initMultisigRequest.getUid(), trade.getMaker().getNodeAddress()); log.info("Send {} with offerId {} and uid {} to maker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getOfferId(), initMultisigRequest.getUid(), trade.getMaker().getNodeAddress());

View file

@ -62,16 +62,21 @@ public class ProcessInitMultisigRequest extends TradeTask {
checkTradeId(processModel.getOfferId(), request); checkTradeId(processModel.getOfferId(), request);
XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService(); XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService();
// get peer multisig participant // get sender
TradePeer multisigParticipant = trade.getTradePeer(processModel.getTempTradePeerNodeAddress()); 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 // reconcile peer's established multisig hex with message
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.getPreparedMultisigHex()); if (sender.getPreparedMultisigHex() == null) sender.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()); 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 (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(request.getMadeMultisigHex()); if (sender.getMadeMultisigHex() == null) sender.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()); 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 (multisigParticipant.getExchangedMultisigHex() == null) multisigParticipant.setExchangedMultisigHex(request.getExchangedMultisigHex()); if (sender.getExchangedMultisigHex() == null) sender.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()); 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 // prepare multisig if applicable
boolean updateParticipants = false; boolean updateParticipants = false;
@ -210,7 +215,8 @@ public class ProcessInitMultisigRequest extends TradeTask {
new Date().getTime(), new Date().getTime(),
trade.getSelf().getPreparedMultisigHex(), trade.getSelf().getPreparedMultisigHex(),
trade.getSelf().getMadeMultisigHex(), 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); log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getOfferId(), request.getUid(), recipient);
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener); processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener);

View file

@ -27,7 +27,6 @@ import haveno.common.ThreadUtils;
import haveno.common.UserThread; import haveno.common.UserThread;
import haveno.common.config.Config; import haveno.common.config.Config;
import haveno.common.file.FileUtil; import haveno.common.file.FileUtil;
import haveno.common.util.Tuple2;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.api.AccountServiceListener; import haveno.core.api.AccountServiceListener;
import haveno.core.api.CoreAccountService; import haveno.core.api.CoreAccountService;
@ -567,19 +566,20 @@ public class XmrWalletService {
* *
* @param penaltyFee penalty fee for breaking protocol * @param penaltyFee penalty fee for breaking protocol
* @param tradeFee trade fee * @param tradeFee trade fee
* @param sendAmount amount to send peer * @param sendTradeAmount trade amount to send peer
* @param securityDeposit security deposit amount * @param securityDeposit security deposit amount
* @param returnAddress return address for reserved funds * @param returnAddress return address for reserved funds
* @param reserveExactAmount specifies to reserve the exact input amount * @param reserveExactAmount specifies to reserve the exact input amount
* @param preferredSubaddressIndex preferred source subaddress to spend from (optional) * @param preferredSubaddressIndex preferred source subaddress to spend from (optional)
* @return the reserve tx * @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 (WALLET_LOCK) {
synchronized (HavenoUtils.getWalletFunctionLock()) { synchronized (HavenoUtils.getWalletFunctionLock()) {
log.info("Creating reserve tx with preferred subaddress index={}, return address={}", preferredSubaddressIndex, returnAddress); log.info("Creating reserve tx with preferred subaddress index={}, return address={}", preferredSubaddressIndex, returnAddress);
long time = System.currentTimeMillis(); 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); log.info("Done creating reserve tx in {} ms", System.currentTimeMillis() - time);
return reserveTx; return reserveTx;
} }
@ -597,27 +597,29 @@ public class XmrWalletService {
public MoneroTxWallet createDepositTx(Trade trade, boolean reserveExactAmount, Integer preferredSubaddressIndex) { public MoneroTxWallet createDepositTx(Trade trade, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
synchronized (WALLET_LOCK) { synchronized (WALLET_LOCK) {
synchronized (HavenoUtils.getWalletFunctionLock()) { synchronized (HavenoUtils.getWalletFunctionLock()) {
String multisigAddress = trade.getProcessModel().getMultisigAddress(); BigInteger feeAmount = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee();
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee(); String feeAddress = trade.getProcessModel().getTradeFeeAddress();
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.ZERO : trade.getAmount(); BigInteger sendTradeAmount = trade instanceof BuyerTrade ? BigInteger.ZERO : trade.getAmount();
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee(); BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
BigInteger sendAmount = sendTradeAmount.add(securityDeposit);
String multisigAddress = trade.getProcessModel().getMultisigAddress();
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
log.info("Creating deposit tx for trade {} {} with multisig address={}", trade.getClass().getSimpleName(), trade.getShortId(), multisigAddress); 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); log.info("Done creating deposit tx for trade {} {} in {} ms", trade.getClass().getSimpleName(), trade.getShortId(), System.currentTimeMillis() - time);
return depositTx; 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) { synchronized (WALLET_LOCK) {
MoneroWallet wallet = getWallet(); MoneroWallet wallet = getWallet();
// create a list of subaddresses to attempt spending from in preferred order // create a list of subaddresses to attempt spending from in preferred order
List<Integer> subaddressIndices = new ArrayList<Integer>(); List<Integer> subaddressIndices = new ArrayList<Integer>();
if (reserveExactAmount) { if (reserveExactAmount) {
BigInteger exactInputAmount = tradeFee.add(sendAmount).add(securityDeposit); BigInteger exactInputAmount = feeAmount.add(sendAmount);
List<Integer> subaddressIndicesWithExactInput = getSubaddressesWithExactInput(exactInputAmount); List<Integer> subaddressIndicesWithExactInput = getSubaddressesWithExactInput(exactInputAmount);
if (preferredSubaddressIndex != null) subaddressIndicesWithExactInput.remove(preferredSubaddressIndex); if (preferredSubaddressIndex != null) subaddressIndicesWithExactInput.remove(preferredSubaddressIndex);
Collections.sort(subaddressIndicesWithExactInput); Collections.sort(subaddressIndicesWithExactInput);
@ -635,30 +637,27 @@ public class XmrWalletService {
// first try preferred subaddressess // first try preferred subaddressess
for (int i = 0; i < subaddressIndices.size(); i++) { for (int i = 0; i < subaddressIndices.size(); i++) {
try { try {
return createTradeTxFromSubaddress(penaltyFee, tradeFee, sendAmount, securityDeposit, address, reserveExactAmount, subaddressIndices.get(i)); return createTradeTxFromSubaddress(feeAmount, feeAddress, sendAmount, sendAddress, subaddressIndices.get(i));
} catch (Exception e) { } catch (Exception e) {
if (i == subaddressIndices.size() - 1 && reserveExactAmount) throw e; // throw if no subaddress with exact output if (i == subaddressIndices.size() - 1 && reserveExactAmount) throw e; // throw if no subaddress with exact output
} }
} }
// try any subaddress // 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 // 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() MoneroTxConfig txConfig = new MoneroTxConfig()
.setAccountIndex(0) .setAccountIndex(0)
.setSubaddressIndices(subaddressIndex) .setSubaddressIndices(subaddressIndex)
.addDestination(address, transferAmount) .addDestination(sendAddress, sendAmount)
.setSubtractFeeFrom(0) // pay fee from transfer amount .setSubtractFeeFrom(0) // pay mining fee from send amount
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY); .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); MoneroTxWallet tradeTx = createTx(txConfig);
// freeze inputs // freeze inputs
@ -668,29 +667,37 @@ public class XmrWalletService {
return tradeTx; return tradeTx;
} }
public MoneroTx verifyReserveTx(String offerId, BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendTradeAmount, BigInteger securityDeposit, String returnAddress, String txHash, String txHex, String txKey, List<String> 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<String> keyImages) {
BigInteger sendAmount = sendTradeAmount.add(securityDeposit);
return verifyTradeTx(offerId, feeAmount, feeAddress, sendAmount, multisigAddress, txHash, txHex, txKey, keyImages);
}
/** /**
* Verify a reserve or deposit transaction. * Verify a reserve or deposit transaction.
* Checks double spends, trade fee, deposit amount and destination, and miner fee. * Checks double spends, trade fee, deposit amount and destination, and miner fee.
* The transaction is submitted to the pool then flushed without relaying. * The transaction is submitted to the pool then flushed without relaying.
* *
* @param offerId id of offer to verify trade tx * @param offerId id of offer to verify trade tx
* @param penaltyFee penalty fee for breaking protocol * @param feeAmount amount sent to fee address
* @param tradeFee trade fee * @param feeAddress fee address
* @param sendAmount amount to give peer * @param sendAmount amount sent to transfer address
* @param securityDeposit security deposit amount * @param sendAddress transfer address
* @param address expected destination address for the deposit amount
* @param txHash transaction hash * @param txHash transaction hash
* @param txHex transaction hex * @param txHex transaction hex
* @param txKey transaction key * @param txKey transaction key
* @param keyImages expected key images of inputs, ignored if null * @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<MoneroTx, BigInteger> verifyTradeTx(String offerId, BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) { public MoneroTx verifyTradeTx(String offerId, BigInteger feeAmount, String feeAddress, BigInteger sendAmount, String sendAddress, String txHash, String txHex, String txKey, List<String> keyImages) {
if (txHash == null) throw new IllegalArgumentException("Cannot verify trade tx with null id"); if (txHash == null) throw new IllegalArgumentException("Cannot verify trade tx with null id");
MoneroDaemonRpc daemon = getDaemon(); MoneroDaemonRpc daemon = getDaemon();
MoneroWallet wallet = getWallet(); MoneroWallet wallet = getWallet();
MoneroTx tx = null; MoneroTx tx = null;
BigInteger actualSecurityDeposit = null;
synchronized (daemon) { synchronized (daemon) {
try { try {
@ -723,39 +730,25 @@ public class XmrWalletService {
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff); log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
// verify proof to fee address // verify proof to fee address
MoneroCheckTx feeCheck = wallet.checkTxKey(txHash, txKey, HavenoUtils.getTradeFeeAddress()); 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"); if (!feeCheck.isGood()) throw new RuntimeException("Invalid proof to trade fee address");
actualFee = feeCheck.getReceivedAmount();
// 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);
} }
// return the result // verify proof to transfer address
return new Tuple2<>(tx, actualSecurityDeposit); 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) { } catch (Exception e) {
log.warn("Error verifying trade tx with offer id=" + offerId + (tx == null ? "" : ", tx=" + tx) + ": " + e.getMessage()); log.warn("Error verifying trade tx with offer id=" + offerId + (tx == null ? "" : ", tx=" + tx) + ": " + e.getMessage());
throw e; throw e;
@ -943,14 +936,6 @@ public class XmrWalletService {
else return getNewAddressEntry(offerId, context); else return getNewAddressEntry(offerId, context);
} }
public synchronized XmrAddressEntry getArbitratorAddressEntry() {
XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR;
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> context == e.getContext())
.findAny();
return addressEntry.isPresent() ? addressEntry.get() : getNewAddressEntryAux(null, context);
}
public synchronized Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) { public synchronized Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) {
List<XmrAddressEntry> entries = getAddressEntryListAsImmutableList().stream().filter(e -> offerId.equals(e.getOfferId())).filter(e -> context == e.getContext()).collect(Collectors.toList()); List<XmrAddressEntry> 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."); 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()); 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<XmrAddressEntry> getFundedAvailableAddressEntries() { public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList()); return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0).collect(Collectors.toList());
} }

View file

@ -254,6 +254,7 @@ message InitMultisigRequest {
string prepared_multisig_hex = 4; string prepared_multisig_hex = 4;
string made_multisig_hex = 5; string made_multisig_hex = 5;
string exchanged_multisig_hex = 6; string exchanged_multisig_hex = 6;
string trade_fee_address = 7;
} }
message SignContractRequest { message SignContractRequest {
@ -1558,6 +1559,7 @@ message ProcessModel {
int64 buyer_payout_amount_from_mediation = 16; int64 buyer_payout_amount_from_mediation = 16;
int64 seller_payout_amount_from_mediation = 17; int64 seller_payout_amount_from_mediation = 17;
int64 delete_backups_height = 18; int64 delete_backups_height = 18;
string trade_fee_address = 19;
} }
message TradePeer { message TradePeer {