mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-25 07:19:30 -05:00
implement protocol v2
This commit is contained in:
parent
65bcd47446
commit
86f7d090b6
@ -40,7 +40,7 @@ configure(subprojects) {
|
|||||||
grpcVersion = '1.25.0'
|
grpcVersion = '1.25.0'
|
||||||
gsonVersion = '2.8.5'
|
gsonVersion = '2.8.5'
|
||||||
guavaVersion = '28.2-jre'
|
guavaVersion = '28.2-jre'
|
||||||
moneroJavaVersion = '0.5.3'
|
moneroJavaVersion = '0.5.4'
|
||||||
httpclient5Version = '5.0'
|
httpclient5Version = '5.0'
|
||||||
guiceVersion = '4.2.2'
|
guiceVersion = '4.2.2'
|
||||||
hamcrestVersion = '1.3'
|
hamcrestVersion = '1.3'
|
||||||
|
@ -161,7 +161,7 @@ public class TradeFormat {
|
|||||||
|
|
||||||
private static final Function<TradeInfo, String> amountFormat = (t) ->
|
private static final Function<TradeInfo, String> amountFormat = (t) ->
|
||||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||||
? formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong()))
|
? formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()))
|
||||||
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
|
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
|
||||||
|
|
||||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
|
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) -> {
|
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) ->
|
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->
|
||||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||||
? formatOfferVolume(t.getOffer().getVolume())
|
? formatOfferVolume(t.getOffer().getVolume())
|
||||||
: formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong()));
|
: formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()));
|
||||||
|
|
||||||
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {
|
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {
|
||||||
if (showBsqBuyerAddress) {
|
if (showBsqBuyerAddress) {
|
||||||
|
@ -302,7 +302,7 @@ public class AccountAgeWitnessService {
|
|||||||
|
|
||||||
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
||||||
if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers
|
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 ||
|
return (tradingPeer == null ||
|
||||||
tradingPeer.getPaymentAccountPayload() == null ||
|
tradingPeer.getPaymentAccountPayload() == null ||
|
||||||
tradingPeer.getPubKeyRing() == null) ?
|
tradingPeer.getPubKeyRing() == null) ?
|
||||||
@ -426,7 +426,7 @@ public class AccountAgeWitnessService {
|
|||||||
long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
||||||
var factor = signedBuyFactor(accountAgeCategory);
|
var factor = signedBuyFactor(accountAgeCategory);
|
||||||
if (factor > 0) {
|
if (factor > 0) {
|
||||||
limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
|
limit = MathUtils.roundDoubleToLong(maxTradeLimit.value * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("limit={}, factor={}, accountAgeWitnessHash={}",
|
log.debug("limit={}, factor={}, accountAgeWitnessHash={}",
|
||||||
@ -726,8 +726,8 @@ public class AccountAgeWitnessService {
|
|||||||
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
||||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||||
Coin tradeAmount = trade.getTradeAmount();
|
Coin tradeAmount = trade.getTradeAmount();
|
||||||
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
checkNotNull(trade.getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
||||||
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
PublicKey peersPubKey = trade.getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
||||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
||||||
trade.toString());
|
trade.toString());
|
||||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||||
@ -767,15 +767,11 @@ public class AccountAgeWitnessService {
|
|||||||
boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) ||
|
boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) ||
|
||||||
filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) ||
|
filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) ||
|
||||||
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
|
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
|
||||||
filterManager.isPaymentMethodBanned(
|
filterManager.isPaymentMethodBanned(PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
||||||
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
filterManager.arePeersPaymentAccountDataBanned(dispute.getBuyerPaymentAccountPayload()) ||
|
||||||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload()) ||
|
filterManager.arePeersPaymentAccountDataBanned(dispute.getSellerPaymentAccountPayload()) ||
|
||||||
filterManager.arePeersPaymentAccountDataBanned(
|
filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
|
||||||
dispute.getContract().getSellerPaymentAccountPayload()) ||
|
filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
|
||||||
filterManager.isWitnessSignerPubKeyBanned(
|
|
||||||
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
|
|
||||||
filterManager.isWitnessSignerPubKeyBanned(
|
|
||||||
Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
|
|
||||||
return !isFiltered;
|
return !isFiltered;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,8 +793,8 @@ public class AccountAgeWitnessService {
|
|||||||
PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
|
PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
|
||||||
PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
|
PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
|
||||||
|
|
||||||
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
|
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getBuyerPaymentAccountPayload();
|
||||||
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload();
|
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getSellerPaymentAccountPayload();
|
||||||
|
|
||||||
TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
|
TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
|
||||||
.map(witness -> new TraderDataItem(
|
.map(witness -> new TraderDataItem(
|
||||||
@ -913,8 +909,7 @@ public class AccountAgeWitnessService {
|
|||||||
public boolean isSignWitnessTrade(Trade trade) {
|
public boolean isSignWitnessTrade(Trade trade) {
|
||||||
checkNotNull(trade, "trade must not be null");
|
checkNotNull(trade, "trade must not be null");
|
||||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||||
Contract contract = checkNotNull(trade.getContract());
|
PaymentAccountPayload sellerPaymentAccountPayload = trade.getSeller().getPaymentAccountPayload();
|
||||||
PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload();
|
|
||||||
AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload);
|
AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload);
|
||||||
|
|
||||||
getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);
|
getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);
|
||||||
|
@ -131,14 +131,14 @@ public class AccountAgeWitnessUtils {
|
|||||||
// Log to find why accounts sometimes don't get signed as expected
|
// Log to find why accounts sometimes don't get signed as expected
|
||||||
// TODO: Demote to debug or remove once account signing is working ok
|
// TODO: Demote to debug or remove once account signing is working ok
|
||||||
checkNotNull(trade.getContract());
|
checkNotNull(trade.getContract());
|
||||||
checkNotNull(trade.getContract().getBuyerPaymentAccountPayload());
|
checkNotNull(trade.getBuyer().getPaymentAccountPayload());
|
||||||
boolean checkingSignTrade = true;
|
boolean checkingSignTrade = true;
|
||||||
boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing());
|
boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing());
|
||||||
AccountAgeWitness witness = myWitness;
|
AccountAgeWitness witness = myWitness;
|
||||||
if (witness == null) {
|
if (witness == null) {
|
||||||
witness = isBuyer ?
|
witness = isBuyer ?
|
||||||
accountAgeWitnessService.getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) :
|
accountAgeWitnessService.getMyWitness(trade.getBuyer().getPaymentAccountPayload()) :
|
||||||
accountAgeWitnessService.getMyWitness(trade.getContract().getSellerPaymentAccountPayload());
|
accountAgeWitnessService.getMyWitness(trade.getSeller().getPaymentAccountPayload());
|
||||||
checkingSignTrade = false;
|
checkingSignTrade = false;
|
||||||
}
|
}
|
||||||
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
||||||
@ -157,9 +157,9 @@ public class AccountAgeWitnessUtils {
|
|||||||
"\nisSignWitnessTrade: {}",
|
"\nisSignWitnessTrade: {}",
|
||||||
trade.getId(),
|
trade.getId(),
|
||||||
isBuyer,
|
isBuyer,
|
||||||
getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(),
|
getWitnessDebugLog(trade.getBuyer().getPaymentAccountPayload(),
|
||||||
trade.getContract().getBuyerPubKeyRing()),
|
trade.getContract().getBuyerPubKeyRing()),
|
||||||
getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(),
|
getWitnessDebugLog(trade.getSeller().getPaymentAccountPayload(),
|
||||||
trade.getContract().getSellerPubKeyRing()),
|
trade.getContract().getSellerPubKeyRing()),
|
||||||
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
||||||
accountAgeWitnessService.accountIsSigner(witness),
|
accountAgeWitnessService.accountIsSigner(witness),
|
||||||
|
@ -109,7 +109,6 @@ class CoreTradesService {
|
|||||||
tradeManager.onTakeOffer(offer.getAmount(),
|
tradeManager.onTakeOffer(offer.getAmount(),
|
||||||
takeOfferModel.getTxFeeFromFeeService(),
|
takeOfferModel.getTxFeeFromFeeService(),
|
||||||
takeOfferModel.getTakerFee(),
|
takeOfferModel.getTakerFee(),
|
||||||
offer.getPrice().getValue(),
|
|
||||||
takeOfferModel.getFundsNeededForTrade(),
|
takeOfferModel.getFundsNeededForTrade(),
|
||||||
offer,
|
offer,
|
||||||
paymentAccountId,
|
paymentAccountId,
|
||||||
|
@ -108,8 +108,8 @@ public class TradeInfo implements Payload {
|
|||||||
contract.isBuyerMakerAndSellerTaker(),
|
contract.isBuyerMakerAndSellerTaker(),
|
||||||
contract.getMakerAccountId(),
|
contract.getMakerAccountId(),
|
||||||
contract.getTakerAccountId(),
|
contract.getTakerAccountId(),
|
||||||
toPaymentAccountPayloadInfo(contract.getMakerPaymentAccountPayload()),
|
toPaymentAccountPayloadInfo(trade.getMaker().getPaymentAccountPayload()),
|
||||||
toPaymentAccountPayloadInfo(contract.getTakerPaymentAccountPayload()),
|
toPaymentAccountPayloadInfo(trade.getTaker().getPaymentAccountPayload()),
|
||||||
contract.getMakerPayoutAddressString(),
|
contract.getMakerPayoutAddressString(),
|
||||||
contract.getTakerPayoutAddressString(),
|
contract.getTakerPayoutAddressString(),
|
||||||
contract.getLockTime());
|
contract.getLockTime());
|
||||||
@ -127,8 +127,8 @@ public class TradeInfo implements Payload {
|
|||||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||||
.withTakerFeeTxId(trade.getTakerFeeTxId())
|
.withTakerFeeTxId(trade.getTakerFeeTxId())
|
||||||
.withMakerDepositTxId(trade.getMakerDepositTxId())
|
.withMakerDepositTxId(trade.getMaker().getDepositTxHash())
|
||||||
.withTakerDepositTxId(trade.getTakerDepositTxId())
|
.withTakerDepositTxId(trade.getTaker().getDepositTxHash())
|
||||||
.withPayoutTxId(trade.getPayoutTxId())
|
.withPayoutTxId(trade.getPayoutTxId())
|
||||||
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
|
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
|
||||||
.withTradePrice(trade.getTradePrice().getValue())
|
.withTradePrice(trade.getTradePrice().getValue())
|
||||||
|
@ -249,10 +249,10 @@ public class WalletAppSetup {
|
|||||||
.filter(trade -> trade.getOffer() != null)
|
.filter(trade -> trade.getOffer() != null)
|
||||||
.forEach(trade -> {
|
.forEach(trade -> {
|
||||||
String details = null;
|
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
|
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");
|
details = Res.get("popup.warning.trade.txRejected.deposit");
|
||||||
}
|
}
|
||||||
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
|
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
|
|
||||||
package bisq.core.btc;
|
package bisq.core.btc;
|
||||||
|
|
||||||
|
import bisq.common.UserThread;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.support.dispute.Dispute;
|
import bisq.core.support.dispute.Dispute;
|
||||||
@ -26,30 +28,21 @@ import bisq.core.trade.Trade;
|
|||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.closed.ClosedTradableManager;
|
||||||
import bisq.core.trade.failed.FailedTradesManager;
|
import bisq.core.trade.failed.FailedTradesManager;
|
||||||
|
import bisq.core.util.ParsingUtils;
|
||||||
import bisq.common.UserThread;
|
import bisq.network.p2p.P2PService;
|
||||||
|
import java.math.BigInteger;
|
||||||
import org.bitcoinj.core.Coin;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javax.inject.Inject;
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.wallet.model.MoneroOutputQuery;
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.model.MoneroAccount;
|
|
||||||
import monero.wallet.model.MoneroOutputWallet;
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroWalletListener;
|
import monero.wallet.model.MoneroWalletListener;
|
||||||
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Balances {
|
public class Balances {
|
||||||
@ -98,29 +91,45 @@ public class Balances {
|
|||||||
// Need to delay a bit to get the balances correct
|
// Need to delay a bit to get the balances correct
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
updateAvailableBalance();
|
updateAvailableBalance();
|
||||||
updateReservedBalance();
|
|
||||||
updateLockedBalance();
|
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() {
|
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() {
|
private void updateLockedBalance() {
|
||||||
BigInteger balance = xmrWalletService.getWallet().getBalance(0);
|
BigInteger balance = xmrWalletService.getWallet().getBalance(0);
|
||||||
BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,17 @@ package bisq.core.btc.listeners;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
public class XmrBalanceListener {
|
public class XmrBalanceListener {
|
||||||
private Integer accountIndex;
|
private Integer subaddressIndex;
|
||||||
|
|
||||||
public XmrBalanceListener() {
|
public XmrBalanceListener() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmrBalanceListener(Integer accountIndex) {
|
public XmrBalanceListener(Integer accountIndex) {
|
||||||
this.accountIndex = accountIndex;
|
this.subaddressIndex = accountIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getAccountIndex() {
|
public Integer getSubaddressIndex() {
|
||||||
return accountIndex;
|
return subaddressIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onBalanceChanged(BigInteger balance) {
|
public void onBalanceChanged(BigInteger balance) {
|
||||||
|
@ -60,7 +60,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
|||||||
@Getter
|
@Getter
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@Getter
|
@Getter
|
||||||
private final int accountIndex;
|
private final int subaddressIndex;
|
||||||
@Getter
|
@Getter
|
||||||
private final String addressString;
|
private final String addressString;
|
||||||
|
|
||||||
@ -71,12 +71,12 @@ public final class XmrAddressEntry implements PersistablePayload {
|
|||||||
// Constructor, initialization
|
// Constructor, initialization
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public XmrAddressEntry(int accountIndex, String address, Context context) {
|
public XmrAddressEntry(int subaddressIndex, String address, Context context) {
|
||||||
this(accountIndex, address, context, null, null);
|
this(subaddressIndex, address, context, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmrAddressEntry(int accountIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
|
public XmrAddressEntry(int subaddressIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
|
||||||
this.accountIndex = accountIndex;
|
this.subaddressIndex = subaddressIndex;
|
||||||
this.addressString = address;
|
this.addressString = address;
|
||||||
this.offerId = offerId;
|
this.offerId = offerId;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@ -89,7 +89,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) {
|
public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) {
|
||||||
return new XmrAddressEntry(proto.getAccountIndex(),
|
return new XmrAddressEntry(proto.getSubaddressIndex(),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getAddressString()),
|
ProtoUtil.stringOrNullFromProto(proto.getAddressString()),
|
||||||
ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()),
|
ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
|
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
|
||||||
@ -99,7 +99,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
|||||||
@Override
|
@Override
|
||||||
public protobuf.XmrAddressEntry toProtoMessage() {
|
public protobuf.XmrAddressEntry toProtoMessage() {
|
||||||
protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder()
|
protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder()
|
||||||
.setAccountIndex(accountIndex)
|
.setSubaddressIndex(subaddressIndex)
|
||||||
.setAddressString(addressString)
|
.setAddressString(addressString)
|
||||||
.setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name()))
|
.setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name()))
|
||||||
.setCoinLockedInMultiSig(coinLockedInMultiSig);
|
.setCoinLockedInMultiSig(coinLockedInMultiSig);
|
||||||
@ -143,7 +143,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
|||||||
return "XmrAddressEntry{" +
|
return "XmrAddressEntry{" +
|
||||||
"offerId='" + getOfferId() + '\'' +
|
"offerId='" + getOfferId() + '\'' +
|
||||||
", context=" + context +
|
", context=" + context +
|
||||||
", accountIndex=" + getAccountIndex() +
|
", subaddressIndex=" + getSubaddressIndex() +
|
||||||
", address=" + getAddressString() +
|
", address=" + getAddressString() +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.model.MoneroAccount;
|
|
||||||
import monero.wallet.model.MoneroOutputWallet;
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroWalletListener;
|
import monero.wallet.model.MoneroWalletListener;
|
||||||
|
|
||||||
@ -131,10 +130,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
|||||||
// });
|
// });
|
||||||
//
|
//
|
||||||
// toBeRemoved.forEach(entrySet::remove);
|
// 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.
|
// 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) {
|
if (entryWithSameOfferIdAndContextAlreadyExist) {
|
||||||
log.error("We have an address entry with the same offer ID and context. We do not add the new one. " +
|
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);
|
"addressEntry={}, entrySet={}", addressEntry, entrySet);
|
||||||
if (true) throw new RuntimeException("why?");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +179,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
|||||||
|
|
||||||
public void swapToAvailable(XmrAddressEntry addressEntry) {
|
public void swapToAvailable(XmrAddressEntry addressEntry) {
|
||||||
boolean setChangedByRemove = entrySet.remove(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));
|
XmrAddressEntry.Context.AVAILABLE));
|
||||||
if (setChangedByRemove || setChangedByAdd) {
|
if (setChangedByRemove || setChangedByAdd) {
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
@ -196,7 +190,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
|||||||
XmrAddressEntry.Context context,
|
XmrAddressEntry.Context context,
|
||||||
String offerId) {
|
String offerId) {
|
||||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
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);
|
boolean setChangedByAdd = entrySet.add(newAddressEntry);
|
||||||
if (setChangedByRemove || setChangedByAdd)
|
if (setChangedByRemove || setChangedByAdd)
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
@ -213,6 +207,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
|||||||
// Private
|
// Private
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// TODO (woodser): this should be removed since only using account 0
|
||||||
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
|
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
|
||||||
if (output.getAccountIndex() == 0) return;
|
if (output.getAccountIndex() == 0) return;
|
||||||
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
|
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
|
||||||
|
@ -89,6 +89,8 @@ import static com.google.common.base.Preconditions.checkState;
|
|||||||
|
|
||||||
|
|
||||||
import monero.common.MoneroUtils;
|
import monero.common.MoneroUtils;
|
||||||
|
import monero.daemon.MoneroDaemon;
|
||||||
|
import monero.daemon.MoneroDaemonRpc;
|
||||||
import monero.daemon.model.MoneroNetworkType;
|
import monero.daemon.model.MoneroNetworkType;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.MoneroWalletRpc;
|
import monero.wallet.MoneroWalletRpc;
|
||||||
@ -137,6 +139,7 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
protected final String filePrefix;
|
protected final String filePrefix;
|
||||||
protected volatile BlockChain vChain;
|
protected volatile BlockChain vChain;
|
||||||
protected volatile SPVBlockStore vStore;
|
protected volatile SPVBlockStore vStore;
|
||||||
|
protected volatile MoneroDaemon vXmrDaemon;
|
||||||
protected volatile MoneroWallet vXmrWallet;
|
protected volatile MoneroWallet vXmrWallet;
|
||||||
protected volatile Wallet vBtcWallet;
|
protected volatile Wallet vBtcWallet;
|
||||||
protected volatile Wallet vBsqWallet;
|
protected volatile Wallet vBsqWallet;
|
||||||
@ -346,6 +349,9 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
File chainFile = new File(directory, filePrefix + ".spvchain");
|
File chainFile = new File(directory, filePrefix + ".spvchain");
|
||||||
boolean chainFileExists = chainFile.exists();
|
boolean chainFileExists = chainFile.exists();
|
||||||
|
|
||||||
|
// XMR daemon
|
||||||
|
vXmrDaemon = new MoneroDaemonRpc(MONERO_DAEMON_URI, MONERO_DAEMON_USERNAME, MONERO_DAEMON_PASSWORD);
|
||||||
|
|
||||||
// XMR wallet
|
// XMR wallet
|
||||||
String xmrPrefix = "_XMR";
|
String xmrPrefix = "_XMR";
|
||||||
vXmrWalletFile = new File(directory, filePrefix + xmrPrefix);
|
vXmrWalletFile = new File(directory, filePrefix + xmrPrefix);
|
||||||
@ -361,7 +367,8 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
// vXmrWallet.rescanBlockchain();
|
// vXmrWallet.rescanBlockchain();
|
||||||
vXmrWallet.sync();
|
vXmrWallet.sync();
|
||||||
vXmrWallet.save();
|
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";
|
String btcPrefix = "_BTC";
|
||||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||||
@ -626,6 +633,12 @@ public class WalletConfig extends AbstractIdleService {
|
|||||||
return vBtcWallet;
|
return vBtcWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public MoneroDaemon getXmrDaemon() {
|
||||||
|
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||||
|
return vXmrDaemon;
|
||||||
|
}
|
||||||
|
|
||||||
public MoneroWallet getXmrWallet() {
|
public MoneroWallet getXmrWallet() {
|
||||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||||
return vXmrWallet;
|
return vXmrWallet;
|
||||||
|
@ -105,7 +105,7 @@ import javax.annotation.Nullable;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
import monero.daemon.MoneroDaemon;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
|
|
||||||
// Setup wallets and use WalletConfig for BitcoinJ wiring.
|
// Setup wallets and use WalletConfig for BitcoinJ wiring.
|
||||||
@ -490,6 +490,10 @@ public class WalletsSetup {
|
|||||||
return walletConfig.btcWallet();
|
return walletConfig.btcWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MoneroDaemon getXmrDaemon() {
|
||||||
|
return walletConfig.getXmrDaemon();
|
||||||
|
}
|
||||||
|
|
||||||
public MoneroWallet getXmrWallet() {
|
public MoneroWallet getXmrWallet() {
|
||||||
return walletConfig.getXmrWallet();
|
return walletConfig.getXmrWallet();
|
||||||
}
|
}
|
||||||
|
@ -146,8 +146,8 @@ public class TradeWalletService {
|
|||||||
return xmrWallet.createTx(new MoneroTxConfig()
|
return xmrWallet.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.setDestinations(
|
.setDestinations(
|
||||||
new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)),
|
new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)),
|
||||||
new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value)))
|
new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer)))
|
||||||
.setRelay(broadcastTx));
|
.setRelay(broadcastTx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package bisq.core.btc.wallet;
|
package bisq.core.btc.wallet;
|
||||||
|
|
||||||
|
import bisq.common.UserThread;
|
||||||
import bisq.core.btc.exceptions.AddressEntryException;
|
import bisq.core.btc.exceptions.AddressEntryException;
|
||||||
import bisq.core.btc.listeners.XmrBalanceListener;
|
import bisq.core.btc.listeners.XmrBalanceListener;
|
||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
@ -36,11 +37,10 @@ import lombok.Getter;
|
|||||||
|
|
||||||
|
|
||||||
import monero.common.MoneroUtils;
|
import monero.common.MoneroUtils;
|
||||||
|
import monero.daemon.MoneroDaemon;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.model.MoneroAccount;
|
|
||||||
import monero.wallet.model.MoneroOutputWallet;
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroSubaddress;
|
import monero.wallet.model.MoneroSubaddress;
|
||||||
import monero.wallet.model.MoneroTransfer;
|
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
import monero.wallet.model.MoneroTxQuery;
|
import monero.wallet.model.MoneroTxQuery;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
@ -57,6 +57,8 @@ public class XmrWalletService {
|
|||||||
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
|
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
|
||||||
private Map<String, MoneroWallet> multisigWallets;
|
private Map<String, MoneroWallet> multisigWallets;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private MoneroDaemon daemon;
|
||||||
@Getter
|
@Getter
|
||||||
private MoneroWallet wallet;
|
private MoneroWallet wallet;
|
||||||
|
|
||||||
@ -69,6 +71,7 @@ public class XmrWalletService {
|
|||||||
this.multisigWallets = new HashMap<String, MoneroWallet>();
|
this.multisigWallets = new HashMap<String, MoneroWallet>();
|
||||||
|
|
||||||
walletsSetup.addSetupCompletedHandler(() -> {
|
walletsSetup.addSetupCompletedHandler(() -> {
|
||||||
|
daemon = walletsSetup.getXmrDaemon();
|
||||||
wallet = walletsSetup.getXmrWallet();
|
wallet = walletsSetup.getXmrWallet();
|
||||||
wallet.addListener(new MoneroWalletListener() {
|
wallet.addListener(new MoneroWalletListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -87,30 +90,29 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
// TODO (woodser): wallet has single password which is passed here?
|
// TODO (woodser): wallet has single password which is passed here?
|
||||||
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
|
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
|
||||||
public MoneroWallet getOrCreateMultisigWallet(String tradeId) {
|
|
||||||
|
public MoneroWallet createMultisigWallet(String tradeId) {
|
||||||
|
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||||
String path = "xmr_multisig_trade_" + tradeId;
|
String path = "xmr_multisig_trade_" + tradeId;
|
||||||
MoneroWallet multisigWallet = null;
|
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 {
|
|
||||||
multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig()
|
multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig()
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.setPassword("abctesting123"));
|
.setPassword("abctesting123"));
|
||||||
}
|
|
||||||
multisigWallets.put(tradeId, multisigWallet);
|
multisigWallets.put(tradeId, multisigWallet);
|
||||||
multisigWallet.startSyncing(5000l);
|
multisigWallet.startSyncing(5000l);
|
||||||
return multisigWallet;
|
return multisigWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmrAddressEntry getArbitratorAddressEntry() {
|
public MoneroWallet getMultisigWallet(String tradeId) {
|
||||||
XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR;
|
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||||
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
String path = "xmr_multisig_trade_" + tradeId;
|
||||||
.filter(e -> context == e.getContext())
|
MoneroWallet multisigWallet = null;
|
||||||
.findAny();
|
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig()
|
||||||
return getOrCreateAddressEntry(context, addressEntry);
|
.setPath(path)
|
||||||
|
.setPassword("abctesting123"));
|
||||||
|
multisigWallets.put(tradeId, multisigWallet);
|
||||||
|
multisigWallet.startSyncing(5000l);
|
||||||
|
return multisigWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
|
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) {
|
public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) {
|
||||||
if (context == XmrAddressEntry.Context.TRADE_PAYOUT) {
|
MoneroSubaddress subaddress = wallet.createSubaddress(0);
|
||||||
XmrAddressEntry entry = new XmrAddressEntry(0, wallet.createSubaddress(0).getAddress(), context, offerId, null);
|
XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null);
|
||||||
System.out.println("Adding address entry: " + entry.getAccountIndex() + ", " + entry.getAddressString());
|
|
||||||
addressEntryList.addAddressEntry(entry);
|
addressEntryList.addAddressEntry(entry);
|
||||||
return 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) {
|
public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) {
|
||||||
@ -142,33 +137,15 @@ public class XmrWalletService {
|
|||||||
if (addressEntry.isPresent()) {
|
if (addressEntry.isPresent()) {
|
||||||
return addressEntry.get();
|
return addressEntry.get();
|
||||||
} else {
|
} 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()
|
Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
|
||||||
.filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
|
.filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
|
||||||
.filter(e -> isAccountUnused(e.getAccountIndex()))
|
.filter(e -> isSubaddressUnused(e.getSubaddressIndex()))
|
||||||
.findAny();
|
.findAny();
|
||||||
if (emptyAvailableAddressEntry.isPresent()) {
|
if (emptyAvailableAddressEntry.isPresent()) {
|
||||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
||||||
} else {
|
} else {
|
||||||
MoneroAccount account = wallet.createAccount();
|
return getNewAddressEntry(offerId, context);
|
||||||
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
|
|
||||||
addressEntryList.addAddressEntry(entry);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,7 +215,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
|
public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
|
||||||
return getAvailableAddressEntries().stream()
|
return getAvailableAddressEntries().stream()
|
||||||
.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive())
|
.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,26 +223,26 @@ public class XmrWalletService {
|
|||||||
return addressEntryList.getAddressEntriesAsListImmutable();
|
return addressEntryList.getAddressEntriesAsListImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAccountUnused(int accountIndex) {
|
public boolean isSubaddressUnused(int subaddressIndex) {
|
||||||
return accountIndex != 0 && getBalanceForAccount(accountIndex).value == 0;
|
return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0;
|
||||||
//return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds
|
//return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getBalanceForAccount(int accountIndex) {
|
public Coin getBalanceForSubaddress(int subaddressIndex) {
|
||||||
|
|
||||||
// get wallet balance
|
// get subaddress balance
|
||||||
BigInteger balance = wallet.getBalance(accountIndex);
|
BigInteger balance = wallet.getBalance(0, subaddressIndex);
|
||||||
|
|
||||||
// balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack?
|
// // 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 (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) {
|
||||||
for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
|
// for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
|
||||||
if (transfer.getAccountIndex() == accountIndex) {
|
// if (transfer.getAccountIndex() == subaddressIndex) {
|
||||||
balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount());
|
// 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());
|
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> availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream());
|
||||||
Stream<XmrAddressEntry> available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
|
Stream<XmrAddressEntry> available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
|
||||||
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).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) {
|
public void addBalanceListener(XmrBalanceListener listener) {
|
||||||
@ -353,7 +330,7 @@ public class XmrWalletService {
|
|||||||
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
|
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(fromAccountIndex)
|
.setAccountIndex(fromAccountIndex)
|
||||||
.setAddress(toAddress)
|
.setAddress(toAddress)
|
||||||
.setAmount(ParsingUtils.satoshisToXmrAtomicUnits(receiverAmount.value))
|
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount))
|
||||||
.setRelay(true));
|
.setRelay(true));
|
||||||
callback.onSuccess(tx);
|
callback.onSuccess(tx);
|
||||||
printTxs("sendFunds", tx);
|
printTxs("sendFunds", tx);
|
||||||
@ -387,17 +364,23 @@ public class XmrWalletService {
|
|||||||
private void notifyBalanceListeners() {
|
private void notifyBalanceListeners() {
|
||||||
for (XmrBalanceListener balanceListener : balanceListeners) {
|
for (XmrBalanceListener balanceListener : balanceListeners) {
|
||||||
Coin balance;
|
Coin balance;
|
||||||
if (balanceListener.getAccountIndex() != null && balanceListener.getAccountIndex() != 0) {
|
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) {
|
||||||
balance = getBalanceForAccount(balanceListener.getAccountIndex());
|
balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
|
||||||
} else {
|
} else {
|
||||||
balance = getAvailableConfirmedBalance();
|
balance = getAvailableConfirmedBalance();
|
||||||
}
|
}
|
||||||
|
UserThread.execute(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
|
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a MoneroWalletListener to notify the Haveno application.
|
* 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 {
|
public class HavenoWalletListener extends MoneroWalletListener {
|
||||||
|
|
||||||
@ -409,27 +392,47 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
||||||
|
UserThread.execute(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
|
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewBlock(long height) {
|
public void onNewBlock(long height) {
|
||||||
|
UserThread.execute(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
listener.onNewBlock(height);
|
listener.onNewBlock(height);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||||
|
UserThread.execute(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOutputReceived(MoneroOutputWallet output) {
|
public void onOutputReceived(MoneroOutputWallet output) {
|
||||||
|
UserThread.execute(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
listener.onOutputReceived(output);
|
listener.onOutputReceived(output);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOutputSpent(MoneroOutputWallet output) {
|
public void onOutputSpent(MoneroOutputWallet output) {
|
||||||
|
UserThread.execute(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
listener.onOutputSpent(output);
|
listener.onOutputSpent(output);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,8 @@ public enum AvailabilityResult {
|
|||||||
@SuppressWarnings("unused") NO_REFUND_AGENTS("cannot take offer because no refund agents are available"),
|
@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"),
|
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"),
|
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;
|
private final String description;
|
||||||
|
|
||||||
|
@ -23,10 +23,14 @@ import bisq.core.btc.wallet.Restrictions;
|
|||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
|
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.PaymentAccountUtil;
|
import bisq.core.payment.PaymentAccountUtil;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
import bisq.core.provider.price.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
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.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.coin.CoinUtil;
|
import bisq.core.util.coin.CoinUtil;
|
||||||
@ -64,6 +68,8 @@ public class CreateOfferService {
|
|||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRing pubKeyRing;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
|
private final MediatorManager mediatorManager;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -77,7 +83,9 @@ public class CreateOfferService {
|
|||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
User user,
|
User user,
|
||||||
BtcWalletService btcWalletService) {
|
BtcWalletService btcWalletService,
|
||||||
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
|
MediatorManager mediatorManager) {
|
||||||
this.offerUtil = offerUtil;
|
this.offerUtil = offerUtil;
|
||||||
this.txFeeEstimationService = txFeeEstimationService;
|
this.txFeeEstimationService = txFeeEstimationService;
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
@ -85,6 +93,8 @@ public class CreateOfferService {
|
|||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
|
this.mediatorManager = mediatorManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -182,6 +192,9 @@ public class CreateOfferService {
|
|||||||
currencyCode,
|
currencyCode,
|
||||||
makerFeeAsCoin);
|
makerFeeAsCoin);
|
||||||
|
|
||||||
|
// select signing arbitrator
|
||||||
|
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
|
||||||
|
|
||||||
OfferPayload offerPayload = new OfferPayload(offerId,
|
OfferPayload offerPayload = new OfferPayload(offerId,
|
||||||
creationTime,
|
creationTime,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
@ -194,8 +207,6 @@ public class CreateOfferService {
|
|||||||
minAmountAsLong,
|
minAmountAsLong,
|
||||||
baseCurrencyCode,
|
baseCurrencyCode,
|
||||||
counterCurrencyCode,
|
counterCurrencyCode,
|
||||||
arbitratorNodeAddresses,
|
|
||||||
mediatorNodeAddresses,
|
|
||||||
paymentAccount.getPaymentMethod().getId(),
|
paymentAccount.getPaymentMethod().getId(),
|
||||||
paymentAccount.getId(),
|
paymentAccount.getId(),
|
||||||
null,
|
null,
|
||||||
@ -219,7 +230,9 @@ public class CreateOfferService {
|
|||||||
isPrivateOffer,
|
isPrivateOffer,
|
||||||
hashOfChallenge,
|
hashOfChallenge,
|
||||||
extraDataMap,
|
extraDataMap,
|
||||||
Version.TRADE_PROTOCOL_VERSION);
|
Version.TRADE_PROTOCOL_VERSION,
|
||||||
|
arbitrator.getNodeAddress(),
|
||||||
|
null);
|
||||||
Offer offer = new Offer(offerPayload);
|
Offer offer = new Offer(offerPayload);
|
||||||
offer.setPriceFeedService(priceFeedService);
|
offer.setPriceFeedService(priceFeedService);
|
||||||
return offer;
|
return offer;
|
||||||
|
@ -82,7 +82,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
|||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
OFFER_FEE_PAID,
|
OFFER_FEE_RESERVED,
|
||||||
AVAILABLE,
|
AVAILABLE,
|
||||||
NOT_AVAILABLE,
|
NOT_AVAILABLE,
|
||||||
REMOVED,
|
REMOVED,
|
||||||
|
@ -22,6 +22,8 @@ import bisq.core.filter.FilterManager;
|
|||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.PaymentAccountUtil;
|
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.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
@ -80,7 +82,8 @@ public class OfferFilter {
|
|||||||
IS_NODE_ADDRESS_BANNED,
|
IS_NODE_ADDRESS_BANNED,
|
||||||
REQUIRE_UPDATE_TO_NEW_VERSION,
|
REQUIRE_UPDATE_TO_NEW_VERSION,
|
||||||
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
||||||
IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
IS_MY_INSUFFICIENT_TRADE_LIMIT,
|
||||||
|
SIGNATURE_NOT_VALIDATED;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final boolean isValid;
|
private final boolean isValid;
|
||||||
@ -128,6 +131,9 @@ public class OfferFilter {
|
|||||||
if (isMyInsufficientTradeLimit(offer)) {
|
if (isMyInsufficientTradeLimit(offer)) {
|
||||||
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
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;
|
return Result.VALID;
|
||||||
}
|
}
|
||||||
@ -206,4 +212,14 @@ public class OfferFilter {
|
|||||||
myInsufficientTradeLimitCache.put(offerId, result);
|
myInsufficientTradeLimitCache.put(offerId, result);
|
||||||
return 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -120,12 +119,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
private final String baseCurrencyCode;
|
private final String baseCurrencyCode;
|
||||||
private final String counterCurrencyCode;
|
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 paymentMethodId;
|
||||||
private final String makerPaymentAccountId;
|
private final String makerPaymentAccountId;
|
||||||
// Mutable property. Has to be set before offer is save in P2P network as it changes the objects hash!
|
// Mutable property. Has to be set before offer is save in P2P network as it changes the objects hash!
|
||||||
@ -175,6 +168,13 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
private final Map<String, String> extraDataMap;
|
private final Map<String, String> extraDataMap;
|
||||||
private final int protocolVersion;
|
private final int protocolVersion;
|
||||||
|
|
||||||
|
// address and signature of signing arbitrator
|
||||||
|
@Setter
|
||||||
|
private NodeAddress arbitratorNodeAddress;
|
||||||
|
@Nullable
|
||||||
|
@Setter
|
||||||
|
private String arbitratorSignature;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
@ -192,8 +192,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
long minAmount,
|
long minAmount,
|
||||||
String baseCurrencyCode,
|
String baseCurrencyCode,
|
||||||
String counterCurrencyCode,
|
String counterCurrencyCode,
|
||||||
List<NodeAddress> arbitratorNodeAddresses,
|
|
||||||
List<NodeAddress> mediatorNodeAddresses,
|
|
||||||
String paymentMethodId,
|
String paymentMethodId,
|
||||||
String makerPaymentAccountId,
|
String makerPaymentAccountId,
|
||||||
@Nullable String offerFeePaymentTxId,
|
@Nullable String offerFeePaymentTxId,
|
||||||
@ -217,7 +215,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
boolean isPrivateOffer,
|
boolean isPrivateOffer,
|
||||||
@Nullable String hashOfChallenge,
|
@Nullable String hashOfChallenge,
|
||||||
@Nullable Map<String, String> extraDataMap,
|
@Nullable Map<String, String> extraDataMap,
|
||||||
int protocolVersion) {
|
int protocolVersion,
|
||||||
|
NodeAddress arbitratorSigner,
|
||||||
|
@Nullable String arbitratorSignature) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.date = date;
|
this.date = date;
|
||||||
this.ownerNodeAddress = ownerNodeAddress;
|
this.ownerNodeAddress = ownerNodeAddress;
|
||||||
@ -230,8 +230,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
this.minAmount = minAmount;
|
this.minAmount = minAmount;
|
||||||
this.baseCurrencyCode = baseCurrencyCode;
|
this.baseCurrencyCode = baseCurrencyCode;
|
||||||
this.counterCurrencyCode = counterCurrencyCode;
|
this.counterCurrencyCode = counterCurrencyCode;
|
||||||
this.arbitratorNodeAddresses = arbitratorNodeAddresses;
|
|
||||||
this.mediatorNodeAddresses = mediatorNodeAddresses;
|
|
||||||
this.paymentMethodId = paymentMethodId;
|
this.paymentMethodId = paymentMethodId;
|
||||||
this.makerPaymentAccountId = makerPaymentAccountId;
|
this.makerPaymentAccountId = makerPaymentAccountId;
|
||||||
this.offerFeePaymentTxId = offerFeePaymentTxId;
|
this.offerFeePaymentTxId = offerFeePaymentTxId;
|
||||||
@ -256,6 +254,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
this.hashOfChallenge = hashOfChallenge;
|
this.hashOfChallenge = hashOfChallenge;
|
||||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||||
this.protocolVersion = protocolVersion;
|
this.protocolVersion = protocolVersion;
|
||||||
|
this.arbitratorNodeAddress = arbitratorSigner;
|
||||||
|
this.arbitratorSignature = arbitratorSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -277,12 +277,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
.setMinAmount(minAmount)
|
.setMinAmount(minAmount)
|
||||||
.setBaseCurrencyCode(baseCurrencyCode)
|
.setBaseCurrencyCode(baseCurrencyCode)
|
||||||
.setCounterCurrencyCode(counterCurrencyCode)
|
.setCounterCurrencyCode(counterCurrencyCode)
|
||||||
.addAllArbitratorNodeAddresses(arbitratorNodeAddresses.stream()
|
|
||||||
.map(NodeAddress::toProtoMessage)
|
|
||||||
.collect(Collectors.toList()))
|
|
||||||
.addAllMediatorNodeAddresses(mediatorNodeAddresses.stream()
|
|
||||||
.map(NodeAddress::toProtoMessage)
|
|
||||||
.collect(Collectors.toList()))
|
|
||||||
.setPaymentMethodId(paymentMethodId)
|
.setPaymentMethodId(paymentMethodId)
|
||||||
.setMakerPaymentAccountId(makerPaymentAccountId)
|
.setMakerPaymentAccountId(makerPaymentAccountId)
|
||||||
.setVersionNr(versionNr)
|
.setVersionNr(versionNr)
|
||||||
@ -311,6 +305,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
|
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
|
||||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||||
|
|
||||||
|
builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage());
|
||||||
|
Optional.ofNullable(arbitratorSignature).ifPresent(builder::setArbitratorSignature);
|
||||||
|
|
||||||
return protobuf.StoragePayload.newBuilder().setOfferPayload(builder).build();
|
return protobuf.StoragePayload.newBuilder().setOfferPayload(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,12 +333,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
proto.getMinAmount(),
|
proto.getMinAmount(),
|
||||||
proto.getBaseCurrencyCode(),
|
proto.getBaseCurrencyCode(),
|
||||||
proto.getCounterCurrencyCode(),
|
proto.getCounterCurrencyCode(),
|
||||||
proto.getArbitratorNodeAddressesList().stream()
|
|
||||||
.map(NodeAddress::fromProto)
|
|
||||||
.collect(Collectors.toList()),
|
|
||||||
proto.getMediatorNodeAddressesList().stream()
|
|
||||||
.map(NodeAddress::fromProto)
|
|
||||||
.collect(Collectors.toList()),
|
|
||||||
proto.getPaymentMethodId(),
|
proto.getPaymentMethodId(),
|
||||||
proto.getMakerPaymentAccountId(),
|
proto.getMakerPaymentAccountId(),
|
||||||
proto.getOfferFeePaymentTxId(),
|
proto.getOfferFeePaymentTxId(),
|
||||||
@ -365,7 +356,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
|||||||
proto.getIsPrivateOffer(),
|
proto.getIsPrivateOffer(),
|
||||||
hashOfChallenge,
|
hashOfChallenge,
|
||||||
extraDataMapMap,
|
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 hashOfChallenge='" + hashOfChallenge + '\'' +
|
||||||
",\n extraDataMap=" + extraDataMap +
|
",\n extraDataMap=" + extraDataMap +
|
||||||
",\n protocolVersion=" + protocolVersion +
|
",\n protocolVersion=" + protocolVersion +
|
||||||
|
",\n arbitratorSigner=" + arbitratorNodeAddress +
|
||||||
|
",\n arbitratorSignature=" + arbitratorSignature +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,9 @@ import bisq.network.p2p.NodeAddress;
|
|||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.proto.ProtoUtil;
|
import bisq.common.proto.ProtoUtil;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@ -58,16 +59,9 @@ public final class OpenOffer implements Tradable {
|
|||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private NodeAddress arbitratorNodeAddress;
|
private NodeAddress arbitratorNodeAddress;
|
||||||
@Getter
|
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
|
||||||
private NodeAddress mediatorNodeAddress;
|
|
||||||
|
|
||||||
// Added v1.2.0
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
private List<String> frozenKeyImages = new ArrayList<>();
|
||||||
@Nullable
|
|
||||||
private NodeAddress refundAgentNodeAddress;
|
|
||||||
|
|
||||||
// Added in v1.5.3.
|
// Added in v1.5.3.
|
||||||
// If market price reaches that trigger price the offer gets deactivated
|
// If market price reaches that trigger price the offer gets deactivated
|
||||||
@ -87,6 +81,13 @@ public final class OpenOffer implements Tradable {
|
|||||||
state = State.AVAILABLE;
|
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
|
// PROTO BUFFER
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -94,14 +95,10 @@ public final class OpenOffer implements Tradable {
|
|||||||
private OpenOffer(Offer offer,
|
private OpenOffer(Offer offer,
|
||||||
State state,
|
State state,
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
@Nullable NodeAddress arbitratorNodeAddress,
|
||||||
@Nullable NodeAddress mediatorNodeAddress,
|
|
||||||
@Nullable NodeAddress refundAgentNodeAddress,
|
|
||||||
long triggerPrice) {
|
long triggerPrice) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
|
||||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
|
||||||
this.triggerPrice = triggerPrice;
|
this.triggerPrice = triggerPrice;
|
||||||
|
|
||||||
if (this.state == State.RESERVED)
|
if (this.state == State.RESERVED)
|
||||||
@ -113,22 +110,21 @@ public final class OpenOffer implements Tradable {
|
|||||||
protobuf.OpenOffer.Builder builder = protobuf.OpenOffer.newBuilder()
|
protobuf.OpenOffer.Builder builder = protobuf.OpenOffer.newBuilder()
|
||||||
.setOffer(offer.toProtoMessage())
|
.setOffer(offer.toProtoMessage())
|
||||||
.setTriggerPrice(triggerPrice)
|
.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(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();
|
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tradable fromProto(protobuf.OpenOffer proto) {
|
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()),
|
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
|
||||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
|
||||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
|
||||||
proto.getTriggerPrice());
|
proto.getTriggerPrice());
|
||||||
|
openOffer.setFrozenKeyImages(proto.getFrozenKeyImagesList());
|
||||||
|
return openOffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -192,8 +188,6 @@ public final class OpenOffer implements Tradable {
|
|||||||
",\n offer=" + offer +
|
",\n offer=" + offer +
|
||||||
",\n state=" + state +
|
",\n state=" + state +
|
||||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
|
||||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
|
||||||
",\n triggerPrice=" + triggerPrice +
|
",\n triggerPrice=" + triggerPrice +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
|
@ -29,18 +29,23 @@ import bisq.core.locale.Res;
|
|||||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
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.PlaceOfferModel;
|
||||||
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
|
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
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.mediation.mediator.MediatorManager;
|
||||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||||
import bisq.core.trade.TradableList;
|
import bisq.core.trade.TradableList;
|
||||||
|
import bisq.core.trade.TradeUtils;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.closed.ClosedTradableManager;
|
||||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
import bisq.core.util.ParsingUtils;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.AckMessage;
|
import bisq.network.p2p.AckMessage;
|
||||||
@ -53,7 +58,6 @@ import bisq.network.p2p.P2PService;
|
|||||||
import bisq.network.p2p.SendDirectMessageListener;
|
import bisq.network.p2p.SendDirectMessageListener;
|
||||||
import bisq.network.p2p.peers.Broadcaster;
|
import bisq.network.p2p.peers.Broadcaster;
|
||||||
import bisq.network.p2p.peers.PeerManager;
|
import bisq.network.p2p.peers.PeerManager;
|
||||||
|
|
||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
@ -61,26 +65,28 @@ import bisq.common.app.Capability;
|
|||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.crypto.Sig;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
import bisq.common.persistence.PersistenceManager;
|
import bisq.common.persistence.PersistenceManager;
|
||||||
import bisq.common.proto.network.NetworkEnvelope;
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
|
import bisq.common.util.Utilities;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -88,7 +94,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import monero.daemon.model.MoneroOutput;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -119,13 +125,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
private final ArbitratorManager arbitratorManager;
|
private final ArbitratorManager arbitratorManager;
|
||||||
private final MediatorManager mediatorManager;
|
private final MediatorManager mediatorManager;
|
||||||
private final RefundAgentManager refundAgentManager;
|
|
||||||
private final DaoFacade daoFacade;
|
private final DaoFacade daoFacade;
|
||||||
private final FilterManager filterManager;
|
private final FilterManager filterManager;
|
||||||
private final Broadcaster broadcaster;
|
private final Broadcaster broadcaster;
|
||||||
private final PersistenceManager<TradableList<OpenOffer>> persistenceManager;
|
private final PersistenceManager<TradableList<OpenOffer>> persistenceManager;
|
||||||
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
|
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
|
||||||
private final TradableList<OpenOffer> openOffers = new TradableList<>();
|
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 boolean stopped;
|
||||||
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
|
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
|
||||||
@Getter
|
@Getter
|
||||||
@ -157,7 +165,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
DaoFacade daoFacade,
|
DaoFacade daoFacade,
|
||||||
FilterManager filterManager,
|
FilterManager filterManager,
|
||||||
Broadcaster broadcaster,
|
Broadcaster broadcaster,
|
||||||
PersistenceManager<TradableList<OpenOffer>> persistenceManager) {
|
PersistenceManager<TradableList<OpenOffer>> persistenceManager,
|
||||||
|
PersistenceManager<SignedOfferList> signedOfferPersistenceManager) {
|
||||||
this.coreContext = coreContext;
|
this.coreContext = coreContext;
|
||||||
this.createOfferService = createOfferService;
|
this.createOfferService = createOfferService;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
@ -174,23 +183,32 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
this.arbitratorManager = arbitratorManager;
|
this.arbitratorManager = arbitratorManager;
|
||||||
this.mediatorManager = mediatorManager;
|
this.mediatorManager = mediatorManager;
|
||||||
this.refundAgentManager = refundAgentManager;
|
|
||||||
this.daoFacade = daoFacade;
|
this.daoFacade = daoFacade;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
this.broadcaster = broadcaster;
|
this.broadcaster = broadcaster;
|
||||||
this.persistenceManager = persistenceManager;
|
this.persistenceManager = persistenceManager;
|
||||||
|
this.signedOfferPersistenceManager = signedOfferPersistenceManager;
|
||||||
|
|
||||||
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
|
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
|
||||||
|
this.signedOfferPersistenceManager.initialize(signedOffers, "SignedOffers", PersistenceManager.Source.PRIVATE); // arbitrator stores reserve tx for signed offers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readPersisted(Runnable completeHandler) {
|
public void readPersisted(Runnable completeHandler) {
|
||||||
|
|
||||||
|
// read open offers
|
||||||
persistenceManager.readPersisted(persisted -> {
|
persistenceManager.readPersisted(persisted -> {
|
||||||
openOffers.setAll(persisted.getList());
|
openOffers.setAll(persisted.getList());
|
||||||
openOffers.forEach(openOffer -> openOffer.getOffer().setPriceFeedService(priceFeedService));
|
openOffers.forEach(openOffer -> openOffer.getOffer().setPriceFeedService(priceFeedService));
|
||||||
|
|
||||||
|
// read signed offers
|
||||||
|
signedOfferPersistenceManager.readPersisted(signedOfferPersisted -> {
|
||||||
|
signedOffers.setAll(signedOfferPersisted.getList());
|
||||||
completeHandler.run();
|
completeHandler.run();
|
||||||
},
|
},
|
||||||
completeHandler);
|
completeHandler);
|
||||||
|
},
|
||||||
|
completeHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
@ -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.
|
// 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
|
// A basic sig check is in done also at decryption time
|
||||||
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
|
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);
|
handleOfferAvailabilityRequest((OfferAvailabilityRequest) networkEnvelope, peerNodeAddress);
|
||||||
} else if (networkEnvelope instanceof AckMessage) {
|
} else if (networkEnvelope instanceof AckMessage) {
|
||||||
AckMessage ackMessage = (AckMessage) networkEnvelope;
|
AckMessage ackMessage = (AckMessage) networkEnvelope;
|
||||||
@ -381,23 +403,35 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
buyerSecurityDeposit,
|
buyerSecurityDeposit,
|
||||||
createOfferService.getSellerSecurityDepositAsDouble(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,
|
PlaceOfferModel model = new PlaceOfferModel(offer,
|
||||||
reservedFundsForOffer,
|
reservedFundsForOffer,
|
||||||
useSavingsWallet,
|
useSavingsWallet,
|
||||||
|
p2PService,
|
||||||
btcWalletService,
|
btcWalletService,
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
tradeWalletService,
|
tradeWalletService,
|
||||||
bsqWalletService,
|
bsqWalletService,
|
||||||
offerBookService,
|
offerBookService,
|
||||||
arbitratorManager,
|
arbitratorManager,
|
||||||
|
mediatorManager,
|
||||||
tradeStatisticsManager,
|
tradeStatisticsManager,
|
||||||
daoFacade,
|
daoFacade,
|
||||||
user,
|
user,
|
||||||
|
keyRing,
|
||||||
filterManager);
|
filterManager);
|
||||||
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
|
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
|
||||||
model,
|
model,
|
||||||
transaction -> {
|
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);
|
openOffers.add(openOffer);
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
resultHandler.handleResult(transaction);
|
resultHandler.handleResult(transaction);
|
||||||
@ -410,7 +444,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
},
|
},
|
||||||
errorMessageHandler
|
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
|
// Remove from offerbook
|
||||||
@ -591,6 +627,127 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
|
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
|
// OfferPayload Availability
|
||||||
@ -606,7 +763,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
if (!p2PService.isBootstrapped()) {
|
if (!p2PService.isBootstrapped()) {
|
||||||
errorMessage = "We got a handleOfferAvailabilityRequest but we have not bootstrapped yet.";
|
errorMessage = "We got a handleOfferAvailabilityRequest but we have not bootstrapped yet.";
|
||||||
log.info(errorMessage);
|
log.info(errorMessage);
|
||||||
sendAckMessage(request, peer, false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,14 +771,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
|
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
|
||||||
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
||||||
log.info(errorMessage);
|
log.info(errorMessage);
|
||||||
sendAckMessage(request, peer, false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
|
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
|
||||||
log.debug(errorMessage);
|
log.debug(errorMessage);
|
||||||
sendAckMessage(request, peer, false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,25 +788,32 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
|
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
|
||||||
log.warn(errorMessage);
|
log.warn(errorMessage);
|
||||||
sendAckMessage(request, peer, false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
||||||
AvailabilityResult availabilityResult;
|
AvailabilityResult availabilityResult;
|
||||||
|
String makerSignature = null;
|
||||||
NodeAddress arbitratorNodeAddress = null;
|
NodeAddress arbitratorNodeAddress = null;
|
||||||
NodeAddress mediatorNodeAddress = null;
|
|
||||||
NodeAddress refundAgentNodeAddress = null;
|
|
||||||
if (openOfferOptional.isPresent()) {
|
if (openOfferOptional.isPresent()) {
|
||||||
OpenOffer openOffer = openOfferOptional.get();
|
OpenOffer openOffer = openOfferOptional.get();
|
||||||
if (!apiUserDeniedByOffer(request)) {
|
if (!apiUserDeniedByOffer(request)) {
|
||||||
|
if (!takerDeniedByMaker(request)) {
|
||||||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||||
arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
|
|
||||||
|
// 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);
|
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 {
|
try {
|
||||||
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
|
// 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
|
// in trade price between the peers. Also here poor connectivity might cause market price API connection
|
||||||
@ -676,6 +840,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
} else {
|
} else {
|
||||||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
availabilityResult = AvailabilityResult.MAKER_DENIED_TAKER;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER;
|
availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER;
|
||||||
}
|
}
|
||||||
@ -692,9 +859,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
|
|
||||||
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
|
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
|
||||||
availabilityResult,
|
availabilityResult,
|
||||||
arbitratorNodeAddress,
|
makerSignature,
|
||||||
mediatorNodeAddress,
|
arbitratorNodeAddress);
|
||||||
refundAgentNodeAddress);
|
|
||||||
log.info("Send {} with offerId {} and uid {} to peer {}",
|
log.info("Send {} with offerId {} and uid {} to peer {}",
|
||||||
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
|
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
|
||||||
offerAvailabilityResponse.getUid(), peer);
|
offerAvailabilityResponse.getUid(), peer);
|
||||||
@ -725,7 +891,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
log.error(errorMessage);
|
log.error(errorMessage);
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
sendAckMessage(request, peer, result, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,45 +899,49 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
return preferences.isDenyApiTaker() && request.isTakerApiUser();
|
return preferences.isDenyApiTaker() && request.isTakerApiUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAckMessage(OfferAvailabilityRequest message,
|
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(Class<?> reqClass,
|
||||||
NodeAddress sender,
|
NodeAddress sender,
|
||||||
|
PubKeyRing senderPubKeyRing,
|
||||||
|
String offerId,
|
||||||
|
String uid,
|
||||||
boolean result,
|
boolean result,
|
||||||
String errorMessage) {
|
String errorMessage) {
|
||||||
String offerId = message.getOfferId();
|
String sourceUid = uid;
|
||||||
String sourceUid = message.getUid();
|
|
||||||
AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(),
|
AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(),
|
||||||
AckMessageSourceType.OFFER_MESSAGE,
|
AckMessageSourceType.OFFER_MESSAGE,
|
||||||
message.getClass().getSimpleName(),
|
reqClass.getSimpleName(),
|
||||||
sourceUid,
|
sourceUid,
|
||||||
offerId,
|
offerId,
|
||||||
result,
|
result,
|
||||||
errorMessage);
|
errorMessage);
|
||||||
|
|
||||||
final NodeAddress takersNodeAddress = sender;
|
log.info("Send AckMessage for {} to peer {} with offerId {} and sourceUid {}",
|
||||||
PubKeyRing takersPubKeyRing = message.getPubKeyRing();
|
reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid());
|
||||||
log.info("Send AckMessage for OfferAvailabilityRequest to peer {} with offerId {} and sourceUid {}",
|
|
||||||
takersNodeAddress, offerId, ackMessage.getSourceUid());
|
|
||||||
p2PService.sendEncryptedDirectMessage(
|
p2PService.sendEncryptedDirectMessage(
|
||||||
takersNodeAddress,
|
sender,
|
||||||
takersPubKeyRing,
|
senderPubKeyRing,
|
||||||
ackMessage,
|
ackMessage,
|
||||||
new SendDirectMessageListener() {
|
new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
public void onArrived() {
|
||||||
log.info("AckMessage for OfferAvailabilityRequest arrived at takersNodeAddress {}. offerId={}, sourceUid={}",
|
log.info("AckMessage for {} arrived at sender {}. offerId={}, sourceUid={}",
|
||||||
takersNodeAddress, offerId, ackMessage.getSourceUid());
|
reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFault(String errorMessage) {
|
public void onFault(String errorMessage) {
|
||||||
log.error("AckMessage for OfferAvailabilityRequest failed. AckMessage={}, takersNodeAddress={}, errorMessage={}",
|
log.error("AckMessage for {} failed. AckMessage={}, sender={}, errorMessage={}",
|
||||||
ackMessage, takersNodeAddress, errorMessage);
|
reqClass.getSimpleName(), ackMessage, sender, errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Update persisted offer if a new capability is required after a software update
|
// 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.getMinAmount(),
|
||||||
originalOfferPayload.getBaseCurrencyCode(),
|
originalOfferPayload.getBaseCurrencyCode(),
|
||||||
originalOfferPayload.getCounterCurrencyCode(),
|
originalOfferPayload.getCounterCurrencyCode(),
|
||||||
originalOfferPayload.getArbitratorNodeAddresses(),
|
|
||||||
originalOfferPayload.getMediatorNodeAddresses(),
|
|
||||||
originalOfferPayload.getPaymentMethodId(),
|
originalOfferPayload.getPaymentMethodId(),
|
||||||
originalOfferPayload.getMakerPaymentAccountId(),
|
originalOfferPayload.getMakerPaymentAccountId(),
|
||||||
originalOfferPayload.getOfferFeePaymentTxId(),
|
originalOfferPayload.getOfferFeePaymentTxId(),
|
||||||
@ -863,7 +1031,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
originalOfferPayload.isPrivateOffer(),
|
originalOfferPayload.isPrivateOffer(),
|
||||||
originalOfferPayload.getHashOfChallenge(),
|
originalOfferPayload.getHashOfChallenge(),
|
||||||
updatedExtraDataMap,
|
updatedExtraDataMap,
|
||||||
protocolVersion);
|
protocolVersion,
|
||||||
|
originalOfferPayload.getArbitratorNodeAddress(),
|
||||||
|
originalOfferPayload.getArbitratorSignature());
|
||||||
|
|
||||||
// Save states from original data to use for the updated
|
// Save states from original data to use for the updated
|
||||||
Offer.State originalOfferState = originalOffer.getState();
|
Offer.State originalOfferState = originalOffer.getState();
|
||||||
@ -1024,6 +1194,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
|
|
||||||
private void requestPersistence() {
|
private void requestPersistence() {
|
||||||
persistenceManager.requestPersistence();
|
persistenceManager.requestPersistence();
|
||||||
|
signedOfferPersistenceManager.requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
79
core/src/main/java/bisq/core/offer/SignedOffer.java
Normal file
79
core/src/main/java/bisq/core/offer/SignedOffer.java
Normal 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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
74
core/src/main/java/bisq/core/offer/SignedOfferList.java
Normal file
74
core/src/main/java/bisq/core/offer/SignedOfferList.java
Normal 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}";
|
||||||
|
}
|
||||||
|
}
|
@ -17,9 +17,12 @@
|
|||||||
|
|
||||||
package bisq.core.offer.availability;
|
package bisq.core.offer.availability;
|
||||||
|
|
||||||
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||||
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
@ -40,6 +43,8 @@ public class OfferAvailabilityModel implements Model {
|
|||||||
@Getter
|
@Getter
|
||||||
private final PubKeyRing pubKeyRing; // takers PubKey (my pubkey)
|
private final PubKeyRing pubKeyRing; // takers PubKey (my pubkey)
|
||||||
@Getter
|
@Getter
|
||||||
|
private final XmrWalletService xmrWalletService;
|
||||||
|
@Getter
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
@Getter
|
@Getter
|
||||||
final private User user;
|
final private User user;
|
||||||
@ -49,22 +54,20 @@ public class OfferAvailabilityModel implements Model {
|
|||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
private NodeAddress peerNodeAddress; // maker
|
private NodeAddress peerNodeAddress; // maker
|
||||||
private OfferAvailabilityResponse message;
|
private OfferAvailabilityResponse message;
|
||||||
|
@Getter
|
||||||
|
private String paymentAccountId;
|
||||||
|
@Getter
|
||||||
|
private OfferUtil offerUtil;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private InitTradeRequest tradeRequest;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
private NodeAddress selectedArbitrator;
|
private String makerSignature;
|
||||||
|
|
||||||
// Added in v1.1.6
|
|
||||||
@Nullable
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
private NodeAddress selectedMediator;
|
private NodeAddress arbitratorNodeAddress;
|
||||||
|
|
||||||
// Added in v1.2.0
|
|
||||||
@Nullable
|
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
private NodeAddress selectedRefundAgent;
|
|
||||||
|
|
||||||
// Added in v1.5.5
|
// Added in v1.5.5
|
||||||
@Getter
|
@Getter
|
||||||
@ -72,18 +75,24 @@ public class OfferAvailabilityModel implements Model {
|
|||||||
|
|
||||||
public OfferAvailabilityModel(Offer offer,
|
public OfferAvailabilityModel(Offer offer,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
|
XmrWalletService xmrWalletService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
User user,
|
User user,
|
||||||
MediatorManager mediatorManager,
|
MediatorManager mediatorManager,
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
boolean isTakerApiUser) {
|
boolean isTakerApiUser,
|
||||||
|
String paymentAccountId,
|
||||||
|
OfferUtil offerUtil) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.mediatorManager = mediatorManager;
|
this.mediatorManager = mediatorManager;
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
this.isTakerApiUser = isTakerApiUser;
|
this.isTakerApiUser = isTakerApiUser;
|
||||||
|
this.paymentAccountId = paymentAccountId;
|
||||||
|
this.offerUtil = offerUtil;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeAddress getPeerNodeAddress() {
|
public NodeAddress getPeerNodeAddress() {
|
||||||
|
@ -19,18 +19,16 @@ package bisq.core.offer.availability.tasks;
|
|||||||
|
|
||||||
import bisq.core.offer.AvailabilityResult;
|
import bisq.core.offer.AvailabilityResult;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
|
||||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||||
|
import bisq.core.trade.TradeUtils;
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
|
|
||||||
import bisq.common.taskrunner.Task;
|
import bisq.common.taskrunner.Task;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityModel> {
|
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");
|
checkArgument(offer.getState() != Offer.State.REMOVED, "Offer state must not be Offer.State.REMOVED");
|
||||||
|
|
||||||
|
// check availability result
|
||||||
OfferAvailabilityResponse offerAvailabilityResponse = model.getMessage();
|
OfferAvailabilityResponse offerAvailabilityResponse = model.getMessage();
|
||||||
|
|
||||||
if (offerAvailabilityResponse.getAvailabilityResult() != AvailabilityResult.AVAILABLE) {
|
if (offerAvailabilityResponse.getAvailabilityResult() != AvailabilityResult.AVAILABLE) {
|
||||||
offer.setState(Offer.State.NOT_AVAILABLE);
|
offer.setState(Offer.State.NOT_AVAILABLE);
|
||||||
failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.getAvailabilityResult());
|
failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.getAvailabilityResult());
|
||||||
return;
|
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);
|
offer.setState(Offer.State.AVAILABLE);
|
||||||
|
|
||||||
model.setSelectedArbitrator(offerAvailabilityResponse.getArbitrator());
|
model.setMakerSignature(offerAvailabilityResponse.getMakerSignature());
|
||||||
|
model.setArbitratorNodeAddress(offerAvailabilityResponse.getArbitratorNodeAddress());
|
||||||
NodeAddress mediator = offerAvailabilityResponse.getMediator();
|
checkNotNull(model.getMakerSignature());
|
||||||
if (mediator == null) {
|
checkNotNull(model.getArbitratorNodeAddress());
|
||||||
// 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());
|
|
||||||
|
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
@ -17,12 +17,21 @@
|
|||||||
|
|
||||||
package bisq.core.offer.availability.tasks;
|
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.Offer;
|
||||||
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
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 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.Task;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
@ -39,8 +48,48 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
|||||||
try {
|
try {
|
||||||
runInterceptHook();
|
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(),
|
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 {}",
|
log.info("Send {} with offerId {} and uid {} to peer {}",
|
||||||
message.getClass().getSimpleName(), message.getOfferId(),
|
message.getClass().getSimpleName(), message.getOfferId(),
|
||||||
message.getUid(), model.getPeerNodeAddress());
|
message.getUid(), model.getPeerNodeAddress());
|
||||||
|
@ -22,7 +22,8 @@ import bisq.network.p2p.SupportedCapabilitiesMessage;
|
|||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -43,18 +44,21 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final Capabilities supportedCapabilities;
|
private final Capabilities supportedCapabilities;
|
||||||
private final boolean isTakerApiUser;
|
private final boolean isTakerApiUser;
|
||||||
|
private final InitTradeRequest tradeRequest;
|
||||||
|
|
||||||
public OfferAvailabilityRequest(String offerId,
|
public OfferAvailabilityRequest(String offerId,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
long takersTradePrice,
|
long takersTradePrice,
|
||||||
boolean isTakerApiUser) {
|
boolean isTakerApiUser,
|
||||||
|
InitTradeRequest tradeRequest) {
|
||||||
this(offerId,
|
this(offerId,
|
||||||
pubKeyRing,
|
pubKeyRing,
|
||||||
takersTradePrice,
|
takersTradePrice,
|
||||||
isTakerApiUser,
|
isTakerApiUser,
|
||||||
Capabilities.app,
|
Capabilities.app,
|
||||||
Version.getP2PMessageVersion(),
|
Version.getP2PMessageVersion(),
|
||||||
UUID.randomUUID().toString());
|
UUID.randomUUID().toString(),
|
||||||
|
tradeRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -68,21 +72,33 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||||||
boolean isTakerApiUser,
|
boolean isTakerApiUser,
|
||||||
@Nullable Capabilities supportedCapabilities,
|
@Nullable Capabilities supportedCapabilities,
|
||||||
int messageVersion,
|
int messageVersion,
|
||||||
@Nullable String uid) {
|
@Nullable String uid,
|
||||||
|
InitTradeRequest tradeRequest) {
|
||||||
super(messageVersion, offerId, uid);
|
super(messageVersion, offerId, uid);
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
this.takersTradePrice = takersTradePrice;
|
this.takersTradePrice = takersTradePrice;
|
||||||
this.isTakerApiUser = isTakerApiUser;
|
this.isTakerApiUser = isTakerApiUser;
|
||||||
this.supportedCapabilities = supportedCapabilities;
|
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
|
@Override
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
final protobuf.OfferAvailabilityRequest.Builder builder = protobuf.OfferAvailabilityRequest.newBuilder()
|
final protobuf.OfferAvailabilityRequest.Builder builder = protobuf.OfferAvailabilityRequest.newBuilder()
|
||||||
.setOfferId(offerId)
|
.setOfferId(offerId)
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||||
.setTakersTradePrice(takersTradePrice)
|
.setTakersTradePrice(takersTradePrice)
|
||||||
.setIsTakerApiUser(isTakerApiUser);
|
.setIsTakerApiUser(isTakerApiUser)
|
||||||
|
.setTradeRequest(tradeRequest.toProtoNetworkEnvelope().getInitTradeRequest());
|
||||||
|
|
||||||
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
||||||
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
||||||
@ -92,13 +108,14 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
|||||||
.build();
|
.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(),
|
return new OfferAvailabilityRequest(proto.getOfferId(),
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||||
proto.getTakersTradePrice(),
|
proto.getTakersTradePrice(),
|
||||||
proto.getIsTakerApiUser(),
|
proto.getIsTakerApiUser(),
|
||||||
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
||||||
messageVersion,
|
messageVersion,
|
||||||
proto.getUid().isEmpty() ? null : proto.getUid());
|
proto.getUid().isEmpty() ? null : proto.getUid(),
|
||||||
|
InitTradeRequest.fromProto(proto.getTradeRequest(), coreProtoResolver, messageVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,28 +44,21 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final Capabilities supportedCapabilities;
|
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
|
@Nullable
|
||||||
private final NodeAddress mediator;
|
private final String makerSignature;
|
||||||
|
private final NodeAddress arbitratorNodeAddress;
|
||||||
// Added v1.2.0
|
|
||||||
@Nullable
|
|
||||||
private final NodeAddress refundAgent;
|
|
||||||
|
|
||||||
public OfferAvailabilityResponse(String offerId,
|
public OfferAvailabilityResponse(String offerId,
|
||||||
AvailabilityResult availabilityResult,
|
AvailabilityResult availabilityResult,
|
||||||
NodeAddress arbitrator,
|
String makerSignature,
|
||||||
NodeAddress mediator,
|
NodeAddress arbitratorNodeAddress) {
|
||||||
NodeAddress refundAgent) {
|
|
||||||
this(offerId,
|
this(offerId,
|
||||||
availabilityResult,
|
availabilityResult,
|
||||||
Capabilities.app,
|
Capabilities.app,
|
||||||
Version.getP2PMessageVersion(),
|
Version.getP2PMessageVersion(),
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
arbitrator,
|
makerSignature,
|
||||||
mediator,
|
arbitratorNodeAddress);
|
||||||
refundAgent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -78,28 +71,25 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
|||||||
@Nullable Capabilities supportedCapabilities,
|
@Nullable Capabilities supportedCapabilities,
|
||||||
int messageVersion,
|
int messageVersion,
|
||||||
@Nullable String uid,
|
@Nullable String uid,
|
||||||
NodeAddress arbitrator,
|
String makerSignature,
|
||||||
@Nullable NodeAddress mediator,
|
NodeAddress arbitratorNodeAddress) {
|
||||||
@Nullable NodeAddress refundAgent) {
|
|
||||||
super(messageVersion, offerId, uid);
|
super(messageVersion, offerId, uid);
|
||||||
this.availabilityResult = availabilityResult;
|
this.availabilityResult = availabilityResult;
|
||||||
this.supportedCapabilities = supportedCapabilities;
|
this.supportedCapabilities = supportedCapabilities;
|
||||||
this.arbitrator = arbitrator;
|
this.makerSignature = makerSignature;
|
||||||
this.mediator = mediator;
|
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||||
this.refundAgent = refundAgent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
|
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
|
||||||
.setOfferId(offerId)
|
.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(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
||||||
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
||||||
Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage()));
|
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
|
||||||
Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage()));
|
|
||||||
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage()));
|
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder()
|
return getNetworkEnvelopeBuilder()
|
||||||
.setOfferAvailabilityResponse(builder)
|
.setOfferAvailabilityResponse(builder)
|
||||||
@ -112,8 +102,7 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
|||||||
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
||||||
messageVersion,
|
messageVersion,
|
||||||
proto.getUid().isEmpty() ? null : proto.getUid(),
|
proto.getUid().isEmpty() ? null : proto.getUid(),
|
||||||
proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null,
|
proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(),
|
||||||
proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null,
|
NodeAddress.fromProto(proto.getArbitratorNodeAddress()));
|
||||||
proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -25,12 +25,16 @@ import bisq.core.dao.DaoFacade;
|
|||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferBookService;
|
import bisq.core.offer.OfferBookService;
|
||||||
|
import bisq.core.offer.messages.SignOfferResponse;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
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.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.taskrunner.Model;
|
import bisq.common.taskrunner.Model;
|
||||||
|
|
||||||
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
|
||||||
@ -49,15 +53,18 @@ public class PlaceOfferModel implements Model {
|
|||||||
private final Offer offer;
|
private final Offer offer;
|
||||||
private final Coin reservedFundsForOffer;
|
private final Coin reservedFundsForOffer;
|
||||||
private final boolean useSavingsWallet;
|
private final boolean useSavingsWallet;
|
||||||
|
private final P2PService p2PService;
|
||||||
private final BtcWalletService walletService;
|
private final BtcWalletService walletService;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final TradeWalletService tradeWalletService;
|
private final TradeWalletService tradeWalletService;
|
||||||
private final BsqWalletService bsqWalletService;
|
private final BsqWalletService bsqWalletService;
|
||||||
private final OfferBookService offerBookService;
|
private final OfferBookService offerBookService;
|
||||||
private final ArbitratorManager arbitratorManager;
|
private final ArbitratorManager arbitratorManager;
|
||||||
|
private final MediatorManager mediatorManager;
|
||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
private final DaoFacade daoFacade;
|
private final DaoFacade daoFacade;
|
||||||
private final User user;
|
private final User user;
|
||||||
|
private final KeyRing keyRing;
|
||||||
@Getter
|
@Getter
|
||||||
private final FilterManager filterManager;
|
private final FilterManager filterManager;
|
||||||
|
|
||||||
@ -67,33 +74,41 @@ public class PlaceOfferModel implements Model {
|
|||||||
@Setter
|
@Setter
|
||||||
private Transaction transaction;
|
private Transaction transaction;
|
||||||
@Setter
|
@Setter
|
||||||
private MoneroTxWallet xmrTransaction;
|
private MoneroTxWallet reserveTx;
|
||||||
|
@Setter
|
||||||
|
private SignOfferResponse signOfferResponse;
|
||||||
|
|
||||||
public PlaceOfferModel(Offer offer,
|
public PlaceOfferModel(Offer offer,
|
||||||
Coin reservedFundsForOffer,
|
Coin reservedFundsForOffer,
|
||||||
boolean useSavingsWallet,
|
boolean useSavingsWallet,
|
||||||
|
P2PService p2PService,
|
||||||
BtcWalletService walletService,
|
BtcWalletService walletService,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
BsqWalletService bsqWalletService,
|
BsqWalletService bsqWalletService,
|
||||||
OfferBookService offerBookService,
|
OfferBookService offerBookService,
|
||||||
ArbitratorManager arbitratorManager,
|
ArbitratorManager arbitratorManager,
|
||||||
|
MediatorManager mediatorManager,
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
DaoFacade daoFacade,
|
DaoFacade daoFacade,
|
||||||
User user,
|
User user,
|
||||||
|
KeyRing keyRing,
|
||||||
FilterManager filterManager) {
|
FilterManager filterManager) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.reservedFundsForOffer = reservedFundsForOffer;
|
this.reservedFundsForOffer = reservedFundsForOffer;
|
||||||
this.useSavingsWallet = useSavingsWallet;
|
this.useSavingsWallet = useSavingsWallet;
|
||||||
|
this.p2PService = p2PService;
|
||||||
this.walletService = walletService;
|
this.walletService = walletService;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.tradeWalletService = tradeWalletService;
|
this.tradeWalletService = tradeWalletService;
|
||||||
this.bsqWalletService = bsqWalletService;
|
this.bsqWalletService = bsqWalletService;
|
||||||
this.offerBookService = offerBookService;
|
this.offerBookService = offerBookService;
|
||||||
this.arbitratorManager = arbitratorManager;
|
this.arbitratorManager = arbitratorManager;
|
||||||
|
this.mediatorManager = mediatorManager;
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
this.daoFacade = daoFacade;
|
this.daoFacade = daoFacade;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.keyRing = keyRing;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,12 +17,14 @@
|
|||||||
|
|
||||||
package bisq.core.offer.placeoffer;
|
package bisq.core.offer.placeoffer;
|
||||||
|
|
||||||
|
import bisq.core.offer.messages.SignOfferResponse;
|
||||||
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
|
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.offer.placeoffer.tasks.ValidateOffer;
|
||||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
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.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
@ -55,15 +57,43 @@ public class PlaceOfferProtocol {
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void placeOffer() {
|
public void placeOffer() {
|
||||||
log.debug("model.offer.id" + model.getOffer().getId());
|
log.debug("placeOffer() " + model.getOffer().getId());
|
||||||
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
|
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
|
||||||
() -> {
|
() -> {
|
||||||
log.debug("sequence at handleRequestTakeOfferMessage completed");
|
log.debug("sequence at placeOffer completed");
|
||||||
resultHandler.handleResult(model.getTransaction());
|
|
||||||
},
|
},
|
||||||
(errorMessage) -> {
|
(errorMessage) -> {
|
||||||
log.error(errorMessage);
|
log.error(errorMessage);
|
||||||
|
model.getOffer().setErrorMessage(errorMessage);
|
||||||
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
taskRunner.addTasks(
|
||||||
|
ValidateOffer.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()) {
|
if (model.isOfferAddedToOfferBook()) {
|
||||||
model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(),
|
model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(),
|
||||||
() -> {
|
() -> {
|
||||||
@ -77,9 +107,7 @@ public class PlaceOfferProtocol {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
taskRunner.addTasks(
|
taskRunner.addTasks(
|
||||||
ValidateOffer.class,
|
MakerProcessesSignOfferResponse.class,
|
||||||
CheckNumberOfUnconfirmedTransactions.class,
|
|
||||||
MakerCreateFeeTx.class,
|
|
||||||
AddToOfferBook.class
|
AddToOfferBook.class
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package bisq.core.offer.placeoffer.tasks;
|
package bisq.core.offer.placeoffer.tasks;
|
||||||
|
|
||||||
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||||
|
|
||||||
import bisq.common.taskrunner.Task;
|
import bisq.common.taskrunner.Task;
|
||||||
@ -32,7 +33,7 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
|
|||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
model.getOfferBookService().addOffer(model.getOffer(),
|
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
|
||||||
() -> {
|
() -> {
|
||||||
model.setOfferAddedToOfferBook(true);
|
model.setOfferAddedToOfferBook(true);
|
||||||
complete();
|
complete();
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -90,7 +90,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
|||||||
model.setTransaction(transaction);
|
model.setTransaction(transaction);
|
||||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
||||||
|
|
||||||
model.getOffer().setState(Offer.State.OFFER_FEE_PAID);
|
model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED);
|
||||||
|
|
||||||
complete();
|
complete();
|
||||||
} else {
|
} else {
|
||||||
@ -137,7 +137,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
|||||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
||||||
|
|
||||||
log.debug("Successfully sent tx with id " + transaction.getTxId().toString());
|
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();
|
complete();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package bisq.core.payment.payload;
|
|||||||
|
|
||||||
import bisq.common.consensus.UsedForTradeContractJson;
|
import bisq.common.consensus.UsedForTradeContractJson;
|
||||||
import bisq.common.crypto.CryptoUtils;
|
import bisq.common.crypto.CryptoUtils;
|
||||||
|
import bisq.common.crypto.Hash;
|
||||||
import bisq.common.proto.network.NetworkPayload;
|
import bisq.common.proto.network.NetworkPayload;
|
||||||
import bisq.common.util.JsonExclude;
|
import bisq.common.util.JsonExclude;
|
||||||
import bisq.common.util.Utilities;
|
import bisq.common.util.Utilities;
|
||||||
@ -118,6 +119,10 @@ public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTr
|
|||||||
|
|
||||||
public abstract String getPaymentDetailsForTradePopup();
|
public abstract String getPaymentDetailsForTradePopup();
|
||||||
|
|
||||||
|
public byte[] getHash() {
|
||||||
|
return Hash.getRipemd160hash(this.toProtoMessage().toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getSalt() {
|
public byte[] getSalt() {
|
||||||
checkArgument(excludeFromJsonDataMap.containsKey(SALT), "Salt must have been set in excludeFromJsonDataMap.");
|
checkArgument(excludeFromJsonDataMap.containsKey(SALT), "Salt must have been set in excludeFromJsonDataMap.");
|
||||||
return Utilities.decodeFromHex(excludeFromJsonDataMap.get(SALT));
|
return Utilities.decodeFromHex(excludeFromJsonDataMap.get(SALT));
|
||||||
|
@ -39,6 +39,8 @@ import bisq.core.network.p2p.inventory.messages.GetInventoryResponse;
|
|||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
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.proto.CoreProtoResolver;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||||
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
|
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.CounterCurrencyTransferStartedMessage;
|
||||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
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.DepositTxAndDelayedPayoutTxMessage;
|
||||||
import bisq.core.trade.messages.DepositTxMessage;
|
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.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
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.MediatedPayoutTxPublishedMessage;
|
||||||
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||||
|
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||||
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
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.TraderSignedWitnessMessage;
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
import bisq.core.trade.messages.UpdateMultisigRequest;
|
||||||
import bisq.core.trade.messages.UpdateMultisigResponse;
|
import bisq.core.trade.messages.UpdateMultisigResponse;
|
||||||
@ -133,8 +138,13 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||||||
case PONG:
|
case PONG:
|
||||||
return Pong.fromProto(proto.getPong(), messageVersion);
|
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:
|
case OFFER_AVAILABILITY_REQUEST:
|
||||||
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), messageVersion);
|
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), this, messageVersion);
|
||||||
case OFFER_AVAILABILITY_RESPONSE:
|
case OFFER_AVAILABILITY_RESPONSE:
|
||||||
return OfferAvailabilityResponse.fromProto(proto.getOfferAvailabilityResponse(), messageVersion);
|
return OfferAvailabilityResponse.fromProto(proto.getOfferAvailabilityResponse(), messageVersion);
|
||||||
case REFRESH_OFFER_MESSAGE:
|
case REFRESH_OFFER_MESSAGE:
|
||||||
@ -157,16 +167,22 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||||||
return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion);
|
return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion);
|
||||||
case INIT_TRADE_REQUEST:
|
case INIT_TRADE_REQUEST:
|
||||||
return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion);
|
return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion);
|
||||||
case INIT_MULTISIG_MESSAGE:
|
case INIT_MULTISIG_REQUEST:
|
||||||
return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion);
|
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:
|
case UPDATE_MULTISIG_REQUEST:
|
||||||
return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
|
return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
|
||||||
case UPDATE_MULTISIG_RESPONSE:
|
case UPDATE_MULTISIG_RESPONSE:
|
||||||
return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion);
|
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:
|
case INPUTS_FOR_DEPOSIT_TX_REQUEST:
|
||||||
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
|
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
|
||||||
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
|
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
|
||||||
@ -265,6 +281,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public NetworkPayload fromProto(protobuf.StorageEntryWrapper proto) {
|
public NetworkPayload fromProto(protobuf.StorageEntryWrapper proto) {
|
||||||
if (proto != null) {
|
if (proto != null) {
|
||||||
switch (proto.getMessageCase()) {
|
switch (proto.getMessageCase()) {
|
||||||
@ -282,6 +299,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public NetworkPayload fromProto(protobuf.StoragePayload proto) {
|
public NetworkPayload fromProto(protobuf.StoragePayload proto) {
|
||||||
if (proto != null) {
|
if (proto != null) {
|
||||||
switch (proto.getMessageCase()) {
|
switch (proto.getMessageCase()) {
|
||||||
|
@ -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.model.governance.BallotList;
|
||||||
import bisq.core.dao.state.storage.DaoStateStore;
|
import bisq.core.dao.state.storage.DaoStateStore;
|
||||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputList;
|
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputList;
|
||||||
|
import bisq.core.offer.SignedOfferList;
|
||||||
import bisq.core.payment.PaymentAccountList;
|
import bisq.core.payment.PaymentAccountList;
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
|
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
|
||||||
@ -85,6 +86,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
|||||||
public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
|
public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
|
||||||
if (proto != null) {
|
if (proto != null) {
|
||||||
switch (proto.getMessageCase()) {
|
switch (proto.getMessageCase()) {
|
||||||
|
case SIGNED_OFFER_LIST:
|
||||||
|
return SignedOfferList.fromProto(proto.getSignedOfferList());
|
||||||
case SEQUENCE_NUMBER_MAP:
|
case SEQUENCE_NUMBER_MAP:
|
||||||
return SequenceNumberMap.fromProto(proto.getSequenceNumberMap());
|
return SequenceNumberMap.fromProto(proto.getSequenceNumberMap());
|
||||||
case PEER_LIST:
|
case PEER_LIST:
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package bisq.core.support.dispute;
|
package bisq.core.support.dispute;
|
||||||
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
import bisq.core.support.SupportType;
|
import bisq.core.support.SupportType;
|
||||||
import bisq.core.support.messages.ChatMessage;
|
import bisq.core.support.messages.ChatMessage;
|
||||||
@ -128,9 +129,6 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private String delayedPayoutTxId;
|
private String delayedPayoutTxId;
|
||||||
|
|
||||||
// Added for XMR integration
|
|
||||||
private boolean isOpener;
|
|
||||||
|
|
||||||
// Added at v1.4.0
|
// Added at v1.4.0
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -145,6 +143,13 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
@Setter
|
@Setter
|
||||||
private Map<String, String> extraDataMap;
|
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.
|
// We do not persist uid, it is only used by dispute agents to guarantee an uid.
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -178,6 +183,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
String contractAsJson,
|
String contractAsJson,
|
||||||
@Nullable String makerContractSignature,
|
@Nullable String makerContractSignature,
|
||||||
@Nullable String takerContractSignature,
|
@Nullable String takerContractSignature,
|
||||||
|
@Nullable PaymentAccountPayload makerPaymentAccountPayload,
|
||||||
|
@Nullable PaymentAccountPayload takerPaymentAccountPayload,
|
||||||
PubKeyRing agentPubKeyRing,
|
PubKeyRing agentPubKeyRing,
|
||||||
boolean isSupportTicket,
|
boolean isSupportTicket,
|
||||||
SupportType supportType) {
|
SupportType supportType) {
|
||||||
@ -199,6 +206,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
this.contractAsJson = contractAsJson;
|
this.contractAsJson = contractAsJson;
|
||||||
this.makerContractSignature = makerContractSignature;
|
this.makerContractSignature = makerContractSignature;
|
||||||
this.takerContractSignature = takerContractSignature;
|
this.takerContractSignature = takerContractSignature;
|
||||||
|
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
|
||||||
|
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
|
||||||
this.agentPubKeyRing = agentPubKeyRing;
|
this.agentPubKeyRing = agentPubKeyRing;
|
||||||
this.isSupportTicket = isSupportTicket;
|
this.isSupportTicket = isSupportTicket;
|
||||||
this.supportType = supportType;
|
this.supportType = supportType;
|
||||||
@ -246,6 +255,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
Optional.ofNullable(disputePayoutTxId).ifPresent(builder::setDisputePayoutTxId);
|
Optional.ofNullable(disputePayoutTxId).ifPresent(builder::setDisputePayoutTxId);
|
||||||
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
|
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
|
||||||
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
|
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(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage()));
|
||||||
Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
|
Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
|
||||||
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
|
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
|
||||||
@ -274,6 +285,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
proto.getContractAsJson(),
|
proto.getContractAsJson(),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
|
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
|
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
|
||||||
|
proto.hasMakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()) : null,
|
||||||
|
proto.hasTakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()) : null,
|
||||||
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
|
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
|
||||||
proto.getIsSupportTicket(),
|
proto.getIsSupportTicket(),
|
||||||
SupportType.fromProto(proto.getSupportType()));
|
SupportType.fromProto(proto.getSupportType()));
|
||||||
@ -449,6 +462,16 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
|
||||||
|
return contract.isBuyerMakerAndSellerTaker() ? makerPaymentAccountPayload : takerPaymentAccountPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public PaymentAccountPayload getSellerPaymentAccountPayload() {
|
||||||
|
return contract.isBuyerMakerAndSellerTaker() ? takerPaymentAccountPayload : makerPaymentAccountPayload;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Dispute{" +
|
return "Dispute{" +
|
||||||
|
@ -244,6 +244,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
super.onAllServicesInitialized();
|
super.onAllServicesInitialized();
|
||||||
disputeListService.onAllServicesInitialized();
|
disputeListService.onAllServicesInitialized();
|
||||||
@ -329,7 +330,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
if (isAgent(dispute)) {
|
if (isAgent(dispute)) {
|
||||||
|
|
||||||
// update arbitrator's multisig wallet
|
// update arbitrator's multisig wallet
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||||
multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
|
multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
|
||||||
System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:");
|
System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:");
|
||||||
System.out.println(multisigWallet.getTxs());
|
System.out.println(multisigWallet.getTxs());
|
||||||
@ -364,12 +365,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
addMediationResultMessage(dispute);
|
addMediationResultMessage(dispute);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
TradeDataValidation.validatePaymentAccountPayloads(dispute);
|
||||||
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
|
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
|
||||||
//TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed?
|
//TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed?
|
||||||
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
|
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
|
||||||
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
|
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
|
||||||
} catch (TradeDataValidation.AddressException |
|
} catch (TradeDataValidation.AddressException |
|
||||||
TradeDataValidation.NodeAddressException e) {
|
TradeDataValidation.NodeAddressException |
|
||||||
|
TradeDataValidation.InvalidPaymentAccountPayloadException e) {
|
||||||
log.error(e.toString());
|
log.error(e.toString());
|
||||||
validationExceptions.add(e);
|
validationExceptions.add(e);
|
||||||
}
|
}
|
||||||
@ -581,6 +584,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
disputeFromOpener.getContractAsJson(),
|
disputeFromOpener.getContractAsJson(),
|
||||||
disputeFromOpener.getMakerContractSignature(),
|
disputeFromOpener.getMakerContractSignature(),
|
||||||
disputeFromOpener.getTakerContractSignature(),
|
disputeFromOpener.getTakerContractSignature(),
|
||||||
|
disputeFromOpener.getMakerPaymentAccountPayload(),
|
||||||
|
disputeFromOpener.getTakerPaymentAccountPayload(),
|
||||||
disputeFromOpener.getAgentPubKeyRing(),
|
disputeFromOpener.getAgentPubKeyRing(),
|
||||||
disputeFromOpener.isSupportTicket(),
|
disputeFromOpener.isSupportTicket(),
|
||||||
disputeFromOpener.getSupportType());
|
disputeFromOpener.getSupportType());
|
||||||
|
@ -24,7 +24,6 @@ import bisq.core.support.dispute.Dispute;
|
|||||||
import bisq.core.support.dispute.DisputeList;
|
import bisq.core.support.dispute.DisputeList;
|
||||||
import bisq.core.support.dispute.DisputeManager;
|
import bisq.core.support.dispute.DisputeManager;
|
||||||
import bisq.core.support.dispute.DisputeResult;
|
import bisq.core.support.dispute.DisputeResult;
|
||||||
import bisq.core.trade.Contract;
|
|
||||||
import bisq.core.user.DontShowAgainLookup;
|
import bisq.core.user.DontShowAgainLookup;
|
||||||
|
|
||||||
import bisq.common.crypto.Hash;
|
import bisq.common.crypto.Hash;
|
||||||
@ -90,8 +89,8 @@ public class MultipleHolderNameDetection {
|
|||||||
|
|
||||||
public static PaymentAccountPayload getPaymentAccountPayload(Dispute dispute) {
|
public static PaymentAccountPayload getPaymentAccountPayload(Dispute dispute) {
|
||||||
return isBuyer(dispute) ?
|
return isBuyer(dispute) ?
|
||||||
dispute.getContract().getBuyerPaymentAccountPayload() :
|
dispute.getBuyerPaymentAccountPayload() :
|
||||||
dispute.getContract().getSellerPaymentAccountPayload();
|
dispute.getSellerPaymentAccountPayload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAddress(Dispute dispute) {
|
public static String getAddress(Dispute dispute) {
|
||||||
@ -202,10 +201,9 @@ public class MultipleHolderNameDetection {
|
|||||||
Map<String, List<Dispute>> allDisputesByTraderMap = new HashMap<>();
|
Map<String, List<Dispute>> allDisputesByTraderMap = new HashMap<>();
|
||||||
disputeManager.getDisputesAsObservableList().stream()
|
disputeManager.getDisputesAsObservableList().stream()
|
||||||
.filter(dispute -> {
|
.filter(dispute -> {
|
||||||
Contract contract = dispute.getContract();
|
|
||||||
PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ?
|
PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ?
|
||||||
contract.getBuyerPaymentAccountPayload() :
|
dispute.getBuyerPaymentAccountPayload() :
|
||||||
contract.getSellerPaymentAccountPayload();
|
dispute.getSellerPaymentAccountPayload();
|
||||||
return paymentAccountPayload instanceof PayloadWithHolderName;
|
return paymentAccountPayload instanceof PayloadWithHolderName;
|
||||||
})
|
})
|
||||||
.forEach(dispute -> {
|
.forEach(dispute -> {
|
||||||
|
@ -240,7 +240,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
String errorMessage = null;
|
String errorMessage = null;
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
boolean requestUpdatedPayoutTx = false;
|
boolean requestUpdatedPayoutTx = false;
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||||
Contract contract = dispute.getContract();
|
Contract contract = dispute.getContract();
|
||||||
try {
|
try {
|
||||||
// We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals
|
// 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);
|
cleanupRetryMap(uid);
|
||||||
|
|
||||||
// update multisig wallet
|
// update multisig wallet
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||||
|
|
||||||
// parse payout tx
|
// 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
|
// update arbitrator's multisig wallet with co-signer's multisig hex
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||||
try {
|
try {
|
||||||
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
|
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -473,7 +473,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
|
private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
|
||||||
|
|
||||||
// gather trade info
|
// gather trade info
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId);
|
||||||
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
|
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
|
||||||
if (!disputeOptional.isPresent()) {
|
if (!disputeOptional.isPresent()) {
|
||||||
log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
|
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();
|
String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
|
||||||
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
|
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
|
||||||
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
|
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
|
||||||
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
|
BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getBuyerPayoutAmount());
|
||||||
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
|
BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getSellerPayoutAmount());
|
||||||
|
|
||||||
//System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
|
//System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
|
||||||
//System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
|
//System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
|
||||||
@ -657,7 +657,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
|
private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
|
||||||
|
|
||||||
// gather trade info
|
// gather trade info
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId);
|
||||||
Optional<Dispute> disputeOptional = findOwnDispute(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);
|
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();
|
Dispute dispute = disputeOptional.get();
|
||||||
@ -665,11 +665,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
|
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
|
||||||
|
|
||||||
// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
|
// 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 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.getTakerDepositTxId() : trade.getMakerDepositTxId()).getIncomingAmount();
|
// 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 tradeAmount = BigInteger.valueOf(contract.getTradeAmount().value).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER);
|
||||||
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
|
BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getBuyerPayoutAmount());
|
||||||
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
|
BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getSellerPayoutAmount());
|
||||||
System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount);
|
System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount);
|
||||||
System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount);
|
System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount);
|
||||||
|
|
||||||
|
@ -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.
|
// 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.
|
// Otherwise we sign and complete with the peers signature the payout tx.
|
||||||
if (processModel.getTradingPeer().getMediatedPayoutTxSignature() == null) {
|
if (trade.getTradingPeer().getMediatedPayoutTxSignature() == null) {
|
||||||
tradeProtocol.onAcceptMediationResult(() -> {
|
tradeProtocol.onAcceptMediationResult(() -> {
|
||||||
if (trade.getPayoutTx() != null) {
|
if (trade.getPayoutTx() != null) {
|
||||||
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.MEDIATION_CLOSED);
|
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.MEDIATION_CLOSED);
|
||||||
|
@ -23,16 +23,15 @@ public class ArbitratorTrade extends Trade {
|
|||||||
|
|
||||||
public ArbitratorTrade(Offer offer,
|
public ArbitratorTrade(Offer offer,
|
||||||
Coin tradeAmount,
|
Coin tradeAmount,
|
||||||
Coin txFee,
|
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
NodeAddress makerNodeAddress,
|
|
||||||
NodeAddress takerNodeAddress,
|
|
||||||
NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
super(offer, tradeAmount, txFee, takerFee, tradePrice, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, xmrWalletService, processModel, uid);
|
NodeAddress makerNodeAddress,
|
||||||
|
NodeAddress takerNodeAddress,
|
||||||
|
NodeAddress arbitratorNodeAddress) {
|
||||||
|
super(offer, tradeAmount, takerFee, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -64,15 +63,14 @@ public class ArbitratorTrade extends Trade {
|
|||||||
return fromProto(new ArbitratorTrade(
|
return fromProto(new ArbitratorTrade(
|
||||||
Offer.fromProto(proto.getOffer()),
|
Offer.fromProto(proto.getOffer()),
|
||||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
|
||||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||||
proto.getTradePrice(),
|
proto.getTradePrice(),
|
||||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
|
||||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
|
||||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
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,
|
proto,
|
||||||
coreProtoResolver);
|
coreProtoResolver);
|
||||||
}
|
}
|
||||||
|
@ -42,23 +42,25 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public BuyerAsMakerTrade(Offer offer,
|
public BuyerAsMakerTrade(Offer offer,
|
||||||
Coin txFee,
|
Coin tradeAmount,
|
||||||
Coin takeOfferFee,
|
Coin takeOfferFee,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
long tradePrice,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
NodeAddress makerNodeAddress,
|
||||||
|
NodeAddress takerNodeAddress,
|
||||||
|
NodeAddress arbitratorNodeAddress) {
|
||||||
super(offer,
|
super(offer,
|
||||||
txFee,
|
tradeAmount,
|
||||||
takeOfferFee,
|
takeOfferFee,
|
||||||
takerNodeAddress,
|
tradePrice,
|
||||||
makerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
uid,
|
||||||
|
makerNodeAddress,
|
||||||
|
takerNodeAddress,
|
||||||
|
arbitratorNodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -84,14 +86,15 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
|||||||
}
|
}
|
||||||
BuyerAsMakerTrade trade = new BuyerAsMakerTrade(
|
BuyerAsMakerTrade trade = new BuyerAsMakerTrade(
|
||||||
Offer.fromProto(proto.getOffer()),
|
Offer.fromProto(proto.getOffer()),
|
||||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
proto.getTradePrice(),
|
||||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
|
||||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
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.setTradeAmountAsLong(proto.getTradeAmountAsLong());
|
||||||
trade.setTradePrice(proto.getTradePrice());
|
trade.setTradePrice(proto.getTradePrice());
|
||||||
|
@ -43,26 +43,24 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
|||||||
|
|
||||||
public BuyerAsTakerTrade(Offer offer,
|
public BuyerAsTakerTrade(Offer offer,
|
||||||
Coin tradeAmount,
|
Coin tradeAmount,
|
||||||
Coin txFee,
|
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
txFee,
|
|
||||||
takerFee,
|
takerFee,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
makerNodeAddress,
|
|
||||||
takerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
uid,
|
||||||
|
makerNodeAddress,
|
||||||
|
takerNodeAddress,
|
||||||
|
arbitratorNodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -90,15 +88,14 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
|||||||
return fromProto(new BuyerAsTakerTrade(
|
return fromProto(new BuyerAsTakerTrade(
|
||||||
Offer.fromProto(proto.getOffer()),
|
Offer.fromProto(proto.getOffer()),
|
||||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
|
||||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||||
proto.getTradePrice(),
|
proto.getTradePrice(),
|
||||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
|
||||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
|
||||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
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,
|
proto,
|
||||||
coreProtoResolver);
|
coreProtoResolver);
|
||||||
}
|
}
|
||||||
|
@ -35,46 +35,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
public abstract class BuyerTrade extends Trade {
|
public abstract class BuyerTrade extends Trade {
|
||||||
BuyerTrade(Offer offer,
|
BuyerTrade(Offer offer,
|
||||||
Coin tradeAmount,
|
Coin tradeAmount,
|
||||||
Coin txFee,
|
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
txFee,
|
|
||||||
takerFee,
|
takerFee,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
takerNodeAddress,
|
|
||||||
makerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
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,
|
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
arbitratorNodeAddress,
|
arbitratorNodeAddress);
|
||||||
xmrWalletService,
|
|
||||||
processModel,
|
|
||||||
uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,13 +21,12 @@ import bisq.core.locale.CurrencyUtil;
|
|||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.monetary.Volume;
|
import bisq.core.monetary.Volume;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
import bisq.core.util.VolumeUtil;
|
import bisq.core.util.VolumeUtil;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.proto.network.NetworkPayload;
|
import bisq.common.proto.network.NetworkPayload;
|
||||||
import bisq.common.util.JsonExclude;
|
import bisq.common.util.JsonExclude;
|
||||||
@ -56,14 +55,18 @@ public final class Contract implements NetworkPayload {
|
|||||||
private final boolean isBuyerMakerAndSellerTaker;
|
private final boolean isBuyerMakerAndSellerTaker;
|
||||||
private final String makerAccountId;
|
private final String makerAccountId;
|
||||||
private final String takerAccountId;
|
private final String takerAccountId;
|
||||||
private final PaymentAccountPayload makerPaymentAccountPayload;
|
private final String makerPaymentMethodId;
|
||||||
private final PaymentAccountPayload takerPaymentAccountPayload;
|
private final String takerPaymentMethodId;
|
||||||
|
private final byte[] makerPaymentAccountPayloadHash;
|
||||||
|
private final byte[] takerPaymentAccountPayloadHash;
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
private final PubKeyRing makerPubKeyRing;
|
private final PubKeyRing makerPubKeyRing;
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
private final PubKeyRing takerPubKeyRing;
|
private final PubKeyRing takerPubKeyRing;
|
||||||
private final String makerPayoutAddressString;
|
private final String makerPayoutAddressString;
|
||||||
private final String takerPayoutAddressString;
|
private final String takerPayoutAddressString;
|
||||||
|
private final String makerDepositTxHash;
|
||||||
|
private final String takerDepositTxHash;
|
||||||
|
|
||||||
// Added in v1.2.0
|
// Added in v1.2.0
|
||||||
private long lockTime;
|
private long lockTime;
|
||||||
@ -77,13 +80,17 @@ public final class Contract implements NetworkPayload {
|
|||||||
boolean isBuyerMakerAndSellerTaker,
|
boolean isBuyerMakerAndSellerTaker,
|
||||||
String makerAccountId,
|
String makerAccountId,
|
||||||
String takerAccountId,
|
String takerAccountId,
|
||||||
PaymentAccountPayload makerPaymentAccountPayload,
|
String makerPaymentMethodId,
|
||||||
PaymentAccountPayload takerPaymentAccountPayload,
|
String takerPaymentMethodId,
|
||||||
|
byte[] makerPaymentAccountPayloadHash,
|
||||||
|
byte[] takerPaymentAccountPayloadHash,
|
||||||
PubKeyRing makerPubKeyRing,
|
PubKeyRing makerPubKeyRing,
|
||||||
PubKeyRing takerPubKeyRing,
|
PubKeyRing takerPubKeyRing,
|
||||||
String makerPayoutAddressString,
|
String makerPayoutAddressString,
|
||||||
String takerPayoutAddressString,
|
String takerPayoutAddressString,
|
||||||
long lockTime) {
|
long lockTime,
|
||||||
|
String makerDepositTxHash,
|
||||||
|
String takerDepositTxHash) {
|
||||||
this.offerPayload = offerPayload;
|
this.offerPayload = offerPayload;
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
this.tradePrice = tradePrice;
|
this.tradePrice = tradePrice;
|
||||||
@ -93,16 +100,18 @@ public final class Contract implements NetworkPayload {
|
|||||||
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
|
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
|
||||||
this.makerAccountId = makerAccountId;
|
this.makerAccountId = makerAccountId;
|
||||||
this.takerAccountId = takerAccountId;
|
this.takerAccountId = takerAccountId;
|
||||||
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
|
this.makerPaymentMethodId = makerPaymentMethodId;
|
||||||
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
|
this.takerPaymentMethodId = takerPaymentMethodId;
|
||||||
|
this.makerPaymentAccountPayloadHash = makerPaymentAccountPayloadHash;
|
||||||
|
this.takerPaymentAccountPayloadHash = takerPaymentAccountPayloadHash;
|
||||||
this.makerPubKeyRing = makerPubKeyRing;
|
this.makerPubKeyRing = makerPubKeyRing;
|
||||||
this.takerPubKeyRing = takerPubKeyRing;
|
this.takerPubKeyRing = takerPubKeyRing;
|
||||||
this.makerPayoutAddressString = makerPayoutAddressString;
|
this.makerPayoutAddressString = makerPayoutAddressString;
|
||||||
this.takerPayoutAddressString = takerPayoutAddressString;
|
this.takerPayoutAddressString = takerPayoutAddressString;
|
||||||
this.lockTime = lockTime;
|
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
|
// For SEPA offers we accept also SEPA_INSTANT takers
|
||||||
// Otherwise both ids need to be the same
|
// Otherwise both ids need to be the same
|
||||||
boolean result = (makerPaymentMethodId.equals(PaymentMethod.SEPA_ID) && takerPaymentMethodId.equals(PaymentMethod.SEPA_INSTANT_ID)) ||
|
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.getIsBuyerMakerAndSellerTaker(),
|
||||||
proto.getMakerAccountId(),
|
proto.getMakerAccountId(),
|
||||||
proto.getTakerAccountId(),
|
proto.getTakerAccountId(),
|
||||||
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
|
proto.getMakerPaymentMethodId(),
|
||||||
coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()),
|
proto.getTakerPaymentMethodId(),
|
||||||
|
proto.getMakerPaymentAccountPayloadHash().toByteArray(),
|
||||||
|
proto.getTakerPaymentAccountPayloadHash().toByteArray(),
|
||||||
PubKeyRing.fromProto(proto.getMakerPubKeyRing()),
|
PubKeyRing.fromProto(proto.getMakerPubKeyRing()),
|
||||||
PubKeyRing.fromProto(proto.getTakerPubKeyRing()),
|
PubKeyRing.fromProto(proto.getTakerPubKeyRing()),
|
||||||
proto.getMakerPayoutAddressString(),
|
proto.getMakerPayoutAddressString(),
|
||||||
proto.getTakerPayoutAddressString(),
|
proto.getTakerPayoutAddressString(),
|
||||||
proto.getLockTime());
|
proto.getLockTime(),
|
||||||
|
proto.getMakerDepositTxHash(),
|
||||||
|
proto.getTakerDepositTxHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -148,13 +161,17 @@ public final class Contract implements NetworkPayload {
|
|||||||
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
||||||
.setMakerAccountId(makerAccountId)
|
.setMakerAccountId(makerAccountId)
|
||||||
.setTakerAccountId(takerAccountId)
|
.setTakerAccountId(takerAccountId)
|
||||||
.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())
|
.setMakerPaymentMethodId(makerPaymentMethodId)
|
||||||
.setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage())
|
.setTakerPaymentMethodId(takerPaymentMethodId)
|
||||||
|
.setMakerPaymentAccountPayloadHash(ByteString.copyFrom(makerPaymentAccountPayloadHash))
|
||||||
|
.setTakerPaymentAccountPayloadHash(ByteString.copyFrom(takerPaymentAccountPayloadHash))
|
||||||
.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())
|
.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())
|
||||||
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
|
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
|
||||||
.setMakerPayoutAddressString(makerPayoutAddressString)
|
.setMakerPayoutAddressString(makerPayoutAddressString)
|
||||||
.setTakerPayoutAddressString(takerPayoutAddressString)
|
.setTakerPayoutAddressString(takerPayoutAddressString)
|
||||||
.setLockTime(lockTime)
|
.setLockTime(lockTime)
|
||||||
|
.setMakerDepositTxHash(makerDepositTxHash)
|
||||||
|
.setTakerDepositTxHash(takerDepositTxHash)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,16 +196,16 @@ public final class Contract implements NetworkPayload {
|
|||||||
return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing;
|
return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
|
public byte[] getBuyerPaymentAccountPayloadHash() {
|
||||||
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload;
|
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayloadHash : takerPaymentAccountPayloadHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentAccountPayload getSellerPaymentAccountPayload() {
|
public byte[] getSellerPaymentAccountPayloadHash() {
|
||||||
return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayload : makerPaymentAccountPayload;
|
return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayloadHash : makerPaymentAccountPayloadHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPaymentMethodId() {
|
public String getPaymentMethodId() {
|
||||||
return makerPaymentAccountPayload.getPaymentMethodId();
|
return makerPaymentMethodId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getTradeAmount() {
|
public Coin getTradeAmount() {
|
||||||
@ -270,13 +287,17 @@ public final class Contract implements NetworkPayload {
|
|||||||
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
|
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
|
||||||
",\n makerAccountId='" + makerAccountId + '\'' +
|
",\n makerAccountId='" + makerAccountId + '\'' +
|
||||||
",\n takerAccountId='" + takerAccountId + '\'' +
|
",\n takerAccountId='" + takerAccountId + '\'' +
|
||||||
",\n makerPaymentAccountPayload=" + makerPaymentAccountPayload +
|
",\n makerPaymentMethodId='" + makerPaymentMethodId + '\'' +
|
||||||
",\n takerPaymentAccountPayload=" + takerPaymentAccountPayload +
|
",\n takerPaymentMethodId='" + takerPaymentMethodId + '\'' +
|
||||||
|
",\n makerPaymentAccountPayloadHash=" + makerPaymentAccountPayloadHash +
|
||||||
|
",\n takerPaymentAccountPayloadHash=" + takerPaymentAccountPayloadHash +
|
||||||
",\n makerPubKeyRing=" + makerPubKeyRing +
|
",\n makerPubKeyRing=" + makerPubKeyRing +
|
||||||
",\n takerPubKeyRing=" + takerPubKeyRing +
|
",\n takerPubKeyRing=" + takerPubKeyRing +
|
||||||
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +
|
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +
|
||||||
",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
|
",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
|
||||||
",\n lockTime=" + lockTime +
|
",\n lockTime=" + lockTime +
|
||||||
|
",\n makerDepositTxHash='" + makerDepositTxHash + '\'' +
|
||||||
|
",\n takerDepositTxHash='" + takerDepositTxHash + '\'' +
|
||||||
"\n}";
|
"\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,23 +42,25 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public SellerAsMakerTrade(Offer offer,
|
public SellerAsMakerTrade(Offer offer,
|
||||||
Coin txFee,
|
Coin tradeAmount,
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
long tradePrice,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
super(offer,
|
super(offer,
|
||||||
txFee,
|
tradeAmount,
|
||||||
takerFee,
|
takerFee,
|
||||||
makerNodeAddress,
|
tradePrice,
|
||||||
takerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
uid,
|
||||||
|
makerNodeAddress,
|
||||||
|
takerNodeAddress,
|
||||||
|
arbitratorNodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -85,14 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
|||||||
}
|
}
|
||||||
SellerAsMakerTrade trade = new SellerAsMakerTrade(
|
SellerAsMakerTrade trade = new SellerAsMakerTrade(
|
||||||
Offer.fromProto(proto.getOffer()),
|
Offer.fromProto(proto.getOffer()),
|
||||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
proto.getTradePrice(),
|
||||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
|
||||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
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.setTradeAmountAsLong(proto.getTradeAmountAsLong());
|
||||||
trade.setTradePrice(proto.getTradePrice());
|
trade.setTradePrice(proto.getTradePrice());
|
||||||
|
@ -43,26 +43,24 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
|||||||
|
|
||||||
public SellerAsTakerTrade(Offer offer,
|
public SellerAsTakerTrade(Offer offer,
|
||||||
Coin tradeAmount,
|
Coin tradeAmount,
|
||||||
Coin txFee,
|
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
txFee,
|
|
||||||
takerFee,
|
takerFee,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
makerNodeAddress,
|
|
||||||
takerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
uid,
|
||||||
|
makerNodeAddress,
|
||||||
|
takerNodeAddress,
|
||||||
|
arbitratorNodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -90,15 +88,14 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
|||||||
return fromProto(new SellerAsTakerTrade(
|
return fromProto(new SellerAsTakerTrade(
|
||||||
Offer.fromProto(proto.getOffer()),
|
Offer.fromProto(proto.getOffer()),
|
||||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
|
||||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||||
proto.getTradePrice(),
|
proto.getTradePrice(),
|
||||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
|
||||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
|
||||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
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,
|
proto,
|
||||||
coreProtoResolver);
|
coreProtoResolver);
|
||||||
}
|
}
|
||||||
|
@ -36,46 +36,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
public abstract class SellerTrade extends Trade {
|
public abstract class SellerTrade extends Trade {
|
||||||
SellerTrade(Offer offer,
|
SellerTrade(Offer offer,
|
||||||
Coin tradeAmount,
|
Coin tradeAmount,
|
||||||
Coin txFee,
|
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
super(offer,
|
super(offer,
|
||||||
tradeAmount,
|
tradeAmount,
|
||||||
txFee,
|
|
||||||
takerFee,
|
takerFee,
|
||||||
tradePrice,
|
tradePrice,
|
||||||
makerNodeAddress,
|
|
||||||
takerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
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,
|
|
||||||
makerNodeAddress,
|
makerNodeAddress,
|
||||||
takerNodeAddress,
|
takerNodeAddress,
|
||||||
arbitratorNodeAddress,
|
arbitratorNodeAddress);
|
||||||
xmrWalletService,
|
|
||||||
processModel,
|
|
||||||
uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -22,6 +22,7 @@ import bisq.core.locale.CurrencyUtil;
|
|||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.monetary.Volume;
|
import bisq.core.monetary.Volume;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.offer.OfferPayload.Direction;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
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.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.ProcessModel;
|
import bisq.core.trade.protocol.ProcessModel;
|
||||||
import bisq.core.trade.protocol.ProcessModelServiceProvider;
|
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.trade.txproof.AssetTxProofResult;
|
||||||
import bisq.core.util.VolumeUtil;
|
import bisq.core.util.VolumeUtil;
|
||||||
|
import bisq.network.p2p.AckMessage;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
@ -80,9 +82,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
|
|
||||||
import monero.common.MoneroError;
|
import monero.common.MoneroError;
|
||||||
import monero.daemon.MoneroDaemon;
|
import monero.daemon.MoneroDaemon;
|
||||||
import monero.daemon.MoneroDaemonRpc;
|
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
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
|
* 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
|
@Nullable
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private String takerContractSignature;
|
|
||||||
@Nullable
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String makerContractSignature;
|
|
||||||
@Nullable
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private NodeAddress arbitratorNodeAddress;
|
private NodeAddress arbitratorNodeAddress;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Setter
|
@Setter
|
||||||
@ -364,14 +359,6 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private NodeAddress mediatorNodeAddress;
|
|
||||||
@Nullable
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private PubKeyRing mediatorPubKeyRing;
|
|
||||||
@Nullable
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String takerPaymentAccountId;
|
private String takerPaymentAccountId;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
@ -460,7 +447,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
|
|
||||||
// Added in XMR integration
|
// 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
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private NodeAddress makerNodeAddress;
|
private NodeAddress makerNodeAddress;
|
||||||
@ -473,18 +460,11 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private PubKeyRing takerPubKeyRing;
|
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;
|
transient private MoneroTxWallet makerDepositTx;
|
||||||
@Nullable
|
|
||||||
transient private MoneroTxWallet takerDepositTx;
|
transient private MoneroTxWallet takerDepositTx;
|
||||||
@Nullable
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String makerDepositTxId;
|
|
||||||
@Nullable
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String takerDepositTxId;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor, initialization
|
// Constructor, initialization
|
||||||
@ -492,28 +472,34 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// maker
|
// maker
|
||||||
protected Trade(Offer offer,
|
protected Trade(Offer offer,
|
||||||
Coin txFee,
|
Coin tradeAmount,
|
||||||
Coin takerFee,
|
Coin takerFee, // TODO (woodser): makerFee, takerFee, but not given one during construction
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
long tradePrice,
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.txFee = txFee;
|
this.tradeAmount = tradeAmount;
|
||||||
|
this.txFee = Coin.valueOf(0); // TODO (woodser): remove this field
|
||||||
this.takerFee = takerFee;
|
this.takerFee = takerFee;
|
||||||
this.makerNodeAddress = makerNodeAddress;
|
this.tradePrice = tradePrice;
|
||||||
this.takerNodeAddress = takerNodeAddress;
|
|
||||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.processModel = processModel;
|
this.processModel = processModel;
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
|
|
||||||
txFeeAsLong = txFee.value;
|
this.txFeeAsLong = txFee.value;
|
||||||
takerFeeAsLong = takerFee.value;
|
this.takerFeeAsLong = takerFee.value;
|
||||||
takeOfferDate = new Date().getTime();
|
this.takeOfferDate = new Date().getTime();
|
||||||
tradeMessageListeners = new ArrayList<TradeMessageListener>();
|
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 txFee,
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
@Nullable NodeAddress makerNodeAddress,
|
@Nullable NodeAddress mediatorNodeAddress, // TODO (woodser): remove mediator, refund agent from trade
|
||||||
@Nullable NodeAddress takerNodeAddress,
|
|
||||||
@Nullable NodeAddress arbitratorNodeAddress,
|
|
||||||
@Nullable NodeAddress mediatorNodeAddress,
|
|
||||||
@Nullable NodeAddress refundAgentNodeAddress,
|
@Nullable NodeAddress refundAgentNodeAddress,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
ProcessModel processModel,
|
ProcessModel processModel,
|
||||||
String uid) {
|
String uid,
|
||||||
|
@Nullable NodeAddress makerNodeAddress,
|
||||||
|
@Nullable NodeAddress takerNodeAddress,
|
||||||
|
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||||
|
|
||||||
this(offer,
|
this(offer,
|
||||||
txFee,
|
tradeAmount,
|
||||||
takerFee,
|
takerFee,
|
||||||
makerNodeAddress,
|
tradePrice,
|
||||||
takerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
uid,
|
||||||
this.tradePrice = tradePrice;
|
makerNodeAddress,
|
||||||
setTradeAmount(tradeAmount);
|
takerNodeAddress,
|
||||||
|
arbitratorNodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove these constructors
|
||||||
// arbitrator
|
// arbitrator
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
protected Trade(Offer offer,
|
protected Trade(Offer offer,
|
||||||
@ -562,16 +548,16 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
String uid) {
|
String uid) {
|
||||||
|
|
||||||
this(offer,
|
this(offer,
|
||||||
txFee,
|
tradeAmount,
|
||||||
takerFee,
|
takerFee,
|
||||||
makerNodeAddress,
|
tradePrice,
|
||||||
takerNodeAddress,
|
|
||||||
arbitratorNodeAddress,
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
processModel,
|
processModel,
|
||||||
uid);
|
uid,
|
||||||
|
makerNodeAddress,
|
||||||
|
takerNodeAddress,
|
||||||
|
arbitratorNodeAddress);
|
||||||
|
|
||||||
this.tradePrice = tradePrice;
|
|
||||||
setTradeAmount(tradeAmount);
|
setTradeAmount(tradeAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,22 +585,16 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
.setUid(uid);
|
.setUid(uid);
|
||||||
|
|
||||||
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
|
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(payoutTxId).ifPresent(builder::setPayoutTxId);
|
||||||
Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage()));
|
Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage()));
|
||||||
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
|
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
|
||||||
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash)));
|
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(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(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()));
|
||||||
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
|
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
|
||||||
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
|
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
|
||||||
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
|
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
|
||||||
Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage()));
|
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(refundAgentPubKeyRing).ifPresent(e -> builder.setRefundAgentPubKeyRing(refundAgentPubKeyRing.toProtoMessage()));
|
||||||
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
|
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
|
||||||
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
|
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(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage()));
|
||||||
Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()));
|
Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()));
|
||||||
Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.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();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,22 +615,16 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
|
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
|
||||||
trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState()));
|
trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState()));
|
||||||
trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
|
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.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
|
||||||
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
|
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
|
||||||
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
|
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
|
||||||
trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()));
|
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.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.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
|
||||||
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
|
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
|
||||||
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
|
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
|
||||||
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
||||||
trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null);
|
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.setRefundAgentPubKeyRing(proto.hasRefundAgentPubKeyRing() ? PubKeyRing.fromProto(proto.getRefundAgentPubKeyRing()) : null);
|
||||||
trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId());
|
trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId());
|
||||||
trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState()));
|
trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState()));
|
||||||
@ -688,12 +662,6 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
arbitratorPubKeyRing = arbitrator.getPubKeyRing();
|
arbitratorPubKeyRing = arbitrator.getPubKeyRing();
|
||||||
});
|
});
|
||||||
|
|
||||||
serviceProvider.getMediatorManager().getDisputeAgentByNodeAddress(mediatorNodeAddress)
|
|
||||||
.ifPresent(mediator -> mediatorPubKeyRing = mediator.getPubKeyRing());
|
|
||||||
|
|
||||||
serviceProvider.getRefundAgentManager().getDisputeAgentByNodeAddress(refundAgentNodeAddress)
|
|
||||||
.ifPresent(refundAgent -> refundAgentPubKeyRing = refundAgent.getPubKeyRing());
|
|
||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,40 +700,84 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
void updateDepositTxFromWallet() {
|
void updateDepositTxFromWallet() {
|
||||||
if (getMakerDepositTx() != null && getTakerDepositTx() != null) {
|
if (getMakerDepositTx() != null && getTakerDepositTx() != null) {
|
||||||
System.out.println(processModel.getProvider().getXmrWalletService());
|
System.out.println(processModel.getProvider().getXmrWalletService());
|
||||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId());
|
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(getId());
|
||||||
applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId()));
|
applyDepositTxs(multisigWallet.getTx(getMakerDepositTx().getHash()), multisigWallet.getTx(getTakerDepositTx().getHash()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
|
public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
|
||||||
this.makerDepositTx = makerDepositTx;
|
this.makerDepositTx = makerDepositTx;
|
||||||
this.takerDepositTx = takerDepositTx;
|
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()) {
|
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
|
||||||
setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
|
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
|
@Nullable
|
||||||
public MoneroTxWallet getTakerDepositTx() {
|
public MoneroTxWallet getTakerDepositTx() {
|
||||||
|
String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
|
||||||
try {
|
try {
|
||||||
if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null;
|
if (takerDepositTx == null) takerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash);
|
||||||
return takerDepositTx;
|
return takerDepositTx;
|
||||||
} catch (MoneroError e) {
|
} catch (MoneroError e) {
|
||||||
log.error("Wallet is missing taker deposit tx " + takerDepositTxId);
|
log.error("Wallet is missing taker deposit tx " + depositTxHash);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MoneroTxWallet getMakerDepositTx() {
|
public MoneroTxWallet getMakerDepositTx() {
|
||||||
|
String depositTxHash = getProcessModel().getMaker().getDepositTxHash();
|
||||||
try {
|
try {
|
||||||
if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null;
|
if (makerDepositTx == null) makerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash);
|
||||||
return makerDepositTx;
|
return makerDepositTx;
|
||||||
} catch (MoneroError e) {
|
} catch (MoneroError e) {
|
||||||
log.error("Wallet is missing maker deposit tx " + makerDepositTxId);
|
log.error("Wallet is missing maker deposit tx " + depositTxHash);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -845,21 +857,28 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// Listeners
|
// Listeners
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void addTradeMessageListener(TradeMessageListener listener) {
|
public void addListener(TradeListener listener) {
|
||||||
tradeMessageListeners.add(listener);
|
tradeListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeTradeMessageListener(TradeMessageListener listener) {
|
public void removeListener(TradeListener listener) {
|
||||||
if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
|
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) {
|
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);
|
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
|
// Setters
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -879,7 +898,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state);
|
log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state);
|
||||||
}
|
}
|
||||||
if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) {
|
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;
|
"Old state is: " + this.state + ". New state is: " + state;
|
||||||
log.warn(message);
|
log.warn(message);
|
||||||
}
|
}
|
||||||
@ -936,6 +955,56 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// Getter
|
// 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() {
|
public Date getTakeOfferDate() {
|
||||||
return new Date(takeOfferDate);
|
return new Date(takeOfferDate);
|
||||||
}
|
}
|
||||||
@ -985,7 +1054,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
|
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
|
||||||
final long tradeTime = getTakeOfferDate().getTime();
|
final long tradeTime = getTakeOfferDate().getTime();
|
||||||
long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight());
|
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();
|
long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
|
||||||
|
|
||||||
// if (depositTx.getConfidence().getDepthInBlocks() > 0) {
|
// if (depositTx.getConfidence().getDepthInBlocks() > 0) {
|
||||||
@ -1158,8 +1227,8 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
public boolean isTxChainInvalid() {
|
public boolean isTxChainInvalid() {
|
||||||
return offer.getOfferFeePaymentTxId() == null ||
|
return offer.getOfferFeePaymentTxId() == null ||
|
||||||
getTakerFeeTxId() == null ||
|
getTakerFeeTxId() == null ||
|
||||||
getMakerDepositTxId() == null ||
|
processModel.getMaker().getDepositTxHash() == null ||
|
||||||
getTakerDepositTxId() == null ||
|
processModel.getMaker().getDepositTxHash() == null ||
|
||||||
getDelayedPayoutTxBytes() == null;
|
getDelayedPayoutTxBytes() == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1241,7 +1310,6 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
",\n takeOfferDate=" + takeOfferDate +
|
",\n takeOfferDate=" + takeOfferDate +
|
||||||
",\n processModel=" + processModel +
|
",\n processModel=" + processModel +
|
||||||
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
|
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
|
||||||
",\n takerDepositTxId='" + takerDepositTxId + '\'' +
|
|
||||||
",\n payoutTxId='" + payoutTxId + '\'' +
|
",\n payoutTxId='" + payoutTxId + '\'' +
|
||||||
",\n tradeAmountAsLong=" + tradeAmountAsLong +
|
",\n tradeAmountAsLong=" + tradeAmountAsLong +
|
||||||
",\n tradePrice=" + tradePrice +
|
",\n tradePrice=" + tradePrice +
|
||||||
@ -1251,11 +1319,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
",\n contract=" + contract +
|
",\n contract=" + contract +
|
||||||
",\n contractAsJson='" + contractAsJson + '\'' +
|
",\n contractAsJson='" + contractAsJson + '\'' +
|
||||||
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
||||||
",\n takerContractSignature='" + takerContractSignature + '\'' +
|
|
||||||
",\n makerContractSignature='" + makerContractSignature + '\'' +
|
|
||||||
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
|
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
|
||||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
|
||||||
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
|
|
||||||
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
|
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
|
||||||
",\n errorMessage='" + errorMessage + '\'' +
|
",\n errorMessage='" + errorMessage + '\'' +
|
||||||
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
|
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
|
||||||
|
@ -36,7 +36,7 @@ import org.bitcoinj.core.Transaction;
|
|||||||
import org.bitcoinj.core.TransactionInput;
|
import org.bitcoinj.core.TransactionInput;
|
||||||
import org.bitcoinj.core.TransactionOutPoint;
|
import org.bitcoinj.core.TransactionOutPoint;
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -55,6 +55,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class TradeDataValidation {
|
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)
|
public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade)
|
||||||
throws AddressException {
|
throws AddressException {
|
||||||
validateDonationAddress(null, addressAsString, daoFacade);
|
validateDonationAddress(null, addressAsString, daoFacade);
|
||||||
@ -397,6 +402,12 @@ public class TradeDataValidation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class InvalidPaymentAccountPayloadException extends ValidationException {
|
||||||
|
InvalidPaymentAccountPayloadException(@Nullable Dispute dispute, String msg) {
|
||||||
|
super(dispute, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class AddressException extends ValidationException {
|
public static class AddressException extends ValidationException {
|
||||||
AddressException(@Nullable Dispute dispute, String msg) {
|
AddressException(@Nullable Dispute dispute, String msg) {
|
||||||
super(dispute, msg);
|
super(dispute, msg);
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package bisq.core.trade;
|
package bisq.core.trade;
|
||||||
|
|
||||||
import bisq.core.btc.exceptions.AddressEntryException;
|
|
||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.btc.wallet.BsqWalletService;
|
import bisq.core.btc.wallet.BsqWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
@ -25,21 +24,27 @@ import bisq.core.locale.Res;
|
|||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferBookService;
|
import bisq.core.offer.OfferBookService;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
import bisq.core.offer.SignedOffer;
|
||||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||||
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
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.mediation.mediator.MediatorManager;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.closed.ClosedTradableManager;
|
||||||
import bisq.core.trade.failed.FailedTradesManager;
|
import bisq.core.trade.failed.FailedTradesManager;
|
||||||
import bisq.core.trade.handlers.TradeResultHandler;
|
import bisq.core.trade.handlers.TradeResultHandler;
|
||||||
import bisq.core.trade.messages.DepositTxMessage;
|
import bisq.core.trade.messages.DepositRequest;
|
||||||
import bisq.core.trade.messages.InitMultisigMessage;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
import bisq.core.trade.messages.SignContractRequest;
|
||||||
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
import bisq.core.trade.messages.UpdateMultisigRequest;
|
||||||
import bisq.core.trade.protocol.ArbitratorProtocol;
|
import bisq.core.trade.protocol.ArbitratorProtocol;
|
||||||
import bisq.core.trade.protocol.MakerProtocol;
|
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.TakerProtocol;
|
||||||
import bisq.core.trade.protocol.TradeProtocol;
|
import bisq.core.trade.protocol.TradeProtocol;
|
||||||
import bisq.core.trade.protocol.TradeProtocolFactory;
|
import bisq.core.trade.protocol.TradeProtocolFactory;
|
||||||
|
import bisq.core.trade.protocol.TraderProtocol;
|
||||||
import bisq.core.trade.statistics.ReferralIdService;
|
import bisq.core.trade.statistics.ReferralIdService;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
import bisq.core.util.coin.CoinUtil;
|
||||||
import bisq.network.p2p.BootstrapListener;
|
import bisq.network.p2p.BootstrapListener;
|
||||||
import bisq.network.p2p.DecryptedDirectMessageListener;
|
import bisq.network.p2p.DecryptedDirectMessageListener;
|
||||||
import bisq.network.p2p.DecryptedMessageWithPubKey;
|
import bisq.network.p2p.DecryptedMessageWithPubKey;
|
||||||
@ -70,15 +76,11 @@ import bisq.common.persistence.PersistenceManager;
|
|||||||
import bisq.common.proto.network.NetworkEnvelope;
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
|
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.LongProperty;
|
import javafx.beans.property.LongProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
@ -106,8 +108,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
@ -132,6 +132,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final PriceFeedService priceFeedService;
|
private final PriceFeedService priceFeedService;
|
||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
|
private final OfferUtil offerUtil;
|
||||||
private final TradeUtil tradeUtil;
|
private final TradeUtil tradeUtil;
|
||||||
@Getter
|
@Getter
|
||||||
private final ArbitratorManager arbitratorManager;
|
private final ArbitratorManager arbitratorManager;
|
||||||
@ -171,6 +172,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
PriceFeedService priceFeedService,
|
PriceFeedService priceFeedService,
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
|
OfferUtil offerUtil,
|
||||||
TradeUtil tradeUtil,
|
TradeUtil tradeUtil,
|
||||||
ArbitratorManager arbitratorManager,
|
ArbitratorManager arbitratorManager,
|
||||||
MediatorManager mediatorManager,
|
MediatorManager mediatorManager,
|
||||||
@ -191,6 +193,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
|
this.offerUtil = offerUtil;
|
||||||
this.tradeUtil = tradeUtil;
|
this.tradeUtil = tradeUtil;
|
||||||
this.arbitratorManager = arbitratorManager;
|
this.arbitratorManager = arbitratorManager;
|
||||||
this.mediatorManager = mediatorManager;
|
this.mediatorManager = mediatorManager;
|
||||||
@ -238,14 +241,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
//handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests
|
//handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests
|
||||||
} else if (networkEnvelope instanceof InitTradeRequest) {
|
} else if (networkEnvelope instanceof InitTradeRequest) {
|
||||||
handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer);
|
handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer);
|
||||||
} else if (networkEnvelope instanceof InitMultisigMessage) {
|
} else if (networkEnvelope instanceof InitMultisigRequest) {
|
||||||
handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer);
|
handleInitMultisigRequest((InitMultisigRequest) networkEnvelope, peer);
|
||||||
} else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) {
|
} else if (networkEnvelope instanceof SignContractRequest) {
|
||||||
handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer);
|
handleSignContractRequest((SignContractRequest) networkEnvelope, peer);
|
||||||
} else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) {
|
} else if (networkEnvelope instanceof SignContractResponse) {
|
||||||
handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer);
|
handleSignContractResponse((SignContractResponse) networkEnvelope, peer);
|
||||||
} else if (networkEnvelope instanceof DepositTxMessage) {
|
} else if (networkEnvelope instanceof DepositRequest) {
|
||||||
handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer);
|
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) {
|
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
|
||||||
handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
|
handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
|
||||||
}
|
}
|
||||||
@ -327,65 +334,96 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
persistenceManager.requestPersistence();
|
persistenceManager.requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) {
|
private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender) {
|
||||||
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid());
|
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", sender, request.getTradeId(), request.getUid());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Validator.nonEmptyStringOf(initTradeRequest.getTradeId());
|
Validator.nonEmptyStringOf(request.getTradeId());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString());
|
log.warn("Invalid InitTradeRequest message " + request.toString());
|
||||||
return;
|
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
|
// handle request as arbitrator
|
||||||
boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
|
boolean isArbitrator = request.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
|
||||||
if (isArbitrator) {
|
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
|
// get offer associated with trade
|
||||||
Offer offer = null;
|
Offer offer = null;
|
||||||
for (Offer anOffer : offerBookService.getOffers()) {
|
for (Offer anOffer : offerBookService.getOffers()) {
|
||||||
if (anOffer.getId().equals(initTradeRequest.getTradeId())) {
|
if (anOffer.getId().equals(request.getTradeId())) {
|
||||||
offer = anOffer;
|
offer = anOffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
Trade trade;
|
Trade trade;
|
||||||
Optional<Trade> tradeOptional = getTradeById(offer.getId());
|
Optional<Trade> tradeOptional = getTradeById(offer.getId());
|
||||||
if (!tradeOptional.isPresent()) {
|
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);
|
|
||||||
} else {
|
|
||||||
trade = tradeOptional.get();
|
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 {
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): do this for arbitrator?
|
// compute expected taker fee
|
||||||
// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(true), Coin.valueOf(offer.getOfferPayload().getAmount()));
|
||||||
// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
|
Coin takerFee = CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee(true));
|
||||||
// if (prev != null) {
|
|
||||||
// log.error("We had already an entry with uid {}", trade.getUid());
|
|
||||||
// }
|
|
||||||
|
|
||||||
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||||
if (takeOfferRequestErrorMessageHandler != null)
|
if (takeOfferRequestErrorMessageHandler != null)
|
||||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler?
|
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? // TODO (woodser): ensure failed trade removed
|
||||||
});
|
});
|
||||||
|
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
@ -394,7 +432,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
// handle request as maker
|
// handle request as maker
|
||||||
else {
|
else {
|
||||||
|
|
||||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId());
|
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(request.getTradeId());
|
||||||
if (!openOfferOptional.isPresent()) {
|
if (!openOfferOptional.isPresent()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -407,100 +445,179 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator?
|
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;
|
Trade trade;
|
||||||
if (offer.isBuyOffer())
|
if (offer.isBuyOffer())
|
||||||
trade = new BuyerAsMakerTrade(offer,
|
trade = new BuyerAsMakerTrade(offer,
|
||||||
Coin.valueOf(initTradeRequest.getTxFee()),
|
Coin.valueOf(offer.getOfferPayload().getAmount()),
|
||||||
Coin.valueOf(initTradeRequest.getTradeFee()),
|
Coin.valueOf(offer.getOfferPayload().getMakerFee()), // TODO (woodser): this is maker fee, but Trade calls it taker fee, why not have both?
|
||||||
initTradeRequest.getMakerNodeAddress(),
|
offer.getOfferPayload().getPrice(),
|
||||||
initTradeRequest.getTakerNodeAddress(),
|
|
||||||
initTradeRequest.getArbitratorNodeAddress(),
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
getNewProcessModel(offer),
|
getNewProcessModel(offer),
|
||||||
UUID.randomUUID().toString());
|
UUID.randomUUID().toString(),
|
||||||
|
request.getMakerNodeAddress(),
|
||||||
|
request.getTakerNodeAddress(),
|
||||||
|
request.getArbitratorNodeAddress());
|
||||||
else
|
else
|
||||||
trade = new SellerAsMakerTrade(offer,
|
trade = new SellerAsMakerTrade(offer,
|
||||||
Coin.valueOf(initTradeRequest.getTxFee()),
|
Coin.valueOf(offer.getOfferPayload().getAmount()),
|
||||||
Coin.valueOf(initTradeRequest.getTradeFee()),
|
Coin.valueOf(offer.getOfferPayload().getMakerFee()),
|
||||||
initTradeRequest.getMakerNodeAddress(),
|
offer.getOfferPayload().getPrice(),
|
||||||
initTradeRequest.getTakerNodeAddress(),
|
|
||||||
openOffer.getArbitratorNodeAddress(),
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
getNewProcessModel(offer),
|
getNewProcessModel(offer),
|
||||||
UUID.randomUUID().toString());
|
UUID.randomUUID().toString(),
|
||||||
|
request.getMakerNodeAddress(),
|
||||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
request.getTakerNodeAddress(),
|
||||||
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
|
request.getArbitratorNodeAddress());
|
||||||
if (prev != null) {
|
|
||||||
log.error("We had already an entry with uid {}", trade.getUid());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//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);
|
tradableList.add(trade);
|
||||||
initTradeAndProtocol(trade, tradeProtocol);
|
|
||||||
|
|
||||||
((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
|
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||||
if (takeOfferRequestErrorMessageHandler != null)
|
if (takeOfferRequestErrorMessageHandler != null) {
|
||||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) {
|
private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer) {
|
||||||
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
log.info("Received InitMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Validator.nonEmptyStringOf(request.getTradeId());
|
Validator.nonEmptyStringOf(request.getTradeId());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.warn("Invalid InitTradeRequest message " + request.toString());
|
log.warn("Invalid InitMultisigRequest " + request.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||||
Trade trade = tradeOptional.get();
|
Trade trade = tradeOptional.get();
|
||||||
((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> {
|
getTradeProtocol(trade).handleInitMultisigRequest(request, peer, errorMessage -> {
|
||||||
if (takeOfferRequestErrorMessageHandler != null)
|
if (takeOfferRequestErrorMessageHandler != null) {
|
||||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) {
|
private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) {
|
||||||
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
|
log.info("Received SignContractRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Validator.nonEmptyStringOf(request.getTradeId());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.warn("Invalid SignContractRequest 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).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());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Validator.nonEmptyStringOf(request.getTradeId());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.warn("Invalid SignContractResponse 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)).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());
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
Validator.nonEmptyStringOf(response.getTradeId());
|
Validator.nonEmptyStringOf(response.getTradeId());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.warn("Invalid InitTradeRequest message " + response.toString());
|
log.warn("Invalid DepositResponse message " + response.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
|
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
|
||||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
|
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
|
||||||
Trade trade = tradeOptional.get();
|
Trade trade = tradeOptional.get();
|
||||||
((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> {
|
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer, errorMessage -> {
|
||||||
if (takeOfferRequestErrorMessageHandler != null)
|
if (takeOfferRequestErrorMessageHandler != null) {
|
||||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) {
|
private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) {
|
||||||
log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid());
|
log.info("Received PaymentAccountPayloadRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Validator.nonEmptyStringOf(multisigMessage.getTradeId());
|
Validator.nonEmptyStringOf(request.getTradeId());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString());
|
log.warn("Invalid PaymentAccountPayloadRequest message " + request.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Trade> tradeOptional = getTradeById(multisigMessage.getTradeId());
|
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling
|
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||||
Trade trade = tradeOptional.get();
|
Trade trade = tradeOptional.get();
|
||||||
getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> {
|
((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer, errorMessage -> {
|
||||||
if (takeOfferRequestErrorMessageHandler != null)
|
if (takeOfferRequestErrorMessageHandler != null) {
|
||||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
// Take offer
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void checkOfferAvailability(Offer offer,
|
public void checkOfferAvailability(Offer offer,
|
||||||
boolean isTakerApiUser,
|
boolean isTakerApiUser,
|
||||||
|
String paymentAccountId,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
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
|
// First we check if offer is still available then we create the trade with the protocol
|
||||||
public void onTakeOffer(Coin amount,
|
public void onTakeOffer(Coin amount,
|
||||||
Coin txFee,
|
Coin txFee,
|
||||||
Coin takerFee,
|
Coin takerFee,
|
||||||
long tradePrice,
|
|
||||||
Coin fundsNeededForTrade,
|
Coin fundsNeededForTrade,
|
||||||
Offer offer,
|
Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
@ -568,7 +666,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
|
|
||||||
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
|
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
|
||||||
|
|
||||||
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser);
|
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId);
|
||||||
offer.checkOfferAvailability(model,
|
offer.checkOfferAvailability(model,
|
||||||
() -> {
|
() -> {
|
||||||
if (offer.getState() == Offer.State.AVAILABLE) {
|
if (offer.getState() == Offer.State.AVAILABLE) {
|
||||||
@ -576,30 +674,33 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
if (offer.isBuyOffer()) {
|
if (offer.isBuyOffer()) {
|
||||||
trade = new SellerAsTakerTrade(offer,
|
trade = new SellerAsTakerTrade(offer,
|
||||||
amount,
|
amount,
|
||||||
txFee,
|
|
||||||
takerFee,
|
takerFee,
|
||||||
tradePrice,
|
model.getTradeRequest().getTradePrice(),
|
||||||
model.getPeerNodeAddress(),
|
|
||||||
P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address?
|
|
||||||
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
getNewProcessModel(offer),
|
getNewProcessModel(offer),
|
||||||
UUID.randomUUID().toString());
|
UUID.randomUUID().toString(),
|
||||||
|
model.getPeerNodeAddress(),
|
||||||
|
P2PService.getMyNodeAddress(),
|
||||||
|
offer.getOfferPayload().getArbitratorNodeAddress());
|
||||||
} else {
|
} else {
|
||||||
trade = new BuyerAsTakerTrade(offer,
|
trade = new BuyerAsTakerTrade(offer,
|
||||||
amount,
|
amount,
|
||||||
txFee,
|
|
||||||
takerFee,
|
takerFee,
|
||||||
tradePrice,
|
model.getTradeRequest().getTradePrice(),
|
||||||
model.getPeerNodeAddress(),
|
|
||||||
P2PService.getMyNodeAddress(),
|
|
||||||
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
|
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
getNewProcessModel(offer),
|
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().setUseSavingsWallet(useSavingsWallet);
|
||||||
trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value);
|
trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value);
|
||||||
|
trade.setTakerPubKeyRing(model.getPubKeyRing());
|
||||||
trade.setTakerPaymentAccountId(paymentAccountId);
|
trade.setTakerPaymentAccountId(paymentAccountId);
|
||||||
|
|
||||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||||
@ -627,15 +728,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
processModelServiceProvider.getKeyRing().getPubKeyRing());
|
processModelServiceProvider.getKeyRing().getPubKeyRing());
|
||||||
}
|
}
|
||||||
|
|
||||||
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) {
|
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser, String paymentAccountId) {
|
||||||
return new OfferAvailabilityModel(
|
return new OfferAvailabilityModel(
|
||||||
offer,
|
offer,
|
||||||
keyRing.getPubKeyRing(),
|
keyRing.getPubKeyRing(),
|
||||||
|
xmrWalletService,
|
||||||
p2PService,
|
p2PService,
|
||||||
user,
|
user,
|
||||||
mediatorManager,
|
mediatorManager,
|
||||||
tradeStatisticsManager,
|
tradeStatisticsManager,
|
||||||
isTakerApiUser);
|
isTakerApiUser,
|
||||||
|
paymentAccountId,
|
||||||
|
offerUtil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -643,6 +747,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
// Complete trade
|
// Complete trade
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// TODO (woodser): remove this function
|
||||||
public void onWithdrawRequest(String toAddress,
|
public void onWithdrawRequest(String toAddress,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
Coin fee,
|
Coin fee,
|
||||||
@ -651,35 +756,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
@Nullable String memo,
|
@Nullable String memo,
|
||||||
ResultHandler resultHandler,
|
ResultHandler resultHandler,
|
||||||
FaultHandler faultHandler) {
|
FaultHandler faultHandler) {
|
||||||
int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(),
|
throw new RuntimeException("Withdraw trade funds after payout to Haveno wallet not supported");
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
|
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
|
||||||
|
@ -17,15 +17,255 @@
|
|||||||
|
|
||||||
package bisq.core.trade;
|
package bisq.core.trade;
|
||||||
|
|
||||||
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
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.KeyRing;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.crypto.Sig;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
|
import bisq.common.util.Utilities;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.Objects;
|
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 {
|
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
|
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
||||||
static Tuple2<String, String> getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService,
|
static Tuple2<String, String> getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService,
|
||||||
KeyRing keyRing) {
|
KeyRing keyRing) {
|
||||||
|
@ -123,7 +123,7 @@ public class CleanupMailboxMessages {
|
|||||||
private boolean isPubKeyValid(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
|
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
|
// 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.
|
// 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;
|
boolean isValid = true;
|
||||||
if (peersPubKeyRing != null &&
|
if (peersPubKeyRing != null &&
|
||||||
!decryptedMessageWithPubKey.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) {
|
!decryptedMessageWithPubKey.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) {
|
||||||
|
103
core/src/main/java/bisq/core/trade/messages/DepositRequest.java
Normal file
103
core/src/main/java/bisq/core/trade/messages/DepositRequest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ import bisq.core.proto.CoreProtoResolver;
|
|||||||
|
|
||||||
import bisq.network.p2p.DirectMessage;
|
import bisq.network.p2p.DirectMessage;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@ -29,18 +29,21 @@ import lombok.Value;
|
|||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Value
|
@Value
|
||||||
public final class MakerReadyToFundMultisigRequest extends TradeMessage implements DirectMessage {
|
public final class DepositResponse extends TradeMessage implements DirectMessage {
|
||||||
private final NodeAddress senderNodeAddress;
|
private final NodeAddress senderNodeAddress;
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRing pubKeyRing;
|
||||||
|
private final long currentDate;
|
||||||
|
|
||||||
public MakerReadyToFundMultisigRequest(String tradeId,
|
public DepositResponse(String tradeId,
|
||||||
NodeAddress senderNodeAddress,
|
NodeAddress senderNodeAddress,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
String uid,
|
String uid,
|
||||||
int messageVersion) {
|
int messageVersion,
|
||||||
|
long currentDate) {
|
||||||
super(messageVersion, tradeId, uid);
|
super(messageVersion, tradeId, uid);
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
this.senderNodeAddress = senderNodeAddress;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
|
this.currentDate = currentDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -50,30 +53,33 @@ public final class MakerReadyToFundMultisigRequest extends TradeMessage implemen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
protobuf.MakerReadyToFundMultisigRequest.Builder builder = protobuf.MakerReadyToFundMultisigRequest.newBuilder()
|
protobuf.DepositResponse.Builder builder = protobuf.DepositResponse.newBuilder()
|
||||||
.setTradeId(tradeId)
|
.setTradeId(tradeId)
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||||
.setUid(uid);
|
.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,
|
CoreProtoResolver coreProtoResolver,
|
||||||
int messageVersion) {
|
int messageVersion) {
|
||||||
return new MakerReadyToFundMultisigRequest(proto.getTradeId(),
|
return new DepositResponse(proto.getTradeId(),
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||||
proto.getUid(),
|
proto.getUid(),
|
||||||
messageVersion);
|
messageVersion,
|
||||||
|
proto.getCurrentDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MakerReadyToFundMultisigRequest{" +
|
return "DepositResponse {" +
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
",\n pubKeyRing=" + pubKeyRing +
|
||||||
|
",\n currentDate=" + currentDate +
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,7 +34,7 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Value
|
@Value
|
||||||
public final class InitMultisigMessage extends TradeMessage implements DirectMessage {
|
public final class InitMultisigRequest extends TradeMessage implements DirectMessage {
|
||||||
private final NodeAddress senderNodeAddress;
|
private final NodeAddress senderNodeAddress;
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRing pubKeyRing;
|
||||||
private final long currentDate;
|
private final long currentDate;
|
||||||
@ -43,7 +43,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final String madeMultisigHex;
|
private final String madeMultisigHex;
|
||||||
|
|
||||||
public InitMultisigMessage(String tradeId,
|
public InitMultisigRequest(String tradeId,
|
||||||
NodeAddress senderNodeAddress,
|
NodeAddress senderNodeAddress,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
String uid,
|
String uid,
|
||||||
@ -66,7 +66,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
protobuf.InitMultisigMessage.Builder builder = protobuf.InitMultisigMessage.newBuilder()
|
protobuf.InitMultisigRequest.Builder builder = protobuf.InitMultisigRequest.newBuilder()
|
||||||
.setTradeId(tradeId)
|
.setTradeId(tradeId)
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||||
@ -77,13 +77,13 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
|||||||
|
|
||||||
builder.setCurrentDate(currentDate);
|
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,
|
CoreProtoResolver coreProtoResolver,
|
||||||
int messageVersion) {
|
int messageVersion) {
|
||||||
return new InitMultisigMessage(proto.getTradeId(),
|
return new InitMultisigRequest(proto.getTradeId(),
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||||
proto.getUid(),
|
proto.getUid(),
|
||||||
@ -95,7 +95,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MultisigMessage {" +
|
return "InitMultisigRequest {" +
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
",\n pubKeyRing=" + pubKeyRing +
|
||||||
",\n currentDate=" + currentDate +
|
",\n currentDate=" + currentDate +
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package bisq.core.trade.messages;
|
package bisq.core.trade.messages;
|
||||||
|
|
||||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
|
|
||||||
import bisq.network.p2p.DirectMessage;
|
import bisq.network.p2p.DirectMessage;
|
||||||
@ -42,59 +41,73 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
private final NodeAddress senderNodeAddress;
|
private final NodeAddress senderNodeAddress;
|
||||||
private final long tradeAmount;
|
private final long tradeAmount;
|
||||||
private final long tradePrice;
|
private final long tradePrice;
|
||||||
private final long txFee;
|
|
||||||
private final long tradeFee;
|
private final long tradeFee;
|
||||||
private final String payoutAddressString;
|
|
||||||
private final PaymentAccountPayload paymentAccountPayload;
|
|
||||||
private final PubKeyRing pubKeyRing;
|
|
||||||
private final String accountId;
|
private final String accountId;
|
||||||
@Nullable
|
private final String paymentAccountId;
|
||||||
private final String tradeFeeTxId;
|
private final String paymentMethodId;
|
||||||
private final NodeAddress arbitratorNodeAddress;
|
private final PubKeyRing pubKeyRing;
|
||||||
|
|
||||||
// added in v 0.6. can be null if we trade with an older peer
|
// added in v 0.6. can be null if we trade with an older peer
|
||||||
@Nullable
|
@Nullable
|
||||||
private final byte[] accountAgeWitnessSignatureOfOfferId;
|
private final byte[] accountAgeWitnessSignatureOfOfferId;
|
||||||
private final long currentDate;
|
private final long currentDate;
|
||||||
|
|
||||||
// added for XMR integration
|
// XMR integration
|
||||||
private final NodeAddress takerNodeAddress;
|
|
||||||
private final NodeAddress makerNodeAddress;
|
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,
|
public InitTradeRequest(String tradeId,
|
||||||
NodeAddress senderNodeAddress,
|
NodeAddress senderNodeAddress,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
long tradeAmount,
|
long tradeAmount,
|
||||||
long tradePrice,
|
long tradePrice,
|
||||||
long txFee,
|
|
||||||
long tradeFee,
|
long tradeFee,
|
||||||
String payoutAddressString,
|
|
||||||
PaymentAccountPayload paymentAccountPayload,
|
|
||||||
String accountId,
|
String accountId,
|
||||||
String tradeFeeTxId,
|
String paymentAccountId,
|
||||||
|
String paymentMethodId,
|
||||||
String uid,
|
String uid,
|
||||||
int messageVersion,
|
int messageVersion,
|
||||||
@Nullable byte[] accountAgeWitnessSignatureOfOfferId,
|
@Nullable byte[] accountAgeWitnessSignatureOfOfferId,
|
||||||
long currentDate,
|
long currentDate,
|
||||||
NodeAddress takerNodeAddress,
|
|
||||||
NodeAddress makerNodeAddress,
|
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);
|
super(messageVersion, tradeId, uid);
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
this.senderNodeAddress = senderNodeAddress;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
this.paymentAccountPayload = paymentAccountPayload;
|
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
this.tradePrice = tradePrice;
|
this.tradePrice = tradePrice;
|
||||||
this.txFee = txFee;
|
|
||||||
this.tradeFee = tradeFee;
|
this.tradeFee = tradeFee;
|
||||||
this.payoutAddressString = payoutAddressString;
|
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
this.tradeFeeTxId = tradeFeeTxId;
|
this.paymentAccountId = paymentAccountId;
|
||||||
|
this.paymentMethodId = paymentMethodId;
|
||||||
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
|
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
|
||||||
this.currentDate = currentDate;
|
this.currentDate = currentDate;
|
||||||
this.takerNodeAddress = takerNodeAddress;
|
|
||||||
this.makerNodeAddress = makerNodeAddress;
|
this.makerNodeAddress = makerNodeAddress;
|
||||||
|
this.takerNodeAddress = takerNodeAddress;
|
||||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
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())
|
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||||
.setTakerNodeAddress(takerNodeAddress.toProtoMessage())
|
.setTakerNodeAddress(takerNodeAddress.toProtoMessage())
|
||||||
.setMakerNodeAddress(makerNodeAddress.toProtoMessage())
|
.setMakerNodeAddress(makerNodeAddress.toProtoMessage())
|
||||||
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
|
|
||||||
.setTradeAmount(tradeAmount)
|
.setTradeAmount(tradeAmount)
|
||||||
.setTradePrice(tradePrice)
|
.setTradePrice(tradePrice)
|
||||||
.setTxFee(txFee)
|
|
||||||
.setTradeFee(tradeFee)
|
.setTradeFee(tradeFee)
|
||||||
.setPayoutAddressString(payoutAddressString)
|
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||||
.setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage())
|
.setPaymentAccountId(paymentAccountId)
|
||||||
|
.setPaymentMethodId(paymentMethodId)
|
||||||
.setAccountId(accountId)
|
.setAccountId(accountId)
|
||||||
.setUid(uid);
|
.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(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
|
||||||
|
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build();
|
return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build();
|
||||||
@ -135,19 +151,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||||
proto.getTradeAmount(),
|
proto.getTradeAmount(),
|
||||||
proto.getTradePrice(),
|
proto.getTradePrice(),
|
||||||
proto.getTxFee(),
|
|
||||||
proto.getTradeFee(),
|
proto.getTradeFee(),
|
||||||
proto.getPayoutAddressString(),
|
|
||||||
coreProtoResolver.fromProto(proto.getPaymentAccountPayload()),
|
|
||||||
proto.getAccountId(),
|
proto.getAccountId(),
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()),
|
proto.getPaymentAccountId(),
|
||||||
|
proto.getPaymentMethodId(),
|
||||||
proto.getUid(),
|
proto.getUid(),
|
||||||
messageVersion,
|
messageVersion,
|
||||||
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
|
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
|
||||||
proto.getCurrentDate(),
|
proto.getCurrentDate(),
|
||||||
NodeAddress.fromProto(proto.getTakerNodeAddress()),
|
|
||||||
NodeAddress.fromProto(proto.getMakerNodeAddress()),
|
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
|
@Override
|
||||||
@ -156,16 +175,19 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
|||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n tradeAmount=" + tradeAmount +
|
",\n tradeAmount=" + tradeAmount +
|
||||||
",\n tradePrice=" + tradePrice +
|
",\n tradePrice=" + tradePrice +
|
||||||
",\n txFee=" + txFee +
|
",\n tradeFee=" + tradeFee +
|
||||||
",\n takerFee=" + tradeFee +
|
|
||||||
",\n payoutAddressString='" + payoutAddressString + '\'' +
|
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
",\n pubKeyRing=" + pubKeyRing +
|
||||||
",\n paymentAccountPayload=" + paymentAccountPayload +
|
",\n accountId='" + accountId + '\'' +
|
||||||
",\n paymentAccountPayload='" + accountId + '\'' +
|
",\n paymentAccountId=" + paymentAccountId +
|
||||||
",\n takerFeeTxId='" + tradeFeeTxId + '\'' +
|
",\n paymentMethodId=" + paymentMethodId +
|
||||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||||
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
|
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
|
||||||
",\n currentDate=" + currentDate +
|
",\n currentDate=" + currentDate +
|
||||||
|
",\n reserveTxHash=" + reserveTxHash +
|
||||||
|
",\n reserveTxHex=" + reserveTxHex +
|
||||||
|
",\n reserveTxKey=" + reserveTxKey +
|
||||||
|
",\n payoutAddress=" + payoutAddress +
|
||||||
|
",\n makerSignature=" + makerSignature +
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -89,7 +89,7 @@ public final class UpdateMultisigRequest extends TradeMessage implements DirectM
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MultisigMessage {" +
|
return "UpdateMultisigRequest {" +
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
",\n pubKeyRing=" + pubKeyRing +
|
||||||
",\n currentDate=" + currentDate +
|
",\n currentDate=" + currentDate +
|
||||||
|
@ -89,7 +89,7 @@ public final class UpdateMultisigResponse extends TradeMessage implements Direct
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MultisigMessage {" +
|
return "UpdateMultisigResponse {" +
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
",\n pubKeyRing=" + pubKeyRing +
|
||||||
",\n currentDate=" + currentDate +
|
",\n currentDate=" + currentDate +
|
||||||
|
@ -2,10 +2,19 @@ package bisq.core.trade.protocol;
|
|||||||
|
|
||||||
import bisq.core.trade.ArbitratorTrade;
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
import bisq.core.trade.Trade;
|
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.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.ProcessInitTradeRequest;
|
||||||
|
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||||
|
import bisq.core.util.Validator;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
@ -15,73 +24,91 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ArbitratorProtocol extends DisputeProtocol {
|
public class ArbitratorProtocol extends DisputeProtocol {
|
||||||
|
|
||||||
private final ArbitratorTrade arbitratorTrade;
|
|
||||||
|
|
||||||
public ArbitratorProtocol(ArbitratorTrade trade) {
|
public ArbitratorProtocol(ArbitratorTrade trade) {
|
||||||
super(trade);
|
super(trade);
|
||||||
this.arbitratorTrade = trade;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Incoming messages
|
// 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
|
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler
|
||||||
|
processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set
|
||||||
|
//processModel.setTempTradingPeerNodeAddress(peer);
|
||||||
expect(phase(Trade.Phase.INIT)
|
expect(phase(Trade.Phase.INIT)
|
||||||
.with(message)
|
.with(message)
|
||||||
.from(peer))
|
.from(peer))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
//ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
ProcessInitTradeRequest.class))
|
ProcessInitTradeRequest.class,
|
||||||
|
ArbitratorProcessesReserveTx.class,
|
||||||
|
ArbitratorSendsInitTradeRequestToMakerIfFromTaker.class,
|
||||||
|
ArbitratorSendsInitMultisigRequestsIfFundsReserved.class))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
|
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
throw new RuntimeException("Not implemented");
|
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
|
@Override
|
||||||
// public void handleTakeOfferRequest(InputsForDepositTxRequest message,
|
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
// NodeAddress peer,
|
System.out.println("ArbitratorProtocol.handleSignContractRequest()");
|
||||||
// ErrorMessageHandler errorMessageHandler) {
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
// expect(phase(Trade.Phase.INIT)
|
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
|
||||||
// .with(message)
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
// .from(peer))
|
.with(message)
|
||||||
// .setup(tasks(
|
.from(sender))
|
||||||
// MakerProcessesInputsForDepositTxRequest.class,
|
.setup(tasks(
|
||||||
// ApplyFilter.class,
|
// TODO (woodser): validate request
|
||||||
// VerifyPeersAccountAgeWitness.class,
|
ProcessSignContractRequest.class)
|
||||||
// getVerifyPeersFeePaymentClass(),
|
.using(new TradeTaskRunner(trade,
|
||||||
// MakerSetsLockTime.class,
|
() -> {
|
||||||
// MakerCreateAndSignContract.class,
|
handleTaskRunnerSuccess(sender, message);
|
||||||
// BuyerAsMakerCreatesAndSignsDepositTx.class,
|
},
|
||||||
// BuyerSetupDepositTxListener.class,
|
errorMessage -> {
|
||||||
// BuyerAsMakerSendsInputsForDepositTxResponse.class).
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
// using(new TradeTaskRunner(trade,
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
// () -> handleTaskRunnerSuccess(message),
|
})))
|
||||||
// errorMessage -> {
|
.executeTasks();
|
||||||
// errorMessageHandler.handleErrorMessage(errorMessage);
|
}
|
||||||
// handleTaskRunnerFault(message, errorMessage);
|
|
||||||
// }))
|
public void handleDepositRequest(DepositRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
// .withTimeout(30))
|
System.out.println("ArbitratorProtocol.handleDepositRequest()");
|
||||||
// .executeTasks();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Message dispatcher
|
// Message dispatcher
|
||||||
|
@ -20,27 +20,28 @@ package bisq.core.trade.protocol;
|
|||||||
import bisq.core.trade.BuyerAsMakerTrade;
|
import bisq.core.trade.BuyerAsMakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||||
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
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.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
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.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.TradeTask;
|
||||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
|
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
|
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.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.maker.MakerVerifyTakerFeePayment;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
|||||||
// Incoming messages Take offer process
|
// Incoming messages Take offer process
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
|
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
|
||||||
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||||
.with(message)
|
.with(message)
|
||||||
@ -138,73 +140,129 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
|||||||
.from(peer))
|
.from(peer))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ProcessInitTradeRequest.class,
|
ProcessInitTradeRequest.class,
|
||||||
ApplyFilter.class,
|
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
|
||||||
VerifyPeersAccountAgeWitness.class,
|
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
|
||||||
MakerVerifyTakerFeePayment.class,
|
//MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this
|
||||||
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
|
MakerRemovesOpenOffer.class).
|
||||||
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
|
|
||||||
MakerSendsReadyToFundMultisigResponse.class).
|
|
||||||
using(new TradeTaskRunner(trade,
|
using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
handleTaskRunnerSuccess(peer, message);
|
||||||
handleTaskRunnerSuccess(message);
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(30))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
|
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
NodeAddress sender,
|
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
|
||||||
processModel.setTradeMessage(message);
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
processModel.setTempTradingPeerNodeAddress(sender);
|
.with(request)
|
||||||
|
|
||||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
|
||||||
.with(message)
|
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
MakerSendsReadyToFundMultisigResponse.class).
|
ProcessInitMultisigRequest.class,
|
||||||
using(new TradeTaskRunner(trade,
|
SendSignContractRequestAfterMultisig.class)
|
||||||
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
handleTaskRunnerSuccess(sender, request);
|
||||||
handleTaskRunnerSuccess(message);
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, request, errorMessage);
|
||||||
}))
|
})))
|
||||||
.withTimeout(30))
|
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleDepositTxMessage(DepositTxMessage message,
|
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
NodeAddress sender,
|
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
processModel.setTradeMessage(message);
|
processModel.setTradeMessage(message);
|
||||||
processModel.setTempTradingPeerNodeAddress(sender);
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
|
|
||||||
// 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)
|
|
||||||
.with(message)
|
.with(message)
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
MakerVerifyTakerDepositTx.class,
|
// TODO (woodser): validate request
|
||||||
MakerCreateAndSignContract.class,
|
ProcessSignContractRequest.class)
|
||||||
MakerCreateAndPublishDepositTx.class,
|
.using(new TradeTaskRunner(trade,
|
||||||
MakerSetupDepositTxsListener.class).
|
() -> {
|
||||||
using(new TradeTaskRunner(trade,
|
handleTaskRunnerSuccess(sender, message);
|
||||||
() -> handleTaskRunnerSuccess(message),
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
|
})))
|
||||||
|
.executeTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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(
|
||||||
|
// 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();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
@ -22,15 +22,22 @@ import bisq.core.offer.Offer;
|
|||||||
import bisq.core.trade.BuyerAsTakerTrade;
|
import bisq.core.trade.BuyerAsTakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||||
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||||
import bisq.core.trade.messages.DepositTxMessage;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitMultisigMessage;
|
|
||||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
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.PayoutTxPublishedMessage;
|
||||||
|
import bisq.core.trade.messages.SignContractRequest;
|
||||||
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
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.TradeTask;
|
||||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
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.BuyerVerifiesPreparedDelayedPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
|
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.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.TakerProcessesInputsForDepositTxResponse;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
|
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
|
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
|
||||||
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.TakerVerifyMakerFeePayment;
|
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.Timer;
|
|
||||||
import bisq.common.UserThread;
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
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
|
// TODO (woodser): remove unused request handling
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
|
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
|
||||||
private ResultHandler takeOfferListener;
|
|
||||||
private Timer initDepositTimer;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
@ -87,7 +75,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||||||
super(trade);
|
super(trade);
|
||||||
|
|
||||||
Offer offer = checkNotNull(trade.getOffer());
|
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?
|
// 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()))
|
.from(trade.getTradingPeerNodeAddress()))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
TakerVerifyMakerFeePayment.class,
|
TakerReservesTradeFunds.class,
|
||||||
TakerSendInitTradeRequests.class)
|
TakerSendsInitTradeRequestToArbitrator.class)
|
||||||
.withTimeout(30))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
@ -125,7 +113,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||||||
.setup(tasks(TakerProcessesInputsForDepositTxResponse.class,
|
.setup(tasks(TakerProcessesInputsForDepositTxResponse.class,
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
VerifyPeersAccountAgeWitness.class,
|
VerifyPeersAccountAgeWitness.class,
|
||||||
TakerVerifyAndSignContract.class,
|
//TakerVerifyAndSignContract.class,
|
||||||
TakerPublishFeeTx.class,
|
TakerPublishFeeTx.class,
|
||||||
BuyerAsTakerSignsDepositTx.class,
|
BuyerAsTakerSignsDepositTx.class,
|
||||||
BuyerSetupDepositTxListener.class,
|
BuyerSetupDepositTxListener.class,
|
||||||
@ -134,6 +122,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
|
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
|
||||||
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||||
.with(message)
|
.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
|
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Incoming message handling
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
|
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
|
||||||
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
|
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||||
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
|
processModel.setTradeMessage(request);
|
||||||
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
processModel.setTradeMessage(message);
|
.with(request)
|
||||||
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)
|
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ProcessInitMultisigMessage.class)
|
ProcessInitMultisigRequest.class,
|
||||||
|
SendSignContractRequestAfterMultisig.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
System.out.println("handle multisig pipeline completed successfully!");
|
System.out.println("handle multisig pipeline completed successfully!");
|
||||||
handleTaskRunnerSuccess(message);
|
handleTaskRunnerSuccess(sender, request);
|
||||||
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
|
|
||||||
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
|
|
||||||
fundMultisig(message, takeOfferListener);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
|
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, request, errorMessage);
|
||||||
takeOfferListener.handleResult();
|
|
||||||
})))
|
|
||||||
.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))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
|
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
|
||||||
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
processModel.setTradeMessage(message);
|
processModel.setTradeMessage(message);
|
||||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
.with(message)
|
.with(message)
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
TakerProcessesMakerDepositTxMessage.class,
|
// TODO (woodser): validate request
|
||||||
TakerSetupDepositTxsListener.class).
|
ProcessSignContractRequest.class)
|
||||||
using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
handleTaskRunnerSuccess(sender, message);
|
||||||
handleTaskRunnerSuccess(message);
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(30))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,12 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
|||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
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.TradeTask;
|
||||||
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
|
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
|
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
|
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.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
@ -57,16 +57,15 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||||||
@Override
|
@Override
|
||||||
protected void onInitialized() {
|
protected void onInitialized() {
|
||||||
super.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.DEPOSIT_PUBLISHED)
|
||||||
given(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
|
||||||
.with(BuyerEvent.STARTUP))
|
.with(BuyerEvent.STARTUP))
|
||||||
.setup(tasks(BuyerSetupDepositTxListener.class))
|
.setup(tasks(SetupDepositTxsListener.class))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
|
|
||||||
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
||||||
.with(BuyerEvent.STARTUP))
|
.with(BuyerEvent.STARTUP))
|
||||||
.setup(tasks(BuyerSetupPayoutTxListener.class))
|
.setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup?
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
|
|
||||||
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
||||||
@ -166,10 +165,10 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||||||
BuyerProcessPayoutTxPublishedMessage.class)
|
BuyerProcessPayoutTxPublishedMessage.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
handleTaskRunnerSuccess(message);
|
handleTaskRunnerSuccess(peer, message);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
})))
|
})))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
|||||||
Trade.Phase.FIAT_SENT,
|
Trade.Phase.FIAT_SENT,
|
||||||
Trade.Phase.FIAT_RECEIVED)
|
Trade.Phase.FIAT_RECEIVED)
|
||||||
.with(event)
|
.with(event)
|
||||||
.preCondition(trade.getProcessModel().getTradingPeer().getMediatedPayoutTxSignature() == null,
|
.preCondition(trade.getTradingPeer().getMediatedPayoutTxSignature() == null,
|
||||||
() -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer."))
|
() -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer."))
|
||||||
.preCondition(trade.getPayoutTx() == null,
|
.preCondition(trade.getPayoutTx() == null,
|
||||||
() -> errorMessageHandler.handleErrorMessage("Payout tx is already published.")))
|
() -> errorMessageHandler.handleErrorMessage("Payout tx is already published.")))
|
||||||
|
@ -98,7 +98,7 @@ public class FluentProtocol {
|
|||||||
|
|
||||||
NodeAddress peer = condition.getPeer();
|
NodeAddress peer = condition.getPeer();
|
||||||
if (peer != null) {
|
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();
|
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ public class FluentProtocol {
|
|||||||
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
TradeTaskRunner taskRunner = setup.getTaskRunner(message, condition.getEvent());
|
TradeTaskRunner taskRunner = setup.getTaskRunner(peer, message, condition.getEvent());
|
||||||
taskRunner.addTasks(setup.getTasks());
|
taskRunner.addTasks(setup.getTasks());
|
||||||
taskRunner.run();
|
taskRunner.run();
|
||||||
return this;
|
return this;
|
||||||
@ -366,12 +366,12 @@ public class FluentProtocol {
|
|||||||
return this;
|
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 (taskRunner == null) {
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
taskRunner = new TradeTaskRunner(trade,
|
taskRunner = new TradeTaskRunner(trade,
|
||||||
() -> tradeProtocol.handleTaskRunnerSuccess(message),
|
() -> tradeProtocol.handleTaskRunnerSuccess(sender, message),
|
||||||
errorMessage -> tradeProtocol.handleTaskRunnerFault(message, errorMessage));
|
errorMessage -> tradeProtocol.handleTaskRunnerFault(sender, message, errorMessage));
|
||||||
} else if (event != null) {
|
} else if (event != null) {
|
||||||
taskRunner = new TradeTaskRunner(trade,
|
taskRunner = new TradeTaskRunner(trade,
|
||||||
() -> tradeProtocol.handleTaskRunnerSuccess(event),
|
() -> tradeProtocol.handleTaskRunnerSuccess(event),
|
||||||
|
@ -19,13 +19,11 @@ package bisq.core.trade.protocol;
|
|||||||
|
|
||||||
|
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
|
|
||||||
public interface MakerProtocol {
|
public interface MakerProtocol extends TraderProtocol {
|
||||||
void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
||||||
void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,12 @@ import bisq.core.btc.model.RawTransactionInput;
|
|||||||
import bisq.core.btc.wallet.BsqWalletService;
|
import bisq.core.btc.wallet.BsqWalletService;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.dao.DaoFacade;
|
import bisq.core.dao.DaoFacade;
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.network.MessageState;
|
import bisq.core.network.MessageState;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.offer.OfferPayload.Direction;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||||
@ -60,7 +62,7 @@ import org.bitcoinj.core.Transaction;
|
|||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -90,14 +92,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
transient private ProcessModelServiceProvider provider;
|
transient private ProcessModelServiceProvider provider;
|
||||||
transient private TradeManager tradeManager;
|
transient private TradeManager tradeManager;
|
||||||
transient private Offer offer;
|
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
|
// Added in v1.2.0
|
||||||
@Setter
|
@Setter
|
||||||
@ -114,7 +108,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
transient private ObjectProperty<MessageState> depositTxMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
transient private ObjectProperty<MessageState> depositTxMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@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)
|
// Persistable Immutable (private setter only used by PB method)
|
||||||
private TradingPeer maker = new TradingPeer();
|
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
|
// After successful verified we copy that over to the trade.tradingPeerAddress
|
||||||
@Nullable
|
@Nullable
|
||||||
@Setter
|
@Setter
|
||||||
private NodeAddress tempTradingPeerNodeAddress;
|
private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely
|
||||||
|
|
||||||
// Added in v.1.1.6
|
// Added in v.1.1.6
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -165,10 +159,33 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
private long sellerPayoutAmountFromMediation;
|
private long sellerPayoutAmountFromMediation;
|
||||||
|
|
||||||
// Added for XMR integration
|
// 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
|
@Nullable
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@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
|
@Nullable
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -180,19 +197,12 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private boolean makerReadyToFundMultisig;
|
private boolean makerReadyToFundMultisig; // TODO (woodser): remove
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private boolean multisigDepositInitiated;
|
private boolean multisigDepositInitiated;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Setter
|
transient private MoneroTxWallet buyerSignedPayoutTx; // TODO (woodser): remove
|
||||||
private String makerPreparedDepositTxId;
|
|
||||||
@Nullable
|
|
||||||
@Setter
|
|
||||||
private String takerPreparedDepositTxId;
|
|
||||||
@Nullable
|
|
||||||
transient private MoneroTxWallet buyerSignedPayoutTx;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// We want to indicate the user the state of the message delivery of the
|
// 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)
|
.setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong)
|
||||||
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
|
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
|
||||||
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
|
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
|
||||||
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
|
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation)
|
||||||
|
.addAllFrozenKeyImages(frozenKeyImages);
|
||||||
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
|
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
|
||||||
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.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(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage()));
|
||||||
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
|
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(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(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
|
||||||
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
|
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
|
||||||
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
|
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
|
||||||
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
|
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(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
|
||||||
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
||||||
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
|
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
|
||||||
@ -272,6 +284,8 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
|
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
|
||||||
|
|
||||||
// nullable
|
// nullable
|
||||||
|
processModel.setReserveTxHash(proto.getReserveTxHash());
|
||||||
|
processModel.setFrozenKeyImages(proto.getFrozenKeyImagesList());
|
||||||
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
|
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
|
||||||
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
|
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
|
||||||
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
|
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
|
||||||
@ -282,13 +296,13 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey()));
|
processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey()));
|
||||||
processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
|
processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
|
||||||
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
|
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.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
|
||||||
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
||||||
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
|
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
|
||||||
processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig());
|
processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig());
|
||||||
processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated());
|
processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated());
|
||||||
processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId());
|
|
||||||
processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId());
|
|
||||||
|
|
||||||
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
|
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
|
||||||
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);
|
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);
|
||||||
@ -350,21 +364,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
void setDepositTxSentAckMessage(AckMessage ackMessage) {
|
||||||
MessageState messageState = ackMessage.isSuccess() ?
|
MessageState messageState = ackMessage.isSuccess() ?
|
||||||
MessageState.ACKNOWLEDGED :
|
MessageState.ACKNOWLEDGED :
|
||||||
@ -388,6 +387,10 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
// Delegates
|
// Delegates
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public XmrWalletService getXmrWalletService() {
|
||||||
|
return provider.getXmrWalletService();
|
||||||
|
}
|
||||||
|
|
||||||
public BtcWalletService getBtcWalletService() {
|
public BtcWalletService getBtcWalletService() {
|
||||||
return provider.getBtcWalletService();
|
return provider.getBtcWalletService();
|
||||||
}
|
}
|
||||||
|
@ -21,21 +21,23 @@ package bisq.core.trade.protocol;
|
|||||||
import bisq.core.trade.SellerAsMakerTrade;
|
import bisq.core.trade.SellerAsMakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||||
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.DepositTxMessage;
|
import bisq.core.trade.messages.DepositTxMessage;
|
||||||
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
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.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.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.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.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.maker.MakerVerifyTakerFeePayment;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
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.SellerAsMakerFinalizesDepositTx;
|
||||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
|
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
@ -127,9 +128,11 @@ 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
|
@Override
|
||||||
public void handleInitTradeRequest(InitTradeRequest message,
|
public void handleInitTradeRequest(InitTradeRequest message,
|
||||||
NodeAddress peer,
|
NodeAddress peer,
|
||||||
@ -139,73 +142,129 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
|||||||
.from(peer))
|
.from(peer))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ProcessInitTradeRequest.class,
|
ProcessInitTradeRequest.class,
|
||||||
ApplyFilter.class,
|
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
|
||||||
VerifyPeersAccountAgeWitness.class,
|
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
|
||||||
MakerVerifyTakerFeePayment.class,
|
//MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this
|
||||||
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
|
MakerRemovesOpenOffer.class).
|
||||||
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
|
|
||||||
MakerSendsReadyToFundMultisigResponse.class).
|
|
||||||
using(new TradeTaskRunner(trade,
|
using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
handleTaskRunnerSuccess(peer, message);
|
||||||
handleTaskRunnerSuccess(message);
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(30))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
|
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
NodeAddress sender,
|
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
|
||||||
processModel.setTradeMessage(message);
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
processModel.setTempTradingPeerNodeAddress(sender);
|
.with(request)
|
||||||
|
|
||||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
|
||||||
.with(message)
|
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
MakerSendsReadyToFundMultisigResponse.class).
|
ProcessInitMultisigRequest.class,
|
||||||
using(new TradeTaskRunner(trade,
|
SendSignContractRequestAfterMultisig.class)
|
||||||
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
handleTaskRunnerSuccess(sender, request);
|
||||||
handleTaskRunnerSuccess(message);
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, request, errorMessage);
|
||||||
}))
|
})))
|
||||||
.withTimeout(30))
|
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleDepositTxMessage(DepositTxMessage message,
|
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
NodeAddress sender,
|
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
|
||||||
ErrorMessageHandler errorMessageHandler) {
|
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
processModel.setTradeMessage(message);
|
processModel.setTradeMessage(message);
|
||||||
processModel.setTempTradingPeerNodeAddress(sender);
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
|
|
||||||
// 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)
|
|
||||||
.with(message)
|
.with(message)
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
MakerVerifyTakerDepositTx.class,
|
// TODO (woodser): validate request
|
||||||
MakerCreateAndSignContract.class,
|
ProcessSignContractRequest.class)
|
||||||
MakerCreateAndPublishDepositTx.class,
|
.using(new TradeTaskRunner(trade,
|
||||||
MakerSetupDepositTxsListener.class).
|
() -> {
|
||||||
using(new TradeTaskRunner(trade,
|
handleTaskRunnerSuccess(sender, message);
|
||||||
() -> handleTaskRunnerSuccess(message),
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
|
})))
|
||||||
|
.executeTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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(
|
||||||
|
// 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();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
@ -22,56 +22,45 @@ import bisq.core.offer.Offer;
|
|||||||
import bisq.core.trade.SellerAsTakerTrade;
|
import bisq.core.trade.SellerAsTakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||||
import bisq.core.trade.messages.DepositTxMessage;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.InitMultisigMessage;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
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.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
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.TradeTask;
|
||||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
|
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.TakerProcessesInputsForDepositTxResponse;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
|
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
|
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
|
||||||
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.TakerVerifyMakerFeePayment;
|
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.Timer;
|
|
||||||
import bisq.common.UserThread;
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
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
|
// TODO (woodser): remove unused request handling
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
|
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
|
||||||
private ResultHandler takeOfferListener;
|
|
||||||
private Timer initDepositTimer;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
@ -80,7 +69,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||||||
public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
|
public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
|
||||||
super(trade);
|
super(trade);
|
||||||
Offer offer = checkNotNull(trade.getOffer());
|
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()))
|
.from(trade.getTradingPeerNodeAddress()))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
TakerVerifyMakerFeePayment.class,
|
TakerReservesTradeFunds.class,
|
||||||
TakerSendInitTradeRequests.class)
|
TakerSendsInitTradeRequestToArbitrator.class)
|
||||||
.withTimeout(30))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
@ -116,7 +105,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||||||
TakerProcessesInputsForDepositTxResponse.class,
|
TakerProcessesInputsForDepositTxResponse.class,
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
VerifyPeersAccountAgeWitness.class,
|
VerifyPeersAccountAgeWitness.class,
|
||||||
TakerVerifyAndSignContract.class,
|
//TakerVerifyAndSignContract.class,
|
||||||
TakerPublishFeeTx.class,
|
TakerPublishFeeTx.class,
|
||||||
SellerAsTakerSignsDepositTx.class,
|
SellerAsTakerSignsDepositTx.class,
|
||||||
SellerCreatesDelayedPayoutTx.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
|
// TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Incoming message handling
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
|
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
|
||||||
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
|
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||||
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
|
processModel.setTradeMessage(request);
|
||||||
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
processModel.setTradeMessage(message);
|
.with(request)
|
||||||
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)
|
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ProcessInitMultisigMessage.class)
|
ProcessInitMultisigRequest.class,
|
||||||
|
SendSignContractRequestAfterMultisig.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
System.out.println("handle multisig pipeline completed successfully!");
|
System.out.println("handle multisig pipeline completed successfully!");
|
||||||
handleTaskRunnerSuccess(message);
|
handleTaskRunnerSuccess(sender, request);
|
||||||
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
|
|
||||||
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
|
|
||||||
fundMultisig(message, takeOfferListener);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
|
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, request, errorMessage);
|
||||||
takeOfferListener.handleResult();
|
|
||||||
})))
|
|
||||||
.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))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.executeTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||||
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
|
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
|
||||||
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
processModel.setTradeMessage(message);
|
processModel.setTradeMessage(message);
|
||||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
expect(anyPhase(Trade.Phase.INIT)
|
||||||
.with(message)
|
.with(message)
|
||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
TakerProcessesMakerDepositTxMessage.class,
|
// TODO (woodser): validate request
|
||||||
TakerSetupDepositTxsListener.class).
|
ProcessSignContractRequest.class)
|
||||||
using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
handleTaskRunnerSuccess(sender, message);
|
||||||
handleTaskRunnerSuccess(message);
|
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(30))
|
.withTimeout(30))
|
||||||
.executeTasks();
|
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ import bisq.core.trade.SellerTrade;
|
|||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
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.ApplyFilter;
|
||||||
|
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
||||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||||
@ -45,6 +47,16 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||||||
super(trade);
|
super(trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onInitialized() {
|
||||||
|
super.onInitialized();
|
||||||
|
|
||||||
|
given(phase(Trade.Phase.DEPOSIT_PUBLISHED)
|
||||||
|
.with(BuyerEvent.STARTUP))
|
||||||
|
.setup(tasks(SetupDepositTxsListener.class))
|
||||||
|
.executeTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Mailbox
|
// Mailbox
|
||||||
@ -78,7 +90,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||||||
log.warn("We received a CounterCurrencyTransferStartedMessage but we have already created the payout tx " +
|
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 " +
|
"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.");
|
"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);
|
removeMailboxMessageAfterProcessing(message);
|
||||||
}))
|
}))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
|
@ -17,20 +17,11 @@
|
|||||||
|
|
||||||
package bisq.core.trade.protocol;
|
package bisq.core.trade.protocol;
|
||||||
|
|
||||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
public interface TakerProtocol extends TraderProtocol {
|
||||||
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
|
||||||
|
|
||||||
public interface TakerProtocol {
|
|
||||||
void onTakeOffer();
|
void onTakeOffer();
|
||||||
|
|
||||||
enum TakerEvent implements FluentProtocol.Event {
|
enum TakerEvent implements FluentProtocol.Event {
|
||||||
TAKE_OFFER
|
TAKE_OFFER
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): update after rebase
|
|
||||||
//åvoid takeAvailableOffer(ResultHandler handler);
|
|
||||||
void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
|
|
||||||
}
|
}
|
@ -1,12 +1,13 @@
|
|||||||
package bisq.core.trade.protocol;
|
package bisq.core.trade.protocol;
|
||||||
|
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
|
import bisq.network.p2p.AckMessage;
|
||||||
import bisq.network.p2p.NodeAddress;
|
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 onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { }
|
||||||
|
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { }
|
||||||
}
|
}
|
@ -22,11 +22,10 @@ import bisq.core.trade.Trade;
|
|||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||||
import bisq.core.trade.messages.DepositTxMessage;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitMultisigMessage;
|
import bisq.core.trade.messages.SignContractRequest;
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
import bisq.core.trade.messages.UpdateMultisigRequest;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
|
import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
@ -70,7 +69,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
public TradeProtocol(Trade trade) {
|
public TradeProtocol(Trade trade) {
|
||||||
this.trade = trade;
|
this.trade = trade;
|
||||||
this.processModel = trade.getProcessModel();
|
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) {
|
if (networkEnvelope instanceof TradeMessage) {
|
||||||
onTradeMessage((TradeMessage) networkEnvelope, peer);
|
onTradeMessage((TradeMessage) networkEnvelope, peer);
|
||||||
|
|
||||||
|
// notify trade listeners
|
||||||
// TODO (woodser): better way to register message notifications for trade?
|
// TODO (woodser): better way to register message notifications for trade?
|
||||||
if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) {
|
if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) {
|
||||||
trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer);
|
trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer);
|
||||||
}
|
}
|
||||||
} else if (networkEnvelope instanceof AckMessage) {
|
} else if (networkEnvelope instanceof AckMessage) {
|
||||||
onAckMessage((AckMessage) networkEnvelope, peer);
|
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);
|
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
|
||||||
|
public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
|
||||||
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
public abstract void handleSignContractRequest(SignContractRequest request, 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);
|
|
||||||
|
|
||||||
// TODO (woodser): update to use fluent for consistency
|
// TODO (woodser): update to use fluent for consistency
|
||||||
public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
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,
|
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
stopTimeout();
|
stopTimeout();
|
||||||
handleTaskRunnerSuccess(message, "handleUpdateMultisigRequest");
|
handleTaskRunnerSuccess(peer, message, "handleUpdateMultisigRequest");
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
handleTaskRunnerFault(message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
});
|
});
|
||||||
taskRunner.addTasks(
|
taskRunner.addTasks(
|
||||||
ProcessUpdateMultisigRequest.class
|
ProcessUpdateMultisigRequest.class
|
||||||
@ -262,6 +242,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
if (!result.isValid()) {
|
if (!result.isValid()) {
|
||||||
log.warn(result.getInfo());
|
log.warn(result.getInfo());
|
||||||
handleTaskRunnerFault(null,
|
handleTaskRunnerFault(null,
|
||||||
|
null,
|
||||||
result.name(),
|
result.name(),
|
||||||
result.getInfo());
|
result.getInfo());
|
||||||
}
|
}
|
||||||
@ -292,9 +273,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
// ACK msg
|
// ACK msg
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// TODO (woodser): support notifications of ack messages
|
||||||
private void onAckMessage(AckMessage ackMessage, NodeAddress peer) {
|
private void onAckMessage(AckMessage ackMessage, NodeAddress peer) {
|
||||||
// We handle the ack for CounterCurrencyTransferStartedMessage and DepositTxAndDelayedPayoutTxMessage
|
// 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
|
// 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())) {
|
if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) {
|
||||||
processModel.setPaymentStartedAckMessage(ackMessage);
|
processModel.setPaymentStartedAckMessage(ackMessage);
|
||||||
} else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) {
|
} 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) {
|
||||||
|
|
||||||
// If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet.
|
// TODO (woodser): remove trade.getTradingPeerNodeAddress() and processModel.getTempTradingPeerNodeAddress() if everything should be maker, taker, or arbitrator
|
||||||
// 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);
|
PubKeyRing peersPubKeyRing = getPeersPubKeyRing(peer);
|
||||||
if (peersPubKeyRing == null) {
|
if (peersPubKeyRing == null) {
|
||||||
log.error("We cannot send the ACK message as peersPubKeyRing is 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
|
// Task runner
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
protected void handleTaskRunnerSuccess(TradeMessage message) {
|
protected void handleTaskRunnerSuccess(NodeAddress sender, TradeMessage message) {
|
||||||
handleTaskRunnerSuccess(message, message.getClass().getSimpleName());
|
handleTaskRunnerSuccess(sender, message, message.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleTaskRunnerSuccess(FluentProtocol.Event event) {
|
protected void handleTaskRunnerSuccess(FluentProtocol.Event event) {
|
||||||
handleTaskRunnerSuccess(null, event.name());
|
handleTaskRunnerSuccess(null, null, event.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleTaskRunnerFault(TradeMessage message, String errorMessage) {
|
protected void handleTaskRunnerFault(NodeAddress sender, TradeMessage message, String errorMessage) {
|
||||||
handleTaskRunnerFault(message, message.getClass().getSimpleName(), errorMessage);
|
handleTaskRunnerFault(sender, message, message.getClass().getSimpleName(), errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleTaskRunnerFault(FluentProtocol.Event event, String 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
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
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());
|
log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId());
|
||||||
if (message != null) {
|
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
|
// 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
|
// 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);
|
log.error("Task runner failed with error {}. Triggered from {}", errorMessage, source);
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
sendAckMessage(message, false, errorMessage);
|
sendAckMessage(ackReceiver, message, false, errorMessage);
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -58,6 +58,12 @@ public final class TradingPeer implements PersistablePayload {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private String accountId;
|
private String accountId;
|
||||||
@Nullable
|
@Nullable
|
||||||
|
private String paymentAccountId;
|
||||||
|
@Nullable
|
||||||
|
private String paymentMethodId;
|
||||||
|
@Nullable
|
||||||
|
private byte[] paymentAccountPayloadHash;
|
||||||
|
@Nullable
|
||||||
private PaymentAccountPayload paymentAccountPayload;
|
private PaymentAccountPayload paymentAccountPayload;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String payoutAddressString;
|
private String payoutAddressString;
|
||||||
@ -90,9 +96,23 @@ public final class TradingPeer implements PersistablePayload {
|
|||||||
|
|
||||||
// Added for XMR integration
|
// Added for XMR integration
|
||||||
@Nullable
|
@Nullable
|
||||||
|
private String reserveTxHash;
|
||||||
|
@Nullable
|
||||||
|
private String reserveTxHex;
|
||||||
|
@Nullable
|
||||||
|
private String reserveTxKey;
|
||||||
|
@Nullable
|
||||||
private String preparedMultisigHex;
|
private String preparedMultisigHex;
|
||||||
|
@Nullable
|
||||||
private String madeMultisigHex;
|
private String madeMultisigHex;
|
||||||
|
@Nullable
|
||||||
private String signedPayoutTxHex;
|
private String signedPayoutTxHex;
|
||||||
|
@Nullable
|
||||||
|
private String depositTxHash;
|
||||||
|
@Nullable
|
||||||
|
private String depositTxHex;
|
||||||
|
@Nullable
|
||||||
|
private String depositTxKey;
|
||||||
|
|
||||||
public TradingPeer() {
|
public TradingPeer() {
|
||||||
}
|
}
|
||||||
@ -102,6 +122,9 @@ public final class TradingPeer implements PersistablePayload {
|
|||||||
final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder()
|
final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder()
|
||||||
.setChangeOutputValue(changeOutputValue);
|
.setChangeOutputValue(changeOutputValue);
|
||||||
Optional.ofNullable(accountId).ifPresent(builder::setAccountId);
|
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(paymentAccountPayload).ifPresent(e -> builder.setPaymentAccountPayload((protobuf.PaymentAccountPayload) e.toProtoMessage()));
|
||||||
Optional.ofNullable(payoutAddressString).ifPresent(builder::setPayoutAddressString);
|
Optional.ofNullable(payoutAddressString).ifPresent(builder::setPayoutAddressString);
|
||||||
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
|
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(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e)));
|
||||||
Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(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(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(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
|
||||||
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
||||||
Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
|
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);
|
builder.setCurrentDate(currentDate);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
@ -130,6 +159,9 @@ public final class TradingPeer implements PersistablePayload {
|
|||||||
TradingPeer tradingPeer = new TradingPeer();
|
TradingPeer tradingPeer = new TradingPeer();
|
||||||
tradingPeer.setChangeOutputValue(proto.getChangeOutputValue());
|
tradingPeer.setChangeOutputValue(proto.getChangeOutputValue());
|
||||||
tradingPeer.setAccountId(ProtoUtil.stringOrNullFromProto(proto.getAccountId()));
|
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.setPaymentAccountPayload(proto.hasPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getPaymentAccountPayload()) : null);
|
||||||
tradingPeer.setPayoutAddressString(ProtoUtil.stringOrNullFromProto(proto.getPayoutAddressString()));
|
tradingPeer.setPayoutAddressString(ProtoUtil.stringOrNullFromProto(proto.getPayoutAddressString()));
|
||||||
tradingPeer.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
|
tradingPeer.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
|
||||||
@ -148,9 +180,15 @@ public final class TradingPeer implements PersistablePayload {
|
|||||||
tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature()));
|
tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature()));
|
||||||
tradingPeer.setCurrentDate(proto.getCurrentDate());
|
tradingPeer.setCurrentDate(proto.getCurrentDate());
|
||||||
tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
|
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.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
|
||||||
tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
||||||
tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()));
|
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;
|
return tradingPeer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,9 @@ package bisq.core.trade.protocol.tasks;
|
|||||||
|
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||||
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
@ -43,8 +44,6 @@ public class ApplyFilter extends TradeTask {
|
|||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress());
|
NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress());
|
||||||
@Nullable
|
|
||||||
PaymentAccountPayload paymentAccountPayload = processModel.getTradingPeer().getPaymentAccountPayload();
|
|
||||||
|
|
||||||
FilterManager filterManager = processModel.getFilterManager();
|
FilterManager filterManager = processModel.getFilterManager();
|
||||||
if (filterManager.isNodeAddressBanned(nodeAddress)) {
|
if (filterManager.isNodeAddressBanned(nodeAddress)) {
|
||||||
@ -59,9 +58,6 @@ public class ApplyFilter extends TradeTask {
|
|||||||
} else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) {
|
} else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) {
|
||||||
failed("Payment method is banned.\n" +
|
failed("Payment method is banned.\n" +
|
||||||
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
|
"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()) {
|
} else if (filterManager.requireUpdateToNewVersionForTrading()) {
|
||||||
failed("Your version of Bisq is not compatible for trading anymore. " +
|
failed("Your version of Bisq is not compatible for trading anymore. " +
|
||||||
"Please update to the latest Bisq version at https://bisq.network/downloads.");
|
"Please update to the latest Bisq version at https://bisq.network/downloads.");
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,34 +15,35 @@
|
|||||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
* 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 static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import bisq.core.trade.messages.InitMultisigMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
|
||||||
|
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
|
import bisq.common.crypto.Sig;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
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.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arbitrator sends InitMultisigRequest to maker and taker if both reserve txs received.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TakerSendInitMultisigMessages extends TradeTask {
|
public class ArbitratorSendsInitMultisigRequestsIfFundsReserved extends TradeTask {
|
||||||
|
|
||||||
|
private boolean takerAck;
|
||||||
private boolean makerAck;
|
private boolean makerAck;
|
||||||
private boolean arbitratorAck;
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
@SuppressWarnings({"unused"})
|
||||||
public TakerSendInitMultisigMessages(TaskRunner taskHandler, Trade trade) {
|
public ArbitratorSendsInitMultisigRequestsIfFundsReserved(TaskRunner taskHandler, Trade trade) {
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,17 +52,22 @@ public class TakerSendInitMultisigMessages extends TradeTask {
|
|||||||
try {
|
try {
|
||||||
runInterceptHook();
|
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
|
// create wallet for multisig
|
||||||
// TODO (woodser): assert that wallet does not already exist
|
MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId());
|
||||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
|
|
||||||
|
|
||||||
// prepare multisig
|
// prepare multisig
|
||||||
String preparedHex = multisigWallet.prepareMultisig();
|
String preparedHex = multisigWallet.prepareMultisig();
|
||||||
processModel.setPreparedMultisigHex(preparedHex);
|
processModel.setPreparedMultisigHex(preparedHex);
|
||||||
System.out.println("Prepared multisig hex: " + preparedHex);
|
|
||||||
|
|
||||||
// create message to initialize trade
|
// create message to initialize multisig
|
||||||
InitMultisigMessage message = new InitMultisigMessage(
|
InitMultisigRequest request = new InitMultisigRequest(
|
||||||
processModel.getOffer().getId(),
|
processModel.getOffer().getId(),
|
||||||
processModel.getMyNodeAddress(),
|
processModel.getMyNodeAddress(),
|
||||||
processModel.getPubKeyRing(),
|
processModel.getPubKeyRing(),
|
||||||
@ -71,45 +77,45 @@ public class TakerSendInitMultisigMessages extends TradeTask {
|
|||||||
preparedHex,
|
preparedHex,
|
||||||
null);
|
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
|
// 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(
|
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||||
trade.getMakerNodeAddress(),
|
trade.getMakerNodeAddress(),
|
||||||
trade.getMakerPubKeyRing(),
|
trade.getMakerPubKeyRing(),
|
||||||
message,
|
request,
|
||||||
new SendDirectMessageListener() {
|
new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
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;
|
makerAck = true;
|
||||||
checkComplete();
|
checkComplete();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onFault(String errorMessage) {
|
public void onFault(String errorMessage) {
|
||||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage);
|
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage);
|
||||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + 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();
|
failed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,6 +126,6 @@ public class TakerSendInitMultisigMessages extends TradeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkComplete() {
|
private void checkComplete() {
|
||||||
if (makerAck && arbitratorAck) complete();
|
if (makerAck && takerAck) complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ import bisq.core.trade.ArbitratorTrade;
|
|||||||
import bisq.core.trade.MakerTrade;
|
import bisq.core.trade.MakerTrade;
|
||||||
import bisq.core.trade.TakerTrade;
|
import bisq.core.trade.TakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
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.core.trade.protocol.TradingPeer;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
@ -46,7 +46,7 @@ import monero.wallet.MoneroWallet;
|
|||||||
import monero.wallet.model.MoneroMultisigInitResult;
|
import monero.wallet.model.MoneroMultisigInitResult;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ProcessInitMultisigMessage extends TradeTask {
|
public class ProcessInitMultisigRequest extends TradeTask {
|
||||||
|
|
||||||
private boolean ack1 = false;
|
private boolean ack1 = false;
|
||||||
private boolean ack2 = false;
|
private boolean ack2 = false;
|
||||||
@ -54,7 +54,7 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
|||||||
MoneroWallet multisigWallet;
|
MoneroWallet multisigWallet;
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
@SuppressWarnings({"unused"})
|
||||||
public ProcessInitMultisigMessage(TaskRunner taskHandler, Trade trade) {
|
public ProcessInitMultisigRequest(TaskRunner taskHandler, Trade trade) {
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,12 +63,12 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
|||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
log.debug("current trade state " + trade.getState());
|
log.debug("current trade state " + trade.getState());
|
||||||
InitMultisigMessage message = (InitMultisigMessage) processModel.getTradeMessage();
|
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
|
||||||
checkNotNull(message);
|
checkNotNull(request);
|
||||||
checkTradeId(processModel.getOfferId(), message);
|
checkTradeId(processModel.getOfferId(), request);
|
||||||
|
|
||||||
System.out.println("PROCESS MULTISIG MESSAGE");
|
System.out.println("PROCESS MULTISIG MESSAGE");
|
||||||
System.out.println(message);
|
System.out.println(request);
|
||||||
// System.out.println("PROCESS MULTISIG MESSAGE TRADE");
|
// System.out.println("PROCESS MULTISIG MESSAGE TRADE");
|
||||||
// System.out.println(trade);
|
// System.out.println(trade);
|
||||||
|
|
||||||
@ -81,26 +81,26 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
|||||||
|
|
||||||
// get peer multisig participant
|
// get peer multisig participant
|
||||||
TradingPeer multisigParticipant;
|
TradingPeer multisigParticipant;
|
||||||
if (message.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker();
|
if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker();
|
||||||
else if (message.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker();
|
else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker();
|
||||||
else if (message.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator();
|
else if (request.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator();
|
||||||
else throw new RuntimeException("Invalid sender to process init trade message: " + trade.getClass().getName());
|
else throw new RuntimeException("Invalid sender to process init trade message: " + trade.getClass().getName());
|
||||||
|
|
||||||
// reconcile peer's established multisig hex with message
|
// reconcile peer's established multisig hex with message
|
||||||
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(message.getPreparedMultisigHex());
|
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.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());
|
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(message.getMadeMultisigHex());
|
if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(request.getMadeMultisigHex());
|
||||||
else if (!multisigParticipant.getMadeMultisigHex().equals(message.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages");
|
else if (!multisigParticipant.getMadeMultisigHex().equals(request.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());
|
|
||||||
|
|
||||||
// prepare multisig if applicable
|
// prepare multisig if applicable
|
||||||
boolean updateParticipants = false;
|
boolean updateParticipants = false;
|
||||||
if (processModel.getPreparedMultisigHex() == null) {
|
if (processModel.getPreparedMultisigHex() == null) {
|
||||||
System.out.println("Preparing multisig wallet!");
|
System.out.println("Preparing multisig wallet!");
|
||||||
|
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId());
|
||||||
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
||||||
updateParticipants = true;
|
updateParticipants = true;
|
||||||
|
} else {
|
||||||
|
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make multisig if applicable
|
// make multisig if applicable
|
||||||
@ -145,38 +145,38 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (peer1Address == null) throw new RuntimeException("Peer1 address is null");
|
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 (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
|
// send to peer 1
|
||||||
sendMultisigMessage(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
|
sendInitMultisigRequest(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
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;
|
ack1 = true;
|
||||||
if (ack1 && ack2) completeAux();
|
if (ack1 && ack2) completeAux();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onFault(String errorMessage) {
|
public void onFault(String errorMessage) {
|
||||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer1Address, errorMessage);
|
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer1Address, errorMessage);
|
||||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||||
failed();
|
failed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// send to peer 2
|
// send to peer 2
|
||||||
sendMultisigMessage(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
|
sendInitMultisigRequest(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
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;
|
ack2 = true;
|
||||||
if (ack1 && ack2) completeAux();
|
if (ack1 && ack2) completeAux();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onFault(String errorMessage) {
|
public void onFault(String errorMessage) {
|
||||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer2Address, errorMessage);
|
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer2Address, errorMessage);
|
||||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||||
failed();
|
failed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -204,10 +204,10 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
|||||||
return peers;
|
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
|
// create multisig message with current multisig hex
|
||||||
InitMultisigMessage message = new InitMultisigMessage(
|
InitMultisigRequest request = new InitMultisigRequest(
|
||||||
processModel.getOffer().getId(),
|
processModel.getOffer().getId(),
|
||||||
processModel.getMyNodeAddress(),
|
processModel.getMyNodeAddress(),
|
||||||
processModel.getPubKeyRing(),
|
processModel.getPubKeyRing(),
|
||||||
@ -217,8 +217,8 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
|||||||
processModel.getPreparedMultisigHex(),
|
processModel.getPreparedMultisigHex(),
|
||||||
processModel.getMadeMultisigHex());
|
processModel.getMadeMultisigHex());
|
||||||
|
|
||||||
log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), recipient);
|
log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient);
|
||||||
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, message, listener);
|
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void completeAux() {
|
private void completeAux() {
|
@ -19,18 +19,15 @@ package bisq.core.trade.protocol.tasks;
|
|||||||
|
|
||||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
|
||||||
import bisq.core.trade.ArbitratorTrade;
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
import bisq.core.trade.MakerTrade;
|
import bisq.core.trade.MakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.core.trade.TradeUtils;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.protocol.TradingPeer;
|
import bisq.core.trade.protocol.TradingPeer;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
@ -49,11 +46,12 @@ public class ProcessInitTradeRequest extends TradeTask {
|
|||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
|
||||||
@Override
|
@Override
|
||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
log.debug("current trade state " + trade.getState());
|
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
||||||
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
|
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
|
||||||
checkNotNull(request);
|
checkNotNull(request);
|
||||||
checkTradeId(processModel.getOfferId(), request);
|
checkTradeId(processModel.getOfferId(), request);
|
||||||
@ -61,70 +59,70 @@ public class ProcessInitTradeRequest extends TradeTask {
|
|||||||
System.out.println("PROCESS INIT TRADE REQUEST");
|
System.out.println("PROCESS INIT TRADE REQUEST");
|
||||||
System.out.println(request);
|
System.out.println(request);
|
||||||
|
|
||||||
User user = checkNotNull(processModel.getUser(), "User must not be null");
|
// handle request as arbitrator
|
||||||
|
|
||||||
// handle maker trade
|
|
||||||
TradingPeer multisigParticipant;
|
TradingPeer multisigParticipant;
|
||||||
if (trade instanceof MakerTrade) {
|
if (trade instanceof ArbitratorTrade) {
|
||||||
|
|
||||||
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?
|
|
||||||
|
|
||||||
|
// handle request from taker
|
||||||
|
if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) {
|
||||||
multisigParticipant = processModel.getTaker();
|
multisigParticipant = processModel.getTaker();
|
||||||
trade.setTakerNodeAddress(request.getTakerNodeAddress());
|
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());
|
trade.setTakerPubKeyRing(request.getPubKeyRing());
|
||||||
trade.setArbitratorNodeAddress(request.getArbitratorNodeAddress());
|
if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
|
||||||
trade.setArbitratorPubKeyRing(mediator.getPubKeyRing());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle arbitrator trade
|
// handle request from maker
|
||||||
else if (trade instanceof ArbitratorTrade) {
|
else if (request.getSenderNodeAddress().equals(request.getMakerNodeAddress())) {
|
||||||
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
|
|
||||||
if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) {
|
|
||||||
multisigParticipant = processModel.getMaker();
|
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.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());
|
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 (!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())) {
|
trade.setMakerPubKeyRing(request.getPubKeyRing());
|
||||||
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 {
|
} else {
|
||||||
throw new RuntimeException("Sender is not trade's maker or taker");
|
throw new RuntimeException("Sender is not trade's maker or taker");
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// handle maker trade
|
||||||
|
else if (trade instanceof MakerTrade) {
|
||||||
|
multisigParticipant = processModel.getTaker();
|
||||||
|
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());
|
throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
multisigParticipant.setPaymentAccountPayload(checkNotNull(request.getPaymentAccountPayload()));
|
// set trading peer info
|
||||||
multisigParticipant.setPayoutAddressString(nonEmptyStringOf(request.getPayoutAddressString()));
|
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.setPubKeyRing(checkNotNull(request.getPubKeyRing()));
|
||||||
|
|
||||||
multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId()));
|
multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId()));
|
||||||
//trade.setTakerFeeTxId(nonEmptyStringOf(request.getTradeFeeTxId())); // TODO (woodser): no trade fee tx yet if creating multisig first
|
multisigParticipant.setPaymentMethodId(nonEmptyStringOf(request.getPaymentMethodId()));
|
||||||
|
|
||||||
// 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.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
|
multisigParticipant.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
|
||||||
multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId());
|
multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId());
|
||||||
multisigParticipant.setCurrentDate(request.getCurrentDate());
|
multisigParticipant.setCurrentDate(request.getCurrentDate());
|
||||||
|
|
||||||
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
// check trade price
|
||||||
try {
|
try {
|
||||||
long takersTradePrice = request.getTradePrice();
|
long tradePrice = request.getTradePrice();
|
||||||
offer.checkTradePriceTolerance(takersTradePrice);
|
offer.checkTradePriceTolerance(tradePrice);
|
||||||
trade.setTradePrice(takersTradePrice);
|
trade.setTradePrice(tradePrice);
|
||||||
} catch (TradePriceOutOfToleranceException e) {
|
} catch (TradePriceOutOfToleranceException e) {
|
||||||
failed(e.getMessage());
|
failed(e.getMessage());
|
||||||
} catch (Throwable e2) {
|
} catch (Throwable e2) {
|
||||||
failed(e2);
|
failed(e2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check trade amount
|
||||||
checkArgument(request.getTradeAmount() > 0);
|
checkArgument(request.getTradeAmount() > 0);
|
||||||
trade.setTradeAmount(Coin.valueOf(request.getTradeAmount()));
|
trade.setTradeAmount(Coin.valueOf(request.getTradeAmount()));
|
||||||
|
|
||||||
|
// persist trade
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask {
|
|||||||
UpdateMultisigRequest request = (UpdateMultisigRequest) processModel.getTradeMessage();
|
UpdateMultisigRequest request = (UpdateMultisigRequest) processModel.getTradeMessage();
|
||||||
checkNotNull(request);
|
checkNotNull(request);
|
||||||
checkTradeId(processModel.getOfferId(), 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("PROCESS UPDATE MULTISIG REQUEST");
|
||||||
System.out.println(request);
|
System.out.println(request);
|
||||||
@ -85,13 +85,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask {
|
|||||||
new Date().getTime(),
|
new Date().getTime(),
|
||||||
updatedMultisigHex);
|
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());
|
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() {
|
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), response, new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
public void onArrived() {
|
||||||
|
@ -54,7 +54,7 @@ public class PublishTradeStatistics extends TradeTask {
|
|||||||
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
|
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.
|
// 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
|
// For testing with regtest/localhost we use the full address as its localhost and would result in
|
||||||
// same values for multiple mediators.
|
// same values for multiple mediators.
|
||||||
@ -62,15 +62,15 @@ public class PublishTradeStatistics extends TradeTask {
|
|||||||
String address = networkNode instanceof TorNetworkNode ?
|
String address = networkNode instanceof TorNetworkNode ?
|
||||||
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
||||||
mediatorNodeAddress.getFullAddress();
|
mediatorNodeAddress.getFullAddress();
|
||||||
extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address);
|
extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address);
|
||||||
|
|
||||||
Offer offer = checkNotNull(trade.getOffer());
|
Offer offer = checkNotNull(trade.getOffer());
|
||||||
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
|
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
|
||||||
trade.getTradePrice(),
|
trade.getTradePrice(),
|
||||||
trade.getTradeAmount(),
|
trade.getTradeAmount(),
|
||||||
trade.getDate(),
|
trade.getDate(),
|
||||||
trade.getMakerDepositTxId(),
|
trade.getMaker().getDepositTxHash(),
|
||||||
trade.getTakerDepositTxId(),
|
trade.getTaker().getDepositTxHash(),
|
||||||
extraDataMap);
|
extraDataMap);
|
||||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ public abstract class SendMailboxMessageTask extends TradeTask {
|
|||||||
|
|
||||||
processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage(
|
processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage(
|
||||||
peersNodeAddress,
|
peersNodeAddress,
|
||||||
processModel.getTradingPeer().getPubKeyRing(),
|
trade.getTradingPeer().getPubKeyRing(),
|
||||||
message,
|
message,
|
||||||
new SendMailboxMessageListener() {
|
new SendMailboxMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user