implement protocol v2

This commit is contained in:
woodser 2021-08-22 16:21:56 -04:00
parent 65bcd47446
commit 86f7d090b6
185 changed files with 5117 additions and 4010 deletions

View File

@ -74,4 +74,4 @@ To bring Haveno to life, we need resources. If you have the possibility, please
![Qr code](https://raw.githubusercontent.com/haveno-dex/haveno/master/media/qrhaveno.png)
If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address.
If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address.

View File

@ -40,7 +40,7 @@ configure(subprojects) {
grpcVersion = '1.25.0'
gsonVersion = '2.8.5'
guavaVersion = '28.2-jre'
moneroJavaVersion = '0.5.3'
moneroJavaVersion = '0.5.4'
httpclient5Version = '5.0'
guiceVersion = '4.2.2'
hamcrestVersion = '1.3'

View File

@ -161,7 +161,7 @@ public class TradeFormat {
private static final Function<TradeInfo, String> amountFormat = (t) ->
t.getOffer().getBaseCurrencyCode().equals("XMR")
? formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong()))
? formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()))
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
@ -173,13 +173,13 @@ public class TradeFormat {
};
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeFormat = (t, isTaker) -> {
return formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTakerFeeAsLong()));
return formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTakerFeeAsLong()));
};
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->
t.getOffer().getBaseCurrencyCode().equals("XMR")
? formatOfferVolume(t.getOffer().getVolume())
: formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong()));
: formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()));
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {
if (showBsqBuyerAddress) {

View File

@ -302,7 +302,7 @@ public class AccountAgeWitnessService {
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
TradingPeer tradingPeer = trade.getTradingPeer();
return (tradingPeer == null ||
tradingPeer.getPaymentAccountPayload() == null ||
tradingPeer.getPubKeyRing() == null) ?
@ -426,7 +426,7 @@ public class AccountAgeWitnessService {
long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
var factor = signedBuyFactor(accountAgeCategory);
if (factor > 0) {
limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
limit = MathUtils.roundDoubleToLong(maxTradeLimit.value * factor);
}
log.debug("limit={}, factor={}, accountAgeWitnessHash={}",
@ -726,8 +726,8 @@ public class AccountAgeWitnessService {
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
Coin tradeAmount = trade.getTradeAmount();
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
checkNotNull(trade.getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
PublicKey peersPubKey = trade.getTradingPeer().getPubKeyRing().getSignaturePubKey();
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
trade.toString());
checkNotNull(tradeAmount, "Trade amount must not be null");
@ -767,15 +767,11 @@ public class AccountAgeWitnessService {
boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) ||
filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) ||
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
filterManager.isPaymentMethodBanned(
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload()) ||
filterManager.arePeersPaymentAccountDataBanned(
dispute.getContract().getSellerPaymentAccountPayload()) ||
filterManager.isWitnessSignerPubKeyBanned(
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
filterManager.isWitnessSignerPubKeyBanned(
Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
filterManager.isPaymentMethodBanned(PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getBuyerPaymentAccountPayload()) ||
filterManager.arePeersPaymentAccountDataBanned(dispute.getSellerPaymentAccountPayload()) ||
filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
return !isFiltered;
}
@ -797,8 +793,8 @@ public class AccountAgeWitnessService {
PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload();
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getBuyerPaymentAccountPayload();
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getSellerPaymentAccountPayload();
TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
.map(witness -> new TraderDataItem(
@ -913,8 +909,7 @@ public class AccountAgeWitnessService {
public boolean isSignWitnessTrade(Trade trade) {
checkNotNull(trade, "trade must not be null");
checkNotNull(trade.getOffer(), "offer must not be null");
Contract contract = checkNotNull(trade.getContract());
PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload();
PaymentAccountPayload sellerPaymentAccountPayload = trade.getSeller().getPaymentAccountPayload();
AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload);
getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);

View File

@ -131,14 +131,14 @@ public class AccountAgeWitnessUtils {
// Log to find why accounts sometimes don't get signed as expected
// TODO: Demote to debug or remove once account signing is working ok
checkNotNull(trade.getContract());
checkNotNull(trade.getContract().getBuyerPaymentAccountPayload());
checkNotNull(trade.getBuyer().getPaymentAccountPayload());
boolean checkingSignTrade = true;
boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing());
AccountAgeWitness witness = myWitness;
if (witness == null) {
witness = isBuyer ?
accountAgeWitnessService.getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) :
accountAgeWitnessService.getMyWitness(trade.getContract().getSellerPaymentAccountPayload());
accountAgeWitnessService.getMyWitness(trade.getBuyer().getPaymentAccountPayload()) :
accountAgeWitnessService.getMyWitness(trade.getSeller().getPaymentAccountPayload());
checkingSignTrade = false;
}
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
@ -157,9 +157,9 @@ public class AccountAgeWitnessUtils {
"\nisSignWitnessTrade: {}",
trade.getId(),
isBuyer,
getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(),
getWitnessDebugLog(trade.getBuyer().getPaymentAccountPayload(),
trade.getContract().getBuyerPubKeyRing()),
getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(),
getWitnessDebugLog(trade.getSeller().getPaymentAccountPayload(),
trade.getContract().getSellerPubKeyRing()),
checkingSignTrade, // Following cases added to use same logic as in seller signing check
accountAgeWitnessService.accountIsSigner(witness),

View File

@ -109,7 +109,6 @@ class CoreTradesService {
tradeManager.onTakeOffer(offer.getAmount(),
takeOfferModel.getTxFeeFromFeeService(),
takeOfferModel.getTakerFee(),
offer.getPrice().getValue(),
takeOfferModel.getFundsNeededForTrade(),
offer,
paymentAccountId,

View File

@ -108,8 +108,8 @@ public class TradeInfo implements Payload {
contract.isBuyerMakerAndSellerTaker(),
contract.getMakerAccountId(),
contract.getTakerAccountId(),
toPaymentAccountPayloadInfo(contract.getMakerPaymentAccountPayload()),
toPaymentAccountPayloadInfo(contract.getTakerPaymentAccountPayload()),
toPaymentAccountPayloadInfo(trade.getMaker().getPaymentAccountPayload()),
toPaymentAccountPayloadInfo(trade.getTaker().getPaymentAccountPayload()),
contract.getMakerPayoutAddressString(),
contract.getTakerPayoutAddressString(),
contract.getLockTime());
@ -127,8 +127,8 @@ public class TradeInfo implements Payload {
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
.withTakerFeeTxId(trade.getTakerFeeTxId())
.withMakerDepositTxId(trade.getMakerDepositTxId())
.withTakerDepositTxId(trade.getTakerDepositTxId())
.withMakerDepositTxId(trade.getMaker().getDepositTxHash())
.withTakerDepositTxId(trade.getTaker().getDepositTxHash())
.withPayoutTxId(trade.getPayoutTxId())
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
.withTradePrice(trade.getTradePrice().getValue())

View File

@ -249,10 +249,10 @@ public class WalletAppSetup {
.filter(trade -> trade.getOffer() != null)
.forEach(trade -> {
String details = null;
if (txId.equals(trade.getMakerDepositTxId())) {
if (txId.equals(trade.getMaker().getDepositTxHash())) {
details = Res.get("popup.warning.trade.txRejected.deposit"); // TODO (woodser): txRejected.maker_deposit, txRejected.taker_deposit
}
if (txId.equals(trade.getTakerDepositTxId())) {
if (txId.equals(trade.getTaker().getDepositTxHash())) {
details = Res.get("popup.warning.trade.txRejected.deposit");
}
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {

View File

@ -17,7 +17,9 @@
package bisq.core.btc;
import bisq.common.UserThread;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.Dispute;
@ -26,30 +28,21 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.common.UserThread;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import bisq.core.util.ParsingUtils;
import bisq.network.p2p.P2PService;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import java.math.BigInteger;
import java.util.List;
import javax.inject.Inject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroAccount;
import monero.wallet.model.MoneroOutputQuery;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroWalletListener;
import org.bitcoinj.core.Coin;
@Slf4j
public class Balances {
@ -98,29 +91,45 @@ public class Balances {
// Need to delay a bit to get the balances correct
UserThread.execute(() -> {
updateAvailableBalance();
updateReservedBalance();
updateLockedBalance();
updateReservedBalance();
});
}
// TODO (woodser): currently reserved balance = reserved for trade (excluding multisig) and locked balance = txs with < 10 confirmations
// TODO (woodser): balances being set as Coin from BigInteger.longValue(), which can lose precision. should be in centineros for consistency with the rest of the application
private void updateAvailableBalance() {
availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValue()));
availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValueExact()));
}
private void updateReservedBalance() {
BigInteger sum = new BigInteger("0");
List<MoneroAccount> accounts = xmrWalletService.getWallet().getAccounts();
for (MoneroAccount account : accounts) {
if (account.getIndex() != 0) sum = sum.add(account.getBalance());
}
reservedBalance.set(Coin.valueOf(sum.longValue()));
}
private void updateLockedBalance() {
BigInteger balance = xmrWalletService.getWallet().getBalance(0);
BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0);
lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValue()));
lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValueExact()));
}
private void updateReservedBalance() {
// add frozen input amounts
Coin sum = Coin.valueOf(0);
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
for (MoneroOutputWallet frozenOutput : frozenOutputs) sum = sum.add(Coin.valueOf(frozenOutput.getAmount().longValueExact()));
// add multisig deposit amounts
List<Trade> openTrades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
for (Trade trade : openTrades) {
if (trade.getContract() == null) continue;
Long reservedAmt;
OfferPayload offerPayload = trade.getContract().getOfferPayload();
if (trade.getArbitratorNodeAddress().equals(P2PService.getMyNodeAddress())) { // TODO (woodser): this only works if node address does not change
reservedAmt = offerPayload.getAmount() + offerPayload.getBuyerSecurityDeposit() + offerPayload.getSellerSecurityDeposit(); // arbitrator reserved balance is sum of amounts sent to multisig
} else {
reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit();
}
sum = sum.add(Coin.valueOf(ParsingUtils.centinerosToAtomicUnits(reservedAmt).longValueExact()));
}
// set reserved balance
reservedBalance.set(sum);
}
}

View File

@ -20,17 +20,17 @@ package bisq.core.btc.listeners;
import java.math.BigInteger;
public class XmrBalanceListener {
private Integer accountIndex;
private Integer subaddressIndex;
public XmrBalanceListener() {
}
public XmrBalanceListener(Integer accountIndex) {
this.accountIndex = accountIndex;
this.subaddressIndex = accountIndex;
}
public Integer getAccountIndex() {
return accountIndex;
public Integer getSubaddressIndex() {
return subaddressIndex;
}
public void onBalanceChanged(BigInteger balance) {

View File

@ -60,7 +60,7 @@ public final class XmrAddressEntry implements PersistablePayload {
@Getter
private final Context context;
@Getter
private final int accountIndex;
private final int subaddressIndex;
@Getter
private final String addressString;
@ -71,12 +71,12 @@ public final class XmrAddressEntry implements PersistablePayload {
// Constructor, initialization
///////////////////////////////////////////////////////////////////////////////////////////
public XmrAddressEntry(int accountIndex, String address, Context context) {
this(accountIndex, address, context, null, null);
public XmrAddressEntry(int subaddressIndex, String address, Context context) {
this(subaddressIndex, address, context, null, null);
}
public XmrAddressEntry(int accountIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
this.accountIndex = accountIndex;
public XmrAddressEntry(int subaddressIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
this.subaddressIndex = subaddressIndex;
this.addressString = address;
this.offerId = offerId;
this.context = context;
@ -89,7 +89,7 @@ public final class XmrAddressEntry implements PersistablePayload {
///////////////////////////////////////////////////////////////////////////////////////////
public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) {
return new XmrAddressEntry(proto.getAccountIndex(),
return new XmrAddressEntry(proto.getSubaddressIndex(),
ProtoUtil.stringOrNullFromProto(proto.getAddressString()),
ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()),
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
@ -99,7 +99,7 @@ public final class XmrAddressEntry implements PersistablePayload {
@Override
public protobuf.XmrAddressEntry toProtoMessage() {
protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder()
.setAccountIndex(accountIndex)
.setSubaddressIndex(subaddressIndex)
.setAddressString(addressString)
.setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name()))
.setCoinLockedInMultiSig(coinLockedInMultiSig);
@ -143,7 +143,7 @@ public final class XmrAddressEntry implements PersistablePayload {
return "XmrAddressEntry{" +
"offerId='" + getOfferId() + '\'' +
", context=" + context +
", accountIndex=" + getAccountIndex() +
", subaddressIndex=" + getSubaddressIndex() +
", address=" + getAddressString() +
'}';
}

View File

@ -38,7 +38,6 @@ import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroAccount;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroWalletListener;
@ -131,10 +130,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
// });
//
// toBeRemoved.forEach(entrySet::remove);
} else {
// As long the old arbitration domain is not removed from the code base we still support it here.
MoneroAccount account = wallet.createAccount();
entrySet.add(new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), XmrAddressEntry.Context.ARBITRATOR));
}
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
@ -174,7 +169,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
if (entryWithSameOfferIdAndContextAlreadyExist) {
log.error("We have an address entry with the same offer ID and context. We do not add the new one. " +
"addressEntry={}, entrySet={}", addressEntry, entrySet);
if (true) throw new RuntimeException("why?");
return;
}
@ -185,7 +179,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
public void swapToAvailable(XmrAddressEntry addressEntry) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(),
boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getSubaddressIndex(), addressEntry.getAddressString(),
XmrAddressEntry.Context.AVAILABLE));
if (setChangedByRemove || setChangedByAdd) {
requestPersistence();
@ -196,7 +190,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
XmrAddressEntry.Context context,
String offerId) {
boolean setChangedByRemove = entrySet.remove(addressEntry);
final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), context, offerId, null);
final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getSubaddressIndex(), addressEntry.getAddressString(), context, offerId, null);
boolean setChangedByAdd = entrySet.add(newAddressEntry);
if (setChangedByRemove || setChangedByAdd)
requestPersistence();
@ -213,6 +207,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
// Private
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): this should be removed since only using account 0
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
if (output.getAccountIndex() == 0) return;
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());

View File

@ -89,6 +89,8 @@ import static com.google.common.base.Preconditions.checkState;
import monero.common.MoneroUtils;
import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc;
import monero.daemon.model.MoneroNetworkType;
import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletRpc;
@ -137,6 +139,7 @@ public class WalletConfig extends AbstractIdleService {
protected final String filePrefix;
protected volatile BlockChain vChain;
protected volatile SPVBlockStore vStore;
protected volatile MoneroDaemon vXmrDaemon;
protected volatile MoneroWallet vXmrWallet;
protected volatile Wallet vBtcWallet;
protected volatile Wallet vBsqWallet;
@ -345,6 +348,9 @@ public class WalletConfig extends AbstractIdleService {
try {
File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists();
// XMR daemon
vXmrDaemon = new MoneroDaemonRpc(MONERO_DAEMON_URI, MONERO_DAEMON_USERNAME, MONERO_DAEMON_PASSWORD);
// XMR wallet
String xmrPrefix = "_XMR";
@ -361,7 +367,8 @@ public class WalletConfig extends AbstractIdleService {
// vXmrWallet.rescanBlockchain();
vXmrWallet.sync();
vXmrWallet.save();
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance());
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance(0));
System.out.println("Loaded wallet unlocked balance: " + vXmrWallet.getUnlockedBalance(0));
String btcPrefix = "_BTC";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
@ -626,10 +633,16 @@ public class WalletConfig extends AbstractIdleService {
return vBtcWallet;
}
public MoneroDaemon getXmrDaemon() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vXmrDaemon;
}
public MoneroWallet getXmrWallet() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vXmrWallet;
}
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vXmrWallet;
}
public Wallet bsqWallet() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");

View File

@ -105,7 +105,7 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.daemon.MoneroDaemon;
import monero.wallet.MoneroWallet;
// Setup wallets and use WalletConfig for BitcoinJ wiring.
@ -490,6 +490,10 @@ public class WalletsSetup {
return walletConfig.btcWallet();
}
public MoneroDaemon getXmrDaemon() {
return walletConfig.getXmrDaemon();
}
public MoneroWallet getXmrWallet() {
return walletConfig.getXmrWallet();
}

View File

@ -146,8 +146,8 @@ public class TradeWalletService {
return xmrWallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setDestinations(
new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)),
new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value)))
new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)),
new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer)))
.setRelay(broadcastTx));
}

View File

@ -1,5 +1,6 @@
package bisq.core.btc.wallet;
import bisq.common.UserThread;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
@ -36,11 +37,10 @@ import lombok.Getter;
import monero.common.MoneroUtils;
import monero.daemon.MoneroDaemon;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroAccount;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroSubaddress;
import monero.wallet.model.MoneroTransfer;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxQuery;
import monero.wallet.model.MoneroTxWallet;
@ -57,6 +57,8 @@ public class XmrWalletService {
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
private Map<String, MoneroWallet> multisigWallets;
@Getter
private MoneroDaemon daemon;
@Getter
private MoneroWallet wallet;
@ -69,48 +71,48 @@ public class XmrWalletService {
this.multisigWallets = new HashMap<String, MoneroWallet>();
walletsSetup.addSetupCompletedHandler(() -> {
wallet = walletsSetup.getXmrWallet();
wallet.addListener(new MoneroWalletListener() {
@Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { }
daemon = walletsSetup.getXmrDaemon();
wallet = walletsSetup.getXmrWallet();
wallet.addListener(new MoneroWalletListener() {
@Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { }
@Override
public void onNewBlock(long height) { }
@Override
public void onNewBlock(long height) { }
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
notifyBalanceListeners();
}
});
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
notifyBalanceListeners();
}
});
});
}
// TODO (woodser): wallet has single password which is passed here?
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
public MoneroWallet getOrCreateMultisigWallet(String tradeId) {
String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null;
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
else if (MoneroUtils.walletExists(new File(walletsSetup.getWalletConfig().directory(), path).getPath())) { // TODO: use monero-wallet-rpc to determine existence?
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig()
.setPath(path)
.setPassword("abctesting123"));
} else {
public MoneroWallet createMultisigWallet(String tradeId) {
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null;
multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig()
.setPath(path)
.setPassword("abctesting123"));
}
multisigWallets.put(tradeId, multisigWallet);
multisigWallet.startSyncing(5000l);
return multisigWallet;
multisigWallets.put(tradeId, multisigWallet);
multisigWallet.startSyncing(5000l);
return multisigWallet;
}
public XmrAddressEntry getArbitratorAddressEntry() {
XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR;
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> context == e.getContext())
.findAny();
return getOrCreateAddressEntry(context, addressEntry);
public MoneroWallet getMultisigWallet(String tradeId) {
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null;
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig()
.setPath(path)
.setPassword("abctesting123"));
multisigWallets.put(tradeId, multisigWallet);
multisigWallet.startSyncing(5000l);
return multisigWallet;
}
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
@ -121,17 +123,10 @@ public class XmrWalletService {
}
public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) {
if (context == XmrAddressEntry.Context.TRADE_PAYOUT) {
XmrAddressEntry entry = new XmrAddressEntry(0, wallet.createSubaddress(0).getAddress(), context, offerId, null);
System.out.println("Adding address entry: " + entry.getAccountIndex() + ", " + entry.getAddressString());
MoneroSubaddress subaddress = wallet.createSubaddress(0);
XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null);
addressEntryList.addAddressEntry(entry);
return entry;
} else {
MoneroAccount account = wallet.createAccount();
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
addressEntryList.addAddressEntry(entry);
return entry;
}
}
public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) {
@ -142,37 +137,19 @@ public class XmrWalletService {
if (addressEntry.isPresent()) {
return addressEntry.get();
} else {
// We try to use available and not yet used entries // TODO (woodser): "available" entries is not applicable in xmr which uses account 0 for main wallet and subsequent accounts for reserved trades, refactor address association for xmr?
// We try to use available and not yet used entries
Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
.filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
.filter(e -> isAccountUnused(e.getAccountIndex()))
.filter(e -> isSubaddressUnused(e.getSubaddressIndex()))
.findAny();
if (emptyAvailableAddressEntry.isPresent()) {
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
} else {
MoneroAccount account = wallet.createAccount();
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
addressEntryList.addAddressEntry(entry);
return entry;
return getNewAddressEntry(offerId, context);
}
}
}
private XmrAddressEntry getOrCreateAddressEntry(XmrAddressEntry.Context context, Optional<XmrAddressEntry> addressEntry) {
if (addressEntry.isPresent()) {
return addressEntry.get();
} else {
if (context == XmrAddressEntry.Context.ARBITRATOR) {
MoneroSubaddress subaddress = wallet.createSubaddress(0);
XmrAddressEntry entry = new XmrAddressEntry(0, subaddress.getAddress(), context);
addressEntryList.addAddressEntry(entry);
return entry;
} else {
throw new RuntimeException("XmrWalletService.getOrCreateAddressEntry(context, addressEntry) not implemented for non-arbitrator context"); // TODO (woodser): this method used with non-arbitrator context?
}
}
}
public Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream()
.filter(e -> offerId.equals(e.getOfferId()))
@ -238,7 +215,7 @@ public class XmrWalletService {
public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
return getAvailableAddressEntries().stream()
.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive())
.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive())
.collect(Collectors.toList());
}
@ -246,26 +223,26 @@ public class XmrWalletService {
return addressEntryList.getAddressEntriesAsListImmutable();
}
public boolean isAccountUnused(int accountIndex) {
return accountIndex != 0 && getBalanceForAccount(accountIndex).value == 0;
public boolean isSubaddressUnused(int subaddressIndex) {
return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0;
//return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds
}
public Coin getBalanceForAccount(int accountIndex) {
public Coin getBalanceForSubaddress(int subaddressIndex) {
// get subaddress balance
BigInteger balance = wallet.getBalance(0, subaddressIndex);
// get wallet balance
BigInteger balance = wallet.getBalance(accountIndex);
// // balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack?
// for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) {
// for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
// if (transfer.getAccountIndex() == subaddressIndex) {
// balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount());
// }
// }
// }
// balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack?
for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) {
for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
if (transfer.getAccountIndex() == accountIndex) {
balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount());
}
}
}
System.out.println("Returning balance for account " + accountIndex + ": " + balance.longValueExact());
System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact());
return Coin.valueOf(balance.longValueExact());
}
@ -283,7 +260,7 @@ public class XmrWalletService {
Stream<XmrAddressEntry> availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream());
Stream<XmrAddressEntry> available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream());
return available.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive());
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive());
}
public void addBalanceListener(XmrBalanceListener listener) {
@ -353,7 +330,7 @@ public class XmrWalletService {
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(fromAccountIndex)
.setAddress(toAddress)
.setAmount(ParsingUtils.satoshisToXmrAtomicUnits(receiverAmount.value))
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount))
.setRelay(true));
callback.onSuccess(tx);
printTxs("sendFunds", tx);
@ -387,17 +364,23 @@ public class XmrWalletService {
private void notifyBalanceListeners() {
for (XmrBalanceListener balanceListener : balanceListeners) {
Coin balance;
if (balanceListener.getAccountIndex() != null && balanceListener.getAccountIndex() != 0) {
balance = getBalanceForAccount(balanceListener.getAccountIndex());
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) {
balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
} else {
balance = getAvailableConfirmedBalance();
}
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
UserThread.execute(new Runnable() {
@Override public void run() {
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
}
});
}
}
/**
* Wraps a MoneroWalletListener to notify the Haveno application.
*
* TODO (woodser): this is no longer necessary since not syncing to thread?
*/
public class HavenoWalletListener extends MoneroWalletListener {
@ -409,27 +392,47 @@ public class XmrWalletService {
@Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
UserThread.execute(new Runnable() {
@Override public void run() {
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
}
});
}
@Override
public void onNewBlock(long height) {
listener.onNewBlock(height);
UserThread.execute(new Runnable() {
@Override public void run() {
listener.onNewBlock(height);
}
});
}
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
listener.onBalancesChanged(newBalance, newUnlockedBalance);
UserThread.execute(new Runnable() {
@Override public void run() {
listener.onBalancesChanged(newBalance, newUnlockedBalance);
}
});
}
@Override
public void onOutputReceived(MoneroOutputWallet output) {
listener.onOutputReceived(output);
UserThread.execute(new Runnable() {
@Override public void run() {
listener.onOutputReceived(output);
}
});
}
@Override
public void onOutputSpent(MoneroOutputWallet output) {
listener.onOutputSpent(output);
UserThread.execute(new Runnable() {
@Override public void run() {
listener.onOutputSpent(output);
}
});
}
}
}

View File

@ -30,7 +30,8 @@ public enum AvailabilityResult {
@SuppressWarnings("unused") NO_REFUND_AGENTS("cannot take offer because no refund agents are available"),
UNCONF_TX_LIMIT_HIT("cannot take offer because you have too many unconfirmed transactions at this moment"),
MAKER_DENIED_API_USER("cannot take offer because maker is api user"),
PRICE_CHECK_FAILED("cannot take offer because trade price check failed");
PRICE_CHECK_FAILED("cannot take offer because trade price check failed"),
MAKER_DENIED_TAKER("cannot take offer because maker denied taker");
private final String description;

View File

@ -23,10 +23,14 @@ import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.monetary.Price;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.coin.CoinUtil;
@ -64,6 +68,8 @@ public class CreateOfferService {
private final PubKeyRing pubKeyRing;
private final User user;
private final BtcWalletService btcWalletService;
private final TradeStatisticsManager tradeStatisticsManager;
private final MediatorManager mediatorManager;
///////////////////////////////////////////////////////////////////////////////////////////
@ -77,7 +83,9 @@ public class CreateOfferService {
P2PService p2PService,
PubKeyRing pubKeyRing,
User user,
BtcWalletService btcWalletService) {
BtcWalletService btcWalletService,
TradeStatisticsManager tradeStatisticsManager,
MediatorManager mediatorManager) {
this.offerUtil = offerUtil;
this.txFeeEstimationService = txFeeEstimationService;
this.priceFeedService = priceFeedService;
@ -85,6 +93,8 @@ public class CreateOfferService {
this.pubKeyRing = pubKeyRing;
this.user = user;
this.btcWalletService = btcWalletService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.mediatorManager = mediatorManager;
}
@ -181,6 +191,9 @@ public class CreateOfferService {
paymentAccount,
currencyCode,
makerFeeAsCoin);
// select signing arbitrator
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
OfferPayload offerPayload = new OfferPayload(offerId,
creationTime,
@ -194,8 +207,6 @@ public class CreateOfferService {
minAmountAsLong,
baseCurrencyCode,
counterCurrencyCode,
arbitratorNodeAddresses,
mediatorNodeAddresses,
paymentAccount.getPaymentMethod().getId(),
paymentAccount.getId(),
null,
@ -219,7 +230,9 @@ public class CreateOfferService {
isPrivateOffer,
hashOfChallenge,
extraDataMap,
Version.TRADE_PROTOCOL_VERSION);
Version.TRADE_PROTOCOL_VERSION,
arbitrator.getNodeAddress(),
null);
Offer offer = new Offer(offerPayload);
offer.setPriceFeedService(priceFeedService);
return offer;

View File

@ -82,7 +82,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
public enum State {
UNKNOWN,
OFFER_FEE_PAID,
OFFER_FEE_RESERVED,
AVAILABLE,
NOT_AVAILABLE,
REMOVED,

View File

@ -22,6 +22,8 @@ import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.TradeUtils;
import bisq.core.user.Preferences;
import bisq.core.user.User;
@ -80,7 +82,8 @@ public class OfferFilter {
IS_NODE_ADDRESS_BANNED,
REQUIRE_UPDATE_TO_NEW_VERSION,
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
IS_MY_INSUFFICIENT_TRADE_LIMIT;
IS_MY_INSUFFICIENT_TRADE_LIMIT,
SIGNATURE_NOT_VALIDATED;
@Getter
private final boolean isValid;
@ -128,6 +131,9 @@ public class OfferFilter {
if (isMyInsufficientTradeLimit(offer)) {
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
}
if (!hasValidSignature(offer)) {
return Result.SIGNATURE_NOT_VALIDATED; // TODO (woodser): handle this wherever IS_MY_INSUFFICIENT_TRADE_LIMIT is handled
}
return Result.VALID;
}
@ -206,4 +212,14 @@ public class OfferFilter {
myInsufficientTradeLimitCache.put(offerId, result);
return result;
}
public boolean hasValidSignature(Offer offer) {
// get arbitrator
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()); // TODO (woodser): does this return null if arbitrator goes offline?
if (arbitrator == null) return false; // TODO (woodser): if arbitrator is null, get arbirator's pub key ring from store, otherwise cannot validate and offer is not seen by takers when arbitrator goes offline
// validate arbitrator signature
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
}
}

View File

@ -36,7 +36,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@ -120,12 +119,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
private final String baseCurrencyCode;
private final String counterCurrencyCode;
@Deprecated
// Not used anymore but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash)
private final List<NodeAddress> arbitratorNodeAddresses;
@Deprecated
// Not used anymore but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash)
private final List<NodeAddress> mediatorNodeAddresses;
private final String paymentMethodId;
private final String makerPaymentAccountId;
// Mutable property. Has to be set before offer is save in P2P network as it changes the objects hash!
@ -174,6 +167,13 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
@Nullable
private final Map<String, String> extraDataMap;
private final int protocolVersion;
// address and signature of signing arbitrator
@Setter
private NodeAddress arbitratorNodeAddress;
@Nullable
@Setter
private String arbitratorSignature;
///////////////////////////////////////////////////////////////////////////////////////////
@ -192,8 +192,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
long minAmount,
String baseCurrencyCode,
String counterCurrencyCode,
List<NodeAddress> arbitratorNodeAddresses,
List<NodeAddress> mediatorNodeAddresses,
String paymentMethodId,
String makerPaymentAccountId,
@Nullable String offerFeePaymentTxId,
@ -217,7 +215,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
boolean isPrivateOffer,
@Nullable String hashOfChallenge,
@Nullable Map<String, String> extraDataMap,
int protocolVersion) {
int protocolVersion,
NodeAddress arbitratorSigner,
@Nullable String arbitratorSignature) {
this.id = id;
this.date = date;
this.ownerNodeAddress = ownerNodeAddress;
@ -230,8 +230,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
this.minAmount = minAmount;
this.baseCurrencyCode = baseCurrencyCode;
this.counterCurrencyCode = counterCurrencyCode;
this.arbitratorNodeAddresses = arbitratorNodeAddresses;
this.mediatorNodeAddresses = mediatorNodeAddresses;
this.paymentMethodId = paymentMethodId;
this.makerPaymentAccountId = makerPaymentAccountId;
this.offerFeePaymentTxId = offerFeePaymentTxId;
@ -256,6 +254,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
this.hashOfChallenge = hashOfChallenge;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
this.protocolVersion = protocolVersion;
this.arbitratorNodeAddress = arbitratorSigner;
this.arbitratorSignature = arbitratorSignature;
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -277,12 +277,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
.setMinAmount(minAmount)
.setBaseCurrencyCode(baseCurrencyCode)
.setCounterCurrencyCode(counterCurrencyCode)
.addAllArbitratorNodeAddresses(arbitratorNodeAddresses.stream()
.map(NodeAddress::toProtoMessage)
.collect(Collectors.toList()))
.addAllMediatorNodeAddresses(mediatorNodeAddresses.stream()
.map(NodeAddress::toProtoMessage)
.collect(Collectors.toList()))
.setPaymentMethodId(paymentMethodId)
.setMakerPaymentAccountId(makerPaymentAccountId)
.setVersionNr(versionNr)
@ -310,6 +304,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes);
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage());
Optional.ofNullable(arbitratorSignature).ifPresent(builder::setArbitratorSignature);
return protobuf.StoragePayload.newBuilder().setOfferPayload(builder).build();
}
@ -336,12 +333,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
proto.getMinAmount(),
proto.getBaseCurrencyCode(),
proto.getCounterCurrencyCode(),
proto.getArbitratorNodeAddressesList().stream()
.map(NodeAddress::fromProto)
.collect(Collectors.toList()),
proto.getMediatorNodeAddressesList().stream()
.map(NodeAddress::fromProto)
.collect(Collectors.toList()),
proto.getPaymentMethodId(),
proto.getMakerPaymentAccountId(),
proto.getOfferFeePaymentTxId(),
@ -365,7 +356,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
proto.getIsPrivateOffer(),
hashOfChallenge,
extraDataMapMap,
proto.getProtocolVersion());
proto.getProtocolVersion(),
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature()));
}
@ -431,6 +424,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
",\n hashOfChallenge='" + hashOfChallenge + '\'' +
",\n extraDataMap=" + extraDataMap +
",\n protocolVersion=" + protocolVersion +
",\n arbitratorSigner=" + arbitratorNodeAddress +
",\n arbitratorSignature=" + arbitratorSignature +
"\n}";
}
}

View File

@ -24,8 +24,9 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.proto.ProtoUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import lombok.EqualsAndHashCode;
@ -58,16 +59,9 @@ public final class OpenOffer implements Tradable {
@Setter
@Nullable
private NodeAddress arbitratorNodeAddress;
@Getter
@Setter
@Nullable
private NodeAddress mediatorNodeAddress;
// Added v1.2.0
@Getter
@Setter
@Nullable
private NodeAddress refundAgentNodeAddress;
private List<String> frozenKeyImages = new ArrayList<>();
// Added in v1.5.3.
// If market price reaches that trigger price the offer gets deactivated
@ -86,6 +80,13 @@ public final class OpenOffer implements Tradable {
this.triggerPrice = triggerPrice;
state = State.AVAILABLE;
}
public OpenOffer(Offer offer, long triggerPrice, List<String> frozenKeyImages) {
this.offer = offer;
this.triggerPrice = triggerPrice;
state = State.AVAILABLE;
this.frozenKeyImages = frozenKeyImages;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
@ -94,14 +95,10 @@ public final class OpenOffer implements Tradable {
private OpenOffer(Offer offer,
State state,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress refundAgentNodeAddress,
long triggerPrice) {
this.offer = offer;
this.state = state;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
this.refundAgentNodeAddress = refundAgentNodeAddress;
this.triggerPrice = triggerPrice;
if (this.state == State.RESERVED)
@ -113,22 +110,21 @@ public final class OpenOffer implements Tradable {
protobuf.OpenOffer.Builder builder = protobuf.OpenOffer.newBuilder()
.setOffer(offer.toProtoMessage())
.setTriggerPrice(triggerPrice)
.setState(protobuf.OpenOffer.State.valueOf(state.name()));
.setState(protobuf.OpenOffer.State.valueOf(state.name()))
.addAllFrozenKeyImages(frozenKeyImages);
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
Optional.ofNullable(mediatorNodeAddress).ifPresent(nodeAddress -> builder.setMediatorNodeAddress(nodeAddress.toProtoMessage()));
Optional.ofNullable(refundAgentNodeAddress).ifPresent(nodeAddress -> builder.setRefundAgentNodeAddress(nodeAddress.toProtoMessage()));
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
}
public static Tradable fromProto(protobuf.OpenOffer proto) {
return new OpenOffer(Offer.fromProto(proto.getOffer()),
OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()),
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
proto.getTriggerPrice());
openOffer.setFrozenKeyImages(proto.getFrozenKeyImagesList());
return openOffer;
}
@ -192,8 +188,6 @@ public final class OpenOffer implements Tradable {
",\n offer=" + offer +
",\n state=" + state +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n triggerPrice=" + triggerPrice +
"\n}";
}

View File

@ -29,18 +29,23 @@ import bisq.core.locale.Res;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.messages.SignOfferRequest;
import bisq.core.offer.messages.SignOfferResponse;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.TradableList;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.ParsingUtils;
import bisq.core.util.Validator;
import bisq.network.p2p.AckMessage;
@ -53,7 +58,6 @@ import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendDirectMessageListener;
import bisq.network.p2p.peers.Broadcaster;
import bisq.network.p2p.peers.PeerManager;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.app.Capabilities;
@ -61,26 +65,28 @@ import bisq.common.app.Capability;
import bisq.common.app.Version;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -88,7 +94,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter;
import monero.daemon.model.MoneroOutput;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@ -119,13 +125,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final TradeStatisticsManager tradeStatisticsManager;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
private final RefundAgentManager refundAgentManager;
private final DaoFacade daoFacade;
private final FilterManager filterManager;
private final Broadcaster broadcaster;
private final PersistenceManager<TradableList<OpenOffer>> persistenceManager;
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
private final TradableList<OpenOffer> openOffers = new TradableList<>();
private final SignedOfferList signedOffers = new SignedOfferList();
private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager;
private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>();
private boolean stopped;
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
@Getter
@ -157,7 +165,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
DaoFacade daoFacade,
FilterManager filterManager,
Broadcaster broadcaster,
PersistenceManager<TradableList<OpenOffer>> persistenceManager) {
PersistenceManager<TradableList<OpenOffer>> persistenceManager,
PersistenceManager<SignedOfferList> signedOfferPersistenceManager) {
this.coreContext = coreContext;
this.createOfferService = createOfferService;
this.keyRing = keyRing;
@ -174,21 +183,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
this.refundAgentManager = refundAgentManager;
this.daoFacade = daoFacade;
this.filterManager = filterManager;
this.broadcaster = broadcaster;
this.persistenceManager = persistenceManager;
this.signedOfferPersistenceManager = signedOfferPersistenceManager;
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
this.signedOfferPersistenceManager.initialize(signedOffers, "SignedOffers", PersistenceManager.Source.PRIVATE); // arbitrator stores reserve tx for signed offers
}
@Override
public void readPersisted(Runnable completeHandler) {
// read open offers
persistenceManager.readPersisted(persisted -> {
openOffers.setAll(persisted.getList());
openOffers.forEach(openOffer -> openOffer.getOffer().setPriceFeedService(priceFeedService));
completeHandler.run();
// read signed offers
signedOfferPersistenceManager.readPersisted(signedOfferPersisted -> {
signedOffers.setAll(signedOfferPersisted.getList());
completeHandler.run();
},
completeHandler);
},
completeHandler);
}
@ -286,7 +304,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// We get an encrypted message but don't do the signature check as we don't know the peer yet.
// A basic sig check is in done also at decryption time
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
if (networkEnvelope instanceof OfferAvailabilityRequest) {
if (networkEnvelope instanceof SignOfferRequest) {
handleSignOfferRequest((SignOfferRequest) networkEnvelope, peerNodeAddress);
} if (networkEnvelope instanceof SignOfferResponse) {
handleSignOfferResponse((SignOfferResponse) networkEnvelope, peerNodeAddress);
} else if (networkEnvelope instanceof OfferAvailabilityRequest) {
handleOfferAvailabilityRequest((OfferAvailabilityRequest) networkEnvelope, peerNodeAddress);
} else if (networkEnvelope instanceof AckMessage) {
AckMessage ackMessage = (AckMessage) networkEnvelope;
@ -380,24 +402,36 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
offer.getAmount(),
buyerSecurityDeposit,
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit));
if (placeOfferProtocols.containsKey(offer.getId())) {
log.warn("We already have a place offer protocol for offer " + offer.getId() + ", ignoring");
throw new RuntimeException("We already have a place offer protocol for offer " + offer.getId() + ", ignoring");
}
PlaceOfferModel model = new PlaceOfferModel(offer,
reservedFundsForOffer,
useSavingsWallet,
p2PService,
btcWalletService,
xmrWalletService,
tradeWalletService,
bsqWalletService,
offerBookService,
arbitratorManager,
mediatorManager,
tradeStatisticsManager,
daoFacade,
user,
keyRing,
filterManager);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model,
transaction -> {
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
// save frozen key images with open offer
List<String> frozenKeyImages = new ArrayList<String>();
for (MoneroOutput output : model.getReserveTx().getInputs()) frozenKeyImages.add(output.getKeyImage().getHex());
OpenOffer openOffer = new OpenOffer(offer, triggerPrice, frozenKeyImages);
openOffers.add(openOffer);
requestPersistence();
resultHandler.handleResult(transaction);
@ -410,7 +444,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
},
errorMessageHandler
);
placeOfferProtocol.placeOffer();
placeOfferProtocols.put(offer.getId(), placeOfferProtocol);
placeOfferProtocol.placeOffer(); // TODO (woodser): if error placing offer (e.g. bad signature), remove protocol and unfreeze trade funds
}
// Remove from offerbook
@ -590,7 +626,128 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
public Optional<OpenOffer> getOpenOfferById(String offerId) {
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
}
public Optional<SignedOffer> getSignedOfferById(String offerId) {
return signedOffers.stream().filter(e -> e.getOfferId().equals(offerId)).findFirst();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Arbitrator Signs Offer
///////////////////////////////////////////////////////////////////////////////////////////
private void handleSignOfferRequest(SignOfferRequest request, NodeAddress peer) {
log.info("Received SignOfferRequest from {} with offerId {} and uid {}",
peer, request.getOfferId(), request.getUid());
String errorMessage = null;
try {
// verify this node is an arbitrator
Mediator thisArbitrator = user.getRegisteredMediator();
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) {
errorMessage = "Cannot sign offer because we are not a registered arbitrator";
log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
// verify arbitrator is signer of offer payload
if (!request.getOfferPayload().getArbitratorNodeAddress().equals(thisAddress)) {
errorMessage = "Cannot sign offer because offer payload is for a different arbitrator";
log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
// verify offer not seen before
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
if (openOfferOptional.isPresent()) {
errorMessage = "We already got a request to sign offer id " + request.offerId;
log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
// verify reserve tx not signed before
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
Offer offer = new Offer(request.getOfferPayload());
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(offer.getDirection() == OfferPayload.Direction.BUY ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
TradeUtils.processTradeTx(
xmrWalletService.getDaemon(),
xmrWalletService.getWallet(),
request.getPayoutAddress(),
depositAmount,
tradeFee,
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKey(),
true);
// arbitrator signs offer to certify they have valid reserve tx
String offerPayloadAsJson = Utilities.objectToJson(request.getOfferPayload());
String signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), offerPayloadAsJson);
OfferPayload signedOfferPayload = request.getOfferPayload();
signedOfferPayload.setArbitratorSignature(signature);
// create record of signed offer
SignedOffer signedOffer = new SignedOffer(signedOfferPayload.getId(), request.getReserveTxHash(), request.getReserveTxHex(), signature); // TODO (woodser): no need for signature to be part of SignedOffer?
signedOffers.add(signedOffer);
requestPersistence();
// send ack
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), true, errorMessage);
// send response with signature
SignOfferResponse response = new SignOfferResponse(request.getOfferId(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
signedOfferPayload);
p2PService.sendEncryptedDirectMessage(peer,
request.getPubKeyRing(),
response,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer: offerId={}; uid={}",
response.getClass().getSimpleName(),
response.getOfferId(),
response.getUid());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}",
response.getClass().getSimpleName(),
response.getUid(),
peer,
errorMessage);
}
});
} catch (Exception e) {
e.printStackTrace();
errorMessage = "Exception at handleSignOfferRequest " + e.getMessage();
log.error(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
}
}
private void handleSignOfferResponse(SignOfferResponse response, NodeAddress peer) {
log.info("Received SignOfferResponse from {} with offerId {} and uid {}",
peer, response.getOfferId(), response.getUid());
// get previously created protocol
PlaceOfferProtocol protocol = placeOfferProtocols.get(response.getOfferId());
if (protocol == null) {
log.warn("No place offer protocol created for offer " + response.getOfferId());
return;
}
// handle response
protocol.handleSignOfferResponse(response, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// OfferPayload Availability
@ -606,7 +763,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (!p2PService.isBootstrapped()) {
errorMessage = "We got a handleOfferAvailabilityRequest but we have not bootstrapped yet.";
log.info(errorMessage);
sendAckMessage(request, peer, false, errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
@ -614,14 +771,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
log.info(errorMessage);
sendAckMessage(request, peer, false, errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
if (stopped) {
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
log.debug(errorMessage);
sendAckMessage(request, peer, false, errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
@ -631,50 +788,60 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} catch (Throwable t) {
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
log.warn(errorMessage);
sendAckMessage(request, peer, false, errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
try {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
AvailabilityResult availabilityResult;
String makerSignature = null;
NodeAddress arbitratorNodeAddress = null;
NodeAddress mediatorNodeAddress = null;
NodeAddress refundAgentNodeAddress = null;
if (openOfferOptional.isPresent()) {
OpenOffer openOffer = openOfferOptional.get();
if (!apiUserDeniedByOffer(request)) {
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
Offer offer = openOffer.getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
if (!takerDeniedByMaker(request)) {
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
Offer offer = openOffer.getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
// use signing arbitrator if available, otherwise use least used arbitrator
boolean isSignerOnline = true;
arbitratorNodeAddress = isSignerOnline ? offer.getOfferPayload().getArbitratorNodeAddress() : DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
// maker signs taker's request // TODO (woodser): should maker signature include selected arbitrator?
String tradeRequestAsJson = Utilities.objectToJson(request.getTradeRequest());
makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson);
try {
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
// in trade price between the peers. Also here poor connectivity might cause market price API connection
// losses and therefore an outdated market price.
offer.checkTradePriceTolerance(request.getTakersTradePrice());
availabilityResult = AvailabilityResult.AVAILABLE;
} catch (TradePriceOutOfToleranceException e) {
log.warn("Trade price check failed because takers price is outside out tolerance.");
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
} catch (MarketPriceNotAvailableException e) {
log.warn(e.getMessage());
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
} catch (Throwable e) {
log.warn("Trade price check failed. " + e.getMessage());
if (coreContext.isApiUser())
// Give api user something more than 'unknown_failure'.
availabilityResult = AvailabilityResult.PRICE_CHECK_FAILED;
else
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
try {
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
// in trade price between the peers. Also here poor connectivity might cause market price API connection
// losses and therefore an outdated market price.
offer.checkTradePriceTolerance(request.getTakersTradePrice());
availabilityResult = AvailabilityResult.AVAILABLE;
} catch (TradePriceOutOfToleranceException e) {
log.warn("Trade price check failed because takers price is outside out tolerance.");
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
} catch (MarketPriceNotAvailableException e) {
log.warn(e.getMessage());
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
} catch (Throwable e) {
log.warn("Trade price check failed. " + e.getMessage());
if (coreContext.isApiUser())
// Give api user something more than 'unknown_failure'.
availabilityResult = AvailabilityResult.PRICE_CHECK_FAILED;
else
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
}
} else {
availabilityResult = AvailabilityResult.USER_IGNORED;
}
} else {
availabilityResult = AvailabilityResult.USER_IGNORED;
availabilityResult = AvailabilityResult.OFFER_TAKEN;
}
} else {
availabilityResult = AvailabilityResult.OFFER_TAKEN;
availabilityResult = AvailabilityResult.MAKER_DENIED_TAKER;
}
} else {
availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER;
@ -689,12 +856,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.warn(errorMessage);
availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT;
}
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
availabilityResult,
arbitratorNodeAddress,
mediatorNodeAddress,
refundAgentNodeAddress);
makerSignature,
arbitratorNodeAddress);
log.info("Send {} with offerId {} and uid {} to peer {}",
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
offerAvailabilityResponse.getUid(), peer);
@ -725,53 +891,57 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.error(errorMessage);
t.printStackTrace();
} finally {
sendAckMessage(request, peer, result, errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
}
}
private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) {
return preferences.isDenyApiTaker() && request.isTakerApiUser();
}
private boolean takerDeniedByMaker(OfferAvailabilityRequest request) {
if (request.getTradeRequest() == null) return true;
return false; // TODO (woodser): implement taker verification here, doing work of ApplyFilter and VerifyPeersAccountAgeWitness
}
private void sendAckMessage(OfferAvailabilityRequest message,
private void sendAckMessage(Class<?> reqClass,
NodeAddress sender,
PubKeyRing senderPubKeyRing,
String offerId,
String uid,
boolean result,
String errorMessage) {
String offerId = message.getOfferId();
String sourceUid = message.getUid();
String sourceUid = uid;
AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(),
AckMessageSourceType.OFFER_MESSAGE,
message.getClass().getSimpleName(),
reqClass.getSimpleName(),
sourceUid,
offerId,
result,
errorMessage);
final NodeAddress takersNodeAddress = sender;
PubKeyRing takersPubKeyRing = message.getPubKeyRing();
log.info("Send AckMessage for OfferAvailabilityRequest to peer {} with offerId {} and sourceUid {}",
takersNodeAddress, offerId, ackMessage.getSourceUid());
log.info("Send AckMessage for {} to peer {} with offerId {} and sourceUid {}",
reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid());
p2PService.sendEncryptedDirectMessage(
takersNodeAddress,
takersPubKeyRing,
sender,
senderPubKeyRing,
ackMessage,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("AckMessage for OfferAvailabilityRequest arrived at takersNodeAddress {}. offerId={}, sourceUid={}",
takersNodeAddress, offerId, ackMessage.getSourceUid());
log.info("AckMessage for {} arrived at sender {}. offerId={}, sourceUid={}",
reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid());
}
@Override
public void onFault(String errorMessage) {
log.error("AckMessage for OfferAvailabilityRequest failed. AckMessage={}, takersNodeAddress={}, errorMessage={}",
ackMessage, takersNodeAddress, errorMessage);
log.error("AckMessage for {} failed. AckMessage={}, sender={}, errorMessage={}",
reqClass.getSimpleName(), ackMessage, sender, errorMessage);
}
}
);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Update persisted offer if a new capability is required after a software update
///////////////////////////////////////////////////////////////////////////////////////////
@ -838,8 +1008,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
originalOfferPayload.getMinAmount(),
originalOfferPayload.getBaseCurrencyCode(),
originalOfferPayload.getCounterCurrencyCode(),
originalOfferPayload.getArbitratorNodeAddresses(),
originalOfferPayload.getMediatorNodeAddresses(),
originalOfferPayload.getPaymentMethodId(),
originalOfferPayload.getMakerPaymentAccountId(),
originalOfferPayload.getOfferFeePaymentTxId(),
@ -863,7 +1031,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
originalOfferPayload.isPrivateOffer(),
originalOfferPayload.getHashOfChallenge(),
updatedExtraDataMap,
protocolVersion);
protocolVersion,
originalOfferPayload.getArbitratorNodeAddress(),
originalOfferPayload.getArbitratorSignature());
// Save states from original data to use for the updated
Offer.State originalOfferState = originalOffer.getState();
@ -1024,6 +1194,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private void requestPersistence() {
persistenceManager.requestPersistence();
signedOfferPersistenceManager.requestPersistence();
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer;
import bisq.common.proto.persistable.PersistablePayload;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@EqualsAndHashCode
@Slf4j
public final class SignedOffer implements PersistablePayload {
@Getter
private final String offerId;
@Getter
private final String reserveTxHash;
@Getter
private final String reserveTxHex;
@Getter
private final String arbitratorSignature;
public SignedOffer(String offerId, String reserveTxHash, String reserveTxHex, String arbitratorSignature) {
this.offerId = offerId;
this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex;
this.arbitratorSignature = arbitratorSignature;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.SignedOffer toProtoMessage() {
protobuf.SignedOffer.Builder builder = protobuf.SignedOffer.newBuilder()
.setOfferId(offerId)
.setReserveTxHash(reserveTxHash)
.setReserveTxHex(reserveTxHex)
.setArbitratorSignature(arbitratorSignature);
return builder.build();
}
public static SignedOffer fromProto(protobuf.SignedOffer proto) {
return new SignedOffer(proto.getOfferId(), proto.getReserveTxHash(), proto.getReserveTxHex(), proto.getArbitratorSignature());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public String toString() {
return "SignedOffer{" +
",\n offerId=" + offerId +
",\n reserveTxHash=" + reserveTxHash +
",\n reserveTxHex=" + reserveTxHex +
",\n arbitratorSignature=" + arbitratorSignature +
"\n}";
}
}

View File

@ -0,0 +1,74 @@
package bisq.core.offer;
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
import bisq.common.proto.ProtoUtil;
import bisq.common.proto.persistable.PersistableListAsObservable;
import com.google.protobuf.Message;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class SignedOfferList extends PersistableListAsObservable<SignedOffer> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public SignedOfferList() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
protected SignedOfferList(Collection<SignedOffer> collection) {
super(collection);
}
@Override
public Message toProtoMessage() {
return protobuf.PersistableEnvelope.newBuilder()
.setSignedOfferList(protobuf.SignedOfferList.newBuilder()
.addAllSignedOffer(ProtoUtil.collectionToProto(getList(), protobuf.SignedOffer.class)))
.build();
}
public static SignedOfferList fromProto(protobuf.SignedOfferList proto) {
List<SignedOffer> list = proto.getSignedOfferList().stream()
.map(signedOffer -> {
return SignedOffer.fromProto(signedOffer);
})
.collect(Collectors.toList());
return new SignedOfferList(list);
}
@Override
public String toString() {
return "SignedOfferList{" +
",\n list=" + getList() +
"\n}";
}
}

View File

@ -17,9 +17,12 @@
package bisq.core.offer.availability;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
@ -40,6 +43,8 @@ public class OfferAvailabilityModel implements Model {
@Getter
private final PubKeyRing pubKeyRing; // takers PubKey (my pubkey)
@Getter
private final XmrWalletService xmrWalletService;
@Getter
private final P2PService p2PService;
@Getter
final private User user;
@ -49,22 +54,20 @@ public class OfferAvailabilityModel implements Model {
private final TradeStatisticsManager tradeStatisticsManager;
private NodeAddress peerNodeAddress; // maker
private OfferAvailabilityResponse message;
@Getter
private String paymentAccountId;
@Getter
private OfferUtil offerUtil;
@Getter
@Setter
private InitTradeRequest tradeRequest;
@Nullable
@Setter
@Getter
private NodeAddress selectedArbitrator;
// Added in v1.1.6
@Nullable
private String makerSignature;
@Setter
@Getter
private NodeAddress selectedMediator;
// Added in v1.2.0
@Nullable
@Setter
@Getter
private NodeAddress selectedRefundAgent;
private NodeAddress arbitratorNodeAddress;
// Added in v1.5.5
@Getter
@ -72,18 +75,24 @@ public class OfferAvailabilityModel implements Model {
public OfferAvailabilityModel(Offer offer,
PubKeyRing pubKeyRing,
XmrWalletService xmrWalletService,
P2PService p2PService,
User user,
MediatorManager mediatorManager,
TradeStatisticsManager tradeStatisticsManager,
boolean isTakerApiUser) {
boolean isTakerApiUser,
String paymentAccountId,
OfferUtil offerUtil) {
this.offer = offer;
this.pubKeyRing = pubKeyRing;
this.xmrWalletService = xmrWalletService;
this.p2PService = p2PService;
this.user = user;
this.mediatorManager = mediatorManager;
this.tradeStatisticsManager = tradeStatisticsManager;
this.isTakerApiUser = isTakerApiUser;
this.paymentAccountId = paymentAccountId;
this.offerUtil = offerUtil;
}
public NodeAddress getPeerNodeAddress() {

View File

@ -19,18 +19,16 @@ package bisq.core.offer.availability.tasks;
import bisq.core.offer.AvailabilityResult;
import bisq.core.offer.Offer;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.network.p2p.NodeAddress;
import bisq.core.trade.TradeUtils;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityModel> {
@ -47,26 +45,27 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
checkArgument(offer.getState() != Offer.State.REMOVED, "Offer state must not be Offer.State.REMOVED");
// check availability result
OfferAvailabilityResponse offerAvailabilityResponse = model.getMessage();
if (offerAvailabilityResponse.getAvailabilityResult() != AvailabilityResult.AVAILABLE) {
offer.setState(Offer.State.NOT_AVAILABLE);
failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.getAvailabilityResult());
return;
}
// check maker signature for trade request
if (!TradeUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) {
offer.setState(Offer.State.NOT_AVAILABLE);
failed("Take offer attempt failed because maker signature is invalid");
return;
}
offer.setState(Offer.State.AVAILABLE);
model.setSelectedArbitrator(offerAvailabilityResponse.getArbitrator());
NodeAddress mediator = offerAvailabilityResponse.getMediator();
if (mediator == null) {
// We do not get a mediator from old clients so we need to handle the null case.
mediator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
}
model.setSelectedMediator(mediator);
model.setSelectedRefundAgent(offerAvailabilityResponse.getRefundAgent());
model.setMakerSignature(offerAvailabilityResponse.getMakerSignature());
model.setArbitratorNodeAddress(offerAvailabilityResponse.getArbitratorNodeAddress());
checkNotNull(model.getMakerSignature());
checkNotNull(model.getArbitratorNodeAddress());
complete();
} catch (Throwable t) {

View File

@ -17,12 +17,21 @@
package bisq.core.offer.availability.tasks;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.user.User;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendDirectMessageListener;
import com.google.common.base.Charsets;
import java.util.Date;
import java.util.UUID;
import bisq.common.app.Version;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
@ -38,9 +47,49 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
protected void run() {
try {
runInterceptHook();
// collect fields
Offer offer = model.getOffer();
User user = model.getUser();
P2PService p2PService = model.getP2PService();
XmrWalletService walletService = model.getXmrWalletService();
OfferUtil offerUtil = model.getOfferUtil();
String paymentAccountId = model.getPaymentAccountId();
String paymentMethodId = user.getPaymentAccount(paymentAccountId).getPaymentAccountPayload().getPaymentMethodId();
String payoutAddress = walletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); // reserve new payout address
// taker signs offer using offer id as nonce to avoid challenge protocol
byte[] sig = Sig.sign(model.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8));
// send InitTradeRequest to maker to sign
InitTradeRequest tradeRequest = new InitTradeRequest(
offer.getId(),
P2PService.getMyNodeAddress(),
p2PService.getKeyRing().getPubKeyRing(),
offer.getAmount().value,
offer.getPrice().getValue(),
offerUtil.getTakerFee(true, offer.getAmount()).value,
user.getAccountId(),
paymentAccountId,
paymentMethodId,
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
sig,
new Date().getTime(),
offer.getMakerNodeAddress(),
P2PService.getMyNodeAddress(),
null, // maker provides node address of arbitrator on response
null, // reserve tx not sent from taker to maker
null,
null,
payoutAddress,
null);
// save trade request to later send to arbitrator
model.setTradeRequest(tradeRequest);
OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(),
model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser());
model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser(), tradeRequest);
log.info("Send {} with offerId {} and uid {} to peer {}",
message.getClass().getSimpleName(), message.getOfferId(),
message.getUid(), model.getPeerNodeAddress());

View File

@ -22,7 +22,8 @@ import bisq.network.p2p.SupportedCapabilitiesMessage;
import bisq.common.app.Capabilities;
import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.messages.InitTradeRequest;
import java.util.Optional;
import java.util.UUID;
@ -43,18 +44,21 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
@Nullable
private final Capabilities supportedCapabilities;
private final boolean isTakerApiUser;
private final InitTradeRequest tradeRequest;
public OfferAvailabilityRequest(String offerId,
PubKeyRing pubKeyRing,
long takersTradePrice,
boolean isTakerApiUser) {
boolean isTakerApiUser,
InitTradeRequest tradeRequest) {
this(offerId,
pubKeyRing,
takersTradePrice,
isTakerApiUser,
Capabilities.app,
Version.getP2PMessageVersion(),
UUID.randomUUID().toString());
UUID.randomUUID().toString(),
tradeRequest);
}
@ -68,13 +72,24 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
boolean isTakerApiUser,
@Nullable Capabilities supportedCapabilities,
int messageVersion,
@Nullable String uid) {
@Nullable String uid,
InitTradeRequest tradeRequest) {
super(messageVersion, offerId, uid);
this.pubKeyRing = pubKeyRing;
this.takersTradePrice = takersTradePrice;
this.isTakerApiUser = isTakerApiUser;
this.supportedCapabilities = supportedCapabilities;
this.tradeRequest = tradeRequest;
}
// @Override
// public protobuf.Offer toProtoMessage() {
// return protobuf.Offer.newBuilder().setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()).build();
// }
//
// public static Offer fromProto(protobuf.Offer proto) {
// return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
// }
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
@ -82,7 +97,8 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
.setOfferId(offerId)
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setTakersTradePrice(takersTradePrice)
.setIsTakerApiUser(isTakerApiUser);
.setIsTakerApiUser(isTakerApiUser)
.setTradeRequest(tradeRequest.toProtoNetworkEnvelope().getInitTradeRequest());
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
@ -92,13 +108,14 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
.build();
}
public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityRequest proto, int messageVersion) {
public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
return new OfferAvailabilityRequest(proto.getOfferId(),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getTakersTradePrice(),
proto.getIsTakerApiUser(),
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
messageVersion,
proto.getUid().isEmpty() ? null : proto.getUid());
proto.getUid().isEmpty() ? null : proto.getUid(),
InitTradeRequest.fromProto(proto.getTradeRequest(), coreProtoResolver, messageVersion));
}
}

View File

@ -44,28 +44,21 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable
private final Capabilities supportedCapabilities;
private final NodeAddress arbitrator;
// Was introduced in v 1.1.6. Might be null if msg received from node with old version
@Nullable
private final NodeAddress mediator;
// Added v1.2.0
@Nullable
private final NodeAddress refundAgent;
private final String makerSignature;
private final NodeAddress arbitratorNodeAddress;
public OfferAvailabilityResponse(String offerId,
AvailabilityResult availabilityResult,
NodeAddress arbitrator,
NodeAddress mediator,
NodeAddress refundAgent) {
String makerSignature,
NodeAddress arbitratorNodeAddress) {
this(offerId,
availabilityResult,
Capabilities.app,
Version.getP2PMessageVersion(),
UUID.randomUUID().toString(),
arbitrator,
mediator,
refundAgent);
makerSignature,
arbitratorNodeAddress);
}
@ -78,28 +71,25 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable Capabilities supportedCapabilities,
int messageVersion,
@Nullable String uid,
NodeAddress arbitrator,
@Nullable NodeAddress mediator,
@Nullable NodeAddress refundAgent) {
String makerSignature,
NodeAddress arbitratorNodeAddress) {
super(messageVersion, offerId, uid);
this.availabilityResult = availabilityResult;
this.supportedCapabilities = supportedCapabilities;
this.arbitrator = arbitrator;
this.mediator = mediator;
this.refundAgent = refundAgent;
this.makerSignature = makerSignature;
this.arbitratorNodeAddress = arbitratorNodeAddress;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
.setOfferId(offerId)
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()));
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()))
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage());
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage()));
Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage()));
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage()));
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
return getNetworkEnvelopeBuilder()
.setOfferAvailabilityResponse(builder)
@ -112,8 +102,7 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
messageVersion,
proto.getUid().isEmpty() ? null : proto.getUid(),
proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null,
proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null,
proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null);
proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(),
NodeAddress.fromProto(proto.getArbitratorNodeAddress()));
}
}

View File

@ -0,0 +1,115 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.messages;
import bisq.common.crypto.PubKeyRing;
import bisq.core.offer.OfferPayload;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class SignOfferRequest extends OfferMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final String senderAccountId;
private final OfferPayload offerPayload;
private final long currentDate;
private final String reserveTxHash;
private final String reserveTxHex;
private final String reserveTxKey;
private final String payoutAddress;
public SignOfferRequest(String offerId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String senderAccountId,
OfferPayload offerPayload,
String uid,
int messageVersion,
long currentDate,
String reserveTxHash,
String reserveTxHex,
String reserveTxKey,
String payoutAddress) {
super(messageVersion, offerId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.senderAccountId = senderAccountId;
this.offerPayload = offerPayload;
this.currentDate = currentDate;
this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex;
this.reserveTxKey = reserveTxKey;
this.payoutAddress = payoutAddress;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.SignOfferRequest.Builder builder = protobuf.SignOfferRequest.newBuilder()
.setOfferId(offerId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setSenderAccountId(senderAccountId)
.setOfferPayload(offerPayload.toProtoMessage().getOfferPayload())
.setUid(uid)
.setCurrentDate(currentDate)
.setReserveTxHash(reserveTxHash)
.setReserveTxHex(reserveTxHex)
.setReserveTxKey(reserveTxKey)
.setPayoutAddress(payoutAddress);
return getNetworkEnvelopeBuilder().setSignOfferRequest(builder).build();
}
public static SignOfferRequest fromProto(protobuf.SignOfferRequest proto,
int messageVersion) {
return new SignOfferRequest(proto.getOfferId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getSenderAccountId(),
OfferPayload.fromProto(proto.getOfferPayload()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
proto.getReserveTxHash(),
proto.getReserveTxHex(),
proto.getReserveTxKey(),
proto.getPayoutAddress());
}
@Override
public String toString() {
return "SignOfferRequest {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n reserveTxHash='" + reserveTxHash +
",\n reserveTxHex='" + reserveTxHex +
",\n reserveTxKey='" + reserveTxKey +
",\n payoutAddress='" + payoutAddress +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,67 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.messages;
import bisq.core.offer.OfferPayload;
import bisq.network.p2p.DirectMessage;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class SignOfferResponse extends OfferMessage implements DirectMessage {
private final OfferPayload signedOfferPayload;
public SignOfferResponse(String offerId,
String uid,
int messageVersion,
OfferPayload signedOfferPayload) {
super(messageVersion, offerId, uid);
this.signedOfferPayload = signedOfferPayload;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.SignOfferResponse.Builder builder = protobuf.SignOfferResponse.newBuilder()
.setOfferId(offerId)
.setUid(uid)
.setSignedOfferPayload(signedOfferPayload.toProtoMessage().getOfferPayload());
return getNetworkEnvelopeBuilder().setSignOfferResponse(builder).build();
}
public static SignOfferResponse fromProto(protobuf.SignOfferResponse proto,
int messageVersion) {
return new SignOfferResponse(proto.getOfferId(),
proto.getUid(),
messageVersion,
OfferPayload.fromProto(proto.getSignedOfferPayload()));
}
@Override
public String toString() {
return "SignOfferResponse {" +
",\n arbitratorSignature='" + signedOfferPayload.getArbitratorSignature() +
"\n} " + super.toString();
}
}

View File

@ -25,12 +25,16 @@ import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.messages.SignOfferResponse;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.common.crypto.KeyRing;
import bisq.common.taskrunner.Model;
import bisq.network.p2p.P2PService;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
@ -49,15 +53,18 @@ public class PlaceOfferModel implements Model {
private final Offer offer;
private final Coin reservedFundsForOffer;
private final boolean useSavingsWallet;
private final P2PService p2PService;
private final BtcWalletService walletService;
private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
private final TradeStatisticsManager tradeStatisticsManager;
private final DaoFacade daoFacade;
private final User user;
private final KeyRing keyRing;
@Getter
private final FilterManager filterManager;
@ -67,33 +74,41 @@ public class PlaceOfferModel implements Model {
@Setter
private Transaction transaction;
@Setter
private MoneroTxWallet xmrTransaction;
private MoneroTxWallet reserveTx;
@Setter
private SignOfferResponse signOfferResponse;
public PlaceOfferModel(Offer offer,
Coin reservedFundsForOffer,
boolean useSavingsWallet,
P2PService p2PService,
BtcWalletService walletService,
XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
TradeStatisticsManager tradeStatisticsManager,
DaoFacade daoFacade,
User user,
KeyRing keyRing,
FilterManager filterManager) {
this.offer = offer;
this.reservedFundsForOffer = reservedFundsForOffer;
this.useSavingsWallet = useSavingsWallet;
this.p2PService = p2PService;
this.walletService = walletService;
this.xmrWalletService = xmrWalletService;
this.tradeWalletService = tradeWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
this.tradeStatisticsManager = tradeStatisticsManager;
this.daoFacade = daoFacade;
this.user = user;
this.keyRing = keyRing;
this.filterManager = filterManager;
}

View File

@ -17,12 +17,14 @@
package bisq.core.offer.placeoffer;
import bisq.core.offer.messages.SignOfferResponse;
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions;
import bisq.core.offer.placeoffer.tasks.MakerReservesTradeFunds;
import bisq.core.offer.placeoffer.tasks.MakerSendsSignOfferRequest;
import bisq.core.offer.placeoffer.tasks.MakerProcessesSignOfferResponse;
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.taskrunner.TaskRunner;
@ -55,34 +57,60 @@ public class PlaceOfferProtocol {
///////////////////////////////////////////////////////////////////////////////////////////
public void placeOffer() {
log.debug("model.offer.id" + model.getOffer().getId());
log.debug("placeOffer() " + model.getOffer().getId());
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("sequence at handleRequestTakeOfferMessage completed");
resultHandler.handleResult(model.getTransaction());
log.debug("sequence at placeOffer completed");
},
(errorMessage) -> {
log.error(errorMessage);
if (model.isOfferAddedToOfferBook()) {
model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(),
() -> {
model.setOfferAddedToOfferBook(false);
log.debug("OfferPayload removed from offer book.");
},
log::error);
}
model.getOffer().setErrorMessage(errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
}
);
taskRunner.addTasks(
ValidateOffer.class,
CheckNumberOfUnconfirmedTransactions.class,
MakerCreateFeeTx.class,
AddToOfferBook.class
MakerReservesTradeFunds.class,
MakerSendsSignOfferRequest.class
);
taskRunner.run();
}
// TODO (woodser): switch to fluent
public void handleSignOfferResponse(SignOfferResponse response, NodeAddress sender) {
log.debug("handleSignOfferResponse() " + model.getOffer().getId());
model.setSignOfferResponse(response);
if (!model.getOffer().getOfferPayload().getArbitratorNodeAddress().equals(sender)) {
log.warn("Ignoring sign offer response from different sender");
return;
}
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("sequence at handleSignOfferResponse completed");
resultHandler.handleResult(model.getTransaction()); // TODO (woodser): XMR transaction instead
},
(errorMessage) -> {
log.error(errorMessage);
if (model.isOfferAddedToOfferBook()) {
model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(),
() -> {
model.setOfferAddedToOfferBook(false);
log.debug("OfferPayload removed from offer book.");
},
log::error);
}
model.getOffer().setErrorMessage(errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
}
);
taskRunner.addTasks(
MakerProcessesSignOfferResponse.class,
AddToOfferBook.class
);
taskRunner.run();
}
}

View File

@ -17,6 +17,7 @@
package bisq.core.offer.placeoffer.tasks;
import bisq.core.offer.Offer;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.common.taskrunner.Task;
@ -32,7 +33,7 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
protected void run() {
try {
runInterceptHook();
model.getOfferBookService().addOffer(model.getOffer(),
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
() -> {
model.setOfferAddedToOfferBook(true);
complete();

View File

@ -1,20 +0,0 @@
package bisq.core.offer.placeoffer.tasks;
import bisq.core.locale.Res;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
public class CheckNumberOfUnconfirmedTransactions extends Task<PlaceOfferModel> {
public CheckNumberOfUnconfirmedTransactions(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
if (model.getWalletService().isUnconfirmedTransactionsLimitHit() || model.getBsqWalletService().isUnconfirmedTransactionsLimitHit())
failed(Res.get("shared.unconfirmedTransactionsLimitReached"));
complete();
}
}

View File

@ -90,7 +90,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
model.setTransaction(transaction);
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
model.getOffer().setState(Offer.State.OFFER_FEE_PAID);
model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED);
complete();
} else {
@ -137,7 +137,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
log.debug("Successfully sent tx with id " + transaction.getTxId().toString());
model.getOffer().setState(Offer.State.OFFER_FEE_PAID);
model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED);
complete();
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.placeoffer.tasks;
import bisq.core.offer.Offer;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.TradeUtils;
import static com.google.common.base.Preconditions.checkNotNull;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
public class MakerProcessesSignOfferResponse extends Task<PlaceOfferModel> {
public MakerProcessesSignOfferResponse(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
Offer offer = model.getOffer();
try {
runInterceptHook();
// validate arbitrator signature
Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(arbitratorNodeAddress) must not be null");
if (!TradeUtils.isArbitratorSignatureValid(model.getSignOfferResponse().getSignedOfferPayload(), arbitrator)) {
throw new RuntimeException("Offer payload has invalid arbitrator signature");
}
// set arbitrator signature for maker's offer
model.getOffer().getOfferPayload().setArbitratorSignature(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature());
complete();
} catch (Exception e) {
offer.setErrorMessage("An error occurred.\n" +
"Error message:\n"
+ e.getMessage());
failed(e);
}
}
}

View File

@ -0,0 +1,76 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.placeoffer.tasks;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.Offer;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.trade.TradeUtils;
import bisq.core.util.ParsingUtils;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import monero.daemon.model.MoneroOutput;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
public class MakerReservesTradeFunds extends Task<PlaceOfferModel> {
public MakerReservesTradeFunds(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
Offer offer = model.getOffer();
try {
runInterceptHook();
// create transaction to reserve trade
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
BigInteger makerFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(model.getReservedFundsForOffer());
MoneroTxWallet reserveTx = TradeUtils.createReserveTx(model.getXmrWalletService(), offer.getId(), makerFee, returnAddress, depositAmount);
// freeze reserved outputs
// TODO (woodser): synchronize to handle potential race condition where concurrent trades freeze each other's outputs
List<String> frozenKeyImages = new ArrayList<String>();
MoneroWallet wallet = model.getXmrWalletService().getWallet();
for (MoneroOutput input : reserveTx.getInputs()) {
frozenKeyImages.add(input.getKeyImage().getHex());
wallet.freezeOutput(input.getKeyImage().getHex());
}
// save offer state
// TODO (woodser): persist
model.setReserveTx(reserveTx);
offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): rename this to reserve tx id
offer.setState(Offer.State.OFFER_FEE_RESERVED);
complete();
} catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" +
"Error message:\n"
+ t.getMessage());
failed(t);
}
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.placeoffer.tasks;
import bisq.common.app.Version;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.Offer;
import bisq.core.offer.messages.SignOfferRequest;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
public class MakerSendsSignOfferRequest extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(MakerSendsSignOfferRequest.class);
@SuppressWarnings({"unused"})
public MakerSendsSignOfferRequest(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
Offer offer = model.getOffer();
try {
runInterceptHook();
// create request for arbitrator to sign offer
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
SignOfferRequest request = new SignOfferRequest(
model.getOffer().getId(),
P2PService.getMyNodeAddress(),
model.getKeyRing().getPubKeyRing(),
model.getUser().getAccountId(),
offer.getOfferPayload(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
new Date().getTime(),
model.getReserveTx().getHash(),
model.getReserveTx().getFullHex(),
model.getReserveTx().getKey(),
returnAddress);
// get signing arbitrator
Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null");
// send request
model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: arbitrator={}; offerId={}; uid={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId());
complete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
} catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" +
"Error message:\n"
+ t.getMessage());
failed(t);
}
}
}

View File

@ -19,6 +19,7 @@ package bisq.core.payment.payload;
import bisq.common.consensus.UsedForTradeContractJson;
import bisq.common.crypto.CryptoUtils;
import bisq.common.crypto.Hash;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.util.JsonExclude;
import bisq.common.util.Utilities;
@ -46,7 +47,7 @@ import static com.google.common.base.Preconditions.checkArgument;
@ToString
@Slf4j
public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTradeContractJson {
// Keys for excludeFromJsonDataMap
public static final String SALT = "salt";
public static final String HOLDER_NAME = "holderName";
@ -117,6 +118,10 @@ public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTr
public abstract String getPaymentDetails();
public abstract String getPaymentDetailsForTradePopup();
public byte[] getHash() {
return Hash.getRipemd160hash(this.toProtoMessage().toByteArray());
}
public byte[] getSalt() {
checkArgument(excludeFromJsonDataMap.containsKey(SALT), "Salt must have been set in excludeFromJsonDataMap.");

View File

@ -39,6 +39,8 @@ import bisq.core.network.p2p.inventory.messages.GetInventoryResponse;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.messages.SignOfferRequest;
import bisq.core.offer.messages.SignOfferResponse;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
@ -53,19 +55,22 @@ import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.messages.UpdateMultisigRequest;
import bisq.core.trade.messages.UpdateMultisigResponse;
@ -133,8 +138,13 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
case PONG:
return Pong.fromProto(proto.getPong(), messageVersion);
case SIGN_OFFER_REQUEST:
return SignOfferRequest.fromProto(proto.getSignOfferRequest(), messageVersion);
case SIGN_OFFER_RESPONSE:
return SignOfferResponse.fromProto(proto.getSignOfferResponse(), messageVersion);
case OFFER_AVAILABILITY_REQUEST:
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), messageVersion);
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), this, messageVersion);
case OFFER_AVAILABILITY_RESPONSE:
return OfferAvailabilityResponse.fromProto(proto.getOfferAvailabilityResponse(), messageVersion);
case REFRESH_OFFER_MESSAGE:
@ -157,16 +167,22 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion);
case INIT_TRADE_REQUEST:
return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion);
case INIT_MULTISIG_MESSAGE:
return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion);
case INIT_MULTISIG_REQUEST:
return InitMultisigRequest.fromProto(proto.getInitMultisigRequest(), this, messageVersion);
case SIGN_CONTRACT_REQUEST:
return SignContractRequest.fromProto(proto.getSignContractRequest(), this, messageVersion);
case SIGN_CONTRACT_RESPONSE:
return SignContractResponse.fromProto(proto.getSignContractResponse(), this, messageVersion);
case DEPOSIT_REQUEST:
return DepositRequest.fromProto(proto.getDepositRequest(), this, messageVersion);
case DEPOSIT_RESPONSE:
return DepositResponse.fromProto(proto.getDepositResponse(), this, messageVersion);
case PAYMENT_ACCOUNT_PAYLOAD_REQUEST:
return PaymentAccountPayloadRequest.fromProto(proto.getPaymentAccountPayloadRequest(), this, messageVersion);
case UPDATE_MULTISIG_REQUEST:
return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
case UPDATE_MULTISIG_RESPONSE:
return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion);
case MAKER_READY_TO_FUND_MULTISIG_REQUEST:
return MakerReadyToFundMultisigRequest.fromProto(proto.getMakerReadyToFundMultisigRequest(), this, messageVersion);
case MAKER_READY_TO_FUND_MULTISIG_RESPONSE:
return MakerReadyToFundMultisigResponse.fromProto(proto.getMakerReadyToFundMultisigResponse(), this, messageVersion);
case INPUTS_FOR_DEPOSIT_TX_REQUEST:
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
@ -265,6 +281,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
}
}
@Override
public NetworkPayload fromProto(protobuf.StorageEntryWrapper proto) {
if (proto != null) {
switch (proto.getMessageCase()) {
@ -282,6 +299,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
}
}
@Override
public NetworkPayload fromProto(protobuf.StoragePayload proto) {
if (proto != null) {
switch (proto.getMessageCase()) {

View File

@ -34,6 +34,7 @@ import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore;
import bisq.core.dao.state.model.governance.BallotList;
import bisq.core.dao.state.storage.DaoStateStore;
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputList;
import bisq.core.offer.SignedOfferList;
import bisq.core.payment.PaymentAccountList;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
@ -85,6 +86,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
if (proto != null) {
switch (proto.getMessageCase()) {
case SIGNED_OFFER_LIST:
return SignedOfferList.fromProto(proto.getSignedOfferList());
case SEQUENCE_NUMBER_MAP:
return SequenceNumberMap.fromProto(proto.getSequenceNumberMap());
case PEER_LIST:

View File

@ -18,6 +18,7 @@
package bisq.core.support.dispute;
import bisq.core.locale.Res;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.SupportType;
import bisq.core.support.messages.ChatMessage;
@ -128,9 +129,6 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
@Nullable
private String delayedPayoutTxId;
// Added for XMR integration
private boolean isOpener;
// Added at v1.4.0
@Setter
@Nullable
@ -144,6 +142,13 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
@Nullable
@Setter
private Map<String, String> extraDataMap;
// Added for XMR integration
private boolean isOpener;
@Nullable
private PaymentAccountPayload makerPaymentAccountPayload;
@Nullable
private PaymentAccountPayload takerPaymentAccountPayload;
// We do not persist uid, it is only used by dispute agents to guarantee an uid.
@Setter
@ -178,6 +183,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
String contractAsJson,
@Nullable String makerContractSignature,
@Nullable String takerContractSignature,
@Nullable PaymentAccountPayload makerPaymentAccountPayload,
@Nullable PaymentAccountPayload takerPaymentAccountPayload,
PubKeyRing agentPubKeyRing,
boolean isSupportTicket,
SupportType supportType) {
@ -199,6 +206,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
this.contractAsJson = contractAsJson;
this.makerContractSignature = makerContractSignature;
this.takerContractSignature = takerContractSignature;
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
this.agentPubKeyRing = agentPubKeyRing;
this.isSupportTicket = isSupportTicket;
this.supportType = supportType;
@ -246,6 +255,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
Optional.ofNullable(disputePayoutTxId).ifPresent(builder::setDisputePayoutTxId);
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage()));
Optional.ofNullable(takerPaymentAccountPayload).ifPresent(e -> builder.setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage()));
Optional.ofNullable(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage()));
Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
@ -274,6 +285,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
proto.getContractAsJson(),
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
proto.hasMakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()) : null,
proto.hasTakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()) : null,
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
proto.getIsSupportTicket(),
SupportType.fromProto(proto.getSupportType()));
@ -448,6 +461,16 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
return Res.get("support.sellerTaker");
}
}
@Nullable
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
return contract.isBuyerMakerAndSellerTaker() ? makerPaymentAccountPayload : takerPaymentAccountPayload;
}
@Nullable
public PaymentAccountPayload getSellerPaymentAccountPayload() {
return contract.isBuyerMakerAndSellerTaker() ? takerPaymentAccountPayload : makerPaymentAccountPayload;
}
@Override
public String toString() {

View File

@ -244,6 +244,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onAllServicesInitialized() {
super.onAllServicesInitialized();
disputeListService.onAllServicesInitialized();
@ -329,7 +330,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
if (isAgent(dispute)) {
// update arbitrator's multisig wallet
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:");
System.out.println(multisigWallet.getTxs());
@ -364,12 +365,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
addMediationResultMessage(dispute);
try {
TradeDataValidation.validatePaymentAccountPayloads(dispute);
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
//TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed?
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
} catch (TradeDataValidation.AddressException |
TradeDataValidation.NodeAddressException e) {
TradeDataValidation.NodeAddressException |
TradeDataValidation.InvalidPaymentAccountPayloadException e) {
log.error(e.toString());
validationExceptions.add(e);
}
@ -581,6 +584,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
disputeFromOpener.getContractAsJson(),
disputeFromOpener.getMakerContractSignature(),
disputeFromOpener.getTakerContractSignature(),
disputeFromOpener.getMakerPaymentAccountPayload(),
disputeFromOpener.getTakerPaymentAccountPayload(),
disputeFromOpener.getAgentPubKeyRing(),
disputeFromOpener.isSupportTicket(),
disputeFromOpener.getSupportType());

View File

@ -24,7 +24,6 @@ import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.DisputeResult;
import bisq.core.trade.Contract;
import bisq.core.user.DontShowAgainLookup;
import bisq.common.crypto.Hash;
@ -90,8 +89,8 @@ public class MultipleHolderNameDetection {
public static PaymentAccountPayload getPaymentAccountPayload(Dispute dispute) {
return isBuyer(dispute) ?
dispute.getContract().getBuyerPaymentAccountPayload() :
dispute.getContract().getSellerPaymentAccountPayload();
dispute.getBuyerPaymentAccountPayload() :
dispute.getSellerPaymentAccountPayload();
}
public static String getAddress(Dispute dispute) {
@ -202,10 +201,9 @@ public class MultipleHolderNameDetection {
Map<String, List<Dispute>> allDisputesByTraderMap = new HashMap<>();
disputeManager.getDisputesAsObservableList().stream()
.filter(dispute -> {
Contract contract = dispute.getContract();
PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ?
contract.getBuyerPaymentAccountPayload() :
contract.getSellerPaymentAccountPayload();
dispute.getBuyerPaymentAccountPayload() :
dispute.getSellerPaymentAccountPayload();
return paymentAccountPayload instanceof PayloadWithHolderName;
})
.forEach(dispute -> {

View File

@ -240,7 +240,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
String errorMessage = null;
boolean success = true;
boolean requestUpdatedPayoutTx = false;
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
Contract contract = dispute.getContract();
try {
// We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals
@ -366,7 +366,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
cleanupRetryMap(uid);
// update multisig wallet
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
// parse payout tx
@ -416,7 +416,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
}
// update arbitrator's multisig wallet with co-signer's multisig hex
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
try {
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
} catch (Exception e) {
@ -473,7 +473,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
// gather trade info
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId);
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
if (!disputeOptional.isPresent()) {
log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
@ -596,8 +596,8 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getBuyerPayoutAmount());
BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getSellerPayoutAmount());
//System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
//System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
@ -657,7 +657,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
// gather trade info
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId);
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
if (!disputeOptional.isPresent()) throw new RuntimeException("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
Dispute dispute = disputeOptional.get();
@ -665,11 +665,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMakerDepositTxId() : trade.getTakerDepositTxId()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids
// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTakerDepositTxId() : trade.getMakerDepositTxId()).getIncomingAmount();
// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMaker().getDepositTxHash() : trade.getTaker().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids
// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTaker().getDepositTxHash() : trade.getMaker().getDepositTxHash()).getIncomingAmount();
// BigInteger tradeAmount = BigInteger.valueOf(contract.getTradeAmount().value).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER);
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getBuyerPayoutAmount());
BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getSellerPayoutAmount());
System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount);
System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount);

View File

@ -248,7 +248,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
// If we have not got yet the peers signature we sign and send to the peer our signature.
// Otherwise we sign and complete with the peers signature the payout tx.
if (processModel.getTradingPeer().getMediatedPayoutTxSignature() == null) {
if (trade.getTradingPeer().getMediatedPayoutTxSignature() == null) {
tradeProtocol.onAcceptMediationResult(() -> {
if (trade.getPayoutTx() != null) {
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.MEDIATION_CLOSED);

View File

@ -20,19 +20,18 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class ArbitratorTrade extends Trade {
public ArbitratorTrade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
NodeAddress makerNodeAddress,
NodeAddress takerNodeAddress,
NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer, tradeAmount, txFee, takerFee, tradePrice, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, xmrWalletService, processModel, uid);
String uid,
NodeAddress makerNodeAddress,
NodeAddress takerNodeAddress,
NodeAddress arbitratorNodeAddress) {
super(offer, tradeAmount, takerFee, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress);
}
@Override
@ -64,15 +63,14 @@ public class ArbitratorTrade extends Trade {
return fromProto(new ArbitratorTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getTradePrice(),
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
xmrWalletService,
processModel,
uid),
uid,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null),
proto,
coreProtoResolver);
}

View File

@ -42,23 +42,25 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
///////////////////////////////////////////////////////////////////////////////////////////
public BuyerAsMakerTrade(Offer offer,
Coin txFee,
Coin tradeAmount,
Coin takeOfferFee,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
long tradePrice,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
NodeAddress makerNodeAddress,
NodeAddress takerNodeAddress,
NodeAddress arbitratorNodeAddress) {
super(offer,
txFee,
tradeAmount,
takeOfferFee,
takerNodeAddress,
makerNodeAddress,
arbitratorNodeAddress,
tradePrice,
xmrWalletService,
processModel,
uid);
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -84,14 +86,15 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
}
BuyerAsMakerTrade trade = new BuyerAsMakerTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.getTradePrice(),
xmrWalletService,
processModel,
uid);
uid,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
trade.setTradePrice(proto.getTradePrice());

View File

@ -43,26 +43,24 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
public BuyerAsTakerTrade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
super(offer,
tradeAmount,
txFee,
takerFee,
tradePrice,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress);
}
@ -90,15 +88,14 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
return fromProto(new BuyerAsTakerTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getTradePrice(),
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
xmrWalletService,
processModel,
uid),
uid,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null),
proto,
coreProtoResolver);
}

View File

@ -35,46 +35,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
public abstract class BuyerTrade extends Trade {
BuyerTrade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
super(offer,
tradeAmount,
txFee,
takerFee,
tradePrice,
takerNodeAddress,
makerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
}
BuyerTrade(Offer offer,
Coin txFee,
Coin takerFee,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takerFee,
uid,
takerNodeAddress,
makerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
arbitratorNodeAddress);
}
@Override

View File

@ -21,13 +21,12 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.util.VolumeUtil;
import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.network.NetworkPayload;
import bisq.common.util.JsonExclude;
@ -56,14 +55,18 @@ public final class Contract implements NetworkPayload {
private final boolean isBuyerMakerAndSellerTaker;
private final String makerAccountId;
private final String takerAccountId;
private final PaymentAccountPayload makerPaymentAccountPayload;
private final PaymentAccountPayload takerPaymentAccountPayload;
private final String makerPaymentMethodId;
private final String takerPaymentMethodId;
private final byte[] makerPaymentAccountPayloadHash;
private final byte[] takerPaymentAccountPayloadHash;
@JsonExclude
private final PubKeyRing makerPubKeyRing;
@JsonExclude
private final PubKeyRing takerPubKeyRing;
private final String makerPayoutAddressString;
private final String takerPayoutAddressString;
private final String makerDepositTxHash;
private final String takerDepositTxHash;
// Added in v1.2.0
private long lockTime;
@ -77,13 +80,17 @@ public final class Contract implements NetworkPayload {
boolean isBuyerMakerAndSellerTaker,
String makerAccountId,
String takerAccountId,
PaymentAccountPayload makerPaymentAccountPayload,
PaymentAccountPayload takerPaymentAccountPayload,
String makerPaymentMethodId,
String takerPaymentMethodId,
byte[] makerPaymentAccountPayloadHash,
byte[] takerPaymentAccountPayloadHash,
PubKeyRing makerPubKeyRing,
PubKeyRing takerPubKeyRing,
String makerPayoutAddressString,
String takerPayoutAddressString,
long lockTime) {
long lockTime,
String makerDepositTxHash,
String takerDepositTxHash) {
this.offerPayload = offerPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
@ -93,16 +100,18 @@ public final class Contract implements NetworkPayload {
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
this.makerAccountId = makerAccountId;
this.takerAccountId = takerAccountId;
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
this.makerPaymentMethodId = makerPaymentMethodId;
this.takerPaymentMethodId = takerPaymentMethodId;
this.makerPaymentAccountPayloadHash = makerPaymentAccountPayloadHash;
this.takerPaymentAccountPayloadHash = takerPaymentAccountPayloadHash;
this.makerPubKeyRing = makerPubKeyRing;
this.takerPubKeyRing = takerPubKeyRing;
this.makerPayoutAddressString = makerPayoutAddressString;
this.takerPayoutAddressString = takerPayoutAddressString;
this.lockTime = lockTime;
this.makerDepositTxHash = makerDepositTxHash;
this.takerDepositTxHash = takerDepositTxHash;
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
// For SEPA offers we accept also SEPA_INSTANT takers
// Otherwise both ids need to be the same
boolean result = (makerPaymentMethodId.equals(PaymentMethod.SEPA_ID) && takerPaymentMethodId.equals(PaymentMethod.SEPA_INSTANT_ID)) ||
@ -127,13 +136,17 @@ public final class Contract implements NetworkPayload {
proto.getIsBuyerMakerAndSellerTaker(),
proto.getMakerAccountId(),
proto.getTakerAccountId(),
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()),
proto.getMakerPaymentMethodId(),
proto.getTakerPaymentMethodId(),
proto.getMakerPaymentAccountPayloadHash().toByteArray(),
proto.getTakerPaymentAccountPayloadHash().toByteArray(),
PubKeyRing.fromProto(proto.getMakerPubKeyRing()),
PubKeyRing.fromProto(proto.getTakerPubKeyRing()),
proto.getMakerPayoutAddressString(),
proto.getTakerPayoutAddressString(),
proto.getLockTime());
proto.getLockTime(),
proto.getMakerDepositTxHash(),
proto.getTakerDepositTxHash());
}
@Override
@ -148,13 +161,17 @@ public final class Contract implements NetworkPayload {
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
.setMakerAccountId(makerAccountId)
.setTakerAccountId(takerAccountId)
.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())
.setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage())
.setMakerPaymentMethodId(makerPaymentMethodId)
.setTakerPaymentMethodId(takerPaymentMethodId)
.setMakerPaymentAccountPayloadHash(ByteString.copyFrom(makerPaymentAccountPayloadHash))
.setTakerPaymentAccountPayloadHash(ByteString.copyFrom(takerPaymentAccountPayloadHash))
.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
.setMakerPayoutAddressString(makerPayoutAddressString)
.setTakerPayoutAddressString(takerPayoutAddressString)
.setLockTime(lockTime)
.setMakerDepositTxHash(makerDepositTxHash)
.setTakerDepositTxHash(takerDepositTxHash)
.build();
}
@ -179,16 +196,16 @@ public final class Contract implements NetworkPayload {
return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing;
}
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload;
public byte[] getBuyerPaymentAccountPayloadHash() {
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayloadHash : takerPaymentAccountPayloadHash;
}
public PaymentAccountPayload getSellerPaymentAccountPayload() {
return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayload : makerPaymentAccountPayload;
public byte[] getSellerPaymentAccountPayloadHash() {
return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayloadHash : makerPaymentAccountPayloadHash;
}
public String getPaymentMethodId() {
return makerPaymentAccountPayload.getPaymentMethodId();
return makerPaymentMethodId;
}
public Coin getTradeAmount() {
@ -270,13 +287,17 @@ public final class Contract implements NetworkPayload {
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n takerAccountId='" + takerAccountId + '\'' +
",\n makerPaymentAccountPayload=" + makerPaymentAccountPayload +
",\n takerPaymentAccountPayload=" + takerPaymentAccountPayload +
",\n makerPaymentMethodId='" + makerPaymentMethodId + '\'' +
",\n takerPaymentMethodId='" + takerPaymentMethodId + '\'' +
",\n makerPaymentAccountPayloadHash=" + makerPaymentAccountPayloadHash +
",\n takerPaymentAccountPayloadHash=" + takerPaymentAccountPayloadHash +
",\n makerPubKeyRing=" + makerPubKeyRing +
",\n takerPubKeyRing=" + takerPubKeyRing +
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +
",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
",\n lockTime=" + lockTime +
",\n makerDepositTxHash='" + makerDepositTxHash + '\'' +
",\n takerDepositTxHash='" + takerDepositTxHash + '\'' +
"\n}";
}
}

View File

@ -42,23 +42,25 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
///////////////////////////////////////////////////////////////////////////////////////////
public SellerAsMakerTrade(Offer offer,
Coin txFee,
Coin tradeAmount,
Coin takerFee,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
long tradePrice,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
super(offer,
txFee,
tradeAmount,
takerFee,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
tradePrice,
xmrWalletService,
processModel,
uid);
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress);
}
@ -85,14 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
}
SellerAsMakerTrade trade = new SellerAsMakerTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.getTradePrice(),
xmrWalletService,
processModel,
uid);
uid,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
trade.setTradePrice(proto.getTradePrice());

View File

@ -43,26 +43,24 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
public SellerAsTakerTrade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
super(offer,
tradeAmount,
txFee,
takerFee,
tradePrice,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress);
}
@ -90,15 +88,14 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
return fromProto(new SellerAsTakerTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTradeAmountAsLong()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getTradePrice(),
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
xmrWalletService,
processModel,
uid),
uid,
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null),
proto,
coreProtoResolver);
}

View File

@ -36,46 +36,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
public abstract class SellerTrade extends Trade {
SellerTrade(Offer offer,
Coin tradeAmount,
Coin txFee,
Coin takerFee,
long tradePrice,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
super(offer,
tradeAmount,
txFee,
takerFee,
tradePrice,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
}
SellerTrade(Offer offer,
Coin txFee,
Coin takeOfferFee,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
super(offer,
txFee,
takeOfferFee,
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
xmrWalletService,
processModel,
uid);
arbitratorNodeAddress);
}
@Override

View File

@ -22,6 +22,7 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload.Direction;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
@ -31,10 +32,11 @@ import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.ProcessModelServiceProvider;
import bisq.core.trade.protocol.TradeMessageListener;
import bisq.core.trade.protocol.TradeListener;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.util.VolumeUtil;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
@ -80,9 +82,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
import monero.common.MoneroError;
import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
/**
* Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are
@ -345,14 +348,6 @@ public abstract class Trade implements Tradable, Model {
@Nullable
@Getter
@Setter
private String takerContractSignature;
@Nullable
@Getter
@Setter
private String makerContractSignature;
@Nullable
@Getter
@Setter
private NodeAddress arbitratorNodeAddress;
@Nullable
@Setter
@ -364,14 +359,6 @@ public abstract class Trade implements Tradable, Model {
@Nullable
@Getter
@Setter
private NodeAddress mediatorNodeAddress;
@Nullable
@Getter
@Setter
private PubKeyRing mediatorPubKeyRing;
@Nullable
@Getter
@Setter
private String takerPaymentAccountId;
@Nullable
private String errorMessage;
@ -460,7 +447,7 @@ public abstract class Trade implements Tradable, Model {
// Added in XMR integration
private transient List<TradeMessageListener> tradeMessageListeners; // notified on fully validated trade messages
private transient List<TradeListener> tradeListeners; // notified on fully validated trade messages
@Getter
@Setter
private NodeAddress makerNodeAddress;
@ -473,18 +460,11 @@ public abstract class Trade implements Tradable, Model {
@Getter
@Setter
private PubKeyRing takerPubKeyRing;
@Nullable
transient MoneroWalletListener depositTxListener;
transient Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked
transient Boolean takerDepositLocked;
transient private MoneroTxWallet makerDepositTx;
@Nullable
transient private MoneroTxWallet takerDepositTx;
@Nullable
@Getter
@Setter
private String makerDepositTxId;
@Nullable
@Getter
@Setter
private String takerDepositTxId;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
@ -492,28 +472,34 @@ public abstract class Trade implements Tradable, Model {
// maker
protected Trade(Offer offer,
Coin txFee,
Coin takerFee,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
Coin tradeAmount,
Coin takerFee, // TODO (woodser): makerFee, takerFee, but not given one during construction
long tradePrice,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
this.offer = offer;
this.txFee = txFee;
this.tradeAmount = tradeAmount;
this.txFee = Coin.valueOf(0); // TODO (woodser): remove this field
this.takerFee = takerFee;
this.makerNodeAddress = makerNodeAddress;
this.takerNodeAddress = takerNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.tradePrice = tradePrice;
this.xmrWalletService = xmrWalletService;
this.processModel = processModel;
this.uid = uid;
txFeeAsLong = txFee.value;
takerFeeAsLong = takerFee.value;
takeOfferDate = new Date().getTime();
tradeMessageListeners = new ArrayList<TradeMessageListener>();
this.txFeeAsLong = txFee.value;
this.takerFeeAsLong = takerFee.value;
this.takeOfferDate = new Date().getTime();
this.tradeListeners = new ArrayList<TradeListener>();
this.makerNodeAddress = makerNodeAddress;
this.takerNodeAddress = takerNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
setTradeAmount(tradeAmount);
}
@ -525,28 +511,28 @@ public abstract class Trade implements Tradable, Model {
Coin txFee,
Coin takerFee,
long tradePrice,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress, // TODO (woodser): remove mediator, refund agent from trade
@Nullable NodeAddress refundAgentNodeAddress,
XmrWalletService xmrWalletService,
ProcessModel processModel,
String uid) {
String uid,
@Nullable NodeAddress makerNodeAddress,
@Nullable NodeAddress takerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress) {
this(offer,
txFee,
tradeAmount,
takerFee,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
tradePrice,
xmrWalletService,
processModel,
uid);
this.tradePrice = tradePrice;
setTradeAmount(tradeAmount);
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress);
}
// TODO: remove these constructors
// arbitrator
@SuppressWarnings("NullableProblems")
protected Trade(Offer offer,
@ -562,16 +548,16 @@ public abstract class Trade implements Tradable, Model {
String uid) {
this(offer,
txFee,
tradeAmount,
takerFee,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress,
tradePrice,
xmrWalletService,
processModel,
uid);
uid,
makerNodeAddress,
takerNodeAddress,
arbitratorNodeAddress);
this.tradePrice = tradePrice;
setTradeAmount(tradeAmount);
}
@ -599,22 +585,16 @@ public abstract class Trade implements Tradable, Model {
.setUid(uid);
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId);
Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId);
Optional.ofNullable(payoutTxId).ifPresent(builder::setPayoutTxId);
Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage()));
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash)));
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
Optional.ofNullable(mediatorNodeAddress).ifPresent(e -> builder.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage()));
Optional.ofNullable(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()));
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage()));
Optional.ofNullable(mediatorPubKeyRing).ifPresent(e -> builder.setMediatorPubKeyRing(mediatorPubKeyRing.toProtoMessage()));
Optional.ofNullable(refundAgentPubKeyRing).ifPresent(e -> builder.setRefundAgentPubKeyRing(refundAgentPubKeyRing.toProtoMessage()));
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
@ -625,7 +605,7 @@ public abstract class Trade implements Tradable, Model {
Optional.ofNullable(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage()));
Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()));
Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.toProtoMessage()));
Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(takerPubKeyRing.toProtoMessage()));
Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage()));
return builder.build();
}
@ -635,22 +615,16 @@ public abstract class Trade implements Tradable, Model {
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState()));
trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
trade.setMakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId()));
trade.setTakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId()));
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()));
trade.setTakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()));
trade.setMakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()));
trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
trade.setMediatorNodeAddress(proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null);
trade.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null);
trade.setMediatorPubKeyRing(proto.hasMediatorPubKeyRing() ? PubKeyRing.fromProto(proto.getMediatorPubKeyRing()) : null);
trade.setRefundAgentPubKeyRing(proto.hasRefundAgentPubKeyRing() ? PubKeyRing.fromProto(proto.getRefundAgentPubKeyRing()) : null);
trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId());
trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState()));
@ -688,12 +662,6 @@ public abstract class Trade implements Tradable, Model {
arbitratorPubKeyRing = arbitrator.getPubKeyRing();
});
serviceProvider.getMediatorManager().getDisputeAgentByNodeAddress(mediatorNodeAddress)
.ifPresent(mediator -> mediatorPubKeyRing = mediator.getPubKeyRing());
serviceProvider.getRefundAgentManager().getDisputeAgentByNodeAddress(refundAgentNodeAddress)
.ifPresent(refundAgent -> refundAgentPubKeyRing = refundAgent.getPubKeyRing());
isInitialized = true;
}
@ -732,42 +700,86 @@ public abstract class Trade implements Tradable, Model {
void updateDepositTxFromWallet() {
if (getMakerDepositTx() != null && getTakerDepositTx() != null) {
System.out.println(processModel.getProvider().getXmrWalletService());
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId());
applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId()));
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(getId());
applyDepositTxs(multisigWallet.getTx(getMakerDepositTx().getHash()), multisigWallet.getTx(getTakerDepositTx().getHash()));
}
}
public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
this.makerDepositTx = makerDepositTx;
this.takerDepositTx = takerDepositTx;
makerDepositTxId = makerDepositTx.getHash();
takerDepositTxId = takerDepositTx.getHash();
//setupConfirmationListener(); // TODO (woodser): listening disabled here, using SetupDepositTxsListener in buyer and seller
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
}
}
public void setupDepositTxsListener() {
// ignore if already listening
if (depositTxListener != null) {
log.warn("Trade {} already listening for deposit txs", getId());
return;
}
// create listener for deposit transactions
MoneroWallet multisigWallet = processModel.getXmrWalletService().getMultisigWallet(getId());
depositTxListener = processModel.getXmrWalletService().new HavenoWalletListener(new MoneroWalletListener() { // TODO (woodser): separate into own class file
@Override
public void onOutputReceived(MoneroOutputWallet output) {
// ignore if no longer listening
if (depositTxListener == null) return;
// TODO (woodser): remove this
if (output.getTx().isConfirmed() && (processModel.getMaker().getDepositTxHash().equals(output.getTx().getHash()) || processModel.getTaker().getDepositTxHash().equals(output.getTx().getHash()))) {
System.out.println("Deposit output for tx " + output.getTx().getHash() + " is confirmed at height " + output.getTx().getHeight());
}
// update locked state
if (output.getTx().getHash().equals(processModel.getMaker().getDepositTxHash())) makerDepositLocked = output.getTx().isLocked();
else if (output.getTx().getHash().equals(processModel.getTaker().getDepositTxHash())) takerDepositLocked = output.getTx().isLocked();
// deposit txs seen when both locked states seen
if (makerDepositLocked != null && takerDepositLocked != null) {
setState(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK);
}
// confirm trade and update ui when both deposits unlock
if (Boolean.FALSE.equals(makerDepositLocked) && Boolean.FALSE.equals(takerDepositLocked)) {
System.out.println("Multisig deposit txs unlocked!");
applyDepositTxs(multisigWallet.getTx(processModel.getMaker().getDepositTxHash()), multisigWallet.getTx(processModel.getTaker().getDepositTxHash()));
multisigWallet.removeListener(depositTxListener); // remove listener when notified
depositTxListener = null; // prevent re-applying trade state in subsequent requests
}
}
});
// register wallet listener
multisigWallet.addListener(depositTxListener);
}
@Nullable
public MoneroTxWallet getTakerDepositTx() {
try {
if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null;
return takerDepositTx;
} catch (MoneroError e) {
log.error("Wallet is missing taker deposit tx " + takerDepositTxId);
return null;
}
String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
try {
if (takerDepositTx == null) takerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash);
return takerDepositTx;
} catch (MoneroError e) {
log.error("Wallet is missing taker deposit tx " + depositTxHash);
return null;
}
}
@Nullable
public MoneroTxWallet getMakerDepositTx() {
try {
if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null;
return makerDepositTx;
} catch (MoneroError e) {
log.error("Wallet is missing maker deposit tx " + makerDepositTxId);
return null;
}
String depositTxHash = getProcessModel().getMaker().getDepositTxHash();
try {
if (makerDepositTx == null) makerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash);
return makerDepositTx;
} catch (MoneroError e) {
log.error("Wallet is missing maker deposit tx " + depositTxHash);
return null;
}
}
public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
@ -845,20 +857,27 @@ public abstract class Trade implements Tradable, Model {
// Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addTradeMessageListener(TradeMessageListener listener) {
tradeMessageListeners.add(listener);
public void addListener(TradeListener listener) {
tradeListeners.add(listener);
}
public void removeTradeMessageListener(TradeMessageListener listener) {
if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
public void removeListener(TradeListener listener) {
if (!tradeListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
}
// notified from TradeProtocol of verified messages
// notified from TradeProtocol of verified trade messages
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) {
for (TradeMessageListener listener : new ArrayList<TradeMessageListener>(tradeMessageListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
for (TradeListener listener : new ArrayList<TradeListener>(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
listener.onVerifiedTradeMessage(message, sender);
}
}
// notified from TradeProtocol of ack messages
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
for (TradeListener listener : new ArrayList<TradeListener>(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
listener.onAckMessage(ackMessage, sender);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
@ -879,7 +898,7 @@ public abstract class Trade implements Tradable, Model {
log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state);
}
if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) {
String message = "We got a state change to a previous phase.\n" +
String message = "We got a state change to a previous phase (id=" + getShortId() + ").\n" +
"Old state is: " + this.state + ". New state is: " + state;
log.warn(message);
}
@ -935,6 +954,56 @@ public abstract class Trade implements Tradable, Model {
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
///////////////////////////////////////////////////////////////////////////////////////////
public TradingPeer getSelf() {
if (this instanceof MakerTrade) return processModel.getMaker();
if (this instanceof TakerTrade) return processModel.getTaker();
if (this instanceof ArbitratorTrade) return processModel.getArbitrator();
throw new RuntimeException("Trade is not maker, taker, or arbitrator");
}
public TradingPeer getMaker() {
return processModel.getMaker();
}
public TradingPeer getTaker() {
return processModel.getTaker();
}
public TradingPeer getBuyer() {
return offer.getDirection() == Direction.BUY ? processModel.getMaker() : processModel.getTaker();
}
public TradingPeer getSeller() {
return offer.getDirection() == Direction.BUY ? processModel.getTaker() : processModel.getMaker();
}
/**
* Get the taker if maker, maker if taker, null if arbitrator.
*
* @return the trade peer
*/
public TradingPeer getTradingPeer() {
if (this instanceof MakerTrade) return processModel.getTaker();
else if (this instanceof TakerTrade) return processModel.getMaker();
else if (this instanceof ArbitratorTrade) return null;
else throw new RuntimeException("Unknown trade type: " + getClass().getName());
}
/**
* Get the peer with the given address which can be self.
*
* TODO (woodser): this naming convention is confusing
*
* @param address is the address of the peer to get
* @return the trade peer
*/
public TradingPeer getTradingPeer(NodeAddress address) {
if (address.equals(getMakerNodeAddress())) return processModel.getMaker();
if (address.equals(getTakerNodeAddress())) return processModel.getTaker();
if (address.equals(getArbitratorNodeAddress())) return processModel.getArbitrator();
throw new RuntimeException("No protocol participant has node address: " + address);
}
public Date getTakeOfferDate() {
return new Date(takeOfferDate);
@ -985,7 +1054,7 @@ public abstract class Trade implements Tradable, Model {
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
final long tradeTime = getTakeOfferDate().getTime();
long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight());
MoneroDaemon daemonRpc = new MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123"); // TODO (woodser): move to common config
MoneroDaemon daemonRpc = xmrWalletService.getDaemon();
long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
// if (depositTx.getConfidence().getDepthInBlocks() > 0) {
@ -1158,8 +1227,8 @@ public abstract class Trade implements Tradable, Model {
public boolean isTxChainInvalid() {
return offer.getOfferFeePaymentTxId() == null ||
getTakerFeeTxId() == null ||
getMakerDepositTxId() == null ||
getTakerDepositTxId() == null ||
processModel.getMaker().getDepositTxHash() == null ||
processModel.getMaker().getDepositTxHash() == null ||
getDelayedPayoutTxBytes() == null;
}
@ -1241,7 +1310,6 @@ public abstract class Trade implements Tradable, Model {
",\n takeOfferDate=" + takeOfferDate +
",\n processModel=" + processModel +
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
",\n takerDepositTxId='" + takerDepositTxId + '\'' +
",\n payoutTxId='" + payoutTxId + '\'' +
",\n tradeAmountAsLong=" + tradeAmountAsLong +
",\n tradePrice=" + tradePrice +
@ -1251,11 +1319,7 @@ public abstract class Trade implements Tradable, Model {
",\n contract=" + contract +
",\n contractAsJson='" + contractAsJson + '\'' +
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
",\n takerContractSignature='" + takerContractSignature + '\'' +
",\n makerContractSignature='" + makerContractSignature + '\'' +
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
",\n errorMessage='" + errorMessage + '\'' +
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +

View File

@ -36,7 +36,7 @@ import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -54,6 +54,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class TradeDataValidation {
public static void validatePaymentAccountPayloads(Dispute dispute) throws InvalidPaymentAccountPayloadException {
if (!Arrays.equals(dispute.getMakerPaymentAccountPayload().getHash(), dispute.getContract().getMakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of maker's payment account payload does not match contract");
if (!Arrays.equals(dispute.getTakerPaymentAccountPayload().getHash(), dispute.getContract().getTakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of taker's payment account payload does not match contract");
}
public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade)
throws AddressException {
@ -396,6 +401,12 @@ public class TradeDataValidation {
this.dispute = dispute;
}
}
public static class InvalidPaymentAccountPayloadException extends ValidationException {
InvalidPaymentAccountPayloadException(@Nullable Dispute dispute, String msg) {
super(dispute, msg);
}
}
public static class AddressException extends ValidationException {
AddressException(@Nullable Dispute dispute, String msg) {

View File

@ -17,7 +17,6 @@
package bisq.core.trade;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.XmrWalletService;
@ -25,21 +24,27 @@ import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.SignedOffer;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.UpdateMultisigRequest;
import bisq.core.trade.protocol.ArbitratorProtocol;
import bisq.core.trade.protocol.MakerProtocol;
@ -48,11 +53,12 @@ import bisq.core.trade.protocol.ProcessModelServiceProvider;
import bisq.core.trade.protocol.TakerProtocol;
import bisq.core.trade.protocol.TradeProtocol;
import bisq.core.trade.protocol.TradeProtocolFactory;
import bisq.core.trade.protocol.TraderProtocol;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.core.util.Validator;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.DecryptedDirectMessageListener;
import bisq.network.p2p.DecryptedMessageWithPubKey;
@ -70,15 +76,11 @@ import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.proto.persistable.PersistedDataHost;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.util.concurrent.FutureCallback;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -106,8 +108,6 @@ import org.slf4j.LoggerFactory;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@ -132,6 +132,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
private final P2PService p2PService;
private final PriceFeedService priceFeedService;
private final TradeStatisticsManager tradeStatisticsManager;
private final OfferUtil offerUtil;
private final TradeUtil tradeUtil;
@Getter
private final ArbitratorManager arbitratorManager;
@ -171,6 +172,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
P2PService p2PService,
PriceFeedService priceFeedService,
TradeStatisticsManager tradeStatisticsManager,
OfferUtil offerUtil,
TradeUtil tradeUtil,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
@ -191,6 +193,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
this.p2PService = p2PService;
this.priceFeedService = priceFeedService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.offerUtil = offerUtil;
this.tradeUtil = tradeUtil;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
@ -238,15 +241,19 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
//handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests
} else if (networkEnvelope instanceof InitTradeRequest) {
handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof InitMultisigMessage) {
handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer);
} else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) {
handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) {
handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer);
} else if (networkEnvelope instanceof DepositTxMessage) {
handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer);
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
} else if (networkEnvelope instanceof InitMultisigRequest) {
handleInitMultisigRequest((InitMultisigRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof SignContractRequest) {
handleSignContractRequest((SignContractRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof SignContractResponse) {
handleSignContractResponse((SignContractResponse) networkEnvelope, peer);
} else if (networkEnvelope instanceof DepositRequest) {
handleDepositRequest((DepositRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof DepositResponse) {
handleDepositResponse((DepositResponse) networkEnvelope, peer);
} else if (networkEnvelope instanceof PaymentAccountPayloadRequest) {
handlePaymentAccountPayloadRequest((PaymentAccountPayloadRequest) networkEnvelope, peer);
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
}
}
@ -327,65 +334,96 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
persistenceManager.requestPersistence();
}
private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) {
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid());
private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender) {
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", sender, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(initTradeRequest.getTradeId());
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString());
log.warn("Invalid InitTradeRequest message " + request.toString());
return;
}
System.out.println("RECEIVED INIT REQUEST INFO");
System.out.println("Sender peer node address: " + initTradeRequest.getSenderNodeAddress());
System.out.println("Maker node address: " + initTradeRequest.getMakerNodeAddress());
System.out.println("Taker node adddress: " + initTradeRequest.getTakerNodeAddress());
System.out.println("Arbitrator node address: " + initTradeRequest.getArbitratorNodeAddress());
// handle request as arbitrator
boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
boolean isArbitrator = request.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
if (isArbitrator) {
// verify this node is registered arbitrator
Mediator thisArbitrator = user.getRegisteredMediator();
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because we are not an arbitrator", sender, request.getTradeId());
return;
}
// get offer associated with trade
Offer offer = null;
for (Offer anOffer : offerBookService.getOffers()) {
if (anOffer.getId().equals(initTradeRequest.getTradeId())) {
offer = anOffer;
}
if (anOffer.getId().equals(request.getTradeId())) {
offer = anOffer;
}
}
if (offer == null) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because no offer is on the books", sender, request.getTradeId());
return;
}
// verify arbitrator is payload signer unless they are offline
// TODO (woodser): handle if payload signer differs from current arbitrator (verify signer is offline)
// verify maker is offer owner
// TODO (woodser): maker address might change if they disconnect and reconnect, should allow maker address to differ if pubKeyRing is same ?
if (!offer.getOwnerNodeAddress().equals(request.getMakerNodeAddress())) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because maker is not offer owner", sender, request.getTradeId());
return;
}
if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling
Trade trade;
Optional<Trade> tradeOptional = getTradeById(offer.getId());
if (!tradeOptional.isPresent()) {
trade = new ArbitratorTrade(offer,
Coin.valueOf(initTradeRequest.getTradeAmount()),
Coin.valueOf(initTradeRequest.getTxFee()),
Coin.valueOf(initTradeRequest.getTradeFee()),
initTradeRequest.getTradePrice(),
initTradeRequest.getMakerNodeAddress(),
initTradeRequest.getTakerNodeAddress(),
initTradeRequest.getArbitratorNodeAddress(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
initTradeAndProtocol(trade, getTradeProtocol(trade));
tradableList.add(trade);
if (tradeOptional.isPresent()) {
trade = tradeOptional.get();
// verify request is from maker
if (!sender.equals(request.getMakerNodeAddress())) {
log.warn("Trade is already taken"); // TODO (woodser): need to respond with bad ack
return;
}
} else {
trade = tradeOptional.get();
// verify request is from taker
if (!sender.equals(request.getTakerNodeAddress())) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from taker when trade is not initialized", sender, request.getTradeId());
return;
}
// compute expected taker fee
Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(true), Coin.valueOf(offer.getOfferPayload().getAmount()));
Coin takerFee = CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee(true));
// create arbitrator trade
trade = new ArbitratorTrade(offer,
Coin.valueOf(offer.getOfferPayload().getAmount()),
takerFee,
offer.getOfferPayload().getPrice(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString(),
request.getMakerNodeAddress(),
request.getTakerNodeAddress(),
request.getArbitratorNodeAddress());
// set reserve tx hash
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId());
if (!signedOfferOptional.isPresent()) return;
SignedOffer signedOffer = signedOfferOptional.get();
trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash());
initTradeAndProtocol(trade, getTradeProtocol(trade));
tradableList.add(trade);
}
// TODO (woodser): do this for arbitrator?
// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
// if (prev != null) {
// log.error("We had already an entry with uid {}", trade.getUid());
// }
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler?
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? // TODO (woodser): ensure failed trade removed
});
requestPersistence();
@ -394,7 +432,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// handle request as maker
else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId());
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(request.getTradeId());
if (!openOfferOptional.isPresent()) {
return;
}
@ -406,102 +444,181 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
Offer offer = openOffer.getOffer();
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator?
// verify request is from signing arbitrator when they're online, else from selected arbitrator
if (!sender.equals(offer.getOfferPayload().getArbitratorNodeAddress())) {
boolean isSignerOnline = true; // TODO (woodser): determine if signer is online and test
if (isSignerOnline) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signing arbitrator when online", sender, request.getTradeId());
return;
} else if (!sender.equals(openOffer.getArbitratorNodeAddress())) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from selected arbitrator when signing arbitrator is offline", sender, request.getTradeId());
return;
}
}
Trade trade;
if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer,
Coin.valueOf(initTradeRequest.getTxFee()),
Coin.valueOf(initTradeRequest.getTradeFee()),
initTradeRequest.getMakerNodeAddress(),
initTradeRequest.getTakerNodeAddress(),
initTradeRequest.getArbitratorNodeAddress(),
Coin.valueOf(offer.getOfferPayload().getAmount()),
Coin.valueOf(offer.getOfferPayload().getMakerFee()), // TODO (woodser): this is maker fee, but Trade calls it taker fee, why not have both?
offer.getOfferPayload().getPrice(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
UUID.randomUUID().toString(),
request.getMakerNodeAddress(),
request.getTakerNodeAddress(),
request.getArbitratorNodeAddress());
else
trade = new SellerAsMakerTrade(offer,
Coin.valueOf(initTradeRequest.getTxFee()),
Coin.valueOf(initTradeRequest.getTradeFee()),
initTradeRequest.getMakerNodeAddress(),
initTradeRequest.getTakerNodeAddress(),
openOffer.getArbitratorNodeAddress(),
Coin.valueOf(offer.getOfferPayload().getAmount()),
Coin.valueOf(offer.getOfferPayload().getMakerFee()),
offer.getOfferPayload().getPrice(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
if (prev != null) {
log.error("We had already an entry with uid {}", trade.getUid());
}
UUID.randomUUID().toString(),
request.getMakerNodeAddress(),
request.getTakerNodeAddress(),
request.getArbitratorNodeAddress());
//System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender);
//trade.setTradingPeerNodeAddress(sender);
// TODO (woodser): what if maker's address changes while offer open, or taker's address changes after multisig deposit available? need to verify and update
trade.setArbitratorPubKeyRing(user.getAcceptedMediatorByAddress(sender).getPubKeyRing());
trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing());
initTradeAndProtocol(trade, getTradeProtocol(trade));
trade.getProcessModel().setReserveTxHash(offer.getOfferFeePaymentTxId()); // TODO (woodser): initialize in initTradeAndProtocol ?
trade.getProcessModel().setFrozenKeyImages(openOffer.getFrozenKeyImages());
tradableList.add(trade);
initTradeAndProtocol(trade, tradeProtocol);
((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
requestPersistence();
}
}
private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) {
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer) {
log.info("Received InitMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + request.toString());
log.warn("Invalid InitMultisigRequest " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
getTradeProtocol(trade).handleInitMultisigRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
}
private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) {
log.info("Received SignContractRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) {
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid SignContractRequest message " + request.toString());
return;
}
try {
Validator.nonEmptyStringOf(response.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + response.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleSignContractRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
}
private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) {
log.info("Received SignContractResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) {
log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid SignContractResponse message " + request.toString());
return;
}
try {
Validator.nonEmptyStringOf(multisigMessage.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((TraderProtocol) getTradeProtocol(trade)).handleSignContractResponse(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
}
private void handleDepositRequest(DepositRequest request, NodeAddress peer) {
log.info("Received DepositRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
Optional<Trade> tradeOptional = getTradeById(multisigMessage.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid DepositRequest message " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((ArbitratorProtocol) getTradeProtocol(trade)).handleDepositRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
}
private void handleDepositResponse(DepositResponse response, NodeAddress peer) {
log.info("Received DepositResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
try {
Validator.nonEmptyStringOf(response.getTradeId());
} catch (Throwable t) {
log.warn("Invalid DepositResponse message " + response.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
}
private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) {
log.info("Received PaymentAccountPayloadRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid PaymentAccountPayloadRequest message " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}
});
}
private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) {
@ -523,41 +640,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
});
}
private void handleDepositTxMessage(DepositTxMessage request, NodeAddress peer) {
log.info("Received DepositTxMessage from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
try {
Validator.nonEmptyStringOf(request.getTradeId());
} catch (Throwable t) {
log.warn("Invalid InitTradeRequest message " + request.toString());
return;
}
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleDepositTxMessage(request, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
public void checkOfferAvailability(Offer offer,
boolean isTakerApiUser,
String paymentAccountId,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler);
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId), resultHandler, errorMessageHandler);
}
// First we check if offer is still available then we create the trade with the protocol
public void onTakeOffer(Coin amount,
Coin txFee,
Coin takerFee,
long tradePrice,
Coin fundsNeededForTrade,
Offer offer,
String paymentAccountId,
@ -567,8 +665,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
ErrorMessageHandler errorMessageHandler) {
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser);
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId);
offer.checkOfferAvailability(model,
() -> {
if (offer.getState() == Offer.State.AVAILABLE) {
@ -576,30 +674,33 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
if (offer.isBuyOffer()) {
trade = new SellerAsTakerTrade(offer,
amount,
txFee,
takerFee,
tradePrice,
model.getPeerNodeAddress(),
P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address?
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
model.getTradeRequest().getTradePrice(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
UUID.randomUUID().toString(),
model.getPeerNodeAddress(),
P2PService.getMyNodeAddress(),
offer.getOfferPayload().getArbitratorNodeAddress());
} else {
trade = new BuyerAsTakerTrade(offer,
amount,
txFee,
takerFee,
tradePrice,
model.getPeerNodeAddress(),
P2PService.getMyNodeAddress(),
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
model.getTradeRequest().getTradePrice(),
xmrWalletService,
getNewProcessModel(offer),
UUID.randomUUID().toString());
UUID.randomUUID().toString(),
model.getPeerNodeAddress(),
P2PService.getMyNodeAddress(),
offer.getOfferPayload().getArbitratorNodeAddress());
}
trade.getProcessModel().setTradeMessage(model.getTradeRequest());
trade.getProcessModel().setMakerSignature(model.getMakerSignature());
trade.getProcessModel().setArbitratorNodeAddress(model.getArbitratorNodeAddress());
trade.getProcessModel().setUseSavingsWallet(useSavingsWallet);
trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value);
trade.setTakerPubKeyRing(model.getPubKeyRing());
trade.setTakerPaymentAccountId(paymentAccountId);
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
@ -627,15 +728,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
processModelServiceProvider.getKeyRing().getPubKeyRing());
}
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) {
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser, String paymentAccountId) {
return new OfferAvailabilityModel(
offer,
keyRing.getPubKeyRing(),
xmrWalletService,
p2PService,
user,
mediatorManager,
tradeStatisticsManager,
isTakerApiUser);
isTakerApiUser,
paymentAccountId,
offerUtil);
}
@ -643,6 +747,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// Complete trade
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): remove this function
public void onWithdrawRequest(String toAddress,
Coin amount,
Coin fee,
@ -651,35 +756,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
@Nullable String memo,
ResultHandler resultHandler,
FaultHandler faultHandler) {
int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(),
XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex();
FutureCallback<MoneroTxWallet> callback = new FutureCallback<MoneroTxWallet>() {
@Override
public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) {
if (transaction != null) {
log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash());
onTradeCompleted(trade);
trade.setState(Trade.State.WITHDRAW_COMPLETED);
getTradeProtocol(trade).onWithdrawCompleted();
requestPersistence();
resultHandler.handleResult();
}
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
}
};
try {
xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback);
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
e.printStackTrace();
log.error(e.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
}
throw new RuntimeException("Withdraw trade funds after payout to Haveno wallet not supported");
}
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades

View File

@ -17,14 +17,254 @@
package bisq.core.trade;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferPayload.Direction;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.messages.InitTradeRequest;
import common.utils.JsonUtils;
import static com.google.common.base.Preconditions.checkNotNull;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
import java.math.BigInteger;
import java.util.Objects;
import monero.daemon.MoneroDaemon;
import monero.daemon.model.MoneroSubmitTxResult;
import monero.daemon.model.MoneroTx;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroCheckTx;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet;
/**
* Collection of utilities for trading.
*
* TODO (woodser): combine with TradeUtil.java ?
*/
public class TradeUtils {
/**
* Address to collect Haveno trade fees. TODO (woodser): move to config constants
*/
public static String FEE_ADDRESS = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM";
/**
* Check if the arbitrator signature for an offer is valid.
*
* @param arbitrator is the possible original arbitrator
* @param signedOfferPayload is a signed offer payload
* @return true if the arbitrator's signature is valid for the offer
*/
public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Mediator arbitrator) {
// remove arbitrator signature from signed payload
String signature = signedOfferPayload.getArbitratorSignature();
signedOfferPayload.setArbitratorSignature(null);
// get unsigned offer payload as json string
String unsignedOfferAsJson = Utilities.objectToJson(signedOfferPayload);
// verify arbitrator signature
boolean isValid = true;
try {
isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), // TODO (woodser): assign isValid
unsignedOfferAsJson,
signature);
} catch (Exception e) {
isValid = false;
}
// replace signature
signedOfferPayload.setArbitratorSignature(signature);
// return result
return isValid;
}
/**
* Check if the maker signature for a trade request is valid.
*
* @param request is the trade request to check
* @return true if the maker's signature is valid for the trade request
*/
public static boolean isMakerSignatureValid(InitTradeRequest request, String signature, PubKeyRing makerPubKeyRing) {
// re-create trade request with signed fields
InitTradeRequest signedRequest = new InitTradeRequest(
request.getTradeId(),
request.getSenderNodeAddress(),
request.getPubKeyRing(),
request.getTradeAmount(),
request.getTradePrice(),
request.getTradeFee(),
request.getAccountId(),
request.getPaymentAccountId(),
request.getPaymentMethodId(),
request.getUid(),
request.getMessageVersion(),
request.getAccountAgeWitnessSignatureOfOfferId(),
request.getCurrentDate(),
request.getMakerNodeAddress(),
request.getTakerNodeAddress(),
null,
null,
null,
null,
request.getPayoutAddress(),
null
);
// get trade request as string
String tradeRequestAsJson = Utilities.objectToJson(signedRequest);
// verify maker signature
try {
return Sig.verify(makerPubKeyRing.getSignaturePubKey(),
tradeRequestAsJson,
signature);
} catch (Exception e) {
return false;
}
}
/**
* Create a transaction to reserve a trade. The deposit amount is returned
* to the sender's payout address. Additional funds are reserved to allow
* fluctuations in the mining fee.
*
* @param xmrWalletService
* @param offerId
* @param tradeFee
* @param depositAmount
* @return a transaction to reserve a trade
*/
public static MoneroTxWallet createReserveTx(XmrWalletService xmrWalletService, String offerId, BigInteger tradeFee, String returnAddress, BigInteger depositAmount) {
// get expected mining fee
MoneroWallet wallet = xmrWalletService.getWallet();
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
.addDestination(returnAddress, depositAmount));
BigInteger miningFee = miningFeeTx.getFee();
// create reserve tx
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
return reserveTx;
}
/**
* Create a transaction to deposit funds to the multisig wallet.
*
* @param xmrWalletService
* @param tradeFee
* @param destinationAddress
* @param depositAddress
* @return MoneroTxWallet
*/
public static MoneroTxWallet createDepositTx(XmrWalletService xmrWalletService, BigInteger tradeFee, String depositAddress, BigInteger depositAmount) {
return xmrWalletService.getWallet().createTx(new MoneroTxConfig()
.setAccountIndex(0)
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
.addDestination(depositAddress, depositAmount));
}
/**
* Process a reserve or deposit transaction used during trading.
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
* The transaction is submitted but not relayed to the pool.
*
* @param daemon is the Monero daemon to check for double spends
* @param wallet is the Monero wallet to verify the tx
* @param depositAddress is the expected destination address for the deposit amount
* @param depositAmount is the expected amount deposited to multisig
* @param tradeFee is the expected fee for trading
* @param txHash is the transaction hash
* @param txHex is the transaction hex
* @param txKey is the transaction key
* @param isReserveTx indicates if the tx is a reserve tx, which requires fee padding
*/
public static void processTradeTx(MoneroDaemon daemon, MoneroWallet wallet, String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, boolean isReserveTx) {
// get tx from daemon
MoneroTx tx = daemon.getTx(txHash);
// if tx is not submitted, submit but do not relay
if (tx == null) {
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
} else if (tx.isRelayed()) {
throw new RuntimeException("Reserve tx must not be relayed");
}
// verify trade fee
String feeAddress = TradeUtils.FEE_ADDRESS;
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
// verify mining fee
BigInteger feeEstimate = daemon.getFeeEstimate().multiply(BigInteger.valueOf(txHex.length())); // TODO (woodser): fee estimates are too high, use more accurate estimate
BigInteger feeThreshold = feeEstimate.multiply(BigInteger.valueOf(1l)).divide(BigInteger.valueOf(2l)); // must be at least 50% of estimated fee
tx = daemon.getTx(txHash);
if (tx.getFee().compareTo(feeThreshold) < 0) {
throw new RuntimeException("Mining fee is not enough, needed " + feeThreshold + " but was " + tx.getFee());
}
// verify deposit amount
check = wallet.checkTxKey(txHash, txKey, depositAddress);
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
BigInteger depositThreshold = depositAmount;
if (isReserveTx) depositThreshold = depositThreshold.add(feeThreshold.multiply(BigInteger.valueOf(3l))); // prove reserve of at least deposit amount + (3 * min mining fee)
if (check.getReceivedAmount().compareTo(depositThreshold) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositThreshold + " but was " + check.getReceivedAmount());
}
/**
* Create a contract from a trade.
*
* TODO (woodser): refactor/reduce trade, process model, and trading peer models
*
* @param trade is the trade to create the contract from
* @return the contract
*/
public static Contract createContract(Trade trade) {
boolean isBuyerMakerAndSellerTaker = trade.getOffer().getDirection() == Direction.BUY;
Contract contract = new Contract(
trade.getOffer().getOfferPayload(),
checkNotNull(trade.getTradeAmount()).value,
trade.getTradePrice().getValue(),
isBuyerMakerAndSellerTaker ? trade.getMakerNodeAddress() : trade.getTakerNodeAddress(), // buyer node address // TODO (woodser): use maker and taker node address instead of buyer and seller node address for consistency
isBuyerMakerAndSellerTaker ? trade.getTakerNodeAddress() : trade.getMakerNodeAddress(), // seller node address
trade.getArbitratorNodeAddress(),
isBuyerMakerAndSellerTaker,
trade instanceof MakerTrade ? trade.getProcessModel().getAccountId() : trade.getMaker().getAccountId(), // maker account id
trade instanceof TakerTrade ? trade.getProcessModel().getAccountId() : trade.getTaker().getAccountId(), // taker account id
checkNotNull(trade instanceof MakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getPaymentMethodId() : trade.getOffer().getOfferPayload().getPaymentMethodId()), // maker payment method id
checkNotNull(trade instanceof TakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getPaymentMethodId() : trade.getTaker().getPaymentMethodId()), // taker payment method id
trade instanceof MakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getHash() : trade.getMaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
trade instanceof TakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getHash() : trade.getTaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
trade.getMakerPubKeyRing(),
trade.getTakerPubKeyRing(),
trade instanceof MakerTrade ? trade.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : trade.getMaker().getPayoutAddressString(), // maker payout address
trade instanceof TakerTrade ? trade.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : trade.getTaker().getPayoutAddressString(), // taker payout address
trade.getLockTime(),
trade.getMaker().getDepositTxHash(),
trade.getTaker().getDepositTxHash()
);
return contract;
}
// TODO (woodser): remove the following utitilites?
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
static Tuple2<String, String> getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService,

View File

@ -123,7 +123,7 @@ public class CleanupMailboxMessages {
private boolean isPubKeyValid(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
// We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer
// Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already.
PubKeyRing peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing();
PubKeyRing peersPubKeyRing = trade.getTradingPeer().getPubKeyRing();
boolean isValid = true;
if (peersPubKeyRing != null &&
!decryptedMessageWithPubKey.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) {

View File

@ -0,0 +1,103 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class DepositRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
private final String contractSignature;
private final String depositTxHex;
private final String depositTxKey;
public DepositRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
String contractSignature,
String depositTxHex,
String depositTxKey) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.contractSignature = contractSignature;
this.depositTxHex = depositTxHex;
this.depositTxKey = depositTxKey;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.DepositRequest.Builder builder = protobuf.DepositRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid)
.setContractSignature(contractSignature)
.setDepositTxHex(depositTxHex)
.setDepositTxKey(depositTxKey);
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setDepositRequest(builder).build();
}
public static DepositRequest fromProto(protobuf.DepositRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new DepositRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
proto.getContractSignature(),
proto.getDepositTxHex(),
proto.getDepositTxKey());
}
@Override
public String toString() {
return "DepositRequest {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n contractSignature=" + contractSignature +
",\n depositTxHex='" + depositTxHex +
",\n depositTxKey='" + depositTxKey +
"\n} " + super.toString();
}
}

View File

@ -21,7 +21,7 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing;
import lombok.EqualsAndHashCode;
@ -29,18 +29,21 @@ import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class MakerReadyToFundMultisigRequest extends TradeMessage implements DirectMessage {
public final class DepositResponse extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
public MakerReadyToFundMultisigRequest(String tradeId,
public DepositResponse(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion) {
int messageVersion,
long currentDate) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
}
@ -50,30 +53,33 @@ public final class MakerReadyToFundMultisigRequest extends TradeMessage implemen
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.MakerReadyToFundMultisigRequest.Builder builder = protobuf.MakerReadyToFundMultisigRequest.newBuilder()
protobuf.DepositResponse.Builder builder = protobuf.DepositResponse.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid);
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigRequest(builder).build();
return getNetworkEnvelopeBuilder().setDepositResponse(builder).build();
}
public static MakerReadyToFundMultisigRequest fromProto(protobuf.MakerReadyToFundMultisigRequest proto,
public static DepositResponse fromProto(protobuf.DepositResponse proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new MakerReadyToFundMultisigRequest(proto.getTradeId(),
return new DepositResponse(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion);
messageVersion,
proto.getCurrentDate());
}
@Override
public String toString() {
return "MakerReadyToFundMultisigRequest{" +
return "DepositResponse {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
"\n} " + super.toString();
}
}

View File

@ -34,7 +34,7 @@ import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class InitMultisigMessage extends TradeMessage implements DirectMessage {
public final class InitMultisigRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
@ -43,7 +43,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
@Nullable
private final String madeMultisigHex;
public InitMultisigMessage(String tradeId,
public InitMultisigRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
@ -66,7 +66,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.InitMultisigMessage.Builder builder = protobuf.InitMultisigMessage.newBuilder()
protobuf.InitMultisigRequest.Builder builder = protobuf.InitMultisigRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
@ -77,13 +77,13 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setInitMultisigMessage(builder).build();
return getNetworkEnvelopeBuilder().setInitMultisigRequest(builder).build();
}
public static InitMultisigMessage fromProto(protobuf.InitMultisigMessage proto,
public static InitMultisigRequest fromProto(protobuf.InitMultisigRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new InitMultisigMessage(proto.getTradeId(),
return new InitMultisigRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
@ -95,7 +95,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
@Override
public String toString() {
return "MultisigMessage {" +
return "InitMultisigRequest {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +

View File

@ -17,7 +17,6 @@
package bisq.core.trade.messages;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
@ -42,59 +41,73 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
private final NodeAddress senderNodeAddress;
private final long tradeAmount;
private final long tradePrice;
private final long txFee;
private final long tradeFee;
private final String payoutAddressString;
private final PaymentAccountPayload paymentAccountPayload;
private final PubKeyRing pubKeyRing;
private final String accountId;
@Nullable
private final String tradeFeeTxId;
private final NodeAddress arbitratorNodeAddress;
private final String paymentAccountId;
private final String paymentMethodId;
private final PubKeyRing pubKeyRing;
// added in v 0.6. can be null if we trade with an older peer
@Nullable
private final byte[] accountAgeWitnessSignatureOfOfferId;
private final long currentDate;
// added for XMR integration
private final NodeAddress takerNodeAddress;
// XMR integration
private final NodeAddress makerNodeAddress;
private final NodeAddress takerNodeAddress;
@Nullable
private final NodeAddress arbitratorNodeAddress;
@Nullable
private final String reserveTxHash;
@Nullable
private final String reserveTxHex;
@Nullable
private final String reserveTxKey;
@Nullable
private final String payoutAddress;
@Nullable
private final String makerSignature;
public InitTradeRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
long tradeAmount,
long tradePrice,
long txFee,
long tradeFee,
String payoutAddressString,
PaymentAccountPayload paymentAccountPayload,
String accountId,
String tradeFeeTxId,
String paymentAccountId,
String paymentMethodId,
String uid,
int messageVersion,
@Nullable byte[] accountAgeWitnessSignatureOfOfferId,
long currentDate,
NodeAddress takerNodeAddress,
NodeAddress makerNodeAddress,
NodeAddress arbitratorNodeAddress) {
NodeAddress takerNodeAddress,
NodeAddress arbitratorNodeAddress,
@Nullable String reserveTxHash,
@Nullable String reserveTxHex,
@Nullable String reserveTxKey,
@Nullable String payoutAddress,
@Nullable String makerSignature) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.paymentAccountPayload = paymentAccountPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
this.txFee = txFee;
this.tradeFee = tradeFee;
this.payoutAddressString = payoutAddressString;
this.accountId = accountId;
this.tradeFeeTxId = tradeFeeTxId;
this.paymentAccountId = paymentAccountId;
this.paymentMethodId = paymentMethodId;
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
this.currentDate = currentDate;
this.takerNodeAddress = takerNodeAddress;
this.makerNodeAddress = makerNodeAddress;
this.takerNodeAddress = takerNodeAddress;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex;
this.reserveTxKey = reserveTxKey;
this.payoutAddress = payoutAddress;
this.makerSignature = makerSignature;
}
@ -109,19 +122,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setTakerNodeAddress(takerNodeAddress.toProtoMessage())
.setMakerNodeAddress(makerNodeAddress.toProtoMessage())
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
.setTradeAmount(tradeAmount)
.setTradePrice(tradePrice)
.setTxFee(txFee)
.setTradeFee(tradeFee)
.setPayoutAddressString(payoutAddressString)
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage())
.setPaymentAccountId(paymentAccountId)
.setPaymentMethodId(paymentMethodId)
.setAccountId(accountId)
.setUid(uid);
Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId));
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
Optional.ofNullable(payoutAddress).ifPresent(e -> builder.setPayoutAddress(payoutAddress));
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build();
@ -135,19 +151,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getTradeAmount(),
proto.getTradePrice(),
proto.getTxFee(),
proto.getTradeFee(),
proto.getPayoutAddressString(),
coreProtoResolver.fromProto(proto.getPaymentAccountPayload()),
proto.getAccountId(),
ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()),
proto.getPaymentAccountId(),
proto.getPaymentMethodId(),
proto.getUid(),
messageVersion,
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
proto.getCurrentDate(),
NodeAddress.fromProto(proto.getTakerNodeAddress()),
NodeAddress.fromProto(proto.getMakerNodeAddress()),
NodeAddress.fromProto(proto.getArbitratorNodeAddress()));
NodeAddress.fromProto(proto.getTakerNodeAddress()),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()),
ProtoUtil.stringOrNullFromProto(proto.getPayoutAddress()),
ProtoUtil.stringOrNullFromProto(proto.getMakerSignature()));
}
@Override
@ -156,16 +175,19 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
"\n senderNodeAddress=" + senderNodeAddress +
",\n tradeAmount=" + tradeAmount +
",\n tradePrice=" + tradePrice +
",\n txFee=" + txFee +
",\n takerFee=" + tradeFee +
",\n payoutAddressString='" + payoutAddressString + '\'' +
",\n tradeFee=" + tradeFee +
",\n pubKeyRing=" + pubKeyRing +
",\n paymentAccountPayload=" + paymentAccountPayload +
",\n paymentAccountPayload='" + accountId + '\'' +
",\n takerFeeTxId='" + tradeFeeTxId + '\'' +
",\n accountId='" + accountId + '\'' +
",\n paymentAccountId=" + paymentAccountId +
",\n paymentMethodId=" + paymentMethodId +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
",\n currentDate=" + currentDate +
",\n reserveTxHash=" + reserveTxHash +
",\n reserveTxHex=" + reserveTxHex +
",\n reserveTxKey=" + reserveTxKey +
",\n payoutAddress=" + payoutAddress +
",\n makerSignature=" + makerSignature +
"\n} " + super.toString();
}
}

View File

@ -1,118 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public class MakerReadyToFundMultisigResponse extends TradeMessage implements DirectMessage {
@Getter
private final boolean isMakerReadyToFundMultisig;
@Getter
@Nullable
private final String makerContractAsJson;
@Getter
@Nullable
private final String makerContractSignature;
@Getter
@Nullable
private final String makerPayoutAddressString;
@Getter
@Nullable
private final PaymentAccountPayload makerPaymentAccountPayload;
@Getter
@Nullable
private final String makerAccountId;
@Getter
private final long currentDate;
public MakerReadyToFundMultisigResponse(String tradeId,
boolean isMakerReadyToFundMultisig,
String uid,
int messageVersion,
String makerContractAsJson,
String makerContractSignature,
String makerPayoutAddressString,
PaymentAccountPayload makerPaymentAccountPayload,
String makerAccountId,
long currentDate) {
super(messageVersion, tradeId, uid);
this.isMakerReadyToFundMultisig = isMakerReadyToFundMultisig;
this.makerContractAsJson = makerContractAsJson;
this.makerContractSignature = makerContractSignature;
this.makerPayoutAddressString = makerPayoutAddressString;
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
this.makerAccountId = makerAccountId;
this.currentDate = currentDate;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.MakerReadyToFundMultisigResponse.Builder builder = protobuf.MakerReadyToFundMultisigResponse.newBuilder()
.setTradeId(tradeId)
.setIsMakerReadyToFundMultisig(isMakerReadyToFundMultisig)
.setCurrentDate(currentDate);
Optional.ofNullable(makerContractAsJson).ifPresent(e -> builder.setMakerContractAsJson(makerContractAsJson));
Optional.ofNullable(makerContractSignature).ifPresent(e -> builder.setMakerContractSignature(makerContractSignature));
Optional.ofNullable(makerPayoutAddressString).ifPresent(e -> builder.setMakerPayoutAddressString(makerPayoutAddressString));
Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage()));
Optional.ofNullable(makerAccountId).ifPresent(e -> builder.setMakerAccountId(makerAccountId));
return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigResponse(builder).build();
}
public static MakerReadyToFundMultisigResponse fromProto(protobuf.MakerReadyToFundMultisigResponse proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new MakerReadyToFundMultisigResponse(proto.getTradeId(),
proto.getIsMakerReadyToFundMultisig(),
proto.getUid(),
messageVersion,
proto.getMakerContractAsJson(),
proto.getMakerContractSignature(),
proto.getMakerPayoutAddressString(),
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
proto.getMakerAccountId(),
proto.getCurrentDate());
}
@Override
public String toString() {
return "MakerReadyToFundMultisigResponse{" +
"\n isMakerReadyToFundMultisig=" + isMakerReadyToFundMultisig +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,92 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class PaymentAccountPayloadRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
private final PaymentAccountPayload paymentAccountPayload;
public PaymentAccountPayloadRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
PaymentAccountPayload paymentAccountPayload) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.paymentAccountPayload = paymentAccountPayload;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.PaymentAccountPayloadRequest.Builder builder = protobuf.PaymentAccountPayloadRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid)
.setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage());
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setPaymentAccountPayloadRequest(builder).build();
}
public static PaymentAccountPayloadRequest fromProto(protobuf.PaymentAccountPayloadRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new PaymentAccountPayloadRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
coreProtoResolver.fromProto(proto.getPaymentAccountPayload()));
}
@Override
public String toString() {
return "PaymentAccountPayloadRequest {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n paymentAccountPayload=" + paymentAccountPayload +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,110 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public final class SignContractRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
private final String accountId;
private final byte[] paymentAccountPayloadHash;
private final String payoutAddress;
private final String depositTxHash;
public SignContractRequest(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
String accountId,
byte[] paymentAccountPayloadHash,
String payoutAddress,
String depositTxHash) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.accountId = accountId;
this.paymentAccountPayloadHash = paymentAccountPayloadHash;
this.payoutAddress = payoutAddress;
this.depositTxHash = depositTxHash;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.SignContractRequest.Builder builder = protobuf.SignContractRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid)
.setAccountId(accountId)
.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash))
.setPayoutAddress(payoutAddress)
.setDepositTxHash(depositTxHash);
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setSignContractRequest(builder).build();
}
public static SignContractRequest fromProto(protobuf.SignContractRequest proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new SignContractRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
proto.getAccountId(),
proto.getPaymentAccountPayloadHash().toByteArray(),
proto.getPayoutAddress(),
proto.getDepositTxHash());
}
@Override
public String toString() {
return "SignContractRequest {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n accountId=" + accountId +
",\n paymentAccountPayloadHash='" + paymentAccountPayloadHash +
",\n payoutAddress='" + payoutAddress +
",\n depositTxHash='" + depositTxHash +
"\n} " + super.toString();
}
}

View File

@ -0,0 +1,98 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.messages;
import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true)
@Value
public final class SignContractResponse extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing;
private final long currentDate;
private final String contractSignature;
public SignContractResponse(String tradeId,
NodeAddress senderNodeAddress,
PubKeyRing pubKeyRing,
String uid,
int messageVersion,
long currentDate,
String contractSignature) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate;
this.contractSignature = contractSignature;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
protobuf.SignContractResponse.Builder builder = protobuf.SignContractResponse.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid);
Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(contractSignature));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setSignContractResponse(builder).build();
}
public static SignContractResponse fromProto(protobuf.SignContractResponse proto,
CoreProtoResolver coreProtoResolver,
int messageVersion) {
return new SignContractResponse(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
PubKeyRing.fromProto(proto.getPubKeyRing()),
proto.getUid(),
messageVersion,
proto.getCurrentDate(),
ProtoUtil.stringOrNullFromProto(proto.getContractSignature()));
}
@Override
public String toString() {
return "SignContractResponse {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +
",\n contractSignature='" + contractSignature +
"\n} " + super.toString();
}
}

View File

@ -89,7 +89,7 @@ public final class UpdateMultisigRequest extends TradeMessage implements DirectM
@Override
public String toString() {
return "MultisigMessage {" +
return "UpdateMultisigRequest {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +

View File

@ -89,7 +89,7 @@ public final class UpdateMultisigResponse extends TradeMessage implements Direct
@Override
public String toString() {
return "MultisigMessage {" +
return "UpdateMultisigResponse {" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate +

View File

@ -2,10 +2,19 @@ package bisq.core.trade.protocol;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeRequestToMakerIfFromTaker;
import bisq.core.trade.protocol.tasks.ProcessDepositRequest;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx;
import bisq.core.trade.protocol.tasks.ArbitratorSendsInitMultisigRequestsIfFundsReserved;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
@ -15,73 +24,91 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ArbitratorProtocol extends DisputeProtocol {
private final ArbitratorTrade arbitratorTrade;
public ArbitratorProtocol(ArbitratorTrade trade) {
super(trade);
this.arbitratorTrade = trade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages
///////////////////////////////////////////////////////////////////////////////////////////
// TODO: new implementation for MakerProtocol
// private void handle(InitTradeRequest message, NodeAddress peer) {
// expect(phase(Trade.Phase.INIT)
// .with(message)
// .from(peer))
// .setup(tasks(ProcessInitTradeRequest.class,
// ApplyFilter.class,
// VerifyPeersAccountAgeWitness.class,
// MakerVerifyTakerFeePayment.class,
// MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
// MakerSendsReadyToFundMultisigResponse.class)
// .withTimeout(30))
// .executeTasks();
// }
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
//ApplyFilter.class,
ProcessInitTradeRequest.class))
.executeTasks();
processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set
//processModel.setTempTradingPeerNodeAddress(peer);
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ApplyFilter.class,
ProcessInitTradeRequest.class,
ArbitratorProcessesReserveTx.class,
ArbitratorSendsInitTradeRequestToMakerIfFromTaker.class,
ArbitratorSendsInitMultisigRequestsIfFundsReserved.class))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
throw new RuntimeException("Not implemented");
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("ArbitratorProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("ArbitratorProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
public void handleDepositRequest(DepositRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("ArbitratorProtocol.handleDepositRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessDepositRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
// @Override
// public void handleTakeOfferRequest(InputsForDepositTxRequest message,
// NodeAddress peer,
// ErrorMessageHandler errorMessageHandler) {
// expect(phase(Trade.Phase.INIT)
// .with(message)
// .from(peer))
// .setup(tasks(
// MakerProcessesInputsForDepositTxRequest.class,
// ApplyFilter.class,
// VerifyPeersAccountAgeWitness.class,
// getVerifyPeersFeePaymentClass(),
// MakerSetsLockTime.class,
// MakerCreateAndSignContract.class,
// BuyerAsMakerCreatesAndSignsDepositTx.class,
// BuyerSetupDepositTxListener.class,
// BuyerAsMakerSendsInputsForDepositTxResponse.class).
// using(new TradeTaskRunner(trade,
// () -> handleTaskRunnerSuccess(message),
// errorMessage -> {
// errorMessageHandler.handleErrorMessage(errorMessage);
// handleTaskRunnerFault(message, errorMessage);
// }))
// .withTimeout(30))
// .executeTasks();
// }
///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher

View File

@ -20,27 +20,28 @@ package bisq.core.trade.protocol;
import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.util.Validator;
@ -73,6 +74,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
// Incoming messages Take offer process
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
@ -138,74 +140,130 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
MakerVerifyTakerFeePayment.class,
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
MakerSendsReadyToFundMultisigResponse.class).
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
//MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this
MakerRemovesOpenOffer.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
MakerSendsReadyToFundMultisigResponse.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
// TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
MakerVerifyTakerDepositTx.class,
MakerCreateAndSignContract.class,
MakerCreateAndPublishDepositTx.class,
MakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess(message),
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
})))
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
}

View File

@ -22,15 +22,22 @@ import bisq.core.offer.Offer;
import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
@ -41,43 +48,24 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.FundMultisig;
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
// TODO (woodser): remove unused request handling
@Slf4j
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
private ResultHandler takeOfferListener;
private Timer initDepositTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -87,7 +75,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
super(trade);
Offer offer = checkNotNull(trade.getOffer());
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
// TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase?
}
@ -107,8 +95,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
.from(trade.getTradingPeerNodeAddress()))
.setup(tasks(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
TakerSendInitTradeRequests.class)
TakerReservesTradeFunds.class,
TakerSendsInitTradeRequestToArbitrator.class)
.withTimeout(30))
.executeTasks();
}
@ -125,7 +113,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
.setup(tasks(TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
TakerVerifyAndSignContract.class,
//TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
BuyerAsTakerSignsDepositTx.class,
BuyerSetupDepositTxListener.class,
@ -134,6 +122,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
.executeTasks();
}
@Override
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
@ -196,188 +185,123 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
// TakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
processModel.setTradeMessage(message);
if (message.isMakerReadyToFundMultisig()) {
createAndFundMultisig(message, takeOfferListener);
} else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests
reserveTrade(message, takeOfferListener);
}
}
private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("BuyerAsTakerProtocol.reserveTrade()");
// define wallet listener which initiates multisig deposit when trade fee tx unlocked
// TODO (woodser): this needs run for reserved trades when client is opened
// TODO (woodser): test initiating multisig when maker offline
MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet();
MoneroWalletListener fundMultisigListener = new MoneroWalletListener() {
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
// get updated offer fee tx
MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId());
// check if tx is unlocked
if (Boolean.FALSE.equals(feeTx.isLocked())) {
System.out.println("TRADE FEE TX IS UNLOCKED!!!");
// stop listening to wallet
wallet.removeListener(this);
// periodically request multisig deposit until successful
Runnable requestMultisigDeposit = new Runnable() {
@Override
public void run() {
if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler);
else initDepositTimer.stop();
}
};
UserThread.execute(requestMultisigDeposit);
initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60);
}
}
};
// run pipeline to publish trade fee tx
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerCreateFeeTx.class,
TakerVerifyMakerFeePayment.class,
//TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx
TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade?
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later?
wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerSendReadyToFundMultisigRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.createAndFundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleMultisigMessage()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigMessage.class)
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
System.out.println("handle multisig pipeline completed successfully!");
handleTaskRunnerSuccess(message);
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
fundMultisig(message, takeOfferListener);
}
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
takeOfferListener.handleResult();
})))
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void fundMultisig(InitMultisigMessage message, ResultHandler handler) {
System.out.println("TakerProtocolBase.fundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
FundMultisig.class). // will receive MultisigMessage in response
using(new TradeTaskRunner(trade,
() -> {
System.out.println("MULTISIG WALLET FUNDED!!!!");
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
TakerProcessesMakerDepositTxMessage.class,
TakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
}

View File

@ -24,12 +24,12 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
import bisq.network.p2p.NodeAddress;
@ -57,16 +57,15 @@ public abstract class BuyerProtocol extends DisputeProtocol {
@Override
protected void onInitialized() {
super.onInitialized();
// We get called the constructor with any possible state and phase. As we don't want to log an error for such
// cases we use the alternative 'given' method instead of 'expect'.
given(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
given(phase(Trade.Phase.DEPOSIT_PUBLISHED)
.with(BuyerEvent.STARTUP))
.setup(tasks(BuyerSetupDepositTxListener.class))
.setup(tasks(SetupDepositTxsListener.class))
.executeTasks();
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
.with(BuyerEvent.STARTUP))
.setup(tasks(BuyerSetupPayoutTxListener.class))
.setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup?
.executeTasks();
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
@ -166,10 +165,10 @@ public abstract class BuyerProtocol extends DisputeProtocol {
BuyerProcessPayoutTxPublishedMessage.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(message);
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
})))
.executeTasks();
}

View File

@ -65,7 +65,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
Trade.Phase.FIAT_SENT,
Trade.Phase.FIAT_RECEIVED)
.with(event)
.preCondition(trade.getProcessModel().getTradingPeer().getMediatedPayoutTxSignature() == null,
.preCondition(trade.getTradingPeer().getMediatedPayoutTxSignature() == null,
() -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer."))
.preCondition(trade.getPayoutTx() == null,
() -> errorMessageHandler.handleErrorMessage("Payout tx is already published.")))

View File

@ -98,7 +98,7 @@ public class FluentProtocol {
NodeAddress peer = condition.getPeer();
if (peer != null) {
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer);
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); // TODO (woodser): node has multiple peers (arbitrator and maker or taker), but fluent protocol assumes only one
tradeProtocol.processModel.getTradeManager().requestPersistence();
}
@ -108,7 +108,7 @@ public class FluentProtocol {
tradeProtocol.processModel.getTradeManager().requestPersistence();
}
TradeTaskRunner taskRunner = setup.getTaskRunner(message, condition.getEvent());
TradeTaskRunner taskRunner = setup.getTaskRunner(peer, message, condition.getEvent());
taskRunner.addTasks(setup.getTasks());
taskRunner.run();
return this;
@ -366,12 +366,12 @@ public class FluentProtocol {
return this;
}
public TradeTaskRunner getTaskRunner(@Nullable TradeMessage message, @Nullable Event event) {
public TradeTaskRunner getTaskRunner(NodeAddress sender, @Nullable TradeMessage message, @Nullable Event event) {
if (taskRunner == null) {
if (message != null) {
taskRunner = new TradeTaskRunner(trade,
() -> tradeProtocol.handleTaskRunnerSuccess(message),
errorMessage -> tradeProtocol.handleTaskRunnerFault(message, errorMessage));
() -> tradeProtocol.handleTaskRunnerSuccess(sender, message),
errorMessage -> tradeProtocol.handleTaskRunnerFault(sender, message, errorMessage));
} else if (event != null) {
taskRunner = new TradeTaskRunner(trade,
() -> tradeProtocol.handleTaskRunnerSuccess(event),

View File

@ -19,13 +19,11 @@ package bisq.core.trade.protocol;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface MakerProtocol {
public interface MakerProtocol extends TraderProtocol {
void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
}

View File

@ -22,10 +22,12 @@ import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.network.MessageState;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload.Direction;
import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentAccountPayload;
@ -60,7 +62,7 @@ import org.bitcoinj.core.Transaction;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ -90,14 +92,6 @@ public class ProcessModel implements Model, PersistablePayload {
transient private ProcessModelServiceProvider provider;
transient private TradeManager tradeManager;
transient private Offer offer;
@Setter
transient private Trade trade;
// Transient/Mutable
@Getter
transient private MoneroTxWallet takeOfferFeeTx;
@Setter
transient private TradeMessage tradeMessage;
// Added in v1.2.0
@Setter
@ -114,7 +108,7 @@ public class ProcessModel implements Model, PersistablePayload {
transient private ObjectProperty<MessageState> depositTxMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
@Setter
@Getter
transient private Transaction depositTx;
transient private Transaction depositTx; // TODO (woodser): remove and rename depositTxBtc with depositTx
// Persistable Immutable (private setter only used by PB method)
private TradingPeer maker = new TradingPeer();
@ -153,7 +147,7 @@ public class ProcessModel implements Model, PersistablePayload {
// After successful verified we copy that over to the trade.tradingPeerAddress
@Nullable
@Setter
private NodeAddress tempTradingPeerNodeAddress;
private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely
// Added in v.1.1.6
@Nullable
@ -165,10 +159,33 @@ public class ProcessModel implements Model, PersistablePayload {
private long sellerPayoutAmountFromMediation;
// Added for XMR integration
@Getter
transient private MoneroTxWallet takeOfferFeeTx; // TODO (woodser): remove
@Setter
transient private TradeMessage tradeMessage;
@Getter
@Setter
private String makerSignature;
@Getter
@Setter
private NodeAddress arbitratorNodeAddress;
@Nullable
@Getter
@Setter
private String preparedMultisigHex;
transient private MoneroTxWallet reserveTx;
@Setter
@Getter
private String reserveTxHash;
@Setter
@Getter
private List<String> frozenKeyImages = new ArrayList<>();
@Getter
@Setter
transient private MoneroTxWallet depositTxXmr;
@Nullable
@Getter
@Setter
private String preparedMultisigHex; // TODO (woodser): ProcessModel shares many fields with TradingPeer; switch to trade getMaker(), getTaker(), getArbitrator(), getSelf(), with common TradingPeer object?
@Nullable
@Getter
@Setter
@ -180,19 +197,12 @@ public class ProcessModel implements Model, PersistablePayload {
@Nullable
@Getter
@Setter
private boolean makerReadyToFundMultisig;
private boolean makerReadyToFundMultisig; // TODO (woodser): remove
@Getter
@Setter
private boolean multisigDepositInitiated;
@Nullable
@Setter
private String makerPreparedDepositTxId;
@Nullable
@Setter
private String takerPreparedDepositTxId;
@Nullable
transient private MoneroTxWallet buyerSignedPayoutTx;
transient private MoneroTxWallet buyerSignedPayoutTx; // TODO (woodser): remove
// We want to indicate the user the state of the message delivery of the
@ -239,18 +249,20 @@ public class ProcessModel implements Model, PersistablePayload {
.setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong)
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation)
.addAllFrozenKeyImages(frozenKeyImages);
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage()));
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage()));
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
Optional.ofNullable(makerPreparedDepositTxId).ifPresent(e -> builder.setMakerPreparedDepositTxId(makerPreparedDepositTxId));
Optional.ofNullable(takerPreparedDepositTxId).ifPresent(e -> builder.setTakerPreparedDepositTxId(takerPreparedDepositTxId));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
@ -272,6 +284,8 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
// nullable
processModel.setReserveTxHash(proto.getReserveTxHash());
processModel.setFrozenKeyImages(proto.getFrozenKeyImagesList());
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
@ -282,13 +296,13 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey()));
processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
processModel.setMakerSignature(proto.getMakerSignature());
processModel.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig());
processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated());
processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId());
processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId());
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);
@ -349,22 +363,7 @@ public class ProcessModel implements Model, PersistablePayload {
tradeManager.requestPersistence();
}
}
public void setTradingPeer(TradingPeer peer) {
if (trade == null) throw new RuntimeException("Cannot set trading peer because trade is null");
else if (trade instanceof MakerTrade) taker = peer;
else if (trade instanceof TakerTrade) maker = peer;
else throw new RuntimeException("Must be maker or taker to set trading peer");
}
public TradingPeer getTradingPeer() {
if (trade == null) throw new RuntimeException("Cannot get trading peer because trade is null");
else if (trade instanceof MakerTrade) return taker;
else if (trade instanceof TakerTrade) return maker;
else if (trade instanceof ArbitratorTrade) return null;
else throw new RuntimeException("Unknown trade type: " + trade.getClass().getName());
}
void setDepositTxSentAckMessage(AckMessage ackMessage) {
MessageState messageState = ackMessage.isSuccess() ?
MessageState.ACKNOWLEDGED :
@ -387,6 +386,10 @@ public class ProcessModel implements Model, PersistablePayload {
///////////////////////////////////////////////////////////////////////////////////////////
// Delegates
///////////////////////////////////////////////////////////////////////////////////////////
public XmrWalletService getXmrWalletService() {
return provider.getXmrWalletService();
}
public BtcWalletService getBtcWalletService() {
return provider.getBtcWalletService();

View File

@ -21,21 +21,23 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
@ -43,7 +45,6 @@ import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
@ -127,8 +128,10 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
@ -139,74 +142,130 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
MakerVerifyTakerFeePayment.class,
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
MakerSendsReadyToFundMultisigResponse.class).
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
//MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this
MakerRemovesOpenOffer.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
MakerSendsReadyToFundMultisigResponse.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
processModel.setTempTradingPeerNodeAddress(sender);
// TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
MakerVerifyTakerDepositTx.class,
MakerCreateAndSignContract.class,
MakerCreateAndPublishDepositTx.class,
MakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess(message),
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
})))
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
}

View File

@ -22,56 +22,45 @@ import bisq.core.offer.Offer;
import bisq.core.trade.SellerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.FundMultisig;
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletListener;
// TODO (woodser): remove unused request handling
@Slf4j
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
private ResultHandler takeOfferListener;
private Timer initDepositTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -80,7 +69,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
super(trade);
Offer offer = checkNotNull(trade.getOffer());
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
}
@ -97,8 +86,8 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
.from(trade.getTradingPeerNodeAddress()))
.setup(tasks(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
TakerSendInitTradeRequests.class)
TakerReservesTradeFunds.class,
TakerSendsInitTradeRequestToArbitrator.class)
.withTimeout(30))
.executeTasks();
}
@ -116,7 +105,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
TakerVerifyAndSignContract.class,
//TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
SellerAsTakerSignsDepositTx.class,
SellerCreatesDelayedPayoutTx.class,
@ -176,182 +165,118 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
// TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
processModel.setTradeMessage(message);
if (message.isMakerReadyToFundMultisig()) {
createAndFundMultisig(message, takeOfferListener);
} else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests
reserveTrade(message, takeOfferListener);
}
}
private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("BuyerAsTakerProtocol.reserveTrade()");
// define wallet listener which initiates multisig deposit when trade fee tx unlocked
// TODO (woodser): this needs run for reserved trades when client is opened
// TODO (woodser): test initiating multisig when maker offline
MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet();
MoneroWalletListener fundMultisigListener = new MoneroWalletListener() {
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
// get updated offer fee tx
MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId());
// check if tx is unlocked
if (Boolean.FALSE.equals(feeTx.isLocked())) {
System.out.println("TRADE FEE TX IS UNLOCKED!!!");
// stop listening to wallet
wallet.removeListener(this);
// periodically request multisig deposit until successful
Runnable requestMultisigDeposit = new Runnable() {
@Override
public void run() {
if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler);
else initDepositTimer.stop();
}
};
UserThread.execute(requestMultisigDeposit);
initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60);
}
}
};
// run pipeline to publish trade fee tx
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerCreateFeeTx.class,
TakerVerifyMakerFeePayment.class,
//TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx
TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade?
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later?
wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerSendReadyToFundMultisigRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
System.out.println("TakerProtocolBase.createAndFundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleMultisigMessage()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
.with(message)
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigMessage.class)
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
System.out.println("handle multisig pipeline completed successfully!");
handleTaskRunnerSuccess(message);
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
fundMultisig(message, takeOfferListener);
}
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
takeOfferListener.handleResult();
})))
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
private void fundMultisig(InitMultisigMessage message, ResultHandler handler) {
System.out.println("TakerProtocolBase.fundMultisig()");
expect(new FluentProtocol.Condition(trade))
.setup(tasks(
FundMultisig.class). // will receive MultisigMessage in response
using(new TradeTaskRunner(trade,
() -> {
System.out.println("MULTISIG WALLET FUNDED!!!!");
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(message)
.from(sender))
.setup(tasks(
TakerProcessesMakerDepositTxMessage.class,
TakerSetupDepositTxsListener.class).
using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
}

View File

@ -21,7 +21,9 @@ import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
@ -44,6 +46,16 @@ public abstract class SellerProtocol extends DisputeProtocol {
public SellerProtocol(SellerTrade trade) {
super(trade);
}
@Override
protected void onInitialized() {
super.onInitialized();
given(phase(Trade.Phase.DEPOSIT_PUBLISHED)
.with(BuyerEvent.STARTUP))
.setup(tasks(SetupDepositTxsListener.class))
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -78,7 +90,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
log.warn("We received a CounterCurrencyTransferStartedMessage but we have already created the payout tx " +
"so we ignore the message. This can happen if the ACK message to the peer did not " +
"arrive and the peer repeats sending us the message. We send another ACK msg.");
sendAckMessage(message, true, null);
sendAckMessage(peer, message, true, null);
removeMailboxMessageAfterProcessing(message);
}))
.setup(tasks(

View File

@ -17,20 +17,11 @@
package bisq.core.trade.protocol;
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface TakerProtocol {
public interface TakerProtocol extends TraderProtocol {
void onTakeOffer();
enum TakerEvent implements FluentProtocol.Event {
TAKE_OFFER
}
// TODO (woodser): update after rebase
//åvoid takeAvailableOffer(ResultHandler handler);
void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
}

View File

@ -1,12 +1,13 @@
package bisq.core.trade.protocol;
import bisq.core.trade.messages.TradeMessage;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress;
/**
* Receives notifications of decrypted, verified trade messages.
* Receives notifications of decrypted, verified trade and ack messages.
*/
public class TradeMessageListener {
public class TradeListener {
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { }
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { }
}

View File

@ -22,11 +22,10 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.messages.UpdateMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
import bisq.core.util.Validator;
@ -70,7 +69,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
public TradeProtocol(Trade trade) {
this.trade = trade;
this.processModel = trade.getProcessModel();
this.processModel.setTrade(trade); // TODO (woodser): added to explicitly set trade circular loop, keep?
}
@ -128,12 +126,14 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
if (networkEnvelope instanceof TradeMessage) {
onTradeMessage((TradeMessage) networkEnvelope, peer);
// notify trade listeners
// TODO (woodser): better way to register message notifications for trade?
if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) {
trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer);
}
} else if (networkEnvelope instanceof AckMessage) {
onAckMessage((AckMessage) networkEnvelope, peer);
trade.onAckMessage((AckMessage) networkEnvelope, peer); // notify trade listeners
}
}
@ -205,28 +205,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
///////////////////////////////////////////////////////////////////////////////////////////
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message, "handleMultisigMessage");
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
});
taskRunner.addTasks(
ProcessInitMultisigMessage.class
);
startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode
taskRunner.run();
}
public abstract void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
// TODO (woodser): update to use fluent for consistency
public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
@ -236,11 +216,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(message, "handleUpdateMultisigRequest");
handleTaskRunnerSuccess(peer, message, "handleUpdateMultisigRequest");
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(message, errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
});
taskRunner.addTasks(
ProcessUpdateMultisigRequest.class
@ -262,6 +242,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
if (!result.isValid()) {
log.warn(result.getInfo());
handleTaskRunnerFault(null,
null,
result.name(),
result.getInfo());
}
@ -292,9 +273,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// ACK msg
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): support notifications of ack messages
private void onAckMessage(AckMessage ackMessage, NodeAddress peer) {
// We handle the ack for CounterCurrencyTransferStartedMessage and DepositTxAndDelayedPayoutTxMessage
// as we support automatic re-send of the msg in case it was not ACKed after a certain time
// TODO (woodser): add AckMessage for InitTradeRequest and support automatic re-send ?
if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) {
processModel.setPaymentStartedAckMessage(ackMessage);
} else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) {
@ -310,15 +293,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
}
protected void sendAckMessage(TradeMessage message, boolean result, @Nullable String errorMessage) {
protected void sendAckMessage(NodeAddress peer, TradeMessage message, boolean result, @Nullable String errorMessage) {
// TODO (woodser): remove trade.getTradingPeerNodeAddress() and processModel.getTempTradingPeerNodeAddress() if everything should be maker, taker, or arbitrator
// If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet.
// We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case.
NodeAddress peer = trade.getTradingPeerNodeAddress() != null ?
trade.getTradingPeerNodeAddress() :
processModel.getTempTradingPeerNodeAddress();
// get destination pub key ring
// get peer's pub key ring
PubKeyRing peersPubKeyRing = getPeersPubKeyRing(peer);
if (peersPubKeyRing == null) {
log.error("We cannot send the ACK message as peersPubKeyRing is null");
@ -392,20 +371,20 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// Task runner
///////////////////////////////////////////////////////////////////////////////////////////
protected void handleTaskRunnerSuccess(TradeMessage message) {
handleTaskRunnerSuccess(message, message.getClass().getSimpleName());
protected void handleTaskRunnerSuccess(NodeAddress sender, TradeMessage message) {
handleTaskRunnerSuccess(sender, message, message.getClass().getSimpleName());
}
protected void handleTaskRunnerSuccess(FluentProtocol.Event event) {
handleTaskRunnerSuccess(null, event.name());
handleTaskRunnerSuccess(null, null, event.name());
}
protected void handleTaskRunnerFault(TradeMessage message, String errorMessage) {
handleTaskRunnerFault(message, message.getClass().getSimpleName(), errorMessage);
protected void handleTaskRunnerFault(NodeAddress sender, TradeMessage message, String errorMessage) {
handleTaskRunnerFault(sender, message, message.getClass().getSimpleName(), errorMessage);
}
protected void handleTaskRunnerFault(FluentProtocol.Event event, String errorMessage) {
handleTaskRunnerFault(null, event.name(), errorMessage);
handleTaskRunnerFault(null, null, event.name(), errorMessage);
}
@ -446,10 +425,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void handleTaskRunnerSuccess(@Nullable TradeMessage message, String source) {
private void handleTaskRunnerSuccess(NodeAddress sender, @Nullable TradeMessage message, String source) {
log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId());
if (message != null) {
sendAckMessage(message, true, null);
sendAckMessage(sender, message, true, null);
// Once a taskRunner is completed we remove the mailbox message. To not remove it directly at the task
// adds some resilience in case of minor errors, so after a restart the mailbox message can be applied
@ -458,11 +437,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
}
void handleTaskRunnerFault(@Nullable TradeMessage message, String source, String errorMessage) {
void handleTaskRunnerFault(NodeAddress ackReceiver, @Nullable TradeMessage message, String source, String errorMessage) {
log.error("Task runner failed with error {}. Triggered from {}", errorMessage, source);
if (message != null) {
sendAckMessage(message, false, errorMessage);
sendAckMessage(ackReceiver, message, false, errorMessage);
}
cleanup();
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface TraderProtocol {
public void handleSignContractResponse(SignContractResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
public void handleDepositResponse(DepositResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
}

View File

@ -58,6 +58,12 @@ public final class TradingPeer implements PersistablePayload {
@Nullable
private String accountId;
@Nullable
private String paymentAccountId;
@Nullable
private String paymentMethodId;
@Nullable
private byte[] paymentAccountPayloadHash;
@Nullable
private PaymentAccountPayload paymentAccountPayload;
@Nullable
private String payoutAddressString;
@ -90,10 +96,24 @@ public final class TradingPeer implements PersistablePayload {
// Added for XMR integration
@Nullable
private String reserveTxHash;
@Nullable
private String reserveTxHex;
@Nullable
private String reserveTxKey;
@Nullable
private String preparedMultisigHex;
@Nullable
private String madeMultisigHex;
@Nullable
private String signedPayoutTxHex;
@Nullable
private String depositTxHash;
@Nullable
private String depositTxHex;
@Nullable
private String depositTxKey;
public TradingPeer() {
}
@ -102,6 +122,9 @@ public final class TradingPeer implements PersistablePayload {
final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder()
.setChangeOutputValue(changeOutputValue);
Optional.ofNullable(accountId).ifPresent(builder::setAccountId);
Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId);
Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId);
Optional.ofNullable(paymentAccountPayloadHash).ifPresent(e -> builder.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash)));
Optional.ofNullable(paymentAccountPayload).ifPresent(e -> builder.setPaymentAccountPayload((protobuf.PaymentAccountPayload) e.toProtoMessage()));
Optional.ofNullable(payoutAddressString).ifPresent(builder::setPayoutAddressString);
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
@ -115,9 +138,15 @@ public final class TradingPeer implements PersistablePayload {
Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e)));
Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e)));
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
builder.setCurrentDate(currentDate);
return builder.build();
@ -130,6 +159,9 @@ public final class TradingPeer implements PersistablePayload {
TradingPeer tradingPeer = new TradingPeer();
tradingPeer.setChangeOutputValue(proto.getChangeOutputValue());
tradingPeer.setAccountId(ProtoUtil.stringOrNullFromProto(proto.getAccountId()));
tradingPeer.setPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getPaymentAccountId()));
tradingPeer.setPaymentMethodId(ProtoUtil.stringOrNullFromProto(proto.getPaymentMethodId()));
tradingPeer.setPaymentAccountPayloadHash(proto.getPaymentAccountPayloadHash().toByteArray());
tradingPeer.setPaymentAccountPayload(proto.hasPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getPaymentAccountPayload()) : null);
tradingPeer.setPayoutAddressString(ProtoUtil.stringOrNullFromProto(proto.getPayoutAddressString()));
tradingPeer.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
@ -148,9 +180,15 @@ public final class TradingPeer implements PersistablePayload {
tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature()));
tradingPeer.setCurrentDate(proto.getCurrentDate());
tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()));
tradingPeer.setReserveTxHex(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()));
tradingPeer.setReserveTxKey(ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()));
tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()));
tradingPeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
tradingPeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
tradingPeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
return tradingPeer;
}
}

View File

@ -19,8 +19,9 @@ package bisq.core.trade.protocol.tasks;
import bisq.core.filter.FilterManager;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.taskrunner.TaskRunner;
@ -43,9 +44,7 @@ public class ApplyFilter extends TradeTask {
runInterceptHook();
NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress());
@Nullable
PaymentAccountPayload paymentAccountPayload = processModel.getTradingPeer().getPaymentAccountPayload();
FilterManager filterManager = processModel.getFilterManager();
if (filterManager.isNodeAddressBanned(nodeAddress)) {
failed("Other trader is banned by their node address.\n" +
@ -59,9 +58,6 @@ public class ApplyFilter extends TradeTask {
} else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) {
failed("Payment method is banned.\n" +
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
} else if (paymentAccountPayload != null && filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload)) {
failed("Other trader is banned by their trading account data.\n" +
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails());
} else if (filterManager.requireUpdateToNewVersionForTrading()) {
failed("Your version of Bisq is not compatible for trading anymore. " +
"Please update to the latest Bisq version at https://bisq.network/downloads.");

View File

@ -0,0 +1,82 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.util.ParsingUtils;
import java.math.BigInteger;
import lombok.extern.slf4j.Slf4j;
/**
* Arbitrator verifies reserve tx from maker or taker.
*
* The maker reserve tx is only verified here if this arbitrator is not
* the original offer signer and thus does not have the original reserve tx.
*/
@Slf4j
public class ArbitratorProcessesReserveTx extends TradeTask {
@SuppressWarnings({"unused"})
public ArbitratorProcessesReserveTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
Offer offer = trade.getOffer();
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTakerNodeAddress());
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferPayload.Direction.SELL : offer.getDirection() == OfferPayload.Direction.BUY;
// TODO (woodser): if signer online, should never be called by maker
// process reserve tx with expected terms
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(isFromTaker ? trade.getTakerFee() : offer.getMakerFee());
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
TradeUtils.processTradeTx(
processModel.getXmrWalletService().getDaemon(),
processModel.getXmrWalletService().getWallet(),
request.getPayoutAddress(),
depositAmount,
tradeFee,
request.getReserveTxHash(),
request.getReserveTxHex(),
request.getReserveTxKey(),
true);
// save reserve tx to model
TradingPeer trader = isFromTaker ? processModel.getTaker() : processModel.getMaker();
trader.setReserveTxHash(request.getReserveTxHash());
trader.setReserveTxHex(request.getReserveTxHex());
trader.setReserveTxKey(request.getReserveTxKey());
// persist trade
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -15,34 +15,35 @@
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks.taker;
package bisq.core.trade.protocol.tasks;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.SendDirectMessageListener;
import static com.google.common.base.Preconditions.checkNotNull;
import bisq.common.app.Version;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.network.p2p.SendDirectMessageListener;
import com.google.common.base.Charsets;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
/**
* Arbitrator sends InitMultisigRequest to maker and taker if both reserve txs received.
*/
@Slf4j
public class TakerSendInitMultisigMessages extends TradeTask {
public class ArbitratorSendsInitMultisigRequestsIfFundsReserved extends TradeTask {
private boolean takerAck;
private boolean makerAck;
private boolean arbitratorAck;
@SuppressWarnings({"unused"})
public TakerSendInitMultisigMessages(TaskRunner taskHandler, Trade trade) {
public ArbitratorSendsInitMultisigRequestsIfFundsReserved(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -50,18 +51,23 @@ public class TakerSendInitMultisigMessages extends TradeTask {
protected void run() {
try {
runInterceptHook();
// skip if arbitrator does not have maker reserve tx
if (processModel.getMaker().getReserveTxHash() == null) {
log.info("Arbitrator does not have maker reserve tx for offerId {}, waiting to receive before initializing multisig wallet", processModel.getOffer().getId());
complete();
return;
}
// create wallet for multisig
// TODO (woodser): assert that wallet does not already exist
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId());
// prepare multisig
String preparedHex = multisigWallet.prepareMultisig();
processModel.setPreparedMultisigHex(preparedHex);
System.out.println("Prepared multisig hex: " + preparedHex);
// create message to initialize trade
InitMultisigMessage message = new InitMultisigMessage(
// create message to initialize multisig
InitMultisigRequest request = new InitMultisigRequest(
processModel.getOffer().getId(),
processModel.getMyNodeAddress(),
processModel.getPubKeyRing(),
@ -71,55 +77,55 @@ public class TakerSendInitMultisigMessages extends TradeTask {
preparedHex,
null);
// send request to arbitrator
log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing());
processModel.getP2PService().sendEncryptedDirectMessage(
trade.getArbitratorNodeAddress(),
trade.getArbitratorPubKeyRing(),
message,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
arbitratorAck = true;
checkComplete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed();
}
}
);
// send request to maker
log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getMakerNodeAddress());
log.info("Send {} with offerId {} and uid {} to maker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress());
processModel.getP2PService().sendEncryptedDirectMessage(
trade.getMakerNodeAddress(),
trade.getMakerPubKeyRing(),
message,
request,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
log.info("{} arrived at arbitrator: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid());
makerAck = true;
checkComplete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
}
);
// send request to taker
log.info("Send {} with offerId {} and uid {} to taker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getTakerNodeAddress());
processModel.getP2PService().sendEncryptedDirectMessage(
trade.getTakerNodeAddress(),
trade.getTakerPubKeyRing(),
request,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid());
takerAck = true;
checkComplete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getTakerNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
}
);
} catch (Throwable t) {
failed(t);
failed(t);
}
}
private void checkComplete() {
if (makerAck && arbitratorAck) complete();
if (makerAck && takerAck) complete();
}
}

View File

@ -0,0 +1,125 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradeListener;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener;
import com.google.common.base.Charsets;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
/**
* Arbitrator sends InitTradeRequest to maker after receiving InitTradeRequest
* from taker and verifying taker reserve tx.
*/
@Slf4j
public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask {
@SuppressWarnings({"unused"})
public ArbitratorSendsInitTradeRequestToMakerIfFromTaker(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// collect fields for request
String offerId = processModel.getOffer().getId();
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
// arbitrator signs offer id as nonce to avoid challenge protocol
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8));
// save pub keys
processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model
trade.setArbitratorPubKeyRing(processModel.getPubKeyRing());
trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing());
trade.setTakerPubKeyRing(request.getPubKeyRing());
// create request to initialize trade with maker
InitTradeRequest makerRequest = new InitTradeRequest(
offerId,
request.getSenderNodeAddress(),
request.getPubKeyRing(),
trade.getTradeAmount().value,
trade.getTradePrice().getValue(),
trade.getTakerFee().getValue(),
request.getAccountId(),
request.getPaymentAccountId(),
request.getPaymentMethodId(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
sig,
new Date().getTime(),
trade.getMakerNodeAddress(),
trade.getTakerNodeAddress(),
trade.getArbitratorNodeAddress(),
null,
null, // do not include taker's reserve tx
null,
null,
null);
// listen for maker to ack InitTradeRequest
TradeListener listener = new TradeListener() {
@Override
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
if (sender.equals(trade.getMakerNodeAddress()) && ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName())) {
trade.removeListener(this);
if (ackMessage.isSuccess()) complete();
else failed("Received unsuccessful ack for InitTradeRequest from maker"); // TODO (woodser): maker should not do this, penalize them by broadcasting reserve tx?
}
}
};
trade.addListener(listener);
// send request to maker
log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing());
processModel.getP2PService().sendEncryptedDirectMessage(
trade.getMakerNodeAddress(), // TODO (woodser): maker's address might be different from original owner address if they disconnect and reconnect, need to validate and update address when requests received
trade.getMakerPubKeyRing(),
makerRequest,
new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at maker: offerId={}; uid={}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage);
trade.removeListener(listener);
failed();
}
}
);
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -0,0 +1,156 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.util.ParsingUtils;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener;
import java.math.BigInteger;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import monero.daemon.MoneroDaemon;
import monero.wallet.MoneroWallet;
@Slf4j
public class ProcessDepositRequest extends TradeTask {
@SuppressWarnings({"unused"})
public ProcessDepositRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// get contract and signature
String contractAsJson = trade.getContractAsJson();
DepositRequest request = (DepositRequest) processModel.getTradeMessage(); // TODO (woodser): verify response
String signature = request.getContractSignature();
// get peer info
// TODO (woodser): make these utilities / refactor model
// TODO (woodser): verify request
PubKeyRing peerPubKeyRing;
TradingPeer peer = trade.getTradingPeer(request.getSenderNodeAddress());
if (peer == processModel.getArbitrator()) peerPubKeyRing = trade.getArbitratorPubKeyRing();
else if (peer == processModel.getMaker()) peerPubKeyRing = trade.getMakerPubKeyRing();
else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing();
else throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
// verify signature
if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid");
// set peer's signature
peer.setContractSignature(signature);
// collect expected values of deposit tx
Offer offer = trade.getOffer();
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTakerNodeAddress());
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferPayload.Direction.SELL : offer.getDirection() == OfferPayload.Direction.BUY;
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId()); // TODO (woodser): only get, do not create
String depositAddress = multisigWallet.getPrimaryAddress();
BigInteger tradeFee;
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
if (trader == processModel.getMaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getOffer().getMakerFee());
else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
else throw new RuntimeException("DepositRequest is not from maker or taker");
// flush reserve tx from pool
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
daemon.flushTxPool(trader.getReserveTxHash());
// process and verify deposit tx which submits to the pool
TradeUtils.processTradeTx(
daemon,
trade.getXmrWalletService().getWallet(),
depositAddress,
depositAmount,
tradeFee,
trader.getDepositTxHash(),
request.getDepositTxHex(),
request.getDepositTxKey(),
false);
// sychronize to send only one response
synchronized(processModel) {
// set deposit info
trader.setDepositTxHex(request.getDepositTxHex());
trader.setDepositTxKey(request.getDepositTxKey());
// relay deposit txs when both available
// TODO (woodser): add small delay so tx has head start against double spend attempts?
if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) {
// relay txs
daemon.relayTxByHash(processModel.getMaker().getDepositTxHash());
daemon.relayTxByHash(processModel.getTaker().getDepositTxHash());
// create deposit response
DepositResponse response = new DepositResponse(
trade.getOffer().getId(),
processModel.getMyNodeAddress(),
processModel.getPubKeyRing(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
new Date().getTime());
// send deposit response to maker and taker
sendDepositResponse(trade.getMakerNodeAddress(), trade.getMakerPubKeyRing(), response);
sendDepositResponse(trade.getTakerNodeAddress(), trade.getTakerPubKeyRing(), response);
}
}
// TODO (woodser): request persistence?
complete();
} catch (Throwable t) {
failed(t);
}
}
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), nodeAddress, trade.getId());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), nodeAddress, trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
failed();
}
});
}
}

View File

@ -0,0 +1,78 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProcessDepositResponse extends TradeTask {
@SuppressWarnings({"unused"})
public ProcessDepositResponse(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// arbitrator has broadcast deposit txs
trade.setState(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); // TODO (woodser): maker and taker?
// set payment account payload
trade.getSelf().setPaymentAccountPayload(processModel.getPaymentAccountPayload(trade));
// create request with payment account payload
PaymentAccountPayloadRequest request = new PaymentAccountPayloadRequest(
trade.getOffer().getId(),
processModel.getMyNodeAddress(),
processModel.getPubKeyRing(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
new Date().getTime(),
trade.getSelf().getPaymentAccountPayload());
// send payment account payload to trading peer
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -21,7 +21,7 @@ import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitMultisigMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.protocol.TradingPeer;
import bisq.network.p2p.NodeAddress;
@ -46,7 +46,7 @@ import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroMultisigInitResult;
@Slf4j
public class ProcessInitMultisigMessage extends TradeTask {
public class ProcessInitMultisigRequest extends TradeTask {
private boolean ack1 = false;
private boolean ack2 = false;
@ -54,7 +54,7 @@ public class ProcessInitMultisigMessage extends TradeTask {
MoneroWallet multisigWallet;
@SuppressWarnings({"unused"})
public ProcessInitMultisigMessage(TaskRunner taskHandler, Trade trade) {
public ProcessInitMultisigRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -63,12 +63,12 @@ public class ProcessInitMultisigMessage extends TradeTask {
try {
runInterceptHook();
log.debug("current trade state " + trade.getState());
InitMultisigMessage message = (InitMultisigMessage) processModel.getTradeMessage();
checkNotNull(message);
checkTradeId(processModel.getOfferId(), message);
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
checkNotNull(request);
checkTradeId(processModel.getOfferId(), request);
System.out.println("PROCESS MULTISIG MESSAGE");
System.out.println(message);
System.out.println(request);
// System.out.println("PROCESS MULTISIG MESSAGE TRADE");
// System.out.println(trade);
@ -81,26 +81,26 @@ public class ProcessInitMultisigMessage extends TradeTask {
// get peer multisig participant
TradingPeer multisigParticipant;
if (message.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker();
else if (message.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker();
else if (message.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator();
if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker();
else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker();
else if (request.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator();
else throw new RuntimeException("Invalid sender to process init trade message: " + trade.getClass().getName());
// reconcile peer's established multisig hex with message
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(message.getPreparedMultisigHex());
else if (!multisigParticipant.getPreparedMultisigHex().equals(message.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + message.getPreparedMultisigHex());
if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(message.getMadeMultisigHex());
else if (!multisigParticipant.getMadeMultisigHex().equals(message.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages");
// get or create multisig wallet // TODO (woodser): ensure multisig wallet is created for first time
multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.getPreparedMultisigHex());
else if (!multisigParticipant.getPreparedMultisigHex().equals(request.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + request.getPreparedMultisigHex());
if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(request.getMadeMultisigHex());
else if (!multisigParticipant.getMadeMultisigHex().equals(request.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages");
// prepare multisig if applicable
boolean updateParticipants = false;
if (processModel.getPreparedMultisigHex() == null) {
System.out.println("Preparing multisig wallet!");
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId());
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
updateParticipants = true;
} else {
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
}
// make multisig if applicable
@ -145,38 +145,38 @@ public class ProcessInitMultisigMessage extends TradeTask {
}
if (peer1Address == null) throw new RuntimeException("Peer1 address is null");
if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring");
if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring is null");
if (peer2Address == null) throw new RuntimeException("Peer2 address is null");
if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring");
if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring null");
// send to peer 1
sendMultisigMessage(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
sendInitMultisigRequest(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer1Address, message.getTradeId(), message.getUid());
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer1Address, request.getTradeId(), request.getUid());
ack1 = true;
if (ack1 && ack2) completeAux();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer1Address, errorMessage);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer1Address, errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
// send to peer 2
sendMultisigMessage(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
sendInitMultisigRequest(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer2Address, message.getTradeId(), message.getUid());
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer2Address, request.getTradeId(), request.getUid());
ack2 = true;
if (ack1 && ack2) completeAux();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer2Address, errorMessage);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer2Address, errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
@ -204,10 +204,10 @@ public class ProcessInitMultisigMessage extends TradeTask {
return peers;
}
private void sendMultisigMessage(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) {
private void sendInitMultisigRequest(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) {
// create multisig message with current multisig hex
InitMultisigMessage message = new InitMultisigMessage(
InitMultisigRequest request = new InitMultisigRequest(
processModel.getOffer().getId(),
processModel.getMyNodeAddress(),
processModel.getPubKeyRing(),
@ -217,8 +217,8 @@ public class ProcessInitMultisigMessage extends TradeTask {
processModel.getPreparedMultisigHex(),
processModel.getMadeMultisigHex());
log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), recipient);
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, message, listener);
log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient);
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener);
}
private void completeAux() {

View File

@ -19,18 +19,15 @@ package bisq.core.trade.protocol.tasks;
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.offer.Offer;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.user.User;
import bisq.network.p2p.NodeAddress;
import bisq.common.taskrunner.TaskRunner;
import org.bitcoinj.core.Coin;
import com.google.common.base.Charsets;
@ -49,82 +46,83 @@ public class ProcessInitTradeRequest extends TradeTask {
super(taskHandler, trade);
}
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
@Override
protected void run() {
try {
runInterceptHook();
log.debug("current trade state " + trade.getState());
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
checkNotNull(request);
checkTradeId(processModel.getOfferId(), request);
System.out.println("PROCESS INIT TRADE REQUEST");
System.out.println(request);
User user = checkNotNull(processModel.getUser(), "User must not be null");
// handle maker trade
// handle request as arbitrator
TradingPeer multisigParticipant;
if (trade instanceof MakerTrade) {
NodeAddress arbitratorNodeAddress = checkNotNull(request.getArbitratorNodeAddress(), "payDepositRequest.getMediatorNodeAddress() must not be null");
Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(arbitratorNodeAddress), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // TODO (woodser): switch to arbitrator?
multisigParticipant = processModel.getTaker();
trade.setTakerNodeAddress(request.getTakerNodeAddress());
trade.setTakerPubKeyRing(request.getPubKeyRing());
trade.setArbitratorNodeAddress(request.getArbitratorNodeAddress());
trade.setArbitratorPubKeyRing(mediator.getPubKeyRing());
if (trade instanceof ArbitratorTrade) {
// handle request from taker
if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) {
multisigParticipant = processModel.getTaker();
if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree");
if (trade.getTakerPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest");
trade.setTakerPubKeyRing(request.getPubKeyRing());
if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
}
// handle request from maker
else if (request.getSenderNodeAddress().equals(request.getMakerNodeAddress())) {
multisigParticipant = processModel.getMaker();
if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling, uninitialize trade for other takers
if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing());
else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
trade.setMakerPubKeyRing(request.getPubKeyRing());
} else {
throw new RuntimeException("Sender is not trade's maker or taker");
}
}
// handle arbitrator trade
else if (trade instanceof ArbitratorTrade) {
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) {
multisigParticipant = processModel.getMaker();
if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling
if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing());
else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
} else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) {
// handle maker trade
else if (trade instanceof MakerTrade) {
multisigParticipant = processModel.getTaker();
if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
if (trade.getTakerPubKeyRing() == null) trade.setTakerPubKeyRing(request.getPubKeyRing());
else if (!trade.getTakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
} else {
throw new RuntimeException("Sender is not trade's maker or taker");
}
} else {
throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName());
trade.setTakerNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring
trade.setTakerPubKeyRing(request.getPubKeyRing());
}
// handle invalid trade type
else {
throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName());
}
multisigParticipant.setPaymentAccountPayload(checkNotNull(request.getPaymentAccountPayload()));
multisigParticipant.setPayoutAddressString(nonEmptyStringOf(request.getPayoutAddressString()));
// set trading peer info
if (multisigParticipant.getPaymentAccountId() == null) multisigParticipant.setPaymentAccountId(request.getPaymentAccountId());
else if (multisigParticipant.getPaymentAccountId() != request.getPaymentAccountId()) throw new RuntimeException("Payment account id is different from previous");
multisigParticipant.setPubKeyRing(checkNotNull(request.getPubKeyRing()));
multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId()));
//trade.setTakerFeeTxId(nonEmptyStringOf(request.getTradeFeeTxId())); // TODO (woodser): no trade fee tx yet if creating multisig first
// Taker has to sign offerId (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed)
multisigParticipant.setPaymentMethodId(nonEmptyStringOf(request.getPaymentMethodId()));
multisigParticipant.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId());
multisigParticipant.setCurrentDate(request.getCurrentDate());
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
// check trade price
try {
long takersTradePrice = request.getTradePrice();
offer.checkTradePriceTolerance(takersTradePrice);
trade.setTradePrice(takersTradePrice);
long tradePrice = request.getTradePrice();
offer.checkTradePriceTolerance(tradePrice);
trade.setTradePrice(tradePrice);
} catch (TradePriceOutOfToleranceException e) {
failed(e.getMessage());
} catch (Throwable e2) {
failed(e2);
}
// check trade amount
checkArgument(request.getTradeAmount() > 0);
trade.setTradeAmount(Coin.valueOf(request.getTradeAmount()));
// persist trade
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {
failed(t);

View File

@ -0,0 +1,89 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import static com.google.common.base.Preconditions.checkNotNull;
import bisq.common.UserThread;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
@Slf4j
public class ProcessPaymentAccountPayloadRequest extends TradeTask {
private Subscription tradeStateSubscription;
@SuppressWarnings({"unused"})
public ProcessPaymentAccountPayloadRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
if (trade.getTradingPeer().getPaymentAccountPayload() != null) throw new RuntimeException("Peer's payment account payload has already been set");
// get peer's payment account payload
PaymentAccountPayloadRequest request = (PaymentAccountPayloadRequest) processModel.getTradeMessage(); // TODO (woodser): verify request
PaymentAccountPayload paymentAccountPayload = request.getPaymentAccountPayload();
// verify hash of payment account payload
byte[] peerPaymentAccountPayloadHash = trade instanceof MakerTrade ? trade.getContract().getTakerPaymentAccountPayloadHash() : trade.getContract().getMakerPaymentAccountPayloadHash();
if (!Arrays.equals(paymentAccountPayload.getHash(), peerPaymentAccountPayloadHash)) throw new RuntimeException("Hash of peer's payment account payload does not match contract");
// set payment account payload
trade.getTradingPeer().setPaymentAccountPayload(paymentAccountPayload);
// subscribe to trade state to notify ui when deposit txs seen in network
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
if (trade.isDepositPublished()) applyPublishedDepositTxs();
});
if (trade.isDepositPublished()) applyPublishedDepositTxs(); // deposit txs might be seen before subcription
// persist and complete
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {
failed(t);
}
}
private void applyPublishedDepositTxs() {
MoneroWallet multisigWallet = processModel.getXmrWalletService().getMultisigWallet(trade.getId());
MoneroTxWallet makerDepositTx = checkNotNull(multisigWallet.getTx(processModel.getMaker().getDepositTxHash()));
MoneroTxWallet takerDepositTx = checkNotNull(multisigWallet.getTx(processModel.getTaker().getDepositTxHash()));
trade.applyDepositTxs(makerDepositTx, takerDepositTx);
UserThread.execute(this::unSubscribe); // remove trade state subscription at callback
}
private void unSubscribe() {
if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe();
}
}

View File

@ -0,0 +1,133 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
import bisq.common.util.Utilities;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.TradingPeer;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProcessSignContractRequest extends TradeTask {
private boolean ack1 = false;
private boolean ack2 = false;
@SuppressWarnings({"unused"})
public ProcessSignContractRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// extract fields from request
// TODO (woodser): verify request and from maker or taker
SignContractRequest request = (SignContractRequest) processModel.getTradeMessage();
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
trader.setDepositTxHash(request.getDepositTxHash());
trader.setAccountId(request.getAccountId());
trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash());
trader.setPayoutAddressString(request.getPayoutAddress());
// return contract signature when ready
// TODO (woodser): synchronize contract creation; both requests received at the same time
// TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade
if (processModel.getMaker().getDepositTxHash() != null && processModel.getTaker().getDepositTxHash() != null) { // TODO (woodser): synchronize on process model before setting hash so response only sent once
// create and sign contract
Contract contract = TradeUtils.createContract(trade);
String contractAsJson = Utilities.objectToJson(contract);
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);
// save contract and signature
trade.setContract(contract);
trade.setContractAsJson(contractAsJson);
trade.getSelf().setContractSignature(signature);
// create response with contract signature
SignContractResponse response = new SignContractResponse(
trade.getOffer().getId(),
processModel.getMyNodeAddress(),
processModel.getPubKeyRing(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
new Date().getTime(),
signature);
// get response recipients. only arbitrator sends response to both peers
NodeAddress recipient1 = trade instanceof ArbitratorTrade ? trade.getMakerNodeAddress() : trade.getTradingPeerNodeAddress();
PubKeyRing recipient1PubKey = trade instanceof ArbitratorTrade ? trade.getMakerPubKeyRing() : trade.getTradingPeerPubKeyRing();
NodeAddress recipient2 = trade instanceof ArbitratorTrade ? trade.getTakerNodeAddress() : null;
PubKeyRing recipient2PubKey = trade instanceof ArbitratorTrade ? trade.getTakerPubKeyRing() : null;
// send response to recipient 1
processModel.getP2PService().sendEncryptedDirectMessage(recipient1, recipient1PubKey, response, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient1, trade.getId());
ack1 = true;
if (ack1 && (recipient2 == null || ack2)) complete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient1, trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
failed();
}
});
// send response to recipient 2 if applicable
if (recipient2 != null) {
processModel.getP2PService().sendEncryptedDirectMessage(recipient2, recipient2PubKey, response, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient2, trade.getId());
ack2 = true;
if (ack1 && ack2) complete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient2, trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
failed();
}
});
}
}
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -0,0 +1,115 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.TradingPeer;
import bisq.network.p2p.SendDirectMessageListener;
import common.utils.GenUtils;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProcessSignContractResponse extends TradeTask {
@SuppressWarnings({"unused"})
public ProcessSignContractResponse(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// wait until contract is available from peer's sign contract request
// TODO (woodser): this will loop if peer disappears; use proper notification
while (trade.getContract() == null) {
GenUtils.waitFor(250);
}
// get contract and signature
String contractAsJson = trade.getContractAsJson();
SignContractResponse response = (SignContractResponse) processModel.getTradeMessage(); // TODO (woodser): verify response
String signature = response.getContractSignature();
// get peer info
// TODO (woodser): make these utilities / refactor model
PubKeyRing peerPubKeyRing;
TradingPeer peer = trade.getTradingPeer(response.getSenderNodeAddress());
if (peer == processModel.getArbitrator()) peerPubKeyRing = trade.getArbitratorPubKeyRing();
else if (peer == processModel.getMaker()) peerPubKeyRing = trade.getMakerPubKeyRing();
else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing();
else throw new RuntimeException(response.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
// verify signature
// TODO (woodser): transfer contract for convenient comparison?
if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid");
// set peer's signature
peer.setContractSignature(signature);
// send deposit request when all contract signatures received
if (processModel.getArbitrator().getContractSignature() != null && processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
// start listening for deposit txs
trade.setupDepositTxsListener();
// create request for arbitrator to deposit funds to multisig
DepositRequest request = new DepositRequest(
trade.getOffer().getId(),
processModel.getMyNodeAddress(),
processModel.getPubKeyRing(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
new Date().getTime(),
trade.getSelf().getContractSignature(),
processModel.getDepositTxXmr().getFullHex(),
processModel.getDepositTxXmr().getKey());
// send request to arbitrator
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing(), request, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
}
// persist and complete
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -55,7 +55,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask {
UpdateMultisigRequest request = (UpdateMultisigRequest) processModel.getTradeMessage();
checkNotNull(request);
checkTradeId(processModel.getOfferId(), request);
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
System.out.println("PROCESS UPDATE MULTISIG REQUEST");
System.out.println(request);
@ -85,13 +85,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask {
new Date().getTime(),
updatedMultisigHex);
System.out.println("SENDING MESSAGE!!!!!!!");
System.out.println(response);
log.info("Send {} with offerId {} and uid {} to peer {}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid(), trade.getTradingPeerNodeAddress());
System.out.println("GONNA BE BAD IF EITHER OF THESE ARE NULL");
System.out.println(trade.getTradingPeerNodeAddress());
System.out.println(trade.getTradingPeerPubKeyRing());
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), response, new SendDirectMessageListener() {
@Override
public void onArrived() {

View File

@ -54,7 +54,7 @@ public class PublishTradeStatistics extends TradeTask {
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
}
NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress());
NodeAddress mediatorNodeAddress = checkNotNull(trade.getArbitratorNodeAddress());
// The first 4 chars are sufficient to identify a mediator.
// For testing with regtest/localhost we use the full address as its localhost and would result in
// same values for multiple mediators.
@ -62,15 +62,15 @@ public class PublishTradeStatistics extends TradeTask {
String address = networkNode instanceof TorNetworkNode ?
mediatorNodeAddress.getFullAddress().substring(0, 4) :
mediatorNodeAddress.getFullAddress();
extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address);
extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address);
Offer offer = checkNotNull(trade.getOffer());
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
trade.getMakerDepositTxId(),
trade.getTakerDepositTxId(),
trade.getMaker().getDepositTxHash(),
trade.getTaker().getDepositTxHash(),
extraDataMap);
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);

Some files were not shown because too many files have changed in this diff Show More