increase penalty fee to security deposit

This commit is contained in:
woodser 2023-03-08 15:06:55 -05:00
parent 8ea556fa4f
commit a16b03bb5c
11 changed files with 82 additions and 55 deletions

View File

@ -987,7 +987,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
BigInteger sendAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.valueOf(0) : offer.getAmount();
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
MoneroTx reserveTx = xmrWalletService.verifyTradeTx(
Tuple2<MoneroTx, BigInteger> txResult = xmrWalletService.verifyTradeTx(
tradeFee,
sendAmount,
securityDeposit,
@ -995,7 +995,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKey(),
request.getReserveTxKeyImages());
request.getReserveTxKeyImages(),
true);
// arbitrator signs offer to certify they have valid reserve tx
String offerPayloadAsJson = JsonUtil.objectToJson(request.getOfferPayload());
@ -1008,11 +1009,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
System.currentTimeMillis(),
signedOfferPayload.getId(),
offer.getAmount().longValueExact(),
HavenoUtils.getMakerFee(offer.getAmount()).longValueExact(),
txResult.second.longValueExact(),
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKeyImages(),
reserveTx.getFee().longValueExact(),
txResult.first.getFee().longValueExact(),
signature); // TODO (woodser): no need for signature to be part of SignedOffer?
addSignedOffer(signedOffer);
requestPersistence();

View File

@ -34,7 +34,7 @@ public final class SignedOffer implements PersistablePayload {
@Getter
private final long tradeAmount;
@Getter
private final long makerTradeFee;
private final long penaltyAmount;
@Getter
private final String reserveTxHash;
@Getter
@ -49,7 +49,7 @@ public final class SignedOffer implements PersistablePayload {
public SignedOffer(long timeStamp,
String offerId,
long tradeAmount,
long makerTradeFee,
long penaltyAmount,
String reserveTxHash,
String reserveTxHex,
List<String> reserveTxKeyImages,
@ -58,7 +58,7 @@ public final class SignedOffer implements PersistablePayload {
this.timeStamp = timeStamp;
this.offerId = offerId;
this.tradeAmount = tradeAmount;
this.makerTradeFee = makerTradeFee;
this.penaltyAmount = penaltyAmount;
this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex;
this.reserveTxKeyImages = reserveTxKeyImages;
@ -76,7 +76,7 @@ public final class SignedOffer implements PersistablePayload {
.setTimeStamp(timeStamp)
.setOfferId(offerId)
.setTradeAmount(tradeAmount)
.setMakerTradeFee(makerTradeFee)
.setPenaltyAmount(penaltyAmount)
.setReserveTxHash(reserveTxHash)
.setReserveTxHex(reserveTxHex)
.addAllReserveTxKeyImages(reserveTxKeyImages)
@ -89,7 +89,7 @@ public final class SignedOffer implements PersistablePayload {
return new SignedOffer(proto.getTimeStamp(),
proto.getOfferId(),
proto.getTradeAmount(),
proto.getMakerTradeFee(),
proto.getPenaltyAmount(),
proto.getReserveTxHash(),
proto.getReserveTxHex(),
proto.getReserveTxKeyImagesList(),
@ -108,7 +108,7 @@ public final class SignedOffer implements PersistablePayload {
",\n timeStamp=" + timeStamp +
",\n offerId=" + offerId +
",\n tradeAmount=" + tradeAmount +
",\n makerTradeFee=" + makerTradeFee +
",\n penaltyAmount=" + penaltyAmount +
",\n reserveTxHash=" + reserveTxHash +
",\n reserveTxHex=" + reserveTxHex +
",\n reserveTxKeyImages=" + reserveTxKeyImages +

View File

@ -1570,12 +1570,12 @@ public abstract class Trade implements Tradable, Model {
public BigInteger getBuyerSecurityDeposit() {
if (getBuyer().getDepositTxHash() == null) return null;
return BigInteger.valueOf(getBuyer().getSecurityDeposit());
return getBuyer().getSecurityDeposit();
}
public BigInteger getSellerSecurityDeposit() {
if (getSeller().getDepositTxHash() == null) return null;
return BigInteger.valueOf(getSeller().getSecurityDeposit());
return getSeller().getSecurityDeposit();
}
@Nullable
@ -1719,11 +1719,11 @@ public abstract class Trade implements Tradable, Model {
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
// set security deposits
if (getBuyer().getSecurityDeposit() == 0) {
if (getBuyer().getSecurityDeposit().longValueExact() == 0) {
BigInteger buyerSecurityDeposit = ((MoneroTxWallet) getBuyer().getDepositTx()).getIncomingAmount();
BigInteger sellerSecurityDeposit = ((MoneroTxWallet) getSeller().getDepositTx()).getIncomingAmount().subtract(getAmount());
getBuyer().setSecurityDeposit(buyerSecurityDeposit.longValueExact());
getSeller().setSecurityDeposit(sellerSecurityDeposit.longValueExact());
getBuyer().setSecurityDeposit(buyerSecurityDeposit);
getSeller().setSecurityDeposit(sellerSecurityDeposit);
}
// set deposits published state

View File

@ -27,6 +27,8 @@ import haveno.core.payment.payload.PaymentAccountPayload;
import haveno.core.proto.CoreProtoResolver;
import haveno.core.xmr.model.RawTransactionInput;
import haveno.network.p2p.NodeAddress;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -133,6 +135,14 @@ public final class TradePeer implements PersistablePayload {
public TradePeer() {
}
public BigInteger getSecurityDeposit() {
return BigInteger.valueOf(securityDeposit);
}
public void setSecurityDeposit(BigInteger securityDeposit) {
this.securityDeposit = securityDeposit.longValueExact();
}
@Override
public Message toProtoMessage() {
final protobuf.TradePeer.Builder builder = protobuf.TradePeer.newBuilder()
@ -219,7 +229,7 @@ public final class TradePeer implements PersistablePayload {
tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
tradePeer.setSecurityDeposit(proto.getSecurityDeposit());
tradePeer.setSecurityDeposit(BigInteger.valueOf(proto.getSecurityDeposit()));
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
return tradePeer;
}

View File

@ -90,7 +90,8 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
trader.getDepositTxHash(),
request.getDepositTxHex(),
request.getDepositTxKey(),
null);
null,
false);
} catch (Exception e) {
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + trader.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
}

View File

@ -18,6 +18,7 @@
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.Trade;
@ -26,6 +27,7 @@ import haveno.core.trade.protocol.TradePeer;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.model.MoneroTx;
/**
* Arbitrator verifies reserve tx from maker or taker.
@ -55,8 +57,9 @@ public class ArbitratorProcessReserveTx extends TradeTask {
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : offer.getAmount();
BigInteger securityDeposit = isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
Tuple2<MoneroTx, BigInteger> txResult;
try {
trade.getXmrWalletService().verifyTradeTx(
txResult = trade.getXmrWalletService().verifyTradeTx(
tradeFee,
sendAmount,
securityDeposit,
@ -64,7 +67,8 @@ public class ArbitratorProcessReserveTx extends TradeTask {
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKey(),
null);
null,
true);
} catch (Exception e) {
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
}
@ -74,6 +78,7 @@ public class ArbitratorProcessReserveTx extends TradeTask {
trader.setReserveTxHash(request.getReserveTxHash());
trader.setReserveTxHex(request.getReserveTxHex());
trader.setReserveTxKey(request.getReserveTxKey());
trader.setSecurityDeposit(txResult.second);
// persist trade
processModel.getTradeManager().requestPersistence();

View File

@ -9,6 +9,7 @@ import haveno.common.UserThread;
import haveno.common.config.BaseCurrencyNetwork;
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;
@ -300,7 +301,7 @@ public class XmrWalletService {
*/
public MoneroTxWallet createReserveTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String returnAddress) {
log.info("Creating reserve tx with fee={}, sendAmount={}, securityDeposit={}", tradeFee, sendAmount, securityDeposit);
return createTradeTx(tradeFee, sendAmount, securityDeposit, returnAddress);
return createTradeTx(tradeFee, sendAmount, securityDeposit, returnAddress, true);
}
/**
@ -326,11 +327,11 @@ public class XmrWalletService {
}
log.info("Creating deposit tx with fee={}, sendAmount={}, securityDeposit={}", tradeFee, sendAmount, securityDeposit);
return createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress);
return createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress, false);
}
}
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address) {
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean isReserveTx) {
MoneroWallet wallet = getWallet();
synchronized (wallet) {
@ -341,10 +342,10 @@ public class XmrWalletService {
for (int i = 0; i < 10; i++) {
try {
BigInteger appliedSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE * appliedTolerance)).toBigInteger();
BigInteger amount = sendAmount.add(appliedSecurityDeposit);
BigInteger amount = sendAmount.add(isReserveTx ? tradeFee : appliedSecurityDeposit);
tradeTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
.addDestination(HavenoUtils.getTradeFeeAddress(), isReserveTx ? appliedSecurityDeposit : tradeFee) // reserve tx charges security deposit if published
.addDestination(address, amount));
appliedTolerance -= searchDiff; // apply less tolerance to increase security deposit
if (appliedTolerance < 0.0) break; // can send full security deposit
@ -375,12 +376,13 @@ public class XmrWalletService {
* @param txHex transaction hex
* @param txKey transaction key
* @param keyImages expected key images of inputs, ignored if null
* @return the verified tx
* @return tuple with the verified tx and its actual security deposit
*/
public MoneroTx verifyTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
public Tuple2<MoneroTx, BigInteger> verifyTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages, boolean isReserveTx) {
MoneroDaemonRpc daemon = getDaemon();
MoneroWallet wallet = getWallet();
MoneroTx tx = null;
BigInteger actualSecurityDeposit = null;
synchronized (daemon) {
try {
@ -403,28 +405,36 @@ public class XmrWalletService {
// verify unlock height
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
// verify trade fee
String feeAddress = HavenoUtils.getTradeFeeAddress();
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 miner fee
BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
// verify transfer proof to fee address
String feeAddress = HavenoUtils.getTradeFeeAddress();
MoneroCheckTx feeCheck = wallet.checkTxKey(txHash, txKey, feeAddress);
if (!feeCheck.isGood()) throw new RuntimeException("Invalid proof of trade fee");
// verify transfer proof to return address
MoneroCheckTx returnCheck = wallet.checkTxKey(txHash, txKey, address);
if (!returnCheck.isGood()) throw new RuntimeException("Invalid proof of return funds");
// collect actual trade fee, send amount, and security deposit
BigInteger actualTradeFee = isReserveTx ? returnCheck.getReceivedAmount().subtract(sendAmount) : feeCheck.getReceivedAmount();
actualSecurityDeposit = isReserveTx ? feeCheck.getReceivedAmount() : returnCheck.getReceivedAmount().subtract(sendAmount);
BigInteger actualSendAmount = returnCheck.getReceivedAmount().subtract(isReserveTx ? actualTradeFee : actualSecurityDeposit);
// verify trade fee
if (!tradeFee.equals(actualTradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + actualTradeFee);
// verify sufficient security deposit
check = wallet.checkTxKey(txHash, txKey, address);
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
BigInteger minSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
BigInteger actualSecurityDeposit = check.getReceivedAmount().subtract(sendAmount);
if (actualSecurityDeposit.compareTo(minSecurityDeposit) < 0) throw new RuntimeException("Security deposit amount is not enough, needed " + minSecurityDeposit + " but was " + actualSecurityDeposit);
// verify deposit amount + miner fee within dust tolerance
BigInteger minDepositAndFee = sendAmount.add(securityDeposit).subtract(new BigDecimal(tx.getFee()).multiply(new BigDecimal(1.0 - DUST_TOLERANCE)).toBigInteger());
BigInteger actualDepositAndFee = check.getReceivedAmount().add(tx.getFee());
BigInteger actualDepositAndFee = actualSendAmount.add(actualSecurityDeposit).add(tx.getFee());
if (actualDepositAndFee.compareTo(minDepositAndFee) < 0) throw new RuntimeException("Deposit amount + fee is not enough, needed " + minDepositAndFee + " but was " + actualDepositAndFee);
} finally {
try {
@ -434,7 +444,7 @@ public class XmrWalletService {
throw err.getCode() == -32601 ? new RuntimeException("Failed to flush tx from pool. Arbitrator must use trusted, unrestricted daemon") : err;
}
}
return tx;
return new Tuple2<>(tx, actualSecurityDeposit);
}
}

View File

@ -993,7 +993,7 @@ portfolio.failed.cantUnfail=This trade cannot be moved back to open trades at th
Try again after completion of trade(s) {0}
portfolio.failed.depositTxNull=The trade cannot be reverted to a open trade. Deposit transaction is null.
portfolio.failed.delayedPayoutTxNull=The trade cannot be reverted to a open trade. Delayed payout transaction is null.
portfolio.failed.penalty.msg=This will charge the {0}/{1} the trade fee of {2} and return the remaining trade funds to their wallet. Are you sure you want to send?\n\n\
portfolio.failed.penalty.msg=This will charge the {0}/{1} a penalty fee of {2} and return the remaining trade funds to their wallet. Are you sure you want to send?\n\n\
Other Info:\n\
Transaction Fee: {3}\n\
Reserve Tx Hash: {4}
@ -1097,9 +1097,9 @@ support.tab.ArbitratorsSupportTickets={0}'s tickets
support.filter=Search disputes
support.filter.prompt=Enter trade ID, date, onion address or account data
support.tab.SignedOffers=Signed Offers
support.prompt.signedOffer.penalty.msg=This will charge the maker the trade fee and return their remaining trade funds to their wallet. Are you sure you want to send?\n\n\
support.prompt.signedOffer.penalty.msg=This will charge the maker a penalty fee and return the remaining trade funds to their wallet. Are you sure you want to send?\n\n\
Offer ID: {0}\n\
Maker Trade Fee: {1}\n\
Maker Penalty Fee: {1}\n\
Reserve Tx Miner Fee: {2}\n\
Reserve Tx Hash: {3}\n\
Reserve Tx Key Images: {4}\n\
@ -1163,15 +1163,15 @@ support.requested=Requested
support.closed=Closed
support.open=Open
support.process=Process
support.buyerMaker=XMR buyer/Maker
support.sellerMaker=XMR seller/Maker
support.buyerTaker=XMR buyer/Taker
support.sellerTaker=XMR seller/Taker
support.buyerMaker=XMR Buyer/Maker
support.sellerMaker=XMR Seller/Maker
support.buyerTaker=XMR Buyer/Taker
support.sellerTaker=XMR Seller/Taker
support.txKeyImages=Key Images
support.txHash=Transaction Hash
support.txHex=Transaction Hex
support.signature=Signature
support.maker.trade.fee=Maker Trade Fee
support.maker.penalty.fee=Maker Penalty Fee
support.tx.miner.fee=Miner Fee
support.backgroundInfo=Haveno is not a company, so it handles disputes differently.\n\n\

View File

@ -218,7 +218,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
handleContextMenu("portfolio.failed.penalty.msg",
Res.get(selectedFailedTrade.getMaker() == selectedFailedTrade.getBuyer() ? "shared.buyer" : "shared.seller"),
Res.get("shared.maker"),
selectedFailedTrade.getMakerFee(),
selectedFailedTrade.getMaker().getSecurityDeposit(),
selectedFailedTrade.getMaker().getReserveTxHash(),
selectedFailedTrade.getMaker().getReserveTxHex());
});
@ -228,7 +228,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
handleContextMenu("portfolio.failed.penalty.msg",
Res.get(selectedFailedTrade.getTaker() == selectedFailedTrade.getBuyer() ? "shared.buyer" : "shared.seller"),
Res.get("shared.taker"),
selectedFailedTrade.getTakerFee(),
selectedFailedTrade.getTaker().getSecurityDeposit(),
selectedFailedTrade.getTaker().getReserveTxHash(),
selectedFailedTrade.getTaker().getReserveTxHex());
});

View File

@ -85,7 +85,7 @@ public class SignedOfferView extends ActivatableView<VBox, Void> {
@FXML
TableColumn<SignedOffer, SignedOffer> reserveTxMinerFeeColumn;
@FXML
TableColumn<SignedOffer, SignedOffer> makerTradeFeeColumn;
TableColumn<SignedOffer, SignedOffer> makerPenaltyFeeColumn;
@FXML
InputTextField filterTextField;
@FXML
@ -163,7 +163,7 @@ public class SignedOfferView extends ActivatableView<VBox, Void> {
if(selectedSignedOffer != null) {
new Popup().warning(Res.get("support.prompt.signedOffer.penalty.msg",
selectedSignedOffer.getOfferId(),
HavenoUtils.formatXmr(selectedSignedOffer.getMakerTradeFee(), true),
HavenoUtils.formatXmr(selectedSignedOffer.getPenaltyAmount(), true),
HavenoUtils.formatXmr(selectedSignedOffer.getReserveTxMinerFee(), true),
selectedSignedOffer.getReserveTxHash(),
selectedSignedOffer.getReserveTxKeyImages())
@ -212,8 +212,8 @@ public class SignedOfferView extends ActivatableView<VBox, Void> {
arbitratorSignatureColumn = getArbitratorSignatureColumn();
tableView.getColumns().add(arbitratorSignatureColumn);
makerTradeFeeColumn = getMakerTradeFeeColumn();
tableView.getColumns().add(makerTradeFeeColumn);
makerPenaltyFeeColumn = getMakerPenaltyFeeColumn();
tableView.getColumns().add(makerPenaltyFeeColumn);
reserveTxMinerFeeColumn = getReserveTxMinerFeeColumn();
tableView.getColumns().add(reserveTxMinerFeeColumn);
@ -389,8 +389,8 @@ public class SignedOfferView extends ActivatableView<VBox, Void> {
return column;
}
private TableColumn<SignedOffer, SignedOffer> getMakerTradeFeeColumn() {
TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.maker.trade.fee")) {
private TableColumn<SignedOffer, SignedOffer> getMakerPenaltyFeeColumn() {
TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.maker.penalty.fee")) {
{
setMinWidth(160);
}
@ -405,7 +405,7 @@ public class SignedOfferView extends ActivatableView<VBox, Void> {
public void updateItem(final SignedOffer item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(HavenoUtils.formatXmr(item.getMakerTradeFee(), true));
setText(HavenoUtils.formatXmr(item.getPenaltyAmount(), true));
else
setText("");
}

View File

@ -1351,7 +1351,7 @@ message SignedOffer {
int64 time_stamp = 1;
string offer_id = 2;
uint64 trade_amount = 3;
uint64 maker_trade_fee = 4;
uint64 penalty_amount = 4;
string reserve_tx_hash = 5;
string reserve_tx_hex = 6;
repeated string reserve_tx_key_images = 7;