mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-06-01 13:04:40 -04:00
support broadcasting maker and taker reserve txs in legacy ui
Co-authored-by: niyid <neeyeed@gmail.com>
This commit is contained in:
parent
34b79e779b
commit
ed0f458bc4
9 changed files with 716 additions and 79 deletions
|
@ -232,7 +232,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
@Override
|
||||
public void readPersisted(Runnable completeHandler) {
|
||||
|
||||
|
||||
// read open offers
|
||||
persistenceManager.readPersisted(persisted -> {
|
||||
openOffers.setAll(persisted.getList());
|
||||
|
@ -496,12 +496,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
checkNotNull(offer.getMakerFee(), "makerFee must not be null");
|
||||
|
||||
boolean autoSplit = false; // TODO: support in api
|
||||
|
||||
|
||||
// TODO (woodser): validate offer
|
||||
|
||||
|
||||
// create open offer
|
||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice, autoSplit);
|
||||
|
||||
|
||||
// process open offer to schedule or post
|
||||
processUnpostedOffer(getOpenOffers(), openOffer, (transaction) -> {
|
||||
addOpenOffer(openOffer);
|
||||
|
@ -702,6 +702,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public ObservableList<SignedOffer> getObservableSignedOffersList() {
|
||||
synchronized (signedOffers) {
|
||||
return signedOffers.getObservableList();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableList<OpenOffer> getObservableList() {
|
||||
return openOffers.getObservableList();
|
||||
}
|
||||
|
@ -711,7 +718,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Optional<SignedOffer> getSignedOfferById(String offerId) {
|
||||
synchronized (signedOffers) {
|
||||
return signedOffers.stream().filter(e -> e.getOfferId().equals(offerId)).findFirst();
|
||||
|
@ -939,11 +946,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
private void handleSignOfferRequest(SignOfferRequest request, NodeAddress peer) {
|
||||
log.info("Received SignOfferRequest from {} with offerId {} and uid {}",
|
||||
peer, request.getOfferId(), request.getUid());
|
||||
|
||||
|
||||
boolean result = false;
|
||||
String errorMessage = null;
|
||||
try {
|
||||
|
||||
|
||||
// verify this node is an arbitrator
|
||||
Arbitrator thisArbitrator = user.getRegisteredArbitrator();
|
||||
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
|
||||
|
@ -953,7 +960,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// verify arbitrator is signer of offer payload
|
||||
if (!thisAddress.equals(request.getOfferPayload().getArbitratorSigner())) {
|
||||
errorMessage = "Cannot sign offer because offer payload is for a different arbitrator";
|
||||
|
@ -961,7 +968,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// verify offer not seen before
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
||||
if (openOfferOptional.isPresent()) {
|
||||
|
@ -980,7 +987,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
|
||||
BigInteger sendAmount = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(offer.getDirection() == OfferDirection.BUY ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
|
@ -999,13 +1006,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
String signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), offerPayloadAsJson);
|
||||
OfferPayload signedOfferPayload = request.getOfferPayload();
|
||||
signedOfferPayload.setArbitratorSignature(signature);
|
||||
|
||||
|
||||
// create record of signed offer
|
||||
SignedOffer signedOffer = new SignedOffer(
|
||||
System.currentTimeMillis(),
|
||||
signedOfferPayload.getId(),
|
||||
offer.getAmount().longValue(),
|
||||
HavenoUtils.getMakerFee(offer.getAmount()).longValue(), // TODO: these values are centineros, whereas reserve tx mining fee is BigInteger
|
||||
HavenoUtils.coinToAtomicUnits(offer.getAmount()).longValueExact(),
|
||||
HavenoUtils.coinToAtomicUnits(HavenoUtils.getMakerFee(offer.getAmount())).longValueExact(),
|
||||
request.getReserveTxHash(),
|
||||
request.getReserveTxHex(),
|
||||
request.getReserveTxKeyImages(),
|
||||
|
@ -1049,7 +1056,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleSignOfferResponse(SignOfferResponse response, NodeAddress peer) {
|
||||
log.info("Received SignOfferResponse from {} with offerId {} and uid {}",
|
||||
peer, response.getOfferId(), response.getUid());
|
||||
|
@ -1122,7 +1129,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||
|
||||
|
||||
// maker signs taker's request
|
||||
String tradeRequestAsJson = JsonUtil.objectToJson(request.getTradeRequest());
|
||||
makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson);
|
||||
|
@ -1204,7 +1211,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) {
|
||||
return preferences.isDenyApiTaker() && request.isTakerApiUser();
|
||||
}
|
||||
|
||||
|
||||
private boolean takerDeniedByMaker(OfferAvailabilityRequest request) {
|
||||
if (request.getTradeRequest() == null) return true;
|
||||
return false; // TODO (woodser): implement taker verification here, doing work of ApplyFilter and VerifyPeersAccountAgeWitness
|
||||
|
@ -1251,7 +1258,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Update persisted offer if a new capability is required after a software update
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// TODO (woodser): arbitrator signature will be invalid if offer updated (exclude updateable fields from signature? re-sign?)
|
||||
|
||||
private void maybeUpdatePersistedOffers() {
|
||||
|
|
|
@ -17,11 +17,6 @@
|
|||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Utilities;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||
|
@ -33,11 +28,27 @@ import bisq.core.util.JsonUtil;
|
|||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.coin.CoinUtil;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -47,12 +58,9 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Charsets;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Collection of utilities.
|
||||
|
@ -62,26 +70,19 @@ public class HavenoUtils {
|
|||
|
||||
public static final String LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
|
||||
public static final String LOCALHOST = "localhost";
|
||||
|
||||
// multipliers to convert units
|
||||
public static BigInteger CENTINEROS_AU_MULTIPLIER = new BigInteger("10000");
|
||||
private static BigInteger XMR_AU_MULTIPLIER = new BigInteger("1000000000000");
|
||||
|
||||
// global thread pool
|
||||
public static final BigInteger CENTINEROS_AU_MULTIPLIER = new BigInteger("10000");
|
||||
private static final BigInteger XMR_AU_MULTIPLIER = new BigInteger("1000000000000");
|
||||
public static final DecimalFormat XMR_FORMATTER = new DecimalFormat("0.000000000000");
|
||||
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
|
||||
private static final int POOL_SIZE = 10;
|
||||
private static final ExecutorService POOL = Executors.newFixedThreadPool(POOL_SIZE);
|
||||
|
||||
// TODO: better way to share reference?
|
||||
public static ArbitrationManager arbitrationManager;
|
||||
|
||||
public static ArbitrationManager arbitrationManager; // TODO: better way to share reference?
|
||||
|
||||
public static BigInteger coinToAtomicUnits(Coin coin) {
|
||||
return centinerosToAtomicUnits(coin.value);
|
||||
}
|
||||
|
||||
public static double coinToXmr(Coin coin) {
|
||||
return atomicUnitsToXmr(coinToAtomicUnits(coin));
|
||||
}
|
||||
|
||||
public static BigInteger centinerosToAtomicUnits(long centineros) {
|
||||
return BigInteger.valueOf(centineros).multiply(CENTINEROS_AU_MULTIPLIER);
|
||||
}
|
||||
|
@ -102,10 +103,18 @@ public class HavenoUtils {
|
|||
return atomicUnits.divide(CENTINEROS_AU_MULTIPLIER).longValueExact();
|
||||
}
|
||||
|
||||
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
|
||||
public static Coin atomicUnitsToCoin(long atomicUnits) {
|
||||
return Coin.valueOf(atomicUnitsToCentineros(atomicUnits));
|
||||
}
|
||||
|
||||
public static Coin atomicUnitsToCoin(BigInteger atomicUnits) {
|
||||
return atomicUnitsToCoin(atomicUnits.longValueExact());
|
||||
}
|
||||
|
||||
public static double atomicUnitsToXmr(long atomicUnits) {
|
||||
return atomicUnitsToXmr(BigInteger.valueOf(atomicUnits));
|
||||
}
|
||||
|
||||
public static double atomicUnitsToXmr(BigInteger atomicUnits) {
|
||||
return new BigDecimal(atomicUnits).divide(new BigDecimal(XMR_AU_MULTIPLIER)).doubleValue();
|
||||
}
|
||||
|
@ -117,7 +126,27 @@ public class HavenoUtils {
|
|||
public static long xmrToCentineros(double xmr) {
|
||||
return atomicUnitsToCentineros(xmrToAtomicUnits(xmr));
|
||||
}
|
||||
|
||||
|
||||
public static double coinToXmr(Coin coin) {
|
||||
return atomicUnitsToXmr(coinToAtomicUnits(coin));
|
||||
}
|
||||
|
||||
public static String formatXmrWithCode(Coin coin) {
|
||||
return formatXmrWithCode(coinToAtomicUnits(coin).longValueExact());
|
||||
}
|
||||
|
||||
public static String formatXmrWithCode(long atomicUnits) {
|
||||
String formatted = XMR_FORMATTER.format(atomicUnitsToXmr(atomicUnits));
|
||||
|
||||
// strip trailing 0s
|
||||
if (formatted.contains(".")) {
|
||||
while (formatted.length() > 3 && formatted.charAt(formatted.length() - 1) == '0') {
|
||||
formatted = formatted.substring(0, formatted.length() - 1);
|
||||
}
|
||||
}
|
||||
return formatted.concat(" ").concat("XMR");
|
||||
}
|
||||
|
||||
private static final MonetaryFormat xmrCoinFormat = Config.baseCurrencyNetworkParameters().getMonetaryFormat();
|
||||
|
||||
@Nullable
|
||||
|
@ -166,7 +195,7 @@ public class HavenoUtils {
|
|||
|
||||
/**
|
||||
* Get address to collect trade fees.
|
||||
*
|
||||
*
|
||||
* @return the address which collects trade fees
|
||||
*/
|
||||
public static String getTradeFeeAddress() {
|
||||
|
@ -196,7 +225,7 @@ public class HavenoUtils {
|
|||
|
||||
/**
|
||||
* Returns a unique deterministic id for sending a trade mailbox message.
|
||||
*
|
||||
*
|
||||
* @param trade the trade
|
||||
* @param tradeMessageClass the trade message class
|
||||
* @param receiver the receiver address
|
||||
|
@ -209,23 +238,23 @@ public class HavenoUtils {
|
|||
|
||||
/**
|
||||
* Check if the arbitrator signature is valid for an offer.
|
||||
*
|
||||
*
|
||||
* @param offer is a signed offer with payload
|
||||
* @param arbitrator is the original signing arbitrator
|
||||
* @return true if the arbitrator's signature is valid for the offer
|
||||
*/
|
||||
public static boolean isArbitratorSignatureValid(Offer offer, Arbitrator arbitrator) {
|
||||
|
||||
|
||||
// copy offer payload
|
||||
OfferPayload offerPayloadCopy = OfferPayload.fromProto(offer.toProtoMessage().getOfferPayload());
|
||||
|
||||
|
||||
// remove arbitrator signature from signed payload
|
||||
String signature = offerPayloadCopy.getArbitratorSignature();
|
||||
offerPayloadCopy.setArbitratorSignature(null);
|
||||
|
||||
|
||||
// get unsigned offer payload as json string
|
||||
String unsignedOfferAsJson = JsonUtil.objectToJson(offerPayloadCopy);
|
||||
|
||||
|
||||
// verify arbitrator signature
|
||||
try {
|
||||
return Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), unsignedOfferAsJson, signature);
|
||||
|
@ -233,15 +262,15 @@ public class HavenoUtils {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the maker signature for a trade request is valid.
|
||||
*
|
||||
*
|
||||
* @param request is the trade request to check
|
||||
* @return true if the maker's signature is valid for the trade request
|
||||
*/
|
||||
public static boolean isMakerSignatureValid(InitTradeRequest request, String signature, PubKeyRing makerPubKeyRing) {
|
||||
|
||||
|
||||
// re-create trade request with signed fields
|
||||
InitTradeRequest signedRequest = new InitTradeRequest(
|
||||
request.getTradeId(),
|
||||
|
@ -266,10 +295,10 @@ public class HavenoUtils {
|
|||
request.getPayoutAddress(),
|
||||
null
|
||||
);
|
||||
|
||||
|
||||
// get trade request as string
|
||||
String tradeRequestAsJson = JsonUtil.objectToJson(signedRequest);
|
||||
|
||||
|
||||
// verify maker signature
|
||||
try {
|
||||
return Sig.verify(makerPubKeyRing.getSignaturePubKey(),
|
||||
|
@ -282,7 +311,7 @@ public class HavenoUtils {
|
|||
|
||||
/**
|
||||
* Verify the buyer signature for a PaymentSentMessage.
|
||||
*
|
||||
*
|
||||
* @param trade - the trade to verify
|
||||
* @param message - signed payment sent message to verify
|
||||
* @return true if the buyer's signature is valid for the message
|
||||
|
@ -298,7 +327,7 @@ public class HavenoUtils {
|
|||
|
||||
// replace signature
|
||||
message.setBuyerSignature(signature);
|
||||
|
||||
|
||||
// verify signature
|
||||
String errMessage = "The buyer signature is invalid for the " + message.getClass().getSimpleName() + " for " + trade.getClass().getSimpleName() + " " + trade.getId();
|
||||
try {
|
||||
|
@ -313,7 +342,7 @@ public class HavenoUtils {
|
|||
|
||||
/**
|
||||
* Verify the seller signature for a PaymentReceivedMessage.
|
||||
*
|
||||
*
|
||||
* @param trade - the trade to verify
|
||||
* @param message - signed payment received message to verify
|
||||
* @return true if the seller's signature is valid for the message
|
||||
|
@ -329,7 +358,7 @@ public class HavenoUtils {
|
|||
|
||||
// replace signature
|
||||
message.setSellerSignature(signature);
|
||||
|
||||
|
||||
// verify signature
|
||||
String errMessage = "The seller signature is invalid for the " + message.getClass().getSimpleName() + " for " + trade.getClass().getSimpleName() + " " + trade.getId();
|
||||
try {
|
||||
|
|
|
@ -226,6 +226,8 @@ shared.numItemsLabel=Number of entries: {0}
|
|||
shared.filter=Filter
|
||||
shared.enabled=Enabled
|
||||
shared.me=Me
|
||||
shared.maker=Maker
|
||||
shared.taker=Taker
|
||||
|
||||
|
||||
####################################################################
|
||||
|
@ -990,7 +992,11 @@ 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\
|
||||
Other Info:\n\
|
||||
Transaction Fee: {3}\n\
|
||||
Reserve Tx Hash: {4}
|
||||
portfolio.failed.error.msg=Trade record does not exist.
|
||||
|
||||
####################################################################
|
||||
# Funds
|
||||
|
@ -1089,6 +1095,18 @@ support.tab.legacyArbitration.support=Legacy Arbitration
|
|||
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\
|
||||
Offer ID: {0}\n\
|
||||
Maker Trade Fee: {1}\n\
|
||||
Reserve Tx Miner Fee: {2}\n\
|
||||
Reserve Tx Hash: {3}\n\
|
||||
Reserve Tx Key Images: {4}\n\
|
||||
|
||||
support.contextmenu.penalize.msg=Penalize {0} by publishing reserve tx
|
||||
support.prompt.signedOffer.error.msg=Signed Offer record does not exist; contact administrator.
|
||||
support.info.submitTxHex=Reserve transaction has been published with the following result:\n
|
||||
support.result.success=Transaction hex has been successfully submitted.
|
||||
|
||||
support.sigCheck.button=Check signature
|
||||
support.sigCheck.popup.info=In case of a reimbursement request to the DAO you need to paste the summary message of the \
|
||||
|
@ -1148,6 +1166,12 @@ 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.tx.miner.fee=Miner Fee
|
||||
|
||||
support.backgroundInfo=Haveno is not a company, so it handles disputes differently.\n\n\
|
||||
Traders can communicate within the application via secure chat on the open trades screen to try solving disputes on their own. \
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue