implement protocol v2
This commit is contained in:
parent
65bcd47446
commit
86f7d090b6
@ -74,4 +74,4 @@ To bring Haveno to life, we need resources. If you have the possibility, please
|
||||
|
||||
![Qr code](https://raw.githubusercontent.com/haveno-dex/haveno/master/media/qrhaveno.png)
|
||||
|
||||
If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address.
|
||||
If you are using a wallet that supports Openalias (like the 'official' CLI and GUI wallets), you can simply put `donations@haveno.network` as the "receiver" address.
|
@ -40,7 +40,7 @@ configure(subprojects) {
|
||||
grpcVersion = '1.25.0'
|
||||
gsonVersion = '2.8.5'
|
||||
guavaVersion = '28.2-jre'
|
||||
moneroJavaVersion = '0.5.3'
|
||||
moneroJavaVersion = '0.5.4'
|
||||
httpclient5Version = '5.0'
|
||||
guiceVersion = '4.2.2'
|
||||
hamcrestVersion = '1.3'
|
||||
|
@ -161,7 +161,7 @@ public class TradeFormat {
|
||||
|
||||
private static final Function<TradeInfo, String> amountFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong()))
|
||||
? formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()))
|
||||
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
|
||||
@ -173,13 +173,13 @@ public class TradeFormat {
|
||||
};
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeFormat = (t, isTaker) -> {
|
||||
return formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTakerFeeAsLong()));
|
||||
return formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTakerFeeAsLong()));
|
||||
};
|
||||
|
||||
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? formatOfferVolume(t.getOffer().getVolume())
|
||||
: formatXmr(ParsingUtils.satoshisToXmrAtomicUnits(t.getTradeAmountAsLong()));
|
||||
: formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()));
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> bsqReceiveAddress = (t, showBsqBuyerAddress) -> {
|
||||
if (showBsqBuyerAddress) {
|
||||
|
@ -302,7 +302,7 @@ public class AccountAgeWitnessService {
|
||||
|
||||
private Optional<AccountAgeWitness> findTradePeerWitness(Trade trade) {
|
||||
if (trade instanceof ArbitratorTrade) return Optional.empty(); // TODO (woodser): arbitrator trade has two peers
|
||||
TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
|
||||
TradingPeer tradingPeer = trade.getTradingPeer();
|
||||
return (tradingPeer == null ||
|
||||
tradingPeer.getPaymentAccountPayload() == null ||
|
||||
tradingPeer.getPubKeyRing() == null) ?
|
||||
@ -426,7 +426,7 @@ public class AccountAgeWitnessService {
|
||||
long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
|
||||
var factor = signedBuyFactor(accountAgeCategory);
|
||||
if (factor > 0) {
|
||||
limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
|
||||
limit = MathUtils.roundDoubleToLong(maxTradeLimit.value * factor);
|
||||
}
|
||||
|
||||
log.debug("limit={}, factor={}, accountAgeWitnessHash={}",
|
||||
@ -726,8 +726,8 @@ public class AccountAgeWitnessService {
|
||||
public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
|
||||
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
|
||||
Coin tradeAmount = trade.getTradeAmount();
|
||||
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
||||
PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
||||
checkNotNull(trade.getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
|
||||
PublicKey peersPubKey = trade.getTradingPeer().getPubKeyRing().getSignaturePubKey();
|
||||
checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}",
|
||||
trade.toString());
|
||||
checkNotNull(tradeAmount, "Trade amount must not be null");
|
||||
@ -767,15 +767,11 @@ public class AccountAgeWitnessService {
|
||||
boolean isFiltered = filterManager.isNodeAddressBanned(dispute.getContract().getBuyerNodeAddress()) ||
|
||||
filterManager.isNodeAddressBanned(dispute.getContract().getSellerNodeAddress()) ||
|
||||
filterManager.isCurrencyBanned(dispute.getContract().getOfferPayload().getCurrencyCode()) ||
|
||||
filterManager.isPaymentMethodBanned(
|
||||
PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
||||
filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload()) ||
|
||||
filterManager.arePeersPaymentAccountDataBanned(
|
||||
dispute.getContract().getSellerPaymentAccountPayload()) ||
|
||||
filterManager.isWitnessSignerPubKeyBanned(
|
||||
Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
|
||||
filterManager.isWitnessSignerPubKeyBanned(
|
||||
Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
|
||||
filterManager.isPaymentMethodBanned(PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) ||
|
||||
filterManager.arePeersPaymentAccountDataBanned(dispute.getBuyerPaymentAccountPayload()) ||
|
||||
filterManager.arePeersPaymentAccountDataBanned(dispute.getSellerPaymentAccountPayload()) ||
|
||||
filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) ||
|
||||
filterManager.isWitnessSignerPubKeyBanned(Utils.HEX.encode(dispute.getContract().getSellerPubKeyRing().getSignaturePubKeyBytes()));
|
||||
return !isFiltered;
|
||||
}
|
||||
|
||||
@ -797,8 +793,8 @@ public class AccountAgeWitnessService {
|
||||
PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
|
||||
PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
|
||||
|
||||
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
|
||||
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload();
|
||||
PaymentAccountPayload buyerPaymentAccountPaload = dispute.getBuyerPaymentAccountPayload();
|
||||
PaymentAccountPayload sellerPaymentAccountPaload = dispute.getSellerPaymentAccountPayload();
|
||||
|
||||
TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
|
||||
.map(witness -> new TraderDataItem(
|
||||
@ -913,8 +909,7 @@ public class AccountAgeWitnessService {
|
||||
public boolean isSignWitnessTrade(Trade trade) {
|
||||
checkNotNull(trade, "trade must not be null");
|
||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
Contract contract = checkNotNull(trade.getContract());
|
||||
PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload();
|
||||
PaymentAccountPayload sellerPaymentAccountPayload = trade.getSeller().getPaymentAccountPayload();
|
||||
AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload);
|
||||
|
||||
getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness);
|
||||
|
@ -131,14 +131,14 @@ public class AccountAgeWitnessUtils {
|
||||
// Log to find why accounts sometimes don't get signed as expected
|
||||
// TODO: Demote to debug or remove once account signing is working ok
|
||||
checkNotNull(trade.getContract());
|
||||
checkNotNull(trade.getContract().getBuyerPaymentAccountPayload());
|
||||
checkNotNull(trade.getBuyer().getPaymentAccountPayload());
|
||||
boolean checkingSignTrade = true;
|
||||
boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing());
|
||||
AccountAgeWitness witness = myWitness;
|
||||
if (witness == null) {
|
||||
witness = isBuyer ?
|
||||
accountAgeWitnessService.getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) :
|
||||
accountAgeWitnessService.getMyWitness(trade.getContract().getSellerPaymentAccountPayload());
|
||||
accountAgeWitnessService.getMyWitness(trade.getBuyer().getPaymentAccountPayload()) :
|
||||
accountAgeWitnessService.getMyWitness(trade.getSeller().getPaymentAccountPayload());
|
||||
checkingSignTrade = false;
|
||||
}
|
||||
boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) &&
|
||||
@ -157,9 +157,9 @@ public class AccountAgeWitnessUtils {
|
||||
"\nisSignWitnessTrade: {}",
|
||||
trade.getId(),
|
||||
isBuyer,
|
||||
getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(),
|
||||
getWitnessDebugLog(trade.getBuyer().getPaymentAccountPayload(),
|
||||
trade.getContract().getBuyerPubKeyRing()),
|
||||
getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(),
|
||||
getWitnessDebugLog(trade.getSeller().getPaymentAccountPayload(),
|
||||
trade.getContract().getSellerPubKeyRing()),
|
||||
checkingSignTrade, // Following cases added to use same logic as in seller signing check
|
||||
accountAgeWitnessService.accountIsSigner(witness),
|
||||
|
@ -109,7 +109,6 @@ class CoreTradesService {
|
||||
tradeManager.onTakeOffer(offer.getAmount(),
|
||||
takeOfferModel.getTxFeeFromFeeService(),
|
||||
takeOfferModel.getTakerFee(),
|
||||
offer.getPrice().getValue(),
|
||||
takeOfferModel.getFundsNeededForTrade(),
|
||||
offer,
|
||||
paymentAccountId,
|
||||
|
@ -108,8 +108,8 @@ public class TradeInfo implements Payload {
|
||||
contract.isBuyerMakerAndSellerTaker(),
|
||||
contract.getMakerAccountId(),
|
||||
contract.getTakerAccountId(),
|
||||
toPaymentAccountPayloadInfo(contract.getMakerPaymentAccountPayload()),
|
||||
toPaymentAccountPayloadInfo(contract.getTakerPaymentAccountPayload()),
|
||||
toPaymentAccountPayloadInfo(trade.getMaker().getPaymentAccountPayload()),
|
||||
toPaymentAccountPayloadInfo(trade.getTaker().getPaymentAccountPayload()),
|
||||
contract.getMakerPayoutAddressString(),
|
||||
contract.getTakerPayoutAddressString(),
|
||||
contract.getLockTime());
|
||||
@ -127,8 +127,8 @@ public class TradeInfo implements Payload {
|
||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||
.withTakerFeeAsLong(trade.getTakerFeeAsLong())
|
||||
.withTakerFeeTxId(trade.getTakerFeeTxId())
|
||||
.withMakerDepositTxId(trade.getMakerDepositTxId())
|
||||
.withTakerDepositTxId(trade.getTakerDepositTxId())
|
||||
.withMakerDepositTxId(trade.getMaker().getDepositTxHash())
|
||||
.withTakerDepositTxId(trade.getTaker().getDepositTxHash())
|
||||
.withPayoutTxId(trade.getPayoutTxId())
|
||||
.withTradeAmountAsLong(trade.getTradeAmountAsLong())
|
||||
.withTradePrice(trade.getTradePrice().getValue())
|
||||
|
@ -249,10 +249,10 @@ public class WalletAppSetup {
|
||||
.filter(trade -> trade.getOffer() != null)
|
||||
.forEach(trade -> {
|
||||
String details = null;
|
||||
if (txId.equals(trade.getMakerDepositTxId())) {
|
||||
if (txId.equals(trade.getMaker().getDepositTxHash())) {
|
||||
details = Res.get("popup.warning.trade.txRejected.deposit"); // TODO (woodser): txRejected.maker_deposit, txRejected.taker_deposit
|
||||
}
|
||||
if (txId.equals(trade.getTakerDepositTxId())) {
|
||||
if (txId.equals(trade.getTaker().getDepositTxHash())) {
|
||||
details = Res.get("popup.warning.trade.txRejected.deposit");
|
||||
}
|
||||
if (txId.equals(trade.getOffer().getOfferFeePaymentTxId()) || txId.equals(trade.getTakerFeeTxId())) {
|
||||
|
@ -17,7 +17,9 @@
|
||||
|
||||
package bisq.core.btc;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
@ -26,30 +28,21 @@ import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.model.MoneroAccount;
|
||||
import monero.wallet.model.MoneroOutputQuery;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
@Slf4j
|
||||
public class Balances {
|
||||
@ -98,29 +91,45 @@ public class Balances {
|
||||
// Need to delay a bit to get the balances correct
|
||||
UserThread.execute(() -> {
|
||||
updateAvailableBalance();
|
||||
updateReservedBalance();
|
||||
updateLockedBalance();
|
||||
updateReservedBalance();
|
||||
});
|
||||
}
|
||||
|
||||
// TODO (woodser): currently reserved balance = reserved for trade (excluding multisig) and locked balance = txs with < 10 confirmations
|
||||
// TODO (woodser): balances being set as Coin from BigInteger.longValue(), which can lose precision. should be in centineros for consistency with the rest of the application
|
||||
|
||||
private void updateAvailableBalance() {
|
||||
availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValue()));
|
||||
availableBalance.set(Coin.valueOf(xmrWalletService.getWallet().getUnlockedBalance(0).longValueExact()));
|
||||
}
|
||||
|
||||
private void updateReservedBalance() {
|
||||
BigInteger sum = new BigInteger("0");
|
||||
List<MoneroAccount> accounts = xmrWalletService.getWallet().getAccounts();
|
||||
for (MoneroAccount account : accounts) {
|
||||
if (account.getIndex() != 0) sum = sum.add(account.getBalance());
|
||||
}
|
||||
reservedBalance.set(Coin.valueOf(sum.longValue()));
|
||||
}
|
||||
|
||||
|
||||
private void updateLockedBalance() {
|
||||
BigInteger balance = xmrWalletService.getWallet().getBalance(0);
|
||||
BigInteger unlockedBalance = xmrWalletService.getWallet().getUnlockedBalance(0);
|
||||
lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValue()));
|
||||
lockedBalance.set(Coin.valueOf(balance.subtract(unlockedBalance).longValueExact()));
|
||||
}
|
||||
|
||||
private void updateReservedBalance() {
|
||||
|
||||
// add frozen input amounts
|
||||
Coin sum = Coin.valueOf(0);
|
||||
List<MoneroOutputWallet> frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false));
|
||||
for (MoneroOutputWallet frozenOutput : frozenOutputs) sum = sum.add(Coin.valueOf(frozenOutput.getAmount().longValueExact()));
|
||||
|
||||
// add multisig deposit amounts
|
||||
List<Trade> openTrades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList());
|
||||
for (Trade trade : openTrades) {
|
||||
if (trade.getContract() == null) continue;
|
||||
Long reservedAmt;
|
||||
OfferPayload offerPayload = trade.getContract().getOfferPayload();
|
||||
if (trade.getArbitratorNodeAddress().equals(P2PService.getMyNodeAddress())) { // TODO (woodser): this only works if node address does not change
|
||||
reservedAmt = offerPayload.getAmount() + offerPayload.getBuyerSecurityDeposit() + offerPayload.getSellerSecurityDeposit(); // arbitrator reserved balance is sum of amounts sent to multisig
|
||||
} else {
|
||||
reservedAmt = trade.getContract().isMyRoleBuyer(tradeManager.getKeyRing().getPubKeyRing()) ? offerPayload.getBuyerSecurityDeposit() : offerPayload.getAmount() + offerPayload.getSellerSecurityDeposit();
|
||||
}
|
||||
sum = sum.add(Coin.valueOf(ParsingUtils.centinerosToAtomicUnits(reservedAmt).longValueExact()));
|
||||
}
|
||||
|
||||
// set reserved balance
|
||||
reservedBalance.set(sum);
|
||||
}
|
||||
}
|
||||
|
@ -20,17 +20,17 @@ package bisq.core.btc.listeners;
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class XmrBalanceListener {
|
||||
private Integer accountIndex;
|
||||
private Integer subaddressIndex;
|
||||
|
||||
public XmrBalanceListener() {
|
||||
}
|
||||
|
||||
public XmrBalanceListener(Integer accountIndex) {
|
||||
this.accountIndex = accountIndex;
|
||||
this.subaddressIndex = accountIndex;
|
||||
}
|
||||
|
||||
public Integer getAccountIndex() {
|
||||
return accountIndex;
|
||||
public Integer getSubaddressIndex() {
|
||||
return subaddressIndex;
|
||||
}
|
||||
|
||||
public void onBalanceChanged(BigInteger balance) {
|
||||
|
@ -60,7 +60,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
||||
@Getter
|
||||
private final Context context;
|
||||
@Getter
|
||||
private final int accountIndex;
|
||||
private final int subaddressIndex;
|
||||
@Getter
|
||||
private final String addressString;
|
||||
|
||||
@ -71,12 +71,12 @@ public final class XmrAddressEntry implements PersistablePayload {
|
||||
// Constructor, initialization
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public XmrAddressEntry(int accountIndex, String address, Context context) {
|
||||
this(accountIndex, address, context, null, null);
|
||||
public XmrAddressEntry(int subaddressIndex, String address, Context context) {
|
||||
this(subaddressIndex, address, context, null, null);
|
||||
}
|
||||
|
||||
public XmrAddressEntry(int accountIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
|
||||
this.accountIndex = accountIndex;
|
||||
public XmrAddressEntry(int subaddressIndex, String address, Context context, @Nullable String offerId, Coin coinLockedInMultiSig) {
|
||||
this.subaddressIndex = subaddressIndex;
|
||||
this.addressString = address;
|
||||
this.offerId = offerId;
|
||||
this.context = context;
|
||||
@ -89,7 +89,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static XmrAddressEntry fromProto(protobuf.XmrAddressEntry proto) {
|
||||
return new XmrAddressEntry(proto.getAccountIndex(),
|
||||
return new XmrAddressEntry(proto.getSubaddressIndex(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getAddressString()),
|
||||
ProtoUtil.enumFromProto(XmrAddressEntry.Context.class, proto.getContext().name()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getOfferId()),
|
||||
@ -99,7 +99,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
||||
@Override
|
||||
public protobuf.XmrAddressEntry toProtoMessage() {
|
||||
protobuf.XmrAddressEntry.Builder builder = protobuf.XmrAddressEntry.newBuilder()
|
||||
.setAccountIndex(accountIndex)
|
||||
.setSubaddressIndex(subaddressIndex)
|
||||
.setAddressString(addressString)
|
||||
.setContext(protobuf.XmrAddressEntry.Context.valueOf(context.name()))
|
||||
.setCoinLockedInMultiSig(coinLockedInMultiSig);
|
||||
@ -143,7 +143,7 @@ public final class XmrAddressEntry implements PersistablePayload {
|
||||
return "XmrAddressEntry{" +
|
||||
"offerId='" + getOfferId() + '\'' +
|
||||
", context=" + context +
|
||||
", accountIndex=" + getAccountIndex() +
|
||||
", subaddressIndex=" + getSubaddressIndex() +
|
||||
", address=" + getAddressString() +
|
||||
'}';
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroAccount;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
@ -131,10 +130,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||
// });
|
||||
//
|
||||
// toBeRemoved.forEach(entrySet::remove);
|
||||
} else {
|
||||
// As long the old arbitration domain is not removed from the code base we still support it here.
|
||||
MoneroAccount account = wallet.createAccount();
|
||||
entrySet.add(new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), XmrAddressEntry.Context.ARBITRATOR));
|
||||
}
|
||||
|
||||
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
|
||||
@ -174,7 +169,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||
if (entryWithSameOfferIdAndContextAlreadyExist) {
|
||||
log.error("We have an address entry with the same offer ID and context. We do not add the new one. " +
|
||||
"addressEntry={}, entrySet={}", addressEntry, entrySet);
|
||||
if (true) throw new RuntimeException("why?");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -185,7 +179,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||
|
||||
public void swapToAvailable(XmrAddressEntry addressEntry) {
|
||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
||||
boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(),
|
||||
boolean setChangedByAdd = entrySet.add(new XmrAddressEntry(addressEntry.getSubaddressIndex(), addressEntry.getAddressString(),
|
||||
XmrAddressEntry.Context.AVAILABLE));
|
||||
if (setChangedByRemove || setChangedByAdd) {
|
||||
requestPersistence();
|
||||
@ -196,7 +190,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||
XmrAddressEntry.Context context,
|
||||
String offerId) {
|
||||
boolean setChangedByRemove = entrySet.remove(addressEntry);
|
||||
final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getAccountIndex(), addressEntry.getAddressString(), context, offerId, null);
|
||||
final XmrAddressEntry newAddressEntry = new XmrAddressEntry(addressEntry.getSubaddressIndex(), addressEntry.getAddressString(), context, offerId, null);
|
||||
boolean setChangedByAdd = entrySet.add(newAddressEntry);
|
||||
if (setChangedByRemove || setChangedByAdd)
|
||||
requestPersistence();
|
||||
@ -213,6 +207,7 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): this should be removed since only using account 0
|
||||
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
|
||||
if (output.getAccountIndex() == 0) return;
|
||||
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
|
||||
|
@ -89,6 +89,8 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
|
||||
import monero.common.MoneroUtils;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
import monero.daemon.model.MoneroNetworkType;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.MoneroWalletRpc;
|
||||
@ -137,6 +139,7 @@ public class WalletConfig extends AbstractIdleService {
|
||||
protected final String filePrefix;
|
||||
protected volatile BlockChain vChain;
|
||||
protected volatile SPVBlockStore vStore;
|
||||
protected volatile MoneroDaemon vXmrDaemon;
|
||||
protected volatile MoneroWallet vXmrWallet;
|
||||
protected volatile Wallet vBtcWallet;
|
||||
protected volatile Wallet vBsqWallet;
|
||||
@ -345,6 +348,9 @@ public class WalletConfig extends AbstractIdleService {
|
||||
try {
|
||||
File chainFile = new File(directory, filePrefix + ".spvchain");
|
||||
boolean chainFileExists = chainFile.exists();
|
||||
|
||||
// XMR daemon
|
||||
vXmrDaemon = new MoneroDaemonRpc(MONERO_DAEMON_URI, MONERO_DAEMON_USERNAME, MONERO_DAEMON_PASSWORD);
|
||||
|
||||
// XMR wallet
|
||||
String xmrPrefix = "_XMR";
|
||||
@ -361,7 +367,8 @@ public class WalletConfig extends AbstractIdleService {
|
||||
// vXmrWallet.rescanBlockchain();
|
||||
vXmrWallet.sync();
|
||||
vXmrWallet.save();
|
||||
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance());
|
||||
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance(0));
|
||||
System.out.println("Loaded wallet unlocked balance: " + vXmrWallet.getUnlockedBalance(0));
|
||||
|
||||
String btcPrefix = "_BTC";
|
||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||
@ -626,10 +633,16 @@ public class WalletConfig extends AbstractIdleService {
|
||||
return vBtcWallet;
|
||||
}
|
||||
|
||||
|
||||
public MoneroDaemon getXmrDaemon() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vXmrDaemon;
|
||||
}
|
||||
|
||||
public MoneroWallet getXmrWallet() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vXmrWallet;
|
||||
}
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
return vXmrWallet;
|
||||
}
|
||||
|
||||
public Wallet bsqWallet() {
|
||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||
|
@ -105,7 +105,7 @@ import javax.annotation.Nullable;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
// Setup wallets and use WalletConfig for BitcoinJ wiring.
|
||||
@ -490,6 +490,10 @@ public class WalletsSetup {
|
||||
return walletConfig.btcWallet();
|
||||
}
|
||||
|
||||
public MoneroDaemon getXmrDaemon() {
|
||||
return walletConfig.getXmrDaemon();
|
||||
}
|
||||
|
||||
public MoneroWallet getXmrWallet() {
|
||||
return walletConfig.getXmrWallet();
|
||||
}
|
||||
|
@ -146,8 +146,8 @@ public class TradeWalletService {
|
||||
return xmrWallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.setDestinations(
|
||||
new MoneroDestination(feeReceiver, ParsingUtils.satoshisToXmrAtomicUnits(makerFee.value)),
|
||||
new MoneroDestination(reservedForTradeAddress, ParsingUtils.satoshisToXmrAtomicUnits(reservedFundsForOffer.value)))
|
||||
new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)),
|
||||
new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer)))
|
||||
.setRelay(broadcastTx));
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package bisq.core.btc.wallet;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.core.btc.exceptions.AddressEntryException;
|
||||
import bisq.core.btc.listeners.XmrBalanceListener;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
@ -36,11 +37,10 @@ import lombok.Getter;
|
||||
|
||||
|
||||
import monero.common.MoneroUtils;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroAccount;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroSubaddress;
|
||||
import monero.wallet.model.MoneroTransfer;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxQuery;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
@ -57,6 +57,8 @@ public class XmrWalletService {
|
||||
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
|
||||
private Map<String, MoneroWallet> multisigWallets;
|
||||
|
||||
@Getter
|
||||
private MoneroDaemon daemon;
|
||||
@Getter
|
||||
private MoneroWallet wallet;
|
||||
|
||||
@ -69,48 +71,48 @@ public class XmrWalletService {
|
||||
this.multisigWallets = new HashMap<String, MoneroWallet>();
|
||||
|
||||
walletsSetup.addSetupCompletedHandler(() -> {
|
||||
wallet = walletsSetup.getXmrWallet();
|
||||
wallet.addListener(new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { }
|
||||
daemon = walletsSetup.getXmrDaemon();
|
||||
wallet = walletsSetup.getXmrWallet();
|
||||
wallet.addListener(new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { }
|
||||
|
||||
@Override
|
||||
public void onNewBlock(long height) { }
|
||||
@Override
|
||||
public void onNewBlock(long height) { }
|
||||
|
||||
@Override
|
||||
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||
notifyBalanceListeners();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||
notifyBalanceListeners();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO (woodser): wallet has single password which is passed here?
|
||||
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
|
||||
public MoneroWallet getOrCreateMultisigWallet(String tradeId) {
|
||||
String path = "xmr_multisig_trade_" + tradeId;
|
||||
MoneroWallet multisigWallet = null;
|
||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||
else if (MoneroUtils.walletExists(new File(walletsSetup.getWalletConfig().directory(), path).getPath())) { // TODO: use monero-wallet-rpc to determine existence?
|
||||
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig()
|
||||
.setPath(path)
|
||||
.setPassword("abctesting123"));
|
||||
} else {
|
||||
|
||||
public MoneroWallet createMultisigWallet(String tradeId) {
|
||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||
String path = "xmr_multisig_trade_" + tradeId;
|
||||
MoneroWallet multisigWallet = null;
|
||||
multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig()
|
||||
.setPath(path)
|
||||
.setPassword("abctesting123"));
|
||||
}
|
||||
multisigWallets.put(tradeId, multisigWallet);
|
||||
multisigWallet.startSyncing(5000l);
|
||||
return multisigWallet;
|
||||
multisigWallets.put(tradeId, multisigWallet);
|
||||
multisigWallet.startSyncing(5000l);
|
||||
return multisigWallet;
|
||||
}
|
||||
|
||||
public XmrAddressEntry getArbitratorAddressEntry() {
|
||||
XmrAddressEntry.Context context = XmrAddressEntry.Context.ARBITRATOR;
|
||||
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> context == e.getContext())
|
||||
.findAny();
|
||||
return getOrCreateAddressEntry(context, addressEntry);
|
||||
|
||||
public MoneroWallet getMultisigWallet(String tradeId) {
|
||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||
String path = "xmr_multisig_trade_" + tradeId;
|
||||
MoneroWallet multisigWallet = null;
|
||||
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig()
|
||||
.setPath(path)
|
||||
.setPassword("abctesting123"));
|
||||
multisigWallets.put(tradeId, multisigWallet);
|
||||
multisigWallet.startSyncing(5000l);
|
||||
return multisigWallet;
|
||||
}
|
||||
|
||||
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
|
||||
@ -121,17 +123,10 @@ public class XmrWalletService {
|
||||
}
|
||||
|
||||
public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) {
|
||||
if (context == XmrAddressEntry.Context.TRADE_PAYOUT) {
|
||||
XmrAddressEntry entry = new XmrAddressEntry(0, wallet.createSubaddress(0).getAddress(), context, offerId, null);
|
||||
System.out.println("Adding address entry: " + entry.getAccountIndex() + ", " + entry.getAddressString());
|
||||
MoneroSubaddress subaddress = wallet.createSubaddress(0);
|
||||
XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
} else {
|
||||
MoneroAccount account = wallet.createAccount();
|
||||
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) {
|
||||
@ -142,37 +137,19 @@ public class XmrWalletService {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
// We try to use available and not yet used entries // TODO (woodser): "available" entries is not applicable in xmr which uses account 0 for main wallet and subsequent accounts for reserved trades, refactor address association for xmr?
|
||||
// We try to use available and not yet used entries
|
||||
Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
|
||||
.filter(e -> isAccountUnused(e.getAccountIndex()))
|
||||
.filter(e -> isSubaddressUnused(e.getSubaddressIndex()))
|
||||
.findAny();
|
||||
if (emptyAvailableAddressEntry.isPresent()) {
|
||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
|
||||
} else {
|
||||
MoneroAccount account = wallet.createAccount();
|
||||
XmrAddressEntry entry = new XmrAddressEntry(account.getIndex(), account.getPrimaryAddress(), context, offerId, null);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
return getNewAddressEntry(offerId, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private XmrAddressEntry getOrCreateAddressEntry(XmrAddressEntry.Context context, Optional<XmrAddressEntry> addressEntry) {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
} else {
|
||||
if (context == XmrAddressEntry.Context.ARBITRATOR) {
|
||||
MoneroSubaddress subaddress = wallet.createSubaddress(0);
|
||||
XmrAddressEntry entry = new XmrAddressEntry(0, subaddress.getAddress(), context);
|
||||
addressEntryList.addAddressEntry(entry);
|
||||
return entry;
|
||||
} else {
|
||||
throw new RuntimeException("XmrWalletService.getOrCreateAddressEntry(context, addressEntry) not implemented for non-arbitrator context"); // TODO (woodser): this method used with non-arbitrator context?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) {
|
||||
return getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> offerId.equals(e.getOfferId()))
|
||||
@ -238,7 +215,7 @@ public class XmrWalletService {
|
||||
|
||||
public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
|
||||
return getAvailableAddressEntries().stream()
|
||||
.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive())
|
||||
.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@ -246,26 +223,26 @@ public class XmrWalletService {
|
||||
return addressEntryList.getAddressEntriesAsListImmutable();
|
||||
}
|
||||
|
||||
public boolean isAccountUnused(int accountIndex) {
|
||||
return accountIndex != 0 && getBalanceForAccount(accountIndex).value == 0;
|
||||
public boolean isSubaddressUnused(int subaddressIndex) {
|
||||
return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0;
|
||||
//return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds
|
||||
}
|
||||
|
||||
public Coin getBalanceForAccount(int accountIndex) {
|
||||
public Coin getBalanceForSubaddress(int subaddressIndex) {
|
||||
|
||||
// get subaddress balance
|
||||
BigInteger balance = wallet.getBalance(0, subaddressIndex);
|
||||
|
||||
// get wallet balance
|
||||
BigInteger balance = wallet.getBalance(accountIndex);
|
||||
// // balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack?
|
||||
// for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) {
|
||||
// for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
|
||||
// if (transfer.getAccountIndex() == subaddressIndex) {
|
||||
// balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// balance from xmr wallet does not include unconfirmed funds, so add them // TODO: support lower in stack?
|
||||
for (MoneroTxWallet unconfirmedTx : wallet.getTxs(new MoneroTxQuery().setIsConfirmed(false))) {
|
||||
for (MoneroTransfer transfer : unconfirmedTx.getTransfers()) {
|
||||
if (transfer.getAccountIndex() == accountIndex) {
|
||||
balance = transfer.isIncoming() ? balance.add(transfer.getAmount()) : balance.subtract(transfer.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Returning balance for account " + accountIndex + ": " + balance.longValueExact());
|
||||
System.out.println("Returning balance for subaddress " + subaddressIndex + ": " + balance.longValueExact());
|
||||
|
||||
return Coin.valueOf(balance.longValueExact());
|
||||
}
|
||||
@ -283,7 +260,7 @@ public class XmrWalletService {
|
||||
Stream<XmrAddressEntry> availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream());
|
||||
Stream<XmrAddressEntry> available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
|
||||
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream());
|
||||
return available.filter(addressEntry -> getBalanceForAccount(addressEntry.getAccountIndex()).isPositive());
|
||||
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive());
|
||||
}
|
||||
|
||||
public void addBalanceListener(XmrBalanceListener listener) {
|
||||
@ -353,7 +330,7 @@ public class XmrWalletService {
|
||||
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(fromAccountIndex)
|
||||
.setAddress(toAddress)
|
||||
.setAmount(ParsingUtils.satoshisToXmrAtomicUnits(receiverAmount.value))
|
||||
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount))
|
||||
.setRelay(true));
|
||||
callback.onSuccess(tx);
|
||||
printTxs("sendFunds", tx);
|
||||
@ -387,17 +364,23 @@ public class XmrWalletService {
|
||||
private void notifyBalanceListeners() {
|
||||
for (XmrBalanceListener balanceListener : balanceListeners) {
|
||||
Coin balance;
|
||||
if (balanceListener.getAccountIndex() != null && balanceListener.getAccountIndex() != 0) {
|
||||
balance = getBalanceForAccount(balanceListener.getAccountIndex());
|
||||
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) {
|
||||
balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
|
||||
} else {
|
||||
balance = getAvailableConfirmedBalance();
|
||||
}
|
||||
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
|
||||
UserThread.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a MoneroWalletListener to notify the Haveno application.
|
||||
*
|
||||
* TODO (woodser): this is no longer necessary since not syncing to thread?
|
||||
*/
|
||||
public class HavenoWalletListener extends MoneroWalletListener {
|
||||
|
||||
@ -409,27 +392,47 @@ public class XmrWalletService {
|
||||
|
||||
@Override
|
||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
||||
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
|
||||
UserThread.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewBlock(long height) {
|
||||
listener.onNewBlock(height);
|
||||
UserThread.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onNewBlock(height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||
listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
||||
UserThread.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onBalancesChanged(newBalance, newUnlockedBalance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputReceived(MoneroOutputWallet output) {
|
||||
listener.onOutputReceived(output);
|
||||
UserThread.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onOutputReceived(output);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputSpent(MoneroOutputWallet output) {
|
||||
listener.onOutputSpent(output);
|
||||
UserThread.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onOutputSpent(output);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ public enum AvailabilityResult {
|
||||
@SuppressWarnings("unused") NO_REFUND_AGENTS("cannot take offer because no refund agents are available"),
|
||||
UNCONF_TX_LIMIT_HIT("cannot take offer because you have too many unconfirmed transactions at this moment"),
|
||||
MAKER_DENIED_API_USER("cannot take offer because maker is api user"),
|
||||
PRICE_CHECK_FAILED("cannot take offer because trade price check failed");
|
||||
PRICE_CHECK_FAILED("cannot take offer because trade price check failed"),
|
||||
MAKER_DENIED_TAKER("cannot take offer because maker denied taker");
|
||||
|
||||
private final String description;
|
||||
|
||||
|
@ -23,10 +23,14 @@ import bisq.core.btc.wallet.Restrictions;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountUtil;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.coin.CoinUtil;
|
||||
@ -64,6 +68,8 @@ public class CreateOfferService {
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final User user;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -77,7 +83,9 @@ public class CreateOfferService {
|
||||
P2PService p2PService,
|
||||
PubKeyRing pubKeyRing,
|
||||
User user,
|
||||
BtcWalletService btcWalletService) {
|
||||
BtcWalletService btcWalletService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
MediatorManager mediatorManager) {
|
||||
this.offerUtil = offerUtil;
|
||||
this.txFeeEstimationService = txFeeEstimationService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
@ -85,6 +93,8 @@ public class CreateOfferService {
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.user = user;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
}
|
||||
|
||||
|
||||
@ -181,6 +191,9 @@ public class CreateOfferService {
|
||||
paymentAccount,
|
||||
currencyCode,
|
||||
makerFeeAsCoin);
|
||||
|
||||
// select signing arbitrator
|
||||
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
|
||||
|
||||
OfferPayload offerPayload = new OfferPayload(offerId,
|
||||
creationTime,
|
||||
@ -194,8 +207,6 @@ public class CreateOfferService {
|
||||
minAmountAsLong,
|
||||
baseCurrencyCode,
|
||||
counterCurrencyCode,
|
||||
arbitratorNodeAddresses,
|
||||
mediatorNodeAddresses,
|
||||
paymentAccount.getPaymentMethod().getId(),
|
||||
paymentAccount.getId(),
|
||||
null,
|
||||
@ -219,7 +230,9 @@ public class CreateOfferService {
|
||||
isPrivateOffer,
|
||||
hashOfChallenge,
|
||||
extraDataMap,
|
||||
Version.TRADE_PROTOCOL_VERSION);
|
||||
Version.TRADE_PROTOCOL_VERSION,
|
||||
arbitrator.getNodeAddress(),
|
||||
null);
|
||||
Offer offer = new Offer(offerPayload);
|
||||
offer.setPriceFeedService(priceFeedService);
|
||||
return offer;
|
||||
|
@ -82,7 +82,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||
|
||||
public enum State {
|
||||
UNKNOWN,
|
||||
OFFER_FEE_PAID,
|
||||
OFFER_FEE_RESERVED,
|
||||
AVAILABLE,
|
||||
NOT_AVAILABLE,
|
||||
REMOVED,
|
||||
|
@ -22,6 +22,8 @@ import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountUtil;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
||||
@ -80,7 +82,8 @@ public class OfferFilter {
|
||||
IS_NODE_ADDRESS_BANNED,
|
||||
REQUIRE_UPDATE_TO_NEW_VERSION,
|
||||
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
|
||||
IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||
IS_MY_INSUFFICIENT_TRADE_LIMIT,
|
||||
SIGNATURE_NOT_VALIDATED;
|
||||
|
||||
@Getter
|
||||
private final boolean isValid;
|
||||
@ -128,6 +131,9 @@ public class OfferFilter {
|
||||
if (isMyInsufficientTradeLimit(offer)) {
|
||||
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
|
||||
}
|
||||
if (!hasValidSignature(offer)) {
|
||||
return Result.SIGNATURE_NOT_VALIDATED; // TODO (woodser): handle this wherever IS_MY_INSUFFICIENT_TRADE_LIMIT is handled
|
||||
}
|
||||
|
||||
return Result.VALID;
|
||||
}
|
||||
@ -206,4 +212,14 @@ public class OfferFilter {
|
||||
myInsufficientTradeLimitCache.put(offerId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean hasValidSignature(Offer offer) {
|
||||
|
||||
// get arbitrator
|
||||
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()); // TODO (woodser): does this return null if arbitrator goes offline?
|
||||
if (arbitrator == null) return false; // TODO (woodser): if arbitrator is null, get arbirator's pub key ring from store, otherwise cannot validate and offer is not seen by takers when arbitrator goes offline
|
||||
|
||||
// validate arbitrator signature
|
||||
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
@ -120,12 +119,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
private final String baseCurrencyCode;
|
||||
private final String counterCurrencyCode;
|
||||
|
||||
@Deprecated
|
||||
// Not used anymore but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash)
|
||||
private final List<NodeAddress> arbitratorNodeAddresses;
|
||||
@Deprecated
|
||||
// Not used anymore but we cannot set it Nullable or remove it to not break backward compatibility (diff. hash)
|
||||
private final List<NodeAddress> mediatorNodeAddresses;
|
||||
private final String paymentMethodId;
|
||||
private final String makerPaymentAccountId;
|
||||
// Mutable property. Has to be set before offer is save in P2P network as it changes the objects hash!
|
||||
@ -174,6 +167,13 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
@Nullable
|
||||
private final Map<String, String> extraDataMap;
|
||||
private final int protocolVersion;
|
||||
|
||||
// address and signature of signing arbitrator
|
||||
@Setter
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String arbitratorSignature;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -192,8 +192,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
long minAmount,
|
||||
String baseCurrencyCode,
|
||||
String counterCurrencyCode,
|
||||
List<NodeAddress> arbitratorNodeAddresses,
|
||||
List<NodeAddress> mediatorNodeAddresses,
|
||||
String paymentMethodId,
|
||||
String makerPaymentAccountId,
|
||||
@Nullable String offerFeePaymentTxId,
|
||||
@ -217,7 +215,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
boolean isPrivateOffer,
|
||||
@Nullable String hashOfChallenge,
|
||||
@Nullable Map<String, String> extraDataMap,
|
||||
int protocolVersion) {
|
||||
int protocolVersion,
|
||||
NodeAddress arbitratorSigner,
|
||||
@Nullable String arbitratorSignature) {
|
||||
this.id = id;
|
||||
this.date = date;
|
||||
this.ownerNodeAddress = ownerNodeAddress;
|
||||
@ -230,8 +230,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
this.minAmount = minAmount;
|
||||
this.baseCurrencyCode = baseCurrencyCode;
|
||||
this.counterCurrencyCode = counterCurrencyCode;
|
||||
this.arbitratorNodeAddresses = arbitratorNodeAddresses;
|
||||
this.mediatorNodeAddresses = mediatorNodeAddresses;
|
||||
this.paymentMethodId = paymentMethodId;
|
||||
this.makerPaymentAccountId = makerPaymentAccountId;
|
||||
this.offerFeePaymentTxId = offerFeePaymentTxId;
|
||||
@ -256,6 +254,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
this.hashOfChallenge = hashOfChallenge;
|
||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.arbitratorNodeAddress = arbitratorSigner;
|
||||
this.arbitratorSignature = arbitratorSignature;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -277,12 +277,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
.setMinAmount(minAmount)
|
||||
.setBaseCurrencyCode(baseCurrencyCode)
|
||||
.setCounterCurrencyCode(counterCurrencyCode)
|
||||
.addAllArbitratorNodeAddresses(arbitratorNodeAddresses.stream()
|
||||
.map(NodeAddress::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
.addAllMediatorNodeAddresses(mediatorNodeAddresses.stream()
|
||||
.map(NodeAddress::toProtoMessage)
|
||||
.collect(Collectors.toList()))
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.setMakerPaymentAccountId(makerPaymentAccountId)
|
||||
.setVersionNr(versionNr)
|
||||
@ -310,6 +304,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
Optional.ofNullable(acceptedCountryCodes).ifPresent(builder::addAllAcceptedCountryCodes);
|
||||
Optional.ofNullable(hashOfChallenge).ifPresent(builder::setHashOfChallenge);
|
||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||
|
||||
builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage());
|
||||
Optional.ofNullable(arbitratorSignature).ifPresent(builder::setArbitratorSignature);
|
||||
|
||||
return protobuf.StoragePayload.newBuilder().setOfferPayload(builder).build();
|
||||
}
|
||||
@ -336,12 +333,6 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
proto.getMinAmount(),
|
||||
proto.getBaseCurrencyCode(),
|
||||
proto.getCounterCurrencyCode(),
|
||||
proto.getArbitratorNodeAddressesList().stream()
|
||||
.map(NodeAddress::fromProto)
|
||||
.collect(Collectors.toList()),
|
||||
proto.getMediatorNodeAddressesList().stream()
|
||||
.map(NodeAddress::fromProto)
|
||||
.collect(Collectors.toList()),
|
||||
proto.getPaymentMethodId(),
|
||||
proto.getMakerPaymentAccountId(),
|
||||
proto.getOfferFeePaymentTxId(),
|
||||
@ -365,7 +356,9 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
proto.getIsPrivateOffer(),
|
||||
hashOfChallenge,
|
||||
extraDataMapMap,
|
||||
proto.getProtocolVersion());
|
||||
proto.getProtocolVersion(),
|
||||
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature()));
|
||||
}
|
||||
|
||||
|
||||
@ -431,6 +424,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
|
||||
",\n hashOfChallenge='" + hashOfChallenge + '\'' +
|
||||
",\n extraDataMap=" + extraDataMap +
|
||||
",\n protocolVersion=" + protocolVersion +
|
||||
",\n arbitratorSigner=" + arbitratorNodeAddress +
|
||||
",\n arbitratorSignature=" + arbitratorSignature +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,9 @@ import bisq.network.p2p.NodeAddress;
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -58,16 +59,9 @@ public final class OpenOffer implements Tradable {
|
||||
@Setter
|
||||
@Nullable
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
@Getter
|
||||
@Setter
|
||||
@Nullable
|
||||
private NodeAddress mediatorNodeAddress;
|
||||
|
||||
// Added v1.2.0
|
||||
@Getter
|
||||
@Setter
|
||||
@Nullable
|
||||
private NodeAddress refundAgentNodeAddress;
|
||||
private List<String> frozenKeyImages = new ArrayList<>();
|
||||
|
||||
// Added in v1.5.3.
|
||||
// If market price reaches that trigger price the offer gets deactivated
|
||||
@ -86,6 +80,13 @@ public final class OpenOffer implements Tradable {
|
||||
this.triggerPrice = triggerPrice;
|
||||
state = State.AVAILABLE;
|
||||
}
|
||||
|
||||
public OpenOffer(Offer offer, long triggerPrice, List<String> frozenKeyImages) {
|
||||
this.offer = offer;
|
||||
this.triggerPrice = triggerPrice;
|
||||
state = State.AVAILABLE;
|
||||
this.frozenKeyImages = frozenKeyImages;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
@ -94,14 +95,10 @@ public final class OpenOffer implements Tradable {
|
||||
private OpenOffer(Offer offer,
|
||||
State state,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
long triggerPrice) {
|
||||
this.offer = offer;
|
||||
this.state = state;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.mediatorNodeAddress = mediatorNodeAddress;
|
||||
this.refundAgentNodeAddress = refundAgentNodeAddress;
|
||||
this.triggerPrice = triggerPrice;
|
||||
|
||||
if (this.state == State.RESERVED)
|
||||
@ -113,22 +110,21 @@ public final class OpenOffer implements Tradable {
|
||||
protobuf.OpenOffer.Builder builder = protobuf.OpenOffer.newBuilder()
|
||||
.setOffer(offer.toProtoMessage())
|
||||
.setTriggerPrice(triggerPrice)
|
||||
.setState(protobuf.OpenOffer.State.valueOf(state.name()));
|
||||
.setState(protobuf.OpenOffer.State.valueOf(state.name()))
|
||||
.addAllFrozenKeyImages(frozenKeyImages);
|
||||
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(mediatorNodeAddress).ifPresent(nodeAddress -> builder.setMediatorNodeAddress(nodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentNodeAddress).ifPresent(nodeAddress -> builder.setRefundAgentNodeAddress(nodeAddress.toProtoMessage()));
|
||||
|
||||
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
|
||||
}
|
||||
|
||||
public static Tradable fromProto(protobuf.OpenOffer proto) {
|
||||
return new OpenOffer(Offer.fromProto(proto.getOffer()),
|
||||
OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()),
|
||||
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
|
||||
proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
|
||||
proto.getTriggerPrice());
|
||||
openOffer.setFrozenKeyImages(proto.getFrozenKeyImagesList());
|
||||
return openOffer;
|
||||
}
|
||||
|
||||
|
||||
@ -192,8 +188,6 @@ public final class OpenOffer implements Tradable {
|
||||
",\n offer=" + offer +
|
||||
",\n state=" + state +
|
||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
|
||||
",\n triggerPrice=" + triggerPrice +
|
||||
"\n}";
|
||||
}
|
||||
|
@ -29,18 +29,23 @@ import bisq.core.locale.Res;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.messages.SignOfferRequest;
|
||||
import bisq.core.offer.messages.SignOfferResponse;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferProtocol;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradableList;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
@ -53,7 +58,6 @@ import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Capabilities;
|
||||
@ -61,26 +65,28 @@ import bisq.common.app.Capability;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -88,7 +94,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@ -119,13 +125,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
private final RefundAgentManager refundAgentManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final FilterManager filterManager;
|
||||
private final Broadcaster broadcaster;
|
||||
private final PersistenceManager<TradableList<OpenOffer>> persistenceManager;
|
||||
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
|
||||
private final TradableList<OpenOffer> openOffers = new TradableList<>();
|
||||
private final SignedOfferList signedOffers = new SignedOfferList();
|
||||
private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager;
|
||||
private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>();
|
||||
private boolean stopped;
|
||||
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
|
||||
@Getter
|
||||
@ -157,7 +165,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
DaoFacade daoFacade,
|
||||
FilterManager filterManager,
|
||||
Broadcaster broadcaster,
|
||||
PersistenceManager<TradableList<OpenOffer>> persistenceManager) {
|
||||
PersistenceManager<TradableList<OpenOffer>> persistenceManager,
|
||||
PersistenceManager<SignedOfferList> signedOfferPersistenceManager) {
|
||||
this.coreContext = coreContext;
|
||||
this.createOfferService = createOfferService;
|
||||
this.keyRing = keyRing;
|
||||
@ -174,21 +183,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.filterManager = filterManager;
|
||||
this.broadcaster = broadcaster;
|
||||
this.persistenceManager = persistenceManager;
|
||||
this.signedOfferPersistenceManager = signedOfferPersistenceManager;
|
||||
|
||||
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
|
||||
this.signedOfferPersistenceManager.initialize(signedOffers, "SignedOffers", PersistenceManager.Source.PRIVATE); // arbitrator stores reserve tx for signed offers
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPersisted(Runnable completeHandler) {
|
||||
|
||||
// read open offers
|
||||
persistenceManager.readPersisted(persisted -> {
|
||||
openOffers.setAll(persisted.getList());
|
||||
openOffers.forEach(openOffer -> openOffer.getOffer().setPriceFeedService(priceFeedService));
|
||||
completeHandler.run();
|
||||
|
||||
// read signed offers
|
||||
signedOfferPersistenceManager.readPersisted(signedOfferPersisted -> {
|
||||
signedOffers.setAll(signedOfferPersisted.getList());
|
||||
completeHandler.run();
|
||||
},
|
||||
completeHandler);
|
||||
},
|
||||
completeHandler);
|
||||
}
|
||||
@ -286,7 +304,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
// We get an encrypted message but don't do the signature check as we don't know the peer yet.
|
||||
// A basic sig check is in done also at decryption time
|
||||
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
|
||||
if (networkEnvelope instanceof OfferAvailabilityRequest) {
|
||||
if (networkEnvelope instanceof SignOfferRequest) {
|
||||
handleSignOfferRequest((SignOfferRequest) networkEnvelope, peerNodeAddress);
|
||||
} if (networkEnvelope instanceof SignOfferResponse) {
|
||||
handleSignOfferResponse((SignOfferResponse) networkEnvelope, peerNodeAddress);
|
||||
} else if (networkEnvelope instanceof OfferAvailabilityRequest) {
|
||||
handleOfferAvailabilityRequest((OfferAvailabilityRequest) networkEnvelope, peerNodeAddress);
|
||||
} else if (networkEnvelope instanceof AckMessage) {
|
||||
AckMessage ackMessage = (AckMessage) networkEnvelope;
|
||||
@ -380,24 +402,36 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
offer.getAmount(),
|
||||
buyerSecurityDeposit,
|
||||
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit));
|
||||
|
||||
if (placeOfferProtocols.containsKey(offer.getId())) {
|
||||
log.warn("We already have a place offer protocol for offer " + offer.getId() + ", ignoring");
|
||||
throw new RuntimeException("We already have a place offer protocol for offer " + offer.getId() + ", ignoring");
|
||||
}
|
||||
|
||||
PlaceOfferModel model = new PlaceOfferModel(offer,
|
||||
reservedFundsForOffer,
|
||||
useSavingsWallet,
|
||||
p2PService,
|
||||
btcWalletService,
|
||||
xmrWalletService,
|
||||
tradeWalletService,
|
||||
bsqWalletService,
|
||||
offerBookService,
|
||||
arbitratorManager,
|
||||
mediatorManager,
|
||||
tradeStatisticsManager,
|
||||
daoFacade,
|
||||
user,
|
||||
keyRing,
|
||||
filterManager);
|
||||
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
|
||||
model,
|
||||
transaction -> {
|
||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice);
|
||||
|
||||
// save frozen key images with open offer
|
||||
List<String> frozenKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput output : model.getReserveTx().getInputs()) frozenKeyImages.add(output.getKeyImage().getHex());
|
||||
OpenOffer openOffer = new OpenOffer(offer, triggerPrice, frozenKeyImages);
|
||||
openOffers.add(openOffer);
|
||||
requestPersistence();
|
||||
resultHandler.handleResult(transaction);
|
||||
@ -410,7 +444,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
},
|
||||
errorMessageHandler
|
||||
);
|
||||
placeOfferProtocol.placeOffer();
|
||||
|
||||
placeOfferProtocols.put(offer.getId(), placeOfferProtocol);
|
||||
placeOfferProtocol.placeOffer(); // TODO (woodser): if error placing offer (e.g. bad signature), remove protocol and unfreeze trade funds
|
||||
}
|
||||
|
||||
// Remove from offerbook
|
||||
@ -590,7 +626,128 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
public Optional<OpenOffer> getOpenOfferById(String offerId) {
|
||||
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
|
||||
}
|
||||
|
||||
public Optional<SignedOffer> getSignedOfferById(String offerId) {
|
||||
return signedOffers.stream().filter(e -> e.getOfferId().equals(offerId)).findFirst();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Arbitrator Signs Offer
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void handleSignOfferRequest(SignOfferRequest request, NodeAddress peer) {
|
||||
log.info("Received SignOfferRequest from {} with offerId {} and uid {}",
|
||||
peer, request.getOfferId(), request.getUid());
|
||||
|
||||
String errorMessage = null;
|
||||
try {
|
||||
|
||||
// verify this node is an arbitrator
|
||||
Mediator thisArbitrator = user.getRegisteredMediator();
|
||||
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
|
||||
if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) {
|
||||
errorMessage = "Cannot sign offer because we are not a registered arbitrator";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify arbitrator is signer of offer payload
|
||||
if (!request.getOfferPayload().getArbitratorNodeAddress().equals(thisAddress)) {
|
||||
errorMessage = "Cannot sign offer because offer payload is for a different arbitrator";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify offer not seen before
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
||||
if (openOfferOptional.isPresent()) {
|
||||
errorMessage = "We already got a request to sign offer id " + request.offerId;
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify reserve tx not signed before
|
||||
|
||||
// verify maker's reserve tx (double spend, trade fee, trade amount, mining fee)
|
||||
Offer offer = new Offer(request.getOfferPayload());
|
||||
BigInteger tradeFee = ParsingUtils.coinToAtomicUnits(offer.getMakerFee());
|
||||
BigInteger depositAmount = ParsingUtils.coinToAtomicUnits(offer.getDirection() == OfferPayload.Direction.BUY ? offer.getBuyerSecurityDeposit() : offer.getAmount().add(offer.getSellerSecurityDeposit()));
|
||||
TradeUtils.processTradeTx(
|
||||
xmrWalletService.getDaemon(),
|
||||
xmrWalletService.getWallet(),
|
||||
request.getPayoutAddress(),
|
||||
depositAmount,
|
||||
tradeFee,
|
||||
request.getReserveTxHash(),
|
||||
request.getReserveTxHex(),
|
||||
request.getReserveTxKey(),
|
||||
true);
|
||||
|
||||
// arbitrator signs offer to certify they have valid reserve tx
|
||||
String offerPayloadAsJson = Utilities.objectToJson(request.getOfferPayload());
|
||||
String signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), offerPayloadAsJson);
|
||||
OfferPayload signedOfferPayload = request.getOfferPayload();
|
||||
signedOfferPayload.setArbitratorSignature(signature);
|
||||
|
||||
// create record of signed offer
|
||||
SignedOffer signedOffer = new SignedOffer(signedOfferPayload.getId(), request.getReserveTxHash(), request.getReserveTxHex(), signature); // TODO (woodser): no need for signature to be part of SignedOffer?
|
||||
signedOffers.add(signedOffer);
|
||||
requestPersistence();
|
||||
|
||||
// send ack
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), true, errorMessage);
|
||||
|
||||
// send response with signature
|
||||
SignOfferResponse response = new SignOfferResponse(request.getOfferId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
signedOfferPayload);
|
||||
p2PService.sendEncryptedDirectMessage(peer,
|
||||
request.getPubKeyRing(),
|
||||
response,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer: offerId={}; uid={}",
|
||||
response.getClass().getSimpleName(),
|
||||
response.getOfferId(),
|
||||
response.getUid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}",
|
||||
response.getClass().getSimpleName(),
|
||||
response.getUid(),
|
||||
peer,
|
||||
errorMessage);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
errorMessage = "Exception at handleSignOfferRequest " + e.getMessage();
|
||||
log.error(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSignOfferResponse(SignOfferResponse response, NodeAddress peer) {
|
||||
log.info("Received SignOfferResponse from {} with offerId {} and uid {}",
|
||||
peer, response.getOfferId(), response.getUid());
|
||||
|
||||
// get previously created protocol
|
||||
PlaceOfferProtocol protocol = placeOfferProtocols.get(response.getOfferId());
|
||||
if (protocol == null) {
|
||||
log.warn("No place offer protocol created for offer " + response.getOfferId());
|
||||
return;
|
||||
}
|
||||
|
||||
// handle response
|
||||
protocol.handleSignOfferResponse(response, peer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// OfferPayload Availability
|
||||
@ -606,7 +763,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
if (!p2PService.isBootstrapped()) {
|
||||
errorMessage = "We got a handleOfferAvailabilityRequest but we have not bootstrapped yet.";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request, peer, false, errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -614,14 +771,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
|
||||
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request, peer, false, errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stopped) {
|
||||
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
|
||||
log.debug(errorMessage);
|
||||
sendAckMessage(request, peer, false, errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -631,50 +788,60 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
} catch (Throwable t) {
|
||||
errorMessage = "Message validation failed. Error=" + t.toString() + ", Message=" + request.toString();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request, peer, false, errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
|
||||
AvailabilityResult availabilityResult;
|
||||
String makerSignature = null;
|
||||
NodeAddress arbitratorNodeAddress = null;
|
||||
NodeAddress mediatorNodeAddress = null;
|
||||
NodeAddress refundAgentNodeAddress = null;
|
||||
if (openOfferOptional.isPresent()) {
|
||||
OpenOffer openOffer = openOfferOptional.get();
|
||||
if (!apiUserDeniedByOffer(request)) {
|
||||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||
arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
|
||||
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
|
||||
if (!takerDeniedByMaker(request)) {
|
||||
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
|
||||
|
||||
// use signing arbitrator if available, otherwise use least used arbitrator
|
||||
boolean isSignerOnline = true;
|
||||
arbitratorNodeAddress = isSignerOnline ? offer.getOfferPayload().getArbitratorNodeAddress() : DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress();
|
||||
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
|
||||
|
||||
// maker signs taker's request // TODO (woodser): should maker signature include selected arbitrator?
|
||||
String tradeRequestAsJson = Utilities.objectToJson(request.getTradeRequest());
|
||||
makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson);
|
||||
|
||||
try {
|
||||
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
|
||||
// in trade price between the peers. Also here poor connectivity might cause market price API connection
|
||||
// losses and therefore an outdated market price.
|
||||
offer.checkTradePriceTolerance(request.getTakersTradePrice());
|
||||
availabilityResult = AvailabilityResult.AVAILABLE;
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
log.warn("Trade price check failed because takers price is outside out tolerance.");
|
||||
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
|
||||
} catch (MarketPriceNotAvailableException e) {
|
||||
log.warn(e.getMessage());
|
||||
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
|
||||
} catch (Throwable e) {
|
||||
log.warn("Trade price check failed. " + e.getMessage());
|
||||
if (coreContext.isApiUser())
|
||||
// Give api user something more than 'unknown_failure'.
|
||||
availabilityResult = AvailabilityResult.PRICE_CHECK_FAILED;
|
||||
else
|
||||
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
|
||||
try {
|
||||
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
|
||||
// in trade price between the peers. Also here poor connectivity might cause market price API connection
|
||||
// losses and therefore an outdated market price.
|
||||
offer.checkTradePriceTolerance(request.getTakersTradePrice());
|
||||
availabilityResult = AvailabilityResult.AVAILABLE;
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
log.warn("Trade price check failed because takers price is outside out tolerance.");
|
||||
availabilityResult = AvailabilityResult.PRICE_OUT_OF_TOLERANCE;
|
||||
} catch (MarketPriceNotAvailableException e) {
|
||||
log.warn(e.getMessage());
|
||||
availabilityResult = AvailabilityResult.MARKET_PRICE_NOT_AVAILABLE;
|
||||
} catch (Throwable e) {
|
||||
log.warn("Trade price check failed. " + e.getMessage());
|
||||
if (coreContext.isApiUser())
|
||||
// Give api user something more than 'unknown_failure'.
|
||||
availabilityResult = AvailabilityResult.PRICE_CHECK_FAILED;
|
||||
else
|
||||
availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.USER_IGNORED;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.USER_IGNORED;
|
||||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||
availabilityResult = AvailabilityResult.MAKER_DENIED_TAKER;
|
||||
}
|
||||
} else {
|
||||
availabilityResult = AvailabilityResult.MAKER_DENIED_API_USER;
|
||||
@ -689,12 +856,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
log.warn(errorMessage);
|
||||
availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT;
|
||||
}
|
||||
|
||||
|
||||
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
|
||||
availabilityResult,
|
||||
arbitratorNodeAddress,
|
||||
mediatorNodeAddress,
|
||||
refundAgentNodeAddress);
|
||||
makerSignature,
|
||||
arbitratorNodeAddress);
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}",
|
||||
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
|
||||
offerAvailabilityResponse.getUid(), peer);
|
||||
@ -725,53 +891,57 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
log.error(errorMessage);
|
||||
t.printStackTrace();
|
||||
} finally {
|
||||
sendAckMessage(request, peer, result, errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean apiUserDeniedByOffer(OfferAvailabilityRequest request) {
|
||||
return preferences.isDenyApiTaker() && request.isTakerApiUser();
|
||||
}
|
||||
|
||||
private boolean takerDeniedByMaker(OfferAvailabilityRequest request) {
|
||||
if (request.getTradeRequest() == null) return true;
|
||||
return false; // TODO (woodser): implement taker verification here, doing work of ApplyFilter and VerifyPeersAccountAgeWitness
|
||||
}
|
||||
|
||||
private void sendAckMessage(OfferAvailabilityRequest message,
|
||||
private void sendAckMessage(Class<?> reqClass,
|
||||
NodeAddress sender,
|
||||
PubKeyRing senderPubKeyRing,
|
||||
String offerId,
|
||||
String uid,
|
||||
boolean result,
|
||||
String errorMessage) {
|
||||
String offerId = message.getOfferId();
|
||||
String sourceUid = message.getUid();
|
||||
String sourceUid = uid;
|
||||
AckMessage ackMessage = new AckMessage(p2PService.getNetworkNode().getNodeAddress(),
|
||||
AckMessageSourceType.OFFER_MESSAGE,
|
||||
message.getClass().getSimpleName(),
|
||||
reqClass.getSimpleName(),
|
||||
sourceUid,
|
||||
offerId,
|
||||
result,
|
||||
errorMessage);
|
||||
|
||||
final NodeAddress takersNodeAddress = sender;
|
||||
PubKeyRing takersPubKeyRing = message.getPubKeyRing();
|
||||
log.info("Send AckMessage for OfferAvailabilityRequest to peer {} with offerId {} and sourceUid {}",
|
||||
takersNodeAddress, offerId, ackMessage.getSourceUid());
|
||||
log.info("Send AckMessage for {} to peer {} with offerId {} and sourceUid {}",
|
||||
reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid());
|
||||
p2PService.sendEncryptedDirectMessage(
|
||||
takersNodeAddress,
|
||||
takersPubKeyRing,
|
||||
sender,
|
||||
senderPubKeyRing,
|
||||
ackMessage,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("AckMessage for OfferAvailabilityRequest arrived at takersNodeAddress {}. offerId={}, sourceUid={}",
|
||||
takersNodeAddress, offerId, ackMessage.getSourceUid());
|
||||
log.info("AckMessage for {} arrived at sender {}. offerId={}, sourceUid={}",
|
||||
reqClass.getSimpleName(), sender, offerId, ackMessage.getSourceUid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("AckMessage for OfferAvailabilityRequest failed. AckMessage={}, takersNodeAddress={}, errorMessage={}",
|
||||
ackMessage, takersNodeAddress, errorMessage);
|
||||
log.error("AckMessage for {} failed. AckMessage={}, sender={}, errorMessage={}",
|
||||
reqClass.getSimpleName(), ackMessage, sender, errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Update persisted offer if a new capability is required after a software update
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -838,8 +1008,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
originalOfferPayload.getMinAmount(),
|
||||
originalOfferPayload.getBaseCurrencyCode(),
|
||||
originalOfferPayload.getCounterCurrencyCode(),
|
||||
originalOfferPayload.getArbitratorNodeAddresses(),
|
||||
originalOfferPayload.getMediatorNodeAddresses(),
|
||||
originalOfferPayload.getPaymentMethodId(),
|
||||
originalOfferPayload.getMakerPaymentAccountId(),
|
||||
originalOfferPayload.getOfferFeePaymentTxId(),
|
||||
@ -863,7 +1031,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
originalOfferPayload.isPrivateOffer(),
|
||||
originalOfferPayload.getHashOfChallenge(),
|
||||
updatedExtraDataMap,
|
||||
protocolVersion);
|
||||
protocolVersion,
|
||||
originalOfferPayload.getArbitratorNodeAddress(),
|
||||
originalOfferPayload.getArbitratorSignature());
|
||||
|
||||
// Save states from original data to use for the updated
|
||||
Offer.State originalOfferState = originalOffer.getState();
|
||||
@ -1024,6 +1194,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
|
||||
private void requestPersistence() {
|
||||
persistenceManager.requestPersistence();
|
||||
signedOfferPersistenceManager.requestPersistence();
|
||||
}
|
||||
|
||||
|
||||
|
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;
|
||||
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.User;
|
||||
|
||||
@ -40,6 +43,8 @@ public class OfferAvailabilityModel implements Model {
|
||||
@Getter
|
||||
private final PubKeyRing pubKeyRing; // takers PubKey (my pubkey)
|
||||
@Getter
|
||||
private final XmrWalletService xmrWalletService;
|
||||
@Getter
|
||||
private final P2PService p2PService;
|
||||
@Getter
|
||||
final private User user;
|
||||
@ -49,22 +54,20 @@ public class OfferAvailabilityModel implements Model {
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private NodeAddress peerNodeAddress; // maker
|
||||
private OfferAvailabilityResponse message;
|
||||
@Getter
|
||||
private String paymentAccountId;
|
||||
@Getter
|
||||
private OfferUtil offerUtil;
|
||||
@Getter
|
||||
@Setter
|
||||
private InitTradeRequest tradeRequest;
|
||||
@Nullable
|
||||
@Setter
|
||||
@Getter
|
||||
private NodeAddress selectedArbitrator;
|
||||
|
||||
// Added in v1.1.6
|
||||
@Nullable
|
||||
private String makerSignature;
|
||||
@Setter
|
||||
@Getter
|
||||
private NodeAddress selectedMediator;
|
||||
|
||||
// Added in v1.2.0
|
||||
@Nullable
|
||||
@Setter
|
||||
@Getter
|
||||
private NodeAddress selectedRefundAgent;
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
|
||||
// Added in v1.5.5
|
||||
@Getter
|
||||
@ -72,18 +75,24 @@ public class OfferAvailabilityModel implements Model {
|
||||
|
||||
public OfferAvailabilityModel(Offer offer,
|
||||
PubKeyRing pubKeyRing,
|
||||
XmrWalletService xmrWalletService,
|
||||
P2PService p2PService,
|
||||
User user,
|
||||
MediatorManager mediatorManager,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
boolean isTakerApiUser) {
|
||||
boolean isTakerApiUser,
|
||||
String paymentAccountId,
|
||||
OfferUtil offerUtil) {
|
||||
this.offer = offer;
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.p2PService = p2PService;
|
||||
this.user = user;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.isTakerApiUser = isTakerApiUser;
|
||||
this.paymentAccountId = paymentAccountId;
|
||||
this.offerUtil = offerUtil;
|
||||
}
|
||||
|
||||
public NodeAddress getPeerNodeAddress() {
|
||||
|
@ -19,18 +19,16 @@ package bisq.core.offer.availability.tasks;
|
||||
|
||||
import bisq.core.offer.AvailabilityResult;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.common.taskrunner.Task;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityModel> {
|
||||
@ -47,26 +45,27 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
|
||||
checkArgument(offer.getState() != Offer.State.REMOVED, "Offer state must not be Offer.State.REMOVED");
|
||||
|
||||
// check availability result
|
||||
OfferAvailabilityResponse offerAvailabilityResponse = model.getMessage();
|
||||
|
||||
if (offerAvailabilityResponse.getAvailabilityResult() != AvailabilityResult.AVAILABLE) {
|
||||
offer.setState(Offer.State.NOT_AVAILABLE);
|
||||
failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.getAvailabilityResult());
|
||||
return;
|
||||
}
|
||||
|
||||
// check maker signature for trade request
|
||||
if (!TradeUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) {
|
||||
offer.setState(Offer.State.NOT_AVAILABLE);
|
||||
failed("Take offer attempt failed because maker signature is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
offer.setState(Offer.State.AVAILABLE);
|
||||
|
||||
model.setSelectedArbitrator(offerAvailabilityResponse.getArbitrator());
|
||||
|
||||
NodeAddress mediator = offerAvailabilityResponse.getMediator();
|
||||
if (mediator == null) {
|
||||
// We do not get a mediator from old clients so we need to handle the null case.
|
||||
mediator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
|
||||
}
|
||||
model.setSelectedMediator(mediator);
|
||||
|
||||
model.setSelectedRefundAgent(offerAvailabilityResponse.getRefundAgent());
|
||||
|
||||
model.setMakerSignature(offerAvailabilityResponse.getMakerSignature());
|
||||
model.setArbitratorNodeAddress(offerAvailabilityResponse.getArbitratorNodeAddress());
|
||||
checkNotNull(model.getMakerSignature());
|
||||
checkNotNull(model.getArbitratorNodeAddress());
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
|
@ -17,12 +17,21 @@
|
||||
|
||||
package bisq.core.offer.availability.tasks;
|
||||
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.user.User;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.taskrunner.Task;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
@ -38,9 +47,49 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
|
||||
// collect fields
|
||||
Offer offer = model.getOffer();
|
||||
User user = model.getUser();
|
||||
P2PService p2PService = model.getP2PService();
|
||||
XmrWalletService walletService = model.getXmrWalletService();
|
||||
OfferUtil offerUtil = model.getOfferUtil();
|
||||
String paymentAccountId = model.getPaymentAccountId();
|
||||
String paymentMethodId = user.getPaymentAccount(paymentAccountId).getPaymentAccountPayload().getPaymentMethodId();
|
||||
String payoutAddress = walletService.getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); // reserve new payout address
|
||||
|
||||
// taker signs offer using offer id as nonce to avoid challenge protocol
|
||||
byte[] sig = Sig.sign(model.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8));
|
||||
|
||||
// send InitTradeRequest to maker to sign
|
||||
InitTradeRequest tradeRequest = new InitTradeRequest(
|
||||
offer.getId(),
|
||||
P2PService.getMyNodeAddress(),
|
||||
p2PService.getKeyRing().getPubKeyRing(),
|
||||
offer.getAmount().value,
|
||||
offer.getPrice().getValue(),
|
||||
offerUtil.getTakerFee(true, offer.getAmount()).value,
|
||||
user.getAccountId(),
|
||||
paymentAccountId,
|
||||
paymentMethodId,
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
sig,
|
||||
new Date().getTime(),
|
||||
offer.getMakerNodeAddress(),
|
||||
P2PService.getMyNodeAddress(),
|
||||
null, // maker provides node address of arbitrator on response
|
||||
null, // reserve tx not sent from taker to maker
|
||||
null,
|
||||
null,
|
||||
payoutAddress,
|
||||
null);
|
||||
|
||||
// save trade request to later send to arbitrator
|
||||
model.setTradeRequest(tradeRequest);
|
||||
|
||||
OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.getOffer().getId(),
|
||||
model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser());
|
||||
model.getPubKeyRing(), model.getTakersTradePrice(), model.isTakerApiUser(), tradeRequest);
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}",
|
||||
message.getClass().getSimpleName(), message.getOfferId(),
|
||||
message.getUid(), model.getPeerNodeAddress());
|
||||
|
@ -22,7 +22,8 @@ import bisq.network.p2p.SupportedCapabilitiesMessage;
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -43,18 +44,21 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
||||
@Nullable
|
||||
private final Capabilities supportedCapabilities;
|
||||
private final boolean isTakerApiUser;
|
||||
private final InitTradeRequest tradeRequest;
|
||||
|
||||
public OfferAvailabilityRequest(String offerId,
|
||||
PubKeyRing pubKeyRing,
|
||||
long takersTradePrice,
|
||||
boolean isTakerApiUser) {
|
||||
boolean isTakerApiUser,
|
||||
InitTradeRequest tradeRequest) {
|
||||
this(offerId,
|
||||
pubKeyRing,
|
||||
takersTradePrice,
|
||||
isTakerApiUser,
|
||||
Capabilities.app,
|
||||
Version.getP2PMessageVersion(),
|
||||
UUID.randomUUID().toString());
|
||||
UUID.randomUUID().toString(),
|
||||
tradeRequest);
|
||||
}
|
||||
|
||||
|
||||
@ -68,13 +72,24 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
||||
boolean isTakerApiUser,
|
||||
@Nullable Capabilities supportedCapabilities,
|
||||
int messageVersion,
|
||||
@Nullable String uid) {
|
||||
@Nullable String uid,
|
||||
InitTradeRequest tradeRequest) {
|
||||
super(messageVersion, offerId, uid);
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.takersTradePrice = takersTradePrice;
|
||||
this.isTakerApiUser = isTakerApiUser;
|
||||
this.supportedCapabilities = supportedCapabilities;
|
||||
this.tradeRequest = tradeRequest;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public protobuf.Offer toProtoMessage() {
|
||||
// return protobuf.Offer.newBuilder().setOfferPayload(offerPayload.toProtoMessage().getOfferPayload()).build();
|
||||
// }
|
||||
//
|
||||
// public static Offer fromProto(protobuf.Offer proto) {
|
||||
// return new Offer(OfferPayload.fromProto(proto.getOfferPayload()));
|
||||
// }
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
@ -82,7 +97,8 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
||||
.setOfferId(offerId)
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.setTakersTradePrice(takersTradePrice)
|
||||
.setIsTakerApiUser(isTakerApiUser);
|
||||
.setIsTakerApiUser(isTakerApiUser)
|
||||
.setTradeRequest(tradeRequest.toProtoNetworkEnvelope().getInitTradeRequest());
|
||||
|
||||
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
||||
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
||||
@ -92,13 +108,14 @@ public final class OfferAvailabilityRequest extends OfferMessage implements Supp
|
||||
.build();
|
||||
}
|
||||
|
||||
public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityRequest proto, int messageVersion) {
|
||||
public static OfferAvailabilityRequest fromProto(protobuf.OfferAvailabilityRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
|
||||
return new OfferAvailabilityRequest(proto.getOfferId(),
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
proto.getTakersTradePrice(),
|
||||
proto.getIsTakerApiUser(),
|
||||
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
||||
messageVersion,
|
||||
proto.getUid().isEmpty() ? null : proto.getUid());
|
||||
proto.getUid().isEmpty() ? null : proto.getUid(),
|
||||
InitTradeRequest.fromProto(proto.getTradeRequest(), coreProtoResolver, messageVersion));
|
||||
}
|
||||
}
|
||||
|
@ -44,28 +44,21 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
||||
@Nullable
|
||||
private final Capabilities supportedCapabilities;
|
||||
|
||||
private final NodeAddress arbitrator;
|
||||
// Was introduced in v 1.1.6. Might be null if msg received from node with old version
|
||||
@Nullable
|
||||
private final NodeAddress mediator;
|
||||
|
||||
// Added v1.2.0
|
||||
@Nullable
|
||||
private final NodeAddress refundAgent;
|
||||
private final String makerSignature;
|
||||
private final NodeAddress arbitratorNodeAddress;
|
||||
|
||||
public OfferAvailabilityResponse(String offerId,
|
||||
AvailabilityResult availabilityResult,
|
||||
NodeAddress arbitrator,
|
||||
NodeAddress mediator,
|
||||
NodeAddress refundAgent) {
|
||||
String makerSignature,
|
||||
NodeAddress arbitratorNodeAddress) {
|
||||
this(offerId,
|
||||
availabilityResult,
|
||||
Capabilities.app,
|
||||
Version.getP2PMessageVersion(),
|
||||
UUID.randomUUID().toString(),
|
||||
arbitrator,
|
||||
mediator,
|
||||
refundAgent);
|
||||
makerSignature,
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
|
||||
@ -78,28 +71,25 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
||||
@Nullable Capabilities supportedCapabilities,
|
||||
int messageVersion,
|
||||
@Nullable String uid,
|
||||
NodeAddress arbitrator,
|
||||
@Nullable NodeAddress mediator,
|
||||
@Nullable NodeAddress refundAgent) {
|
||||
String makerSignature,
|
||||
NodeAddress arbitratorNodeAddress) {
|
||||
super(messageVersion, offerId, uid);
|
||||
this.availabilityResult = availabilityResult;
|
||||
this.supportedCapabilities = supportedCapabilities;
|
||||
this.arbitrator = arbitrator;
|
||||
this.mediator = mediator;
|
||||
this.refundAgent = refundAgent;
|
||||
this.makerSignature = makerSignature;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()));
|
||||
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()))
|
||||
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage());
|
||||
|
||||
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
|
||||
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
|
||||
Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage()));
|
||||
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage()));
|
||||
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
|
||||
|
||||
return getNetworkEnvelopeBuilder()
|
||||
.setOfferAvailabilityResponse(builder)
|
||||
@ -112,8 +102,7 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
|
||||
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
|
||||
messageVersion,
|
||||
proto.getUid().isEmpty() ? null : proto.getUid(),
|
||||
proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null,
|
||||
proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null,
|
||||
proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null);
|
||||
proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(),
|
||||
NodeAddress.fromProto(proto.getArbitratorNodeAddress()));
|
||||
}
|
||||
}
|
||||
|
@ -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.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.offer.messages.SignOfferResponse;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.taskrunner.Model;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
@ -49,15 +53,18 @@ public class PlaceOfferModel implements Model {
|
||||
private final Offer offer;
|
||||
private final Coin reservedFundsForOffer;
|
||||
private final boolean useSavingsWallet;
|
||||
private final P2PService p2PService;
|
||||
private final BtcWalletService walletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final TradeWalletService tradeWalletService;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final OfferBookService offerBookService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final DaoFacade daoFacade;
|
||||
private final User user;
|
||||
private final KeyRing keyRing;
|
||||
@Getter
|
||||
private final FilterManager filterManager;
|
||||
|
||||
@ -67,33 +74,41 @@ public class PlaceOfferModel implements Model {
|
||||
@Setter
|
||||
private Transaction transaction;
|
||||
@Setter
|
||||
private MoneroTxWallet xmrTransaction;
|
||||
private MoneroTxWallet reserveTx;
|
||||
@Setter
|
||||
private SignOfferResponse signOfferResponse;
|
||||
|
||||
public PlaceOfferModel(Offer offer,
|
||||
Coin reservedFundsForOffer,
|
||||
boolean useSavingsWallet,
|
||||
P2PService p2PService,
|
||||
BtcWalletService walletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
OfferBookService offerBookService,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
DaoFacade daoFacade,
|
||||
User user,
|
||||
KeyRing keyRing,
|
||||
FilterManager filterManager) {
|
||||
this.offer = offer;
|
||||
this.reservedFundsForOffer = reservedFundsForOffer;
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
this.p2PService = p2PService;
|
||||
this.walletService = walletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.offerBookService = offerBookService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.user = user;
|
||||
this.keyRing = keyRing;
|
||||
this.filterManager = filterManager;
|
||||
}
|
||||
|
||||
|
@ -17,12 +17,14 @@
|
||||
|
||||
package bisq.core.offer.placeoffer;
|
||||
|
||||
import bisq.core.offer.messages.SignOfferResponse;
|
||||
import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
|
||||
import bisq.core.offer.placeoffer.tasks.CheckNumberOfUnconfirmedTransactions;
|
||||
import bisq.core.offer.placeoffer.tasks.MakerReservesTradeFunds;
|
||||
import bisq.core.offer.placeoffer.tasks.MakerSendsSignOfferRequest;
|
||||
import bisq.core.offer.placeoffer.tasks.MakerProcessesSignOfferResponse;
|
||||
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
|
||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateFeeTx;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
@ -55,34 +57,60 @@ public class PlaceOfferProtocol {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void placeOffer() {
|
||||
log.debug("model.offer.id" + model.getOffer().getId());
|
||||
log.debug("placeOffer() " + model.getOffer().getId());
|
||||
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
|
||||
() -> {
|
||||
log.debug("sequence at handleRequestTakeOfferMessage completed");
|
||||
resultHandler.handleResult(model.getTransaction());
|
||||
log.debug("sequence at placeOffer completed");
|
||||
},
|
||||
(errorMessage) -> {
|
||||
log.error(errorMessage);
|
||||
|
||||
if (model.isOfferAddedToOfferBook()) {
|
||||
model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(),
|
||||
() -> {
|
||||
model.setOfferAddedToOfferBook(false);
|
||||
log.debug("OfferPayload removed from offer book.");
|
||||
},
|
||||
log::error);
|
||||
}
|
||||
model.getOffer().setErrorMessage(errorMessage);
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
);
|
||||
taskRunner.addTasks(
|
||||
ValidateOffer.class,
|
||||
CheckNumberOfUnconfirmedTransactions.class,
|
||||
MakerCreateFeeTx.class,
|
||||
AddToOfferBook.class
|
||||
MakerReservesTradeFunds.class,
|
||||
MakerSendsSignOfferRequest.class
|
||||
);
|
||||
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
// TODO (woodser): switch to fluent
|
||||
public void handleSignOfferResponse(SignOfferResponse response, NodeAddress sender) {
|
||||
log.debug("handleSignOfferResponse() " + model.getOffer().getId());
|
||||
model.setSignOfferResponse(response);
|
||||
|
||||
if (!model.getOffer().getOfferPayload().getArbitratorNodeAddress().equals(sender)) {
|
||||
log.warn("Ignoring sign offer response from different sender");
|
||||
return;
|
||||
}
|
||||
|
||||
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
|
||||
() -> {
|
||||
log.debug("sequence at handleSignOfferResponse completed");
|
||||
resultHandler.handleResult(model.getTransaction()); // TODO (woodser): XMR transaction instead
|
||||
},
|
||||
(errorMessage) -> {
|
||||
log.error(errorMessage);
|
||||
if (model.isOfferAddedToOfferBook()) {
|
||||
model.getOfferBookService().removeOffer(model.getOffer().getOfferPayload(),
|
||||
() -> {
|
||||
model.setOfferAddedToOfferBook(false);
|
||||
log.debug("OfferPayload removed from offer book.");
|
||||
},
|
||||
log::error);
|
||||
}
|
||||
model.getOffer().setErrorMessage(errorMessage);
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
);
|
||||
taskRunner.addTasks(
|
||||
MakerProcessesSignOfferResponse.class,
|
||||
AddToOfferBook.class
|
||||
);
|
||||
|
||||
taskRunner.run();
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package bisq.core.offer.placeoffer.tasks;
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||
|
||||
import bisq.common.taskrunner.Task;
|
||||
@ -32,7 +33,7 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
model.getOfferBookService().addOffer(model.getOffer(),
|
||||
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
|
||||
() -> {
|
||||
model.setOfferAddedToOfferBook(true);
|
||||
complete();
|
||||
|
@ -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);
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
||||
|
||||
model.getOffer().setState(Offer.State.OFFER_FEE_PAID);
|
||||
model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED);
|
||||
|
||||
complete();
|
||||
} else {
|
||||
@ -137,7 +137,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
|
||||
|
||||
log.debug("Successfully sent tx with id " + transaction.getTxId().toString());
|
||||
model.getOffer().setState(Offer.State.OFFER_FEE_PAID);
|
||||
model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED);
|
||||
|
||||
complete();
|
||||
}
|
||||
|
@ -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.crypto.CryptoUtils;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.proto.network.NetworkPayload;
|
||||
import bisq.common.util.JsonExclude;
|
||||
import bisq.common.util.Utilities;
|
||||
@ -46,7 +47,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
@ToString
|
||||
@Slf4j
|
||||
public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTradeContractJson {
|
||||
|
||||
|
||||
// Keys for excludeFromJsonDataMap
|
||||
public static final String SALT = "salt";
|
||||
public static final String HOLDER_NAME = "holderName";
|
||||
@ -117,6 +118,10 @@ public abstract class PaymentAccountPayload implements NetworkPayload, UsedForTr
|
||||
public abstract String getPaymentDetails();
|
||||
|
||||
public abstract String getPaymentDetailsForTradePopup();
|
||||
|
||||
public byte[] getHash() {
|
||||
return Hash.getRipemd160hash(this.toProtoMessage().toByteArray());
|
||||
}
|
||||
|
||||
public byte[] getSalt() {
|
||||
checkArgument(excludeFromJsonDataMap.containsKey(SALT), "Salt must have been set in excludeFromJsonDataMap.");
|
||||
|
@ -39,6 +39,8 @@ import bisq.core.network.p2p.inventory.messages.GetInventoryResponse;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
import bisq.core.offer.messages.SignOfferRequest;
|
||||
import bisq.core.offer.messages.SignOfferResponse;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
import bisq.core.support.dispute.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
|
||||
@ -53,19 +55,22 @@ import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.messages.DepositRequest;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.messages.TraderSignedWitnessMessage;
|
||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
||||
import bisq.core.trade.messages.UpdateMultisigResponse;
|
||||
@ -133,8 +138,13 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||
case PONG:
|
||||
return Pong.fromProto(proto.getPong(), messageVersion);
|
||||
|
||||
case SIGN_OFFER_REQUEST:
|
||||
return SignOfferRequest.fromProto(proto.getSignOfferRequest(), messageVersion);
|
||||
case SIGN_OFFER_RESPONSE:
|
||||
return SignOfferResponse.fromProto(proto.getSignOfferResponse(), messageVersion);
|
||||
|
||||
case OFFER_AVAILABILITY_REQUEST:
|
||||
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), messageVersion);
|
||||
return OfferAvailabilityRequest.fromProto(proto.getOfferAvailabilityRequest(), this, messageVersion);
|
||||
case OFFER_AVAILABILITY_RESPONSE:
|
||||
return OfferAvailabilityResponse.fromProto(proto.getOfferAvailabilityResponse(), messageVersion);
|
||||
case REFRESH_OFFER_MESSAGE:
|
||||
@ -157,16 +167,22 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||
return RefreshTradeStateRequest.fromProto(proto.getRefreshTradeStateRequest(), messageVersion);
|
||||
case INIT_TRADE_REQUEST:
|
||||
return InitTradeRequest.fromProto(proto.getInitTradeRequest(), this, messageVersion);
|
||||
case INIT_MULTISIG_MESSAGE:
|
||||
return InitMultisigMessage.fromProto(proto.getInitMultisigMessage(), this, messageVersion);
|
||||
case INIT_MULTISIG_REQUEST:
|
||||
return InitMultisigRequest.fromProto(proto.getInitMultisigRequest(), this, messageVersion);
|
||||
case SIGN_CONTRACT_REQUEST:
|
||||
return SignContractRequest.fromProto(proto.getSignContractRequest(), this, messageVersion);
|
||||
case SIGN_CONTRACT_RESPONSE:
|
||||
return SignContractResponse.fromProto(proto.getSignContractResponse(), this, messageVersion);
|
||||
case DEPOSIT_REQUEST:
|
||||
return DepositRequest.fromProto(proto.getDepositRequest(), this, messageVersion);
|
||||
case DEPOSIT_RESPONSE:
|
||||
return DepositResponse.fromProto(proto.getDepositResponse(), this, messageVersion);
|
||||
case PAYMENT_ACCOUNT_PAYLOAD_REQUEST:
|
||||
return PaymentAccountPayloadRequest.fromProto(proto.getPaymentAccountPayloadRequest(), this, messageVersion);
|
||||
case UPDATE_MULTISIG_REQUEST:
|
||||
return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
|
||||
case UPDATE_MULTISIG_RESPONSE:
|
||||
return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion);
|
||||
case MAKER_READY_TO_FUND_MULTISIG_REQUEST:
|
||||
return MakerReadyToFundMultisigRequest.fromProto(proto.getMakerReadyToFundMultisigRequest(), this, messageVersion);
|
||||
case MAKER_READY_TO_FUND_MULTISIG_RESPONSE:
|
||||
return MakerReadyToFundMultisigResponse.fromProto(proto.getMakerReadyToFundMultisigResponse(), this, messageVersion);
|
||||
case INPUTS_FOR_DEPOSIT_TX_REQUEST:
|
||||
return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
|
||||
case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
|
||||
@ -265,6 +281,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkPayload fromProto(protobuf.StorageEntryWrapper proto) {
|
||||
if (proto != null) {
|
||||
switch (proto.getMessageCase()) {
|
||||
@ -282,6 +299,7 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkPayload fromProto(protobuf.StoragePayload proto) {
|
||||
if (proto != null) {
|
||||
switch (proto.getMessageCase()) {
|
||||
|
@ -34,6 +34,7 @@ import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore;
|
||||
import bisq.core.dao.state.model.governance.BallotList;
|
||||
import bisq.core.dao.state.storage.DaoStateStore;
|
||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputList;
|
||||
import bisq.core.offer.SignedOfferList;
|
||||
import bisq.core.payment.PaymentAccountList;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
|
||||
@ -85,6 +86,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
||||
public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
|
||||
if (proto != null) {
|
||||
switch (proto.getMessageCase()) {
|
||||
case SIGNED_OFFER_LIST:
|
||||
return SignedOfferList.fromProto(proto.getSignedOfferList());
|
||||
case SEQUENCE_NUMBER_MAP:
|
||||
return SequenceNumberMap.fromProto(proto.getSequenceNumberMap());
|
||||
case PEER_LIST:
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.core.support.dispute;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.SupportType;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
@ -128,9 +129,6 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
@Nullable
|
||||
private String delayedPayoutTxId;
|
||||
|
||||
// Added for XMR integration
|
||||
private boolean isOpener;
|
||||
|
||||
// Added at v1.4.0
|
||||
@Setter
|
||||
@Nullable
|
||||
@ -144,6 +142,13 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
@Nullable
|
||||
@Setter
|
||||
private Map<String, String> extraDataMap;
|
||||
|
||||
// Added for XMR integration
|
||||
private boolean isOpener;
|
||||
@Nullable
|
||||
private PaymentAccountPayload makerPaymentAccountPayload;
|
||||
@Nullable
|
||||
private PaymentAccountPayload takerPaymentAccountPayload;
|
||||
|
||||
// We do not persist uid, it is only used by dispute agents to guarantee an uid.
|
||||
@Setter
|
||||
@ -178,6 +183,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
String contractAsJson,
|
||||
@Nullable String makerContractSignature,
|
||||
@Nullable String takerContractSignature,
|
||||
@Nullable PaymentAccountPayload makerPaymentAccountPayload,
|
||||
@Nullable PaymentAccountPayload takerPaymentAccountPayload,
|
||||
PubKeyRing agentPubKeyRing,
|
||||
boolean isSupportTicket,
|
||||
SupportType supportType) {
|
||||
@ -199,6 +206,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
this.contractAsJson = contractAsJson;
|
||||
this.makerContractSignature = makerContractSignature;
|
||||
this.takerContractSignature = takerContractSignature;
|
||||
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
|
||||
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
|
||||
this.agentPubKeyRing = agentPubKeyRing;
|
||||
this.isSupportTicket = isSupportTicket;
|
||||
this.supportType = supportType;
|
||||
@ -246,6 +255,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
Optional.ofNullable(disputePayoutTxId).ifPresent(builder::setDisputePayoutTxId);
|
||||
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
|
||||
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
|
||||
Optional.ofNullable(makerPaymentAccountPayload).ifPresent(e -> builder.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage()));
|
||||
Optional.ofNullable(takerPaymentAccountPayload).ifPresent(e -> builder.setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage()));
|
||||
Optional.ofNullable(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage()));
|
||||
Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
|
||||
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
|
||||
@ -274,6 +285,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
proto.getContractAsJson(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
|
||||
proto.hasMakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()) : null,
|
||||
proto.hasTakerPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()) : null,
|
||||
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
|
||||
proto.getIsSupportTicket(),
|
||||
SupportType.fromProto(proto.getSupportType()));
|
||||
@ -448,6 +461,16 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
return Res.get("support.sellerTaker");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
|
||||
return contract.isBuyerMakerAndSellerTaker() ? makerPaymentAccountPayload : takerPaymentAccountPayload;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PaymentAccountPayload getSellerPaymentAccountPayload() {
|
||||
return contract.isBuyerMakerAndSellerTaker() ? takerPaymentAccountPayload : makerPaymentAccountPayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -244,6 +244,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onAllServicesInitialized() {
|
||||
super.onAllServicesInitialized();
|
||||
disputeListService.onAllServicesInitialized();
|
||||
@ -329,7 +330,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
if (isAgent(dispute)) {
|
||||
|
||||
// update arbitrator's multisig wallet
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
multisigWallet.importMultisigHex(Arrays.asList(openNewDisputeMessage.getUpdatedMultisigHex()));
|
||||
System.out.println("Arbitrator multisig wallet updated on new dispute message, current txs:");
|
||||
System.out.println(multisigWallet.getTxs());
|
||||
@ -364,12 +365,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
addMediationResultMessage(dispute);
|
||||
|
||||
try {
|
||||
TradeDataValidation.validatePaymentAccountPayloads(dispute);
|
||||
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
|
||||
//TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed?
|
||||
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
|
||||
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
|
||||
} catch (TradeDataValidation.AddressException |
|
||||
TradeDataValidation.NodeAddressException e) {
|
||||
TradeDataValidation.NodeAddressException |
|
||||
TradeDataValidation.InvalidPaymentAccountPayloadException e) {
|
||||
log.error(e.toString());
|
||||
validationExceptions.add(e);
|
||||
}
|
||||
@ -581,6 +584,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
disputeFromOpener.getContractAsJson(),
|
||||
disputeFromOpener.getMakerContractSignature(),
|
||||
disputeFromOpener.getTakerContractSignature(),
|
||||
disputeFromOpener.getMakerPaymentAccountPayload(),
|
||||
disputeFromOpener.getTakerPaymentAccountPayload(),
|
||||
disputeFromOpener.getAgentPubKeyRing(),
|
||||
disputeFromOpener.isSupportTicket(),
|
||||
disputeFromOpener.getSupportType());
|
||||
|
@ -24,7 +24,6 @@ import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
import bisq.core.support.dispute.DisputeManager;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
|
||||
import bisq.common.crypto.Hash;
|
||||
@ -90,8 +89,8 @@ public class MultipleHolderNameDetection {
|
||||
|
||||
public static PaymentAccountPayload getPaymentAccountPayload(Dispute dispute) {
|
||||
return isBuyer(dispute) ?
|
||||
dispute.getContract().getBuyerPaymentAccountPayload() :
|
||||
dispute.getContract().getSellerPaymentAccountPayload();
|
||||
dispute.getBuyerPaymentAccountPayload() :
|
||||
dispute.getSellerPaymentAccountPayload();
|
||||
}
|
||||
|
||||
public static String getAddress(Dispute dispute) {
|
||||
@ -202,10 +201,9 @@ public class MultipleHolderNameDetection {
|
||||
Map<String, List<Dispute>> allDisputesByTraderMap = new HashMap<>();
|
||||
disputeManager.getDisputesAsObservableList().stream()
|
||||
.filter(dispute -> {
|
||||
Contract contract = dispute.getContract();
|
||||
PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ?
|
||||
contract.getBuyerPaymentAccountPayload() :
|
||||
contract.getSellerPaymentAccountPayload();
|
||||
dispute.getBuyerPaymentAccountPayload() :
|
||||
dispute.getSellerPaymentAccountPayload();
|
||||
return paymentAccountPayload instanceof PayloadWithHolderName;
|
||||
})
|
||||
.forEach(dispute -> {
|
||||
|
@ -240,7 +240,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
String errorMessage = null;
|
||||
boolean success = true;
|
||||
boolean requestUpdatedPayoutTx = false;
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
Contract contract = dispute.getContract();
|
||||
try {
|
||||
// We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals
|
||||
@ -366,7 +366,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
cleanupRetryMap(uid);
|
||||
|
||||
// update multisig wallet
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||
|
||||
// parse payout tx
|
||||
@ -416,7 +416,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
}
|
||||
|
||||
// update arbitrator's multisig wallet with co-signer's multisig hex
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(dispute.getTradeId());
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||
try {
|
||||
multisigWallet.importMultisigHex(Arrays.asList(request.getUpdatedMultisigHex()));
|
||||
} catch (Exception e) {
|
||||
@ -473,7 +473,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
private void onTraderSignedDisputePayoutTx(String tradeId, MoneroTxSet txSet) {
|
||||
|
||||
// gather trade info
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId);
|
||||
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
|
||||
if (!disputeOptional.isPresent()) {
|
||||
log.warn("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
|
||||
@ -596,8 +596,8 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
String sellerPayoutAddress = contract.isBuyerMakerAndSellerTaker() ? contract.getTakerPayoutAddressString() : contract.getMakerPayoutAddressString();
|
||||
Preconditions.checkNotNull(buyerPayoutAddress, "buyerPayoutAddress must not be null");
|
||||
Preconditions.checkNotNull(sellerPayoutAddress, "sellerPayoutAddress must not be null");
|
||||
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
|
||||
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
|
||||
BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getBuyerPayoutAmount());
|
||||
BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getSellerPayoutAmount());
|
||||
|
||||
//System.out.println("buyerPayoutAddress: " + buyerPayoutAddress);
|
||||
//System.out.println("buyerPayoutAmount: " + buyerPayoutAmount);
|
||||
@ -657,7 +657,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
private MoneroTxSet traderSignsDisputePayoutTx(String tradeId, String payoutTxHex) {
|
||||
|
||||
// gather trade info
|
||||
MoneroWallet multisigWallet = xmrWalletService.getOrCreateMultisigWallet(tradeId);
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId);
|
||||
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
|
||||
if (!disputeOptional.isPresent()) throw new RuntimeException("Trader has no dispute when signing dispute payout tx. This should never happen. TradeId = " + tradeId);
|
||||
Dispute dispute = disputeOptional.get();
|
||||
@ -665,11 +665,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
DisputeResult disputeResult = dispute.getDisputeResultProperty().get();
|
||||
|
||||
// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMakerDepositTxId() : trade.getTakerDepositTxId()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids
|
||||
// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTakerDepositTxId() : trade.getMakerDepositTxId()).getIncomingAmount();
|
||||
// BigInteger sellerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getMaker().getDepositTxHash() : trade.getTaker().getDepositTxHash()).getIncomingAmount(); // TODO (woodser): use contract instead of trade to get deposit tx ids when contract has deposit tx ids
|
||||
// BigInteger buyerDepositAmount = multisigWallet.getTx(trade instanceof MakerTrade ? trade.getTaker().getDepositTxHash() : trade.getMaker().getDepositTxHash()).getIncomingAmount();
|
||||
// BigInteger tradeAmount = BigInteger.valueOf(contract.getTradeAmount().value).multiply(ParsingUtils.XMR_SATOSHI_MULTIPLIER);
|
||||
BigInteger buyerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getBuyerPayoutAmount().value);
|
||||
BigInteger sellerPayoutAmount = ParsingUtils.satoshisToXmrAtomicUnits(disputeResult.getSellerPayoutAmount().value);
|
||||
BigInteger buyerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getBuyerPayoutAmount());
|
||||
BigInteger sellerPayoutAmount = ParsingUtils.coinToAtomicUnits(disputeResult.getSellerPayoutAmount());
|
||||
System.out.println("Buyer payout amount (with multiplier): " + buyerPayoutAmount);
|
||||
System.out.println("Seller payout amount (with multiplier): " + sellerPayoutAmount);
|
||||
|
||||
|
@ -248,7 +248,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
||||
|
||||
// If we have not got yet the peers signature we sign and send to the peer our signature.
|
||||
// Otherwise we sign and complete with the peers signature the payout tx.
|
||||
if (processModel.getTradingPeer().getMediatedPayoutTxSignature() == null) {
|
||||
if (trade.getTradingPeer().getMediatedPayoutTxSignature() == null) {
|
||||
tradeProtocol.onAcceptMediationResult(() -> {
|
||||
if (trade.getPayoutTx() != null) {
|
||||
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.MEDIATION_CLOSED);
|
||||
|
@ -20,19 +20,18 @@ import lombok.extern.slf4j.Slf4j;
|
||||
*/
|
||||
@Slf4j
|
||||
public class ArbitratorTrade extends Trade {
|
||||
|
||||
|
||||
public ArbitratorTrade(Offer offer,
|
||||
Coin tradeAmount,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
NodeAddress makerNodeAddress,
|
||||
NodeAddress takerNodeAddress,
|
||||
NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
super(offer, tradeAmount, txFee, takerFee, tradePrice, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress, xmrWalletService, processModel, uid);
|
||||
String uid,
|
||||
NodeAddress makerNodeAddress,
|
||||
NodeAddress takerNodeAddress,
|
||||
NodeAddress arbitratorNodeAddress) {
|
||||
super(offer, tradeAmount, takerFee, tradePrice, xmrWalletService, processModel, uid, makerNodeAddress, takerNodeAddress, arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,15 +63,14 @@ public class ArbitratorTrade extends Trade {
|
||||
return fromProto(new ArbitratorTrade(
|
||||
Offer.fromProto(proto.getOffer()),
|
||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||
proto.getTradePrice(),
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid),
|
||||
uid,
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null),
|
||||
proto,
|
||||
coreProtoResolver);
|
||||
}
|
||||
|
@ -42,23 +42,25 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BuyerAsMakerTrade(Offer offer,
|
||||
Coin txFee,
|
||||
Coin tradeAmount,
|
||||
Coin takeOfferFee,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
long tradePrice,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
NodeAddress makerNodeAddress,
|
||||
NodeAddress takerNodeAddress,
|
||||
NodeAddress arbitratorNodeAddress) {
|
||||
super(offer,
|
||||
txFee,
|
||||
tradeAmount,
|
||||
takeOfferFee,
|
||||
takerNodeAddress,
|
||||
makerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
tradePrice,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -84,14 +86,15 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
|
||||
}
|
||||
BuyerAsMakerTrade trade = new BuyerAsMakerTrade(
|
||||
Offer.fromProto(proto.getOffer()),
|
||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.getTradePrice(),
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
|
||||
|
||||
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
|
||||
trade.setTradePrice(proto.getTradePrice());
|
||||
|
@ -43,26 +43,24 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||
|
||||
public BuyerAsTakerTrade(Offer offer,
|
||||
Coin tradeAmount,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
super(offer,
|
||||
tradeAmount,
|
||||
txFee,
|
||||
takerFee,
|
||||
tradePrice,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
|
||||
@ -90,15 +88,14 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||
return fromProto(new BuyerAsTakerTrade(
|
||||
Offer.fromProto(proto.getOffer()),
|
||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||
proto.getTradePrice(),
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid),
|
||||
uid,
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null),
|
||||
proto,
|
||||
coreProtoResolver);
|
||||
}
|
||||
|
@ -35,46 +35,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
public abstract class BuyerTrade extends Trade {
|
||||
BuyerTrade(Offer offer,
|
||||
Coin tradeAmount,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
super(offer,
|
||||
tradeAmount,
|
||||
txFee,
|
||||
takerFee,
|
||||
tradePrice,
|
||||
takerNodeAddress,
|
||||
makerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
}
|
||||
|
||||
BuyerTrade(Offer offer,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
super(offer,
|
||||
txFee,
|
||||
takerFee,
|
||||
uid,
|
||||
takerNodeAddress,
|
||||
makerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -21,13 +21,12 @@ import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.util.VolumeUtil;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.proto.network.NetworkPayload;
|
||||
import bisq.common.util.JsonExclude;
|
||||
@ -56,14 +55,18 @@ public final class Contract implements NetworkPayload {
|
||||
private final boolean isBuyerMakerAndSellerTaker;
|
||||
private final String makerAccountId;
|
||||
private final String takerAccountId;
|
||||
private final PaymentAccountPayload makerPaymentAccountPayload;
|
||||
private final PaymentAccountPayload takerPaymentAccountPayload;
|
||||
private final String makerPaymentMethodId;
|
||||
private final String takerPaymentMethodId;
|
||||
private final byte[] makerPaymentAccountPayloadHash;
|
||||
private final byte[] takerPaymentAccountPayloadHash;
|
||||
@JsonExclude
|
||||
private final PubKeyRing makerPubKeyRing;
|
||||
@JsonExclude
|
||||
private final PubKeyRing takerPubKeyRing;
|
||||
private final String makerPayoutAddressString;
|
||||
private final String takerPayoutAddressString;
|
||||
private final String makerDepositTxHash;
|
||||
private final String takerDepositTxHash;
|
||||
|
||||
// Added in v1.2.0
|
||||
private long lockTime;
|
||||
@ -77,13 +80,17 @@ public final class Contract implements NetworkPayload {
|
||||
boolean isBuyerMakerAndSellerTaker,
|
||||
String makerAccountId,
|
||||
String takerAccountId,
|
||||
PaymentAccountPayload makerPaymentAccountPayload,
|
||||
PaymentAccountPayload takerPaymentAccountPayload,
|
||||
String makerPaymentMethodId,
|
||||
String takerPaymentMethodId,
|
||||
byte[] makerPaymentAccountPayloadHash,
|
||||
byte[] takerPaymentAccountPayloadHash,
|
||||
PubKeyRing makerPubKeyRing,
|
||||
PubKeyRing takerPubKeyRing,
|
||||
String makerPayoutAddressString,
|
||||
String takerPayoutAddressString,
|
||||
long lockTime) {
|
||||
long lockTime,
|
||||
String makerDepositTxHash,
|
||||
String takerDepositTxHash) {
|
||||
this.offerPayload = offerPayload;
|
||||
this.tradeAmount = tradeAmount;
|
||||
this.tradePrice = tradePrice;
|
||||
@ -93,16 +100,18 @@ public final class Contract implements NetworkPayload {
|
||||
this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker;
|
||||
this.makerAccountId = makerAccountId;
|
||||
this.takerAccountId = takerAccountId;
|
||||
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
|
||||
this.takerPaymentAccountPayload = takerPaymentAccountPayload;
|
||||
this.makerPaymentMethodId = makerPaymentMethodId;
|
||||
this.takerPaymentMethodId = takerPaymentMethodId;
|
||||
this.makerPaymentAccountPayloadHash = makerPaymentAccountPayloadHash;
|
||||
this.takerPaymentAccountPayloadHash = takerPaymentAccountPayloadHash;
|
||||
this.makerPubKeyRing = makerPubKeyRing;
|
||||
this.takerPubKeyRing = takerPubKeyRing;
|
||||
this.makerPayoutAddressString = makerPayoutAddressString;
|
||||
this.takerPayoutAddressString = takerPayoutAddressString;
|
||||
this.lockTime = lockTime;
|
||||
this.makerDepositTxHash = makerDepositTxHash;
|
||||
this.takerDepositTxHash = takerDepositTxHash;
|
||||
|
||||
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
|
||||
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
|
||||
// For SEPA offers we accept also SEPA_INSTANT takers
|
||||
// Otherwise both ids need to be the same
|
||||
boolean result = (makerPaymentMethodId.equals(PaymentMethod.SEPA_ID) && takerPaymentMethodId.equals(PaymentMethod.SEPA_INSTANT_ID)) ||
|
||||
@ -127,13 +136,17 @@ public final class Contract implements NetworkPayload {
|
||||
proto.getIsBuyerMakerAndSellerTaker(),
|
||||
proto.getMakerAccountId(),
|
||||
proto.getTakerAccountId(),
|
||||
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
|
||||
coreProtoResolver.fromProto(proto.getTakerPaymentAccountPayload()),
|
||||
proto.getMakerPaymentMethodId(),
|
||||
proto.getTakerPaymentMethodId(),
|
||||
proto.getMakerPaymentAccountPayloadHash().toByteArray(),
|
||||
proto.getTakerPaymentAccountPayloadHash().toByteArray(),
|
||||
PubKeyRing.fromProto(proto.getMakerPubKeyRing()),
|
||||
PubKeyRing.fromProto(proto.getTakerPubKeyRing()),
|
||||
proto.getMakerPayoutAddressString(),
|
||||
proto.getTakerPayoutAddressString(),
|
||||
proto.getLockTime());
|
||||
proto.getLockTime(),
|
||||
proto.getMakerDepositTxHash(),
|
||||
proto.getTakerDepositTxHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -148,13 +161,17 @@ public final class Contract implements NetworkPayload {
|
||||
.setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker)
|
||||
.setMakerAccountId(makerAccountId)
|
||||
.setTakerAccountId(takerAccountId)
|
||||
.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())
|
||||
.setTakerPaymentAccountPayload((protobuf.PaymentAccountPayload) takerPaymentAccountPayload.toProtoMessage())
|
||||
.setMakerPaymentMethodId(makerPaymentMethodId)
|
||||
.setTakerPaymentMethodId(takerPaymentMethodId)
|
||||
.setMakerPaymentAccountPayloadHash(ByteString.copyFrom(makerPaymentAccountPayloadHash))
|
||||
.setTakerPaymentAccountPayloadHash(ByteString.copyFrom(takerPaymentAccountPayloadHash))
|
||||
.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage())
|
||||
.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage())
|
||||
.setMakerPayoutAddressString(makerPayoutAddressString)
|
||||
.setTakerPayoutAddressString(takerPayoutAddressString)
|
||||
.setLockTime(lockTime)
|
||||
.setMakerDepositTxHash(makerDepositTxHash)
|
||||
.setTakerDepositTxHash(takerDepositTxHash)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -179,16 +196,16 @@ public final class Contract implements NetworkPayload {
|
||||
return isBuyerMakerAndSellerTaker ? takerPubKeyRing : makerPubKeyRing;
|
||||
}
|
||||
|
||||
public PaymentAccountPayload getBuyerPaymentAccountPayload() {
|
||||
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayload : takerPaymentAccountPayload;
|
||||
public byte[] getBuyerPaymentAccountPayloadHash() {
|
||||
return isBuyerMakerAndSellerTaker ? makerPaymentAccountPayloadHash : takerPaymentAccountPayloadHash;
|
||||
}
|
||||
|
||||
public PaymentAccountPayload getSellerPaymentAccountPayload() {
|
||||
return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayload : makerPaymentAccountPayload;
|
||||
public byte[] getSellerPaymentAccountPayloadHash() {
|
||||
return isBuyerMakerAndSellerTaker ? takerPaymentAccountPayloadHash : makerPaymentAccountPayloadHash;
|
||||
}
|
||||
|
||||
|
||||
public String getPaymentMethodId() {
|
||||
return makerPaymentAccountPayload.getPaymentMethodId();
|
||||
return makerPaymentMethodId;
|
||||
}
|
||||
|
||||
public Coin getTradeAmount() {
|
||||
@ -270,13 +287,17 @@ public final class Contract implements NetworkPayload {
|
||||
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
|
||||
",\n makerAccountId='" + makerAccountId + '\'' +
|
||||
",\n takerAccountId='" + takerAccountId + '\'' +
|
||||
",\n makerPaymentAccountPayload=" + makerPaymentAccountPayload +
|
||||
",\n takerPaymentAccountPayload=" + takerPaymentAccountPayload +
|
||||
",\n makerPaymentMethodId='" + makerPaymentMethodId + '\'' +
|
||||
",\n takerPaymentMethodId='" + takerPaymentMethodId + '\'' +
|
||||
",\n makerPaymentAccountPayloadHash=" + makerPaymentAccountPayloadHash +
|
||||
",\n takerPaymentAccountPayloadHash=" + takerPaymentAccountPayloadHash +
|
||||
",\n makerPubKeyRing=" + makerPubKeyRing +
|
||||
",\n takerPubKeyRing=" + takerPubKeyRing +
|
||||
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +
|
||||
",\n takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
|
||||
",\n lockTime=" + lockTime +
|
||||
",\n makerDepositTxHash='" + makerDepositTxHash + '\'' +
|
||||
",\n takerDepositTxHash='" + takerDepositTxHash + '\'' +
|
||||
"\n}";
|
||||
}
|
||||
}
|
||||
|
@ -42,23 +42,25 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public SellerAsMakerTrade(Offer offer,
|
||||
Coin txFee,
|
||||
Coin tradeAmount,
|
||||
Coin takerFee,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
long tradePrice,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
super(offer,
|
||||
txFee,
|
||||
tradeAmount,
|
||||
takerFee,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
tradePrice,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
|
||||
@ -85,14 +87,15 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
|
||||
}
|
||||
SellerAsMakerTrade trade = new SellerAsMakerTrade(
|
||||
Offer.fromProto(proto.getOffer()),
|
||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
proto.getTradePrice(),
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
|
||||
|
||||
trade.setTradeAmountAsLong(proto.getTradeAmountAsLong());
|
||||
trade.setTradePrice(proto.getTradePrice());
|
||||
|
@ -43,26 +43,24 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
||||
|
||||
public SellerAsTakerTrade(Offer offer,
|
||||
Coin tradeAmount,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
super(offer,
|
||||
tradeAmount,
|
||||
txFee,
|
||||
takerFee,
|
||||
tradePrice,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
|
||||
@ -90,15 +88,14 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
||||
return fromProto(new SellerAsTakerTrade(
|
||||
Offer.fromProto(proto.getOffer()),
|
||||
Coin.valueOf(proto.getTradeAmountAsLong()),
|
||||
Coin.valueOf(proto.getTxFeeAsLong()),
|
||||
Coin.valueOf(proto.getTakerFeeAsLong()),
|
||||
proto.getTradePrice(),
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid),
|
||||
uid,
|
||||
proto.hasMakerNodeAddress() ? NodeAddress.fromProto(proto.getMakerNodeAddress()) : null,
|
||||
proto.hasTakerNodeAddress() ? NodeAddress.fromProto(proto.getTakerNodeAddress()) : null,
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null),
|
||||
proto,
|
||||
coreProtoResolver);
|
||||
}
|
||||
|
@ -36,46 +36,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
public abstract class SellerTrade extends Trade {
|
||||
SellerTrade(Offer offer,
|
||||
Coin tradeAmount,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
super(offer,
|
||||
tradeAmount,
|
||||
txFee,
|
||||
takerFee,
|
||||
tradePrice,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
}
|
||||
|
||||
SellerTrade(Offer offer,
|
||||
Coin txFee,
|
||||
Coin takeOfferFee,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
super(offer,
|
||||
txFee,
|
||||
takeOfferFee,
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,6 +22,7 @@ import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload.Direction;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
@ -31,10 +32,11 @@ import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.ProcessModel;
|
||||
import bisq.core.trade.protocol.ProcessModelServiceProvider;
|
||||
import bisq.core.trade.protocol.TradeMessageListener;
|
||||
import bisq.core.trade.protocol.TradeListener;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.trade.txproof.AssetTxProofResult;
|
||||
import bisq.core.util.VolumeUtil;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
@ -80,9 +82,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import monero.common.MoneroError;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroOutputWallet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
/**
|
||||
* Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are
|
||||
@ -345,14 +348,6 @@ public abstract class Trade implements Tradable, Model {
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String takerContractSignature;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String makerContractSignature;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
@Nullable
|
||||
@Setter
|
||||
@ -364,14 +359,6 @@ public abstract class Trade implements Tradable, Model {
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private NodeAddress mediatorNodeAddress;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private PubKeyRing mediatorPubKeyRing;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String takerPaymentAccountId;
|
||||
@Nullable
|
||||
private String errorMessage;
|
||||
@ -460,7 +447,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
|
||||
|
||||
// Added in XMR integration
|
||||
private transient List<TradeMessageListener> tradeMessageListeners; // notified on fully validated trade messages
|
||||
private transient List<TradeListener> tradeListeners; // notified on fully validated trade messages
|
||||
@Getter
|
||||
@Setter
|
||||
private NodeAddress makerNodeAddress;
|
||||
@ -473,18 +460,11 @@ public abstract class Trade implements Tradable, Model {
|
||||
@Getter
|
||||
@Setter
|
||||
private PubKeyRing takerPubKeyRing;
|
||||
@Nullable
|
||||
transient MoneroWalletListener depositTxListener;
|
||||
transient Boolean makerDepositLocked; // null when unknown, true while locked, false when unlocked
|
||||
transient Boolean takerDepositLocked;
|
||||
transient private MoneroTxWallet makerDepositTx;
|
||||
@Nullable
|
||||
transient private MoneroTxWallet takerDepositTx;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String makerDepositTxId;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String takerDepositTxId;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, initialization
|
||||
@ -492,28 +472,34 @@ public abstract class Trade implements Tradable, Model {
|
||||
|
||||
// maker
|
||||
protected Trade(Offer offer,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
Coin tradeAmount,
|
||||
Coin takerFee, // TODO (woodser): makerFee, takerFee, but not given one during construction
|
||||
long tradePrice,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
this.offer = offer;
|
||||
this.txFee = txFee;
|
||||
this.tradeAmount = tradeAmount;
|
||||
this.txFee = Coin.valueOf(0); // TODO (woodser): remove this field
|
||||
this.takerFee = takerFee;
|
||||
this.makerNodeAddress = makerNodeAddress;
|
||||
this.takerNodeAddress = takerNodeAddress;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.tradePrice = tradePrice;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.processModel = processModel;
|
||||
this.uid = uid;
|
||||
|
||||
txFeeAsLong = txFee.value;
|
||||
takerFeeAsLong = takerFee.value;
|
||||
takeOfferDate = new Date().getTime();
|
||||
tradeMessageListeners = new ArrayList<TradeMessageListener>();
|
||||
this.txFeeAsLong = txFee.value;
|
||||
this.takerFeeAsLong = takerFee.value;
|
||||
this.takeOfferDate = new Date().getTime();
|
||||
this.tradeListeners = new ArrayList<TradeListener>();
|
||||
|
||||
this.makerNodeAddress = makerNodeAddress;
|
||||
this.takerNodeAddress = takerNodeAddress;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
|
||||
setTradeAmount(tradeAmount);
|
||||
}
|
||||
|
||||
|
||||
@ -525,28 +511,28 @@ public abstract class Trade implements Tradable, Model {
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress,
|
||||
@Nullable NodeAddress mediatorNodeAddress, // TODO (woodser): remove mediator, refund agent from trade
|
||||
@Nullable NodeAddress refundAgentNodeAddress,
|
||||
XmrWalletService xmrWalletService,
|
||||
ProcessModel processModel,
|
||||
String uid) {
|
||||
String uid,
|
||||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
|
||||
this(offer,
|
||||
txFee,
|
||||
tradeAmount,
|
||||
takerFee,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
tradePrice,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
this.tradePrice = tradePrice;
|
||||
setTradeAmount(tradeAmount);
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress);
|
||||
}
|
||||
|
||||
// TODO: remove these constructors
|
||||
// arbitrator
|
||||
@SuppressWarnings("NullableProblems")
|
||||
protected Trade(Offer offer,
|
||||
@ -562,16 +548,16 @@ public abstract class Trade implements Tradable, Model {
|
||||
String uid) {
|
||||
|
||||
this(offer,
|
||||
txFee,
|
||||
tradeAmount,
|
||||
takerFee,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress,
|
||||
tradePrice,
|
||||
xmrWalletService,
|
||||
processModel,
|
||||
uid);
|
||||
uid,
|
||||
makerNodeAddress,
|
||||
takerNodeAddress,
|
||||
arbitratorNodeAddress);
|
||||
|
||||
this.tradePrice = tradePrice;
|
||||
setTradeAmount(tradeAmount);
|
||||
}
|
||||
|
||||
@ -599,22 +585,16 @@ public abstract class Trade implements Tradable, Model {
|
||||
.setUid(uid);
|
||||
|
||||
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
|
||||
Optional.ofNullable(takerDepositTxId).ifPresent(builder::setTakerDepositTxId);
|
||||
Optional.ofNullable(makerDepositTxId).ifPresent(builder::setMakerDepositTxId);
|
||||
Optional.ofNullable(payoutTxId).ifPresent(builder::setPayoutTxId);
|
||||
Optional.ofNullable(contract).ifPresent(e -> builder.setContract(contract.toProtoMessage()));
|
||||
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
|
||||
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash)));
|
||||
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
|
||||
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(mediatorNodeAddress).ifPresent(e -> builder.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
|
||||
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
|
||||
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
|
||||
Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(mediatorPubKeyRing).ifPresent(e -> builder.setMediatorPubKeyRing(mediatorPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentPubKeyRing).ifPresent(e -> builder.setRefundAgentPubKeyRing(refundAgentPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId));
|
||||
Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState)));
|
||||
@ -625,7 +605,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
Optional.ofNullable(makerNodeAddress).ifPresent(e -> builder.setMakerNodeAddress(makerNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(makerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(makerPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(takerNodeAddress).ifPresent(e -> builder.setTakerNodeAddress(takerNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setMakerPubKeyRing(takerPubKeyRing.toProtoMessage()));
|
||||
Optional.ofNullable(takerPubKeyRing).ifPresent(e -> builder.setTakerPubKeyRing(takerPubKeyRing.toProtoMessage()));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ -635,22 +615,16 @@ public abstract class Trade implements Tradable, Model {
|
||||
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
|
||||
trade.setTradePeriodState(TradePeriodState.fromProto(proto.getTradePeriodState()));
|
||||
trade.setTakerFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerFeeTxId()));
|
||||
trade.setMakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getMakerDepositTxId()));
|
||||
trade.setTakerDepositTxId(ProtoUtil.stringOrNullFromProto(proto.getTakerDepositTxId()));
|
||||
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
|
||||
trade.setContract(proto.hasContract() ? Contract.fromProto(proto.getContract(), coreProtoResolver) : null);
|
||||
trade.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
|
||||
trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()));
|
||||
trade.setTakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()));
|
||||
trade.setMakerContractSignature(ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()));
|
||||
trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
|
||||
trade.setMediatorNodeAddress(proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null);
|
||||
trade.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
|
||||
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
|
||||
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
|
||||
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
||||
trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null);
|
||||
trade.setMediatorPubKeyRing(proto.hasMediatorPubKeyRing() ? PubKeyRing.fromProto(proto.getMediatorPubKeyRing()) : null);
|
||||
trade.setRefundAgentPubKeyRing(proto.hasRefundAgentPubKeyRing() ? PubKeyRing.fromProto(proto.getRefundAgentPubKeyRing()) : null);
|
||||
trade.setCounterCurrencyTxId(proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId());
|
||||
trade.setMediationResultState(MediationResultState.fromProto(proto.getMediationResultState()));
|
||||
@ -688,12 +662,6 @@ public abstract class Trade implements Tradable, Model {
|
||||
arbitratorPubKeyRing = arbitrator.getPubKeyRing();
|
||||
});
|
||||
|
||||
serviceProvider.getMediatorManager().getDisputeAgentByNodeAddress(mediatorNodeAddress)
|
||||
.ifPresent(mediator -> mediatorPubKeyRing = mediator.getPubKeyRing());
|
||||
|
||||
serviceProvider.getRefundAgentManager().getDisputeAgentByNodeAddress(refundAgentNodeAddress)
|
||||
.ifPresent(refundAgent -> refundAgentPubKeyRing = refundAgent.getPubKeyRing());
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
@ -732,42 +700,86 @@ public abstract class Trade implements Tradable, Model {
|
||||
void updateDepositTxFromWallet() {
|
||||
if (getMakerDepositTx() != null && getTakerDepositTx() != null) {
|
||||
System.out.println(processModel.getProvider().getXmrWalletService());
|
||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(getId());
|
||||
applyDepositTxs(multisigWallet.getTx(getMakerDepositTxId()), multisigWallet.getTx(getTakerDepositTxId()));
|
||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(getId());
|
||||
applyDepositTxs(multisigWallet.getTx(getMakerDepositTx().getHash()), multisigWallet.getTx(getTakerDepositTx().getHash()));
|
||||
}
|
||||
}
|
||||
|
||||
public void applyDepositTxs(MoneroTxWallet makerDepositTx, MoneroTxWallet takerDepositTx) {
|
||||
this.makerDepositTx = makerDepositTx;
|
||||
this.takerDepositTx = takerDepositTx;
|
||||
makerDepositTxId = makerDepositTx.getHash();
|
||||
takerDepositTxId = takerDepositTx.getHash();
|
||||
//setupConfirmationListener(); // TODO (woodser): listening disabled here, using SetupDepositTxsListener in buyer and seller
|
||||
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
|
||||
setConfirmedState(); // TODO (woodser): bisq "confirmed" = xmr unlocked after 10 confirmations
|
||||
}
|
||||
}
|
||||
|
||||
public void setupDepositTxsListener() {
|
||||
|
||||
// ignore if already listening
|
||||
if (depositTxListener != null) {
|
||||
log.warn("Trade {} already listening for deposit txs", getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// create listener for deposit transactions
|
||||
MoneroWallet multisigWallet = processModel.getXmrWalletService().getMultisigWallet(getId());
|
||||
depositTxListener = processModel.getXmrWalletService().new HavenoWalletListener(new MoneroWalletListener() { // TODO (woodser): separate into own class file
|
||||
@Override
|
||||
public void onOutputReceived(MoneroOutputWallet output) {
|
||||
|
||||
// ignore if no longer listening
|
||||
if (depositTxListener == null) return;
|
||||
|
||||
// TODO (woodser): remove this
|
||||
if (output.getTx().isConfirmed() && (processModel.getMaker().getDepositTxHash().equals(output.getTx().getHash()) || processModel.getTaker().getDepositTxHash().equals(output.getTx().getHash()))) {
|
||||
System.out.println("Deposit output for tx " + output.getTx().getHash() + " is confirmed at height " + output.getTx().getHeight());
|
||||
}
|
||||
|
||||
// update locked state
|
||||
if (output.getTx().getHash().equals(processModel.getMaker().getDepositTxHash())) makerDepositLocked = output.getTx().isLocked();
|
||||
else if (output.getTx().getHash().equals(processModel.getTaker().getDepositTxHash())) takerDepositLocked = output.getTx().isLocked();
|
||||
|
||||
// deposit txs seen when both locked states seen
|
||||
if (makerDepositLocked != null && takerDepositLocked != null) {
|
||||
setState(this instanceof MakerTrade ? Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK : Trade.State.TAKER_SAW_DEPOSIT_TX_IN_NETWORK);
|
||||
}
|
||||
|
||||
// confirm trade and update ui when both deposits unlock
|
||||
if (Boolean.FALSE.equals(makerDepositLocked) && Boolean.FALSE.equals(takerDepositLocked)) {
|
||||
System.out.println("Multisig deposit txs unlocked!");
|
||||
applyDepositTxs(multisigWallet.getTx(processModel.getMaker().getDepositTxHash()), multisigWallet.getTx(processModel.getTaker().getDepositTxHash()));
|
||||
multisigWallet.removeListener(depositTxListener); // remove listener when notified
|
||||
depositTxListener = null; // prevent re-applying trade state in subsequent requests
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// register wallet listener
|
||||
multisigWallet.addListener(depositTxListener);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MoneroTxWallet getTakerDepositTx() {
|
||||
try {
|
||||
if (takerDepositTx == null) takerDepositTx = takerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(takerDepositTxId) : null;
|
||||
return takerDepositTx;
|
||||
} catch (MoneroError e) {
|
||||
log.error("Wallet is missing taker deposit tx " + takerDepositTxId);
|
||||
return null;
|
||||
}
|
||||
String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
|
||||
try {
|
||||
if (takerDepositTx == null) takerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash);
|
||||
return takerDepositTx;
|
||||
} catch (MoneroError e) {
|
||||
log.error("Wallet is missing taker deposit tx " + depositTxHash);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MoneroTxWallet getMakerDepositTx() {
|
||||
try {
|
||||
if (makerDepositTx == null) makerDepositTx = makerDepositTxId != null ? xmrWalletService.getOrCreateMultisigWallet(getId()).getTx(makerDepositTxId) : null;
|
||||
return makerDepositTx;
|
||||
} catch (MoneroError e) {
|
||||
log.error("Wallet is missing maker deposit tx " + makerDepositTxId);
|
||||
return null;
|
||||
}
|
||||
String depositTxHash = getProcessModel().getMaker().getDepositTxHash();
|
||||
try {
|
||||
if (makerDepositTx == null) makerDepositTx = depositTxHash == null ? null : xmrWalletService.getMultisigWallet(getId()).getTx(depositTxHash);
|
||||
return makerDepositTx;
|
||||
} catch (MoneroError e) {
|
||||
log.error("Wallet is missing maker deposit tx " + depositTxHash);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
|
||||
@ -845,20 +857,27 @@ public abstract class Trade implements Tradable, Model {
|
||||
// Listeners
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addTradeMessageListener(TradeMessageListener listener) {
|
||||
tradeMessageListeners.add(listener);
|
||||
public void addListener(TradeListener listener) {
|
||||
tradeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeTradeMessageListener(TradeMessageListener listener) {
|
||||
if (!tradeMessageListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
|
||||
public void removeListener(TradeListener listener) {
|
||||
if (!tradeListeners.remove(listener)) throw new RuntimeException("TradeMessageListener is not registered");
|
||||
}
|
||||
|
||||
// notified from TradeProtocol of verified messages
|
||||
// notified from TradeProtocol of verified trade messages
|
||||
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) {
|
||||
for (TradeMessageListener listener : new ArrayList<TradeMessageListener>(tradeMessageListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
|
||||
for (TradeListener listener : new ArrayList<TradeListener>(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
|
||||
listener.onVerifiedTradeMessage(message, sender);
|
||||
}
|
||||
}
|
||||
|
||||
// notified from TradeProtocol of ack messages
|
||||
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
for (TradeListener listener : new ArrayList<TradeListener>(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
|
||||
listener.onAckMessage(ackMessage, sender);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Setters
|
||||
@ -879,7 +898,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state);
|
||||
}
|
||||
if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) {
|
||||
String message = "We got a state change to a previous phase.\n" +
|
||||
String message = "We got a state change to a previous phase (id=" + getShortId() + ").\n" +
|
||||
"Old state is: " + this.state + ". New state is: " + state;
|
||||
log.warn(message);
|
||||
}
|
||||
@ -935,6 +954,56 @@ public abstract class Trade implements Tradable, Model {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getter
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public TradingPeer getSelf() {
|
||||
if (this instanceof MakerTrade) return processModel.getMaker();
|
||||
if (this instanceof TakerTrade) return processModel.getTaker();
|
||||
if (this instanceof ArbitratorTrade) return processModel.getArbitrator();
|
||||
throw new RuntimeException("Trade is not maker, taker, or arbitrator");
|
||||
}
|
||||
|
||||
public TradingPeer getMaker() {
|
||||
return processModel.getMaker();
|
||||
}
|
||||
|
||||
public TradingPeer getTaker() {
|
||||
return processModel.getTaker();
|
||||
}
|
||||
|
||||
public TradingPeer getBuyer() {
|
||||
return offer.getDirection() == Direction.BUY ? processModel.getMaker() : processModel.getTaker();
|
||||
}
|
||||
|
||||
public TradingPeer getSeller() {
|
||||
return offer.getDirection() == Direction.BUY ? processModel.getTaker() : processModel.getMaker();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the taker if maker, maker if taker, null if arbitrator.
|
||||
*
|
||||
* @return the trade peer
|
||||
*/
|
||||
public TradingPeer getTradingPeer() {
|
||||
if (this instanceof MakerTrade) return processModel.getTaker();
|
||||
else if (this instanceof TakerTrade) return processModel.getMaker();
|
||||
else if (this instanceof ArbitratorTrade) return null;
|
||||
else throw new RuntimeException("Unknown trade type: " + getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer with the given address which can be self.
|
||||
*
|
||||
* TODO (woodser): this naming convention is confusing
|
||||
*
|
||||
* @param address is the address of the peer to get
|
||||
* @return the trade peer
|
||||
*/
|
||||
public TradingPeer getTradingPeer(NodeAddress address) {
|
||||
if (address.equals(getMakerNodeAddress())) return processModel.getMaker();
|
||||
if (address.equals(getTakerNodeAddress())) return processModel.getTaker();
|
||||
if (address.equals(getArbitratorNodeAddress())) return processModel.getArbitrator();
|
||||
throw new RuntimeException("No protocol participant has node address: " + address);
|
||||
}
|
||||
|
||||
public Date getTakeOfferDate() {
|
||||
return new Date(takeOfferDate);
|
||||
@ -985,7 +1054,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
if (!makerDepositTx.isLocked() && !takerDepositTx.isLocked()) {
|
||||
final long tradeTime = getTakeOfferDate().getTime();
|
||||
long maxHeight = Math.max(makerDepositTx.getHeight(), takerDepositTx.getHeight());
|
||||
MoneroDaemon daemonRpc = new MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123"); // TODO (woodser): move to common config
|
||||
MoneroDaemon daemonRpc = xmrWalletService.getDaemon();
|
||||
long blockTime = daemonRpc.getBlockByHeight(maxHeight).getTimestamp();
|
||||
|
||||
// if (depositTx.getConfidence().getDepthInBlocks() > 0) {
|
||||
@ -1158,8 +1227,8 @@ public abstract class Trade implements Tradable, Model {
|
||||
public boolean isTxChainInvalid() {
|
||||
return offer.getOfferFeePaymentTxId() == null ||
|
||||
getTakerFeeTxId() == null ||
|
||||
getMakerDepositTxId() == null ||
|
||||
getTakerDepositTxId() == null ||
|
||||
processModel.getMaker().getDepositTxHash() == null ||
|
||||
processModel.getMaker().getDepositTxHash() == null ||
|
||||
getDelayedPayoutTxBytes() == null;
|
||||
}
|
||||
|
||||
@ -1241,7 +1310,6 @@ public abstract class Trade implements Tradable, Model {
|
||||
",\n takeOfferDate=" + takeOfferDate +
|
||||
",\n processModel=" + processModel +
|
||||
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
|
||||
",\n takerDepositTxId='" + takerDepositTxId + '\'' +
|
||||
",\n payoutTxId='" + payoutTxId + '\'' +
|
||||
",\n tradeAmountAsLong=" + tradeAmountAsLong +
|
||||
",\n tradePrice=" + tradePrice +
|
||||
@ -1251,11 +1319,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
",\n contract=" + contract +
|
||||
",\n contractAsJson='" + contractAsJson + '\'' +
|
||||
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
||||
",\n takerContractSignature='" + takerContractSignature + '\'' +
|
||||
",\n makerContractSignature='" + makerContractSignature + '\'' +
|
||||
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
|
||||
",\n mediatorNodeAddress=" + mediatorNodeAddress +
|
||||
",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
|
||||
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
|
||||
",\n errorMessage='" + errorMessage + '\'' +
|
||||
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
|
||||
|
@ -36,7 +36,7 @@ import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutPoint;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -54,6 +54,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class TradeDataValidation {
|
||||
|
||||
public static void validatePaymentAccountPayloads(Dispute dispute) throws InvalidPaymentAccountPayloadException {
|
||||
if (!Arrays.equals(dispute.getMakerPaymentAccountPayload().getHash(), dispute.getContract().getMakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of maker's payment account payload does not match contract");
|
||||
if (!Arrays.equals(dispute.getTakerPaymentAccountPayload().getHash(), dispute.getContract().getTakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of taker's payment account payload does not match contract");
|
||||
}
|
||||
|
||||
public static void validateDonationAddress(String addressAsString, DaoFacade daoFacade)
|
||||
throws AddressException {
|
||||
@ -396,6 +401,12 @@ public class TradeDataValidation {
|
||||
this.dispute = dispute;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidPaymentAccountPayloadException extends ValidationException {
|
||||
InvalidPaymentAccountPayloadException(@Nullable Dispute dispute, String msg) {
|
||||
super(dispute, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AddressException extends ValidationException {
|
||||
AddressException(@Nullable Dispute dispute, String msg) {
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.core.btc.exceptions.AddressEntryException;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
@ -25,21 +24,27 @@ import bisq.core.locale.Res;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferBookService;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.offer.SignedOffer;
|
||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.failed.FailedTradesManager;
|
||||
import bisq.core.trade.handlers.TradeResultHandler;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.messages.DepositRequest;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxRequest;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
||||
import bisq.core.trade.protocol.ArbitratorProtocol;
|
||||
import bisq.core.trade.protocol.MakerProtocol;
|
||||
@ -48,11 +53,12 @@ import bisq.core.trade.protocol.ProcessModelServiceProvider;
|
||||
import bisq.core.trade.protocol.TakerProtocol;
|
||||
import bisq.core.trade.protocol.TradeProtocol;
|
||||
import bisq.core.trade.protocol.TradeProtocolFactory;
|
||||
import bisq.core.trade.protocol.TraderProtocol;
|
||||
import bisq.core.trade.statistics.ReferralIdService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.core.util.coin.CoinUtil;
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.DecryptedDirectMessageListener;
|
||||
import bisq.network.p2p.DecryptedMessageWithPubKey;
|
||||
@ -70,15 +76,11 @@ import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
@ -106,8 +108,6 @@ import org.slf4j.LoggerFactory;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
@ -132,6 +132,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
private final P2PService p2PService;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final OfferUtil offerUtil;
|
||||
private final TradeUtil tradeUtil;
|
||||
@Getter
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
@ -171,6 +172,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
P2PService p2PService,
|
||||
PriceFeedService priceFeedService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
OfferUtil offerUtil,
|
||||
TradeUtil tradeUtil,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
@ -191,6 +193,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
this.p2PService = p2PService;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.offerUtil = offerUtil;
|
||||
this.tradeUtil = tradeUtil;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
@ -238,15 +241,19 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
//handleTakeOfferRequest(peer, (InputsForDepositTxRequest) networkEnvelope); // ignore bisq requests
|
||||
} else if (networkEnvelope instanceof InitTradeRequest) {
|
||||
handleInitTradeRequest((InitTradeRequest) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof InitMultisigMessage) {
|
||||
handleMultisigMessage((InitMultisigMessage) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof MakerReadyToFundMultisigRequest) {
|
||||
handleMakerReadyToFundMultisigRequest((MakerReadyToFundMultisigRequest) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof MakerReadyToFundMultisigResponse) {
|
||||
handleMakerReadyToFundMultisigResponse((MakerReadyToFundMultisigResponse) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof DepositTxMessage) {
|
||||
handleDepositTxMessage((DepositTxMessage) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
|
||||
} else if (networkEnvelope instanceof InitMultisigRequest) {
|
||||
handleInitMultisigRequest((InitMultisigRequest) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof SignContractRequest) {
|
||||
handleSignContractRequest((SignContractRequest) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof SignContractResponse) {
|
||||
handleSignContractResponse((SignContractResponse) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof DepositRequest) {
|
||||
handleDepositRequest((DepositRequest) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof DepositResponse) {
|
||||
handleDepositResponse((DepositResponse) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof PaymentAccountPayloadRequest) {
|
||||
handlePaymentAccountPayloadRequest((PaymentAccountPayloadRequest) networkEnvelope, peer);
|
||||
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
|
||||
handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
|
||||
}
|
||||
}
|
||||
@ -327,65 +334,96 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
persistenceManager.requestPersistence();
|
||||
}
|
||||
|
||||
private void handleInitTradeRequest(InitTradeRequest initTradeRequest, NodeAddress peer) {
|
||||
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", peer, initTradeRequest.getTradeId(), initTradeRequest.getUid());
|
||||
private void handleInitTradeRequest(InitTradeRequest request, NodeAddress sender) {
|
||||
log.info("Received InitTradeRequest from {} with tradeId {} and uid {}", sender, request.getTradeId(), request.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(initTradeRequest.getTradeId());
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid InitTradeRequest message " + initTradeRequest.toString());
|
||||
log.warn("Invalid InitTradeRequest message " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("RECEIVED INIT REQUEST INFO");
|
||||
System.out.println("Sender peer node address: " + initTradeRequest.getSenderNodeAddress());
|
||||
System.out.println("Maker node address: " + initTradeRequest.getMakerNodeAddress());
|
||||
System.out.println("Taker node adddress: " + initTradeRequest.getTakerNodeAddress());
|
||||
System.out.println("Arbitrator node address: " + initTradeRequest.getArbitratorNodeAddress());
|
||||
|
||||
// handle request as arbitrator
|
||||
boolean isArbitrator = initTradeRequest.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
|
||||
boolean isArbitrator = request.getArbitratorNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress());
|
||||
if (isArbitrator) {
|
||||
|
||||
// verify this node is registered arbitrator
|
||||
Mediator thisArbitrator = user.getRegisteredMediator();
|
||||
NodeAddress thisAddress = p2PService.getNetworkNode().getNodeAddress();
|
||||
if (thisArbitrator == null || !thisArbitrator.getNodeAddress().equals(thisAddress)) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because we are not an arbitrator", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
|
||||
// get offer associated with trade
|
||||
Offer offer = null;
|
||||
for (Offer anOffer : offerBookService.getOffers()) {
|
||||
if (anOffer.getId().equals(initTradeRequest.getTradeId())) {
|
||||
offer = anOffer;
|
||||
}
|
||||
if (anOffer.getId().equals(request.getTradeId())) {
|
||||
offer = anOffer;
|
||||
}
|
||||
}
|
||||
if (offer == null) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because no offer is on the books", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
|
||||
// verify arbitrator is payload signer unless they are offline
|
||||
// TODO (woodser): handle if payload signer differs from current arbitrator (verify signer is offline)
|
||||
|
||||
// verify maker is offer owner
|
||||
// TODO (woodser): maker address might change if they disconnect and reconnect, should allow maker address to differ if pubKeyRing is same ?
|
||||
if (!offer.getOwnerNodeAddress().equals(request.getMakerNodeAddress())) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because maker is not offer owner", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
if (offer == null) throw new RuntimeException("No offer on the books with trade id: " + initTradeRequest.getTradeId()); // TODO (woodser): proper error handling
|
||||
|
||||
Trade trade;
|
||||
Optional<Trade> tradeOptional = getTradeById(offer.getId());
|
||||
if (!tradeOptional.isPresent()) {
|
||||
trade = new ArbitratorTrade(offer,
|
||||
Coin.valueOf(initTradeRequest.getTradeAmount()),
|
||||
Coin.valueOf(initTradeRequest.getTxFee()),
|
||||
Coin.valueOf(initTradeRequest.getTradeFee()),
|
||||
initTradeRequest.getTradePrice(),
|
||||
initTradeRequest.getMakerNodeAddress(),
|
||||
initTradeRequest.getTakerNodeAddress(),
|
||||
initTradeRequest.getArbitratorNodeAddress(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString());
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
tradableList.add(trade);
|
||||
if (tradeOptional.isPresent()) {
|
||||
trade = tradeOptional.get();
|
||||
|
||||
// verify request is from maker
|
||||
if (!sender.equals(request.getMakerNodeAddress())) {
|
||||
log.warn("Trade is already taken"); // TODO (woodser): need to respond with bad ack
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
trade = tradeOptional.get();
|
||||
|
||||
// verify request is from taker
|
||||
if (!sender.equals(request.getTakerNodeAddress())) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from taker when trade is not initialized", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
|
||||
// compute expected taker fee
|
||||
Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(true), Coin.valueOf(offer.getOfferPayload().getAmount()));
|
||||
Coin takerFee = CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee(true));
|
||||
|
||||
// create arbitrator trade
|
||||
trade = new ArbitratorTrade(offer,
|
||||
Coin.valueOf(offer.getOfferPayload().getAmount()),
|
||||
takerFee,
|
||||
offer.getOfferPayload().getPrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString(),
|
||||
request.getMakerNodeAddress(),
|
||||
request.getTakerNodeAddress(),
|
||||
request.getArbitratorNodeAddress());
|
||||
|
||||
// set reserve tx hash
|
||||
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId());
|
||||
if (!signedOfferOptional.isPresent()) return;
|
||||
SignedOffer signedOffer = signedOfferOptional.get();
|
||||
trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash());
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
tradableList.add(trade);
|
||||
}
|
||||
|
||||
// TODO (woodser): do this for arbitrator?
|
||||
// TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||
// TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
|
||||
// if (prev != null) {
|
||||
// log.error("We had already an entry with uid {}", trade.getUid());
|
||||
// }
|
||||
|
||||
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
|
||||
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler?
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? // TODO (woodser): ensure failed trade removed
|
||||
});
|
||||
|
||||
requestPersistence();
|
||||
@ -394,7 +432,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
// handle request as maker
|
||||
else {
|
||||
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(initTradeRequest.getTradeId());
|
||||
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(request.getTradeId());
|
||||
if (!openOfferOptional.isPresent()) {
|
||||
return;
|
||||
}
|
||||
@ -406,102 +444,181 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
|
||||
Offer offer = openOffer.getOffer();
|
||||
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator?
|
||||
|
||||
// verify request is from signing arbitrator when they're online, else from selected arbitrator
|
||||
if (!sender.equals(offer.getOfferPayload().getArbitratorNodeAddress())) {
|
||||
boolean isSignerOnline = true; // TODO (woodser): determine if signer is online and test
|
||||
if (isSignerOnline) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signing arbitrator when online", sender, request.getTradeId());
|
||||
return;
|
||||
} else if (!sender.equals(openOffer.getArbitratorNodeAddress())) {
|
||||
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from selected arbitrator when signing arbitrator is offline", sender, request.getTradeId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Trade trade;
|
||||
if (offer.isBuyOffer())
|
||||
trade = new BuyerAsMakerTrade(offer,
|
||||
Coin.valueOf(initTradeRequest.getTxFee()),
|
||||
Coin.valueOf(initTradeRequest.getTradeFee()),
|
||||
initTradeRequest.getMakerNodeAddress(),
|
||||
initTradeRequest.getTakerNodeAddress(),
|
||||
initTradeRequest.getArbitratorNodeAddress(),
|
||||
Coin.valueOf(offer.getOfferPayload().getAmount()),
|
||||
Coin.valueOf(offer.getOfferPayload().getMakerFee()), // TODO (woodser): this is maker fee, but Trade calls it taker fee, why not have both?
|
||||
offer.getOfferPayload().getPrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString());
|
||||
UUID.randomUUID().toString(),
|
||||
request.getMakerNodeAddress(),
|
||||
request.getTakerNodeAddress(),
|
||||
request.getArbitratorNodeAddress());
|
||||
else
|
||||
trade = new SellerAsMakerTrade(offer,
|
||||
Coin.valueOf(initTradeRequest.getTxFee()),
|
||||
Coin.valueOf(initTradeRequest.getTradeFee()),
|
||||
initTradeRequest.getMakerNodeAddress(),
|
||||
initTradeRequest.getTakerNodeAddress(),
|
||||
openOffer.getArbitratorNodeAddress(),
|
||||
Coin.valueOf(offer.getOfferPayload().getAmount()),
|
||||
Coin.valueOf(offer.getOfferPayload().getMakerFee()),
|
||||
offer.getOfferPayload().getPrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString());
|
||||
|
||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||
TradeProtocol prev = tradeProtocolByTradeId.put(trade.getUid(), tradeProtocol);
|
||||
if (prev != null) {
|
||||
log.error("We had already an entry with uid {}", trade.getUid());
|
||||
}
|
||||
UUID.randomUUID().toString(),
|
||||
request.getMakerNodeAddress(),
|
||||
request.getTakerNodeAddress(),
|
||||
request.getArbitratorNodeAddress());
|
||||
|
||||
//System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender);
|
||||
//trade.setTradingPeerNodeAddress(sender);
|
||||
// TODO (woodser): what if maker's address changes while offer open, or taker's address changes after multisig deposit available? need to verify and update
|
||||
trade.setArbitratorPubKeyRing(user.getAcceptedMediatorByAddress(sender).getPubKeyRing());
|
||||
trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing());
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
trade.getProcessModel().setReserveTxHash(offer.getOfferFeePaymentTxId()); // TODO (woodser): initialize in initTradeAndProtocol ?
|
||||
trade.getProcessModel().setFrozenKeyImages(openOffer.getFrozenKeyImages());
|
||||
tradableList.add(trade);
|
||||
initTradeAndProtocol(trade, tradeProtocol);
|
||||
|
||||
((MakerProtocol) tradeProtocol).handleInitTradeRequest(initTradeRequest, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
|
||||
requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest request, NodeAddress peer) {
|
||||
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer) {
|
||||
log.info("Received InitMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid InitTradeRequest message " + request.toString());
|
||||
log.warn("Invalid InitMultisigRequest " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
((MakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigRequest(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
getTradeProtocol(trade).handleInitMultisigRequest(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) {
|
||||
log.info("Received SignContractRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
|
||||
private void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer) {
|
||||
log.info("Received MakerReadyToFundMultisigResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid SignContractRequest message " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(response.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid InitTradeRequest message " + response.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
((TakerProtocol) getTradeProtocol(trade)).handleMakerReadyToFundMultisigResponse(response, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
getTradeProtocol(trade).handleSignContractRequest(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) {
|
||||
log.info("Received SignContractResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
|
||||
private void handleMultisigMessage(InitMultisigMessage multisigMessage, NodeAddress peer) {
|
||||
log.info("Received InitMultisigMessage from {} with tradeId {} and uid {}", peer, multisigMessage.getTradeId(), multisigMessage.getUid());
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid SignContractResponse message " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(multisigMessage.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid InitMultisigMessage message " + multisigMessage.toString());
|
||||
return;
|
||||
}
|
||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
((TraderProtocol) getTradeProtocol(trade)).handleSignContractResponse(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleDepositRequest(DepositRequest request, NodeAddress peer) {
|
||||
log.info("Received DepositRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(multisigMessage.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + multisigMessage.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
getTradeProtocol(trade).handleMultisigMessage(multisigMessage, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid DepositRequest message " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
((ArbitratorProtocol) getTradeProtocol(trade)).handleDepositRequest(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleDepositResponse(DepositResponse response, NodeAddress peer) {
|
||||
log.info("Received DepositResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(response.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid DepositResponse message " + response.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) {
|
||||
log.info("Received PaymentAccountPayloadRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid PaymentAccountPayloadRequest message " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null) {
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) {
|
||||
@ -523,41 +640,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
});
|
||||
}
|
||||
|
||||
private void handleDepositTxMessage(DepositTxMessage request, NodeAddress peer) {
|
||||
log.info("Received DepositTxMessage from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
||||
|
||||
try {
|
||||
Validator.nonEmptyStringOf(request.getTradeId());
|
||||
} catch (Throwable t) {
|
||||
log.warn("Invalid InitTradeRequest message " + request.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
|
||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
||||
Trade trade = tradeOptional.get();
|
||||
getTradeProtocol(trade).handleDepositTxMessage(request, peer, errorMessage -> {
|
||||
if (takeOfferRequestErrorMessageHandler != null)
|
||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Take offer
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void checkOfferAvailability(Offer offer,
|
||||
boolean isTakerApiUser,
|
||||
String paymentAccountId,
|
||||
ResultHandler resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser), resultHandler, errorMessageHandler);
|
||||
offer.checkOfferAvailability(getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId), resultHandler, errorMessageHandler);
|
||||
}
|
||||
|
||||
// First we check if offer is still available then we create the trade with the protocol
|
||||
public void onTakeOffer(Coin amount,
|
||||
Coin txFee,
|
||||
Coin takerFee,
|
||||
long tradePrice,
|
||||
Coin fundsNeededForTrade,
|
||||
Offer offer,
|
||||
String paymentAccountId,
|
||||
@ -567,8 +665,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
||||
checkArgument(!wasOfferAlreadyUsedInTrade(offer.getId()));
|
||||
|
||||
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser);
|
||||
|
||||
OfferAvailabilityModel model = getOfferAvailabilityModel(offer, isTakerApiUser, paymentAccountId);
|
||||
offer.checkOfferAvailability(model,
|
||||
() -> {
|
||||
if (offer.getState() == Offer.State.AVAILABLE) {
|
||||
@ -576,30 +674,33 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
if (offer.isBuyOffer()) {
|
||||
trade = new SellerAsTakerTrade(offer,
|
||||
amount,
|
||||
txFee,
|
||||
takerFee,
|
||||
tradePrice,
|
||||
model.getPeerNodeAddress(),
|
||||
P2PService.getMyNodeAddress(), // TODO (woodser): correct taker node address?
|
||||
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
|
||||
model.getTradeRequest().getTradePrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString());
|
||||
UUID.randomUUID().toString(),
|
||||
model.getPeerNodeAddress(),
|
||||
P2PService.getMyNodeAddress(),
|
||||
offer.getOfferPayload().getArbitratorNodeAddress());
|
||||
} else {
|
||||
trade = new BuyerAsTakerTrade(offer,
|
||||
amount,
|
||||
txFee,
|
||||
takerFee,
|
||||
tradePrice,
|
||||
model.getPeerNodeAddress(),
|
||||
P2PService.getMyNodeAddress(),
|
||||
model.getSelectedMediator(), // TODO (woodser): using mediator as arbitrator which is assigned upfront
|
||||
model.getTradeRequest().getTradePrice(),
|
||||
xmrWalletService,
|
||||
getNewProcessModel(offer),
|
||||
UUID.randomUUID().toString());
|
||||
UUID.randomUUID().toString(),
|
||||
model.getPeerNodeAddress(),
|
||||
P2PService.getMyNodeAddress(),
|
||||
offer.getOfferPayload().getArbitratorNodeAddress());
|
||||
}
|
||||
|
||||
trade.getProcessModel().setTradeMessage(model.getTradeRequest());
|
||||
trade.getProcessModel().setMakerSignature(model.getMakerSignature());
|
||||
trade.getProcessModel().setArbitratorNodeAddress(model.getArbitratorNodeAddress());
|
||||
trade.getProcessModel().setUseSavingsWallet(useSavingsWallet);
|
||||
trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value);
|
||||
trade.setTakerPubKeyRing(model.getPubKeyRing());
|
||||
trade.setTakerPaymentAccountId(paymentAccountId);
|
||||
|
||||
TradeProtocol tradeProtocol = TradeProtocolFactory.getNewTradeProtocol(trade);
|
||||
@ -627,15 +728,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
processModelServiceProvider.getKeyRing().getPubKeyRing());
|
||||
}
|
||||
|
||||
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser) {
|
||||
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer, boolean isTakerApiUser, String paymentAccountId) {
|
||||
return new OfferAvailabilityModel(
|
||||
offer,
|
||||
keyRing.getPubKeyRing(),
|
||||
xmrWalletService,
|
||||
p2PService,
|
||||
user,
|
||||
mediatorManager,
|
||||
tradeStatisticsManager,
|
||||
isTakerApiUser);
|
||||
isTakerApiUser,
|
||||
paymentAccountId,
|
||||
offerUtil);
|
||||
}
|
||||
|
||||
|
||||
@ -643,6 +747,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
// Complete trade
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): remove this function
|
||||
public void onWithdrawRequest(String toAddress,
|
||||
Coin amount,
|
||||
Coin fee,
|
||||
@ -651,35 +756,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||
@Nullable String memo,
|
||||
ResultHandler resultHandler,
|
||||
FaultHandler faultHandler) {
|
||||
int fromAccountIdx = xmrWalletService.getOrCreateAddressEntry(trade.getId(),
|
||||
XmrAddressEntry.Context.TRADE_PAYOUT).getAccountIndex();
|
||||
FutureCallback<MoneroTxWallet> callback = new FutureCallback<MoneroTxWallet>() {
|
||||
@Override
|
||||
public void onSuccess(@javax.annotation.Nullable MoneroTxWallet transaction) {
|
||||
if (transaction != null) {
|
||||
log.debug("onWithdraw onSuccess tx ID:" + transaction.getHash());
|
||||
onTradeCompleted(trade);
|
||||
trade.setState(Trade.State.WITHDRAW_COMPLETED);
|
||||
getTradeProtocol(trade).onWithdrawCompleted();
|
||||
requestPersistence();
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
t.printStackTrace();
|
||||
log.error(t.getMessage());
|
||||
faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
|
||||
}
|
||||
};
|
||||
try {
|
||||
xmrWalletService.sendFunds(fromAccountIdx, toAddress, amount, XmrAddressEntry.Context.TRADE_PAYOUT, callback);
|
||||
} catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
|
||||
}
|
||||
throw new RuntimeException("Withdraw trade funds after payout to Haveno wallet not supported");
|
||||
}
|
||||
|
||||
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
|
||||
|
@ -17,14 +17,254 @@
|
||||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferPayload.Direction;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import common.utils.JsonUtils;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Objects;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroSubmitTxResult;
|
||||
import monero.daemon.model.MoneroTx;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroCheckTx;
|
||||
import monero.wallet.model.MoneroTxConfig;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
/**
|
||||
* Collection of utilities for trading.
|
||||
*
|
||||
* TODO (woodser): combine with TradeUtil.java ?
|
||||
*/
|
||||
public class TradeUtils {
|
||||
|
||||
/**
|
||||
* Address to collect Haveno trade fees. TODO (woodser): move to config constants
|
||||
*/
|
||||
public static String FEE_ADDRESS = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM";
|
||||
|
||||
/**
|
||||
* Check if the arbitrator signature for an offer is valid.
|
||||
*
|
||||
* @param arbitrator is the possible original arbitrator
|
||||
* @param signedOfferPayload is a signed offer payload
|
||||
* @return true if the arbitrator's signature is valid for the offer
|
||||
*/
|
||||
public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Mediator arbitrator) {
|
||||
|
||||
// remove arbitrator signature from signed payload
|
||||
String signature = signedOfferPayload.getArbitratorSignature();
|
||||
signedOfferPayload.setArbitratorSignature(null);
|
||||
|
||||
// get unsigned offer payload as json string
|
||||
String unsignedOfferAsJson = Utilities.objectToJson(signedOfferPayload);
|
||||
|
||||
// verify arbitrator signature
|
||||
boolean isValid = true;
|
||||
try {
|
||||
isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), // TODO (woodser): assign isValid
|
||||
unsignedOfferAsJson,
|
||||
signature);
|
||||
} catch (Exception e) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// replace signature
|
||||
signedOfferPayload.setArbitratorSignature(signature);
|
||||
|
||||
// return result
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the maker signature for a trade request is valid.
|
||||
*
|
||||
* @param request is the trade request to check
|
||||
* @return true if the maker's signature is valid for the trade request
|
||||
*/
|
||||
public static boolean isMakerSignatureValid(InitTradeRequest request, String signature, PubKeyRing makerPubKeyRing) {
|
||||
|
||||
// re-create trade request with signed fields
|
||||
InitTradeRequest signedRequest = new InitTradeRequest(
|
||||
request.getTradeId(),
|
||||
request.getSenderNodeAddress(),
|
||||
request.getPubKeyRing(),
|
||||
request.getTradeAmount(),
|
||||
request.getTradePrice(),
|
||||
request.getTradeFee(),
|
||||
request.getAccountId(),
|
||||
request.getPaymentAccountId(),
|
||||
request.getPaymentMethodId(),
|
||||
request.getUid(),
|
||||
request.getMessageVersion(),
|
||||
request.getAccountAgeWitnessSignatureOfOfferId(),
|
||||
request.getCurrentDate(),
|
||||
request.getMakerNodeAddress(),
|
||||
request.getTakerNodeAddress(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
request.getPayoutAddress(),
|
||||
null
|
||||
);
|
||||
|
||||
// get trade request as string
|
||||
String tradeRequestAsJson = Utilities.objectToJson(signedRequest);
|
||||
|
||||
// verify maker signature
|
||||
try {
|
||||
return Sig.verify(makerPubKeyRing.getSignaturePubKey(),
|
||||
tradeRequestAsJson,
|
||||
signature);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction to reserve a trade. The deposit amount is returned
|
||||
* to the sender's payout address. Additional funds are reserved to allow
|
||||
* fluctuations in the mining fee.
|
||||
*
|
||||
* @param xmrWalletService
|
||||
* @param offerId
|
||||
* @param tradeFee
|
||||
* @param depositAmount
|
||||
* @return a transaction to reserve a trade
|
||||
*/
|
||||
public static MoneroTxWallet createReserveTx(XmrWalletService xmrWalletService, String offerId, BigInteger tradeFee, String returnAddress, BigInteger depositAmount) {
|
||||
|
||||
// get expected mining fee
|
||||
MoneroWallet wallet = xmrWalletService.getWallet();
|
||||
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(returnAddress, depositAmount));
|
||||
BigInteger miningFee = miningFeeTx.getFee();
|
||||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
|
||||
|
||||
return reserveTx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction to deposit funds to the multisig wallet.
|
||||
*
|
||||
* @param xmrWalletService
|
||||
* @param tradeFee
|
||||
* @param destinationAddress
|
||||
* @param depositAddress
|
||||
* @return MoneroTxWallet
|
||||
*/
|
||||
public static MoneroTxWallet createDepositTx(XmrWalletService xmrWalletService, BigInteger tradeFee, String depositAddress, BigInteger depositAmount) {
|
||||
return xmrWalletService.getWallet().createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(depositAddress, depositAmount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a reserve or deposit transaction used during trading.
|
||||
* Checks double spends, deposit amount and destination, trade fee, and mining fee.
|
||||
* The transaction is submitted but not relayed to the pool.
|
||||
*
|
||||
* @param daemon is the Monero daemon to check for double spends
|
||||
* @param wallet is the Monero wallet to verify the tx
|
||||
* @param depositAddress is the expected destination address for the deposit amount
|
||||
* @param depositAmount is the expected amount deposited to multisig
|
||||
* @param tradeFee is the expected fee for trading
|
||||
* @param txHash is the transaction hash
|
||||
* @param txHex is the transaction hex
|
||||
* @param txKey is the transaction key
|
||||
* @param isReserveTx indicates if the tx is a reserve tx, which requires fee padding
|
||||
*/
|
||||
public static void processTradeTx(MoneroDaemon daemon, MoneroWallet wallet, String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, boolean isReserveTx) {
|
||||
|
||||
// get tx from daemon
|
||||
MoneroTx tx = daemon.getTx(txHash);
|
||||
|
||||
// if tx is not submitted, submit but do not relay
|
||||
if (tx == null) {
|
||||
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
|
||||
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
||||
} else if (tx.isRelayed()) {
|
||||
throw new RuntimeException("Reserve tx must not be relayed");
|
||||
}
|
||||
|
||||
// verify trade fee
|
||||
String feeAddress = TradeUtils.FEE_ADDRESS;
|
||||
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
||||
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
||||
|
||||
// verify mining fee
|
||||
BigInteger feeEstimate = daemon.getFeeEstimate().multiply(BigInteger.valueOf(txHex.length())); // TODO (woodser): fee estimates are too high, use more accurate estimate
|
||||
BigInteger feeThreshold = feeEstimate.multiply(BigInteger.valueOf(1l)).divide(BigInteger.valueOf(2l)); // must be at least 50% of estimated fee
|
||||
tx = daemon.getTx(txHash);
|
||||
if (tx.getFee().compareTo(feeThreshold) < 0) {
|
||||
throw new RuntimeException("Mining fee is not enough, needed " + feeThreshold + " but was " + tx.getFee());
|
||||
}
|
||||
|
||||
// verify deposit amount
|
||||
check = wallet.checkTxKey(txHash, txKey, depositAddress);
|
||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
||||
BigInteger depositThreshold = depositAmount;
|
||||
if (isReserveTx) depositThreshold = depositThreshold.add(feeThreshold.multiply(BigInteger.valueOf(3l))); // prove reserve of at least deposit amount + (3 * min mining fee)
|
||||
if (check.getReceivedAmount().compareTo(depositThreshold) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositThreshold + " but was " + check.getReceivedAmount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a contract from a trade.
|
||||
*
|
||||
* TODO (woodser): refactor/reduce trade, process model, and trading peer models
|
||||
*
|
||||
* @param trade is the trade to create the contract from
|
||||
* @return the contract
|
||||
*/
|
||||
public static Contract createContract(Trade trade) {
|
||||
boolean isBuyerMakerAndSellerTaker = trade.getOffer().getDirection() == Direction.BUY;
|
||||
Contract contract = new Contract(
|
||||
trade.getOffer().getOfferPayload(),
|
||||
checkNotNull(trade.getTradeAmount()).value,
|
||||
trade.getTradePrice().getValue(),
|
||||
isBuyerMakerAndSellerTaker ? trade.getMakerNodeAddress() : trade.getTakerNodeAddress(), // buyer node address // TODO (woodser): use maker and taker node address instead of buyer and seller node address for consistency
|
||||
isBuyerMakerAndSellerTaker ? trade.getTakerNodeAddress() : trade.getMakerNodeAddress(), // seller node address
|
||||
trade.getArbitratorNodeAddress(),
|
||||
isBuyerMakerAndSellerTaker,
|
||||
trade instanceof MakerTrade ? trade.getProcessModel().getAccountId() : trade.getMaker().getAccountId(), // maker account id
|
||||
trade instanceof TakerTrade ? trade.getProcessModel().getAccountId() : trade.getTaker().getAccountId(), // taker account id
|
||||
checkNotNull(trade instanceof MakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getPaymentMethodId() : trade.getOffer().getOfferPayload().getPaymentMethodId()), // maker payment method id
|
||||
checkNotNull(trade instanceof TakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getPaymentMethodId() : trade.getTaker().getPaymentMethodId()), // taker payment method id
|
||||
trade instanceof MakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getHash() : trade.getMaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
|
||||
trade instanceof TakerTrade ? trade.getProcessModel().getPaymentAccountPayload(trade).getHash() : trade.getTaker().getPaymentAccountPayloadHash(), // maker payment account payload hash
|
||||
trade.getMakerPubKeyRing(),
|
||||
trade.getTakerPubKeyRing(),
|
||||
trade instanceof MakerTrade ? trade.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : trade.getMaker().getPayoutAddressString(), // maker payout address
|
||||
trade instanceof TakerTrade ? trade.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString() : trade.getTaker().getPayoutAddressString(), // taker payout address
|
||||
trade.getLockTime(),
|
||||
trade.getMaker().getDepositTxHash(),
|
||||
trade.getTaker().getDepositTxHash()
|
||||
);
|
||||
return contract;
|
||||
}
|
||||
|
||||
// TODO (woodser): remove the following utitilites?
|
||||
|
||||
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
||||
static Tuple2<String, String> getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService,
|
||||
|
@ -123,7 +123,7 @@ public class CleanupMailboxMessages {
|
||||
private boolean isPubKeyValid(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
|
||||
// We can only validate the peers pubKey if we have it already. If we are the taker we get it from the offer
|
||||
// Otherwise it depends on the state of the trade protocol if we have received the peers pubKeyRing already.
|
||||
PubKeyRing peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing();
|
||||
PubKeyRing peersPubKeyRing = trade.getTradingPeer().getPubKeyRing();
|
||||
boolean isValid = true;
|
||||
if (peersPubKeyRing != null &&
|
||||
!decryptedMessageWithPubKey.getSignaturePubKey().equals(peersPubKeyRing.getSignaturePubKey())) {
|
||||
|
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.NodeAddress;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -29,18 +29,21 @@ import lombok.Value;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class MakerReadyToFundMultisigRequest extends TradeMessage implements DirectMessage {
|
||||
public final class DepositResponse extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final long currentDate;
|
||||
|
||||
public MakerReadyToFundMultisigRequest(String tradeId,
|
||||
public DepositResponse(String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
PubKeyRing pubKeyRing,
|
||||
String uid,
|
||||
int messageVersion) {
|
||||
int messageVersion,
|
||||
long currentDate) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.currentDate = currentDate;
|
||||
}
|
||||
|
||||
|
||||
@ -50,30 +53,33 @@ public final class MakerReadyToFundMultisigRequest extends TradeMessage implemen
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
protobuf.MakerReadyToFundMultisigRequest.Builder builder = protobuf.MakerReadyToFundMultisigRequest.newBuilder()
|
||||
protobuf.DepositResponse.Builder builder = protobuf.DepositResponse.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.setUid(uid);
|
||||
builder.setCurrentDate(currentDate);
|
||||
|
||||
return getNetworkEnvelopeBuilder().setMakerReadyToFundMultisigRequest(builder).build();
|
||||
return getNetworkEnvelopeBuilder().setDepositResponse(builder).build();
|
||||
}
|
||||
|
||||
public static MakerReadyToFundMultisigRequest fromProto(protobuf.MakerReadyToFundMultisigRequest proto,
|
||||
public static DepositResponse fromProto(protobuf.DepositResponse proto,
|
||||
CoreProtoResolver coreProtoResolver,
|
||||
int messageVersion) {
|
||||
return new MakerReadyToFundMultisigRequest(proto.getTradeId(),
|
||||
return new DepositResponse(proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
proto.getUid(),
|
||||
messageVersion);
|
||||
messageVersion,
|
||||
proto.getCurrentDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MakerReadyToFundMultisigRequest{" +
|
||||
return "DepositResponse {" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n pubKeyRing=" + pubKeyRing +
|
||||
",\n currentDate=" + currentDate +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class InitMultisigMessage extends TradeMessage implements DirectMessage {
|
||||
public final class InitMultisigRequest extends TradeMessage implements DirectMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final long currentDate;
|
||||
@ -43,7 +43,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
||||
@Nullable
|
||||
private final String madeMultisigHex;
|
||||
|
||||
public InitMultisigMessage(String tradeId,
|
||||
public InitMultisigRequest(String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
PubKeyRing pubKeyRing,
|
||||
String uid,
|
||||
@ -66,7 +66,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
||||
|
||||
@Override
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
protobuf.InitMultisigMessage.Builder builder = protobuf.InitMultisigMessage.newBuilder()
|
||||
protobuf.InitMultisigRequest.Builder builder = protobuf.InitMultisigRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
@ -77,13 +77,13 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
||||
|
||||
builder.setCurrentDate(currentDate);
|
||||
|
||||
return getNetworkEnvelopeBuilder().setInitMultisigMessage(builder).build();
|
||||
return getNetworkEnvelopeBuilder().setInitMultisigRequest(builder).build();
|
||||
}
|
||||
|
||||
public static InitMultisigMessage fromProto(protobuf.InitMultisigMessage proto,
|
||||
public static InitMultisigRequest fromProto(protobuf.InitMultisigRequest proto,
|
||||
CoreProtoResolver coreProtoResolver,
|
||||
int messageVersion) {
|
||||
return new InitMultisigMessage(proto.getTradeId(),
|
||||
return new InitMultisigRequest(proto.getTradeId(),
|
||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
proto.getUid(),
|
||||
@ -95,7 +95,7 @@ public final class InitMultisigMessage extends TradeMessage implements DirectMes
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MultisigMessage {" +
|
||||
return "InitMultisigRequest {" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n pubKeyRing=" + pubKeyRing +
|
||||
",\n currentDate=" + currentDate +
|
@ -17,7 +17,6 @@
|
||||
|
||||
package bisq.core.trade.messages;
|
||||
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
|
||||
import bisq.network.p2p.DirectMessage;
|
||||
@ -42,59 +41,73 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final long tradeAmount;
|
||||
private final long tradePrice;
|
||||
private final long txFee;
|
||||
private final long tradeFee;
|
||||
private final String payoutAddressString;
|
||||
private final PaymentAccountPayload paymentAccountPayload;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
private final String accountId;
|
||||
@Nullable
|
||||
private final String tradeFeeTxId;
|
||||
private final NodeAddress arbitratorNodeAddress;
|
||||
private final String paymentAccountId;
|
||||
private final String paymentMethodId;
|
||||
private final PubKeyRing pubKeyRing;
|
||||
|
||||
// added in v 0.6. can be null if we trade with an older peer
|
||||
@Nullable
|
||||
private final byte[] accountAgeWitnessSignatureOfOfferId;
|
||||
private final long currentDate;
|
||||
|
||||
// added for XMR integration
|
||||
private final NodeAddress takerNodeAddress;
|
||||
// XMR integration
|
||||
private final NodeAddress makerNodeAddress;
|
||||
private final NodeAddress takerNodeAddress;
|
||||
@Nullable
|
||||
private final NodeAddress arbitratorNodeAddress;
|
||||
@Nullable
|
||||
private final String reserveTxHash;
|
||||
@Nullable
|
||||
private final String reserveTxHex;
|
||||
@Nullable
|
||||
private final String reserveTxKey;
|
||||
@Nullable
|
||||
private final String payoutAddress;
|
||||
@Nullable
|
||||
private final String makerSignature;
|
||||
|
||||
public InitTradeRequest(String tradeId,
|
||||
NodeAddress senderNodeAddress,
|
||||
PubKeyRing pubKeyRing,
|
||||
long tradeAmount,
|
||||
long tradePrice,
|
||||
long txFee,
|
||||
long tradeFee,
|
||||
String payoutAddressString,
|
||||
PaymentAccountPayload paymentAccountPayload,
|
||||
String accountId,
|
||||
String tradeFeeTxId,
|
||||
String paymentAccountId,
|
||||
String paymentMethodId,
|
||||
String uid,
|
||||
int messageVersion,
|
||||
@Nullable byte[] accountAgeWitnessSignatureOfOfferId,
|
||||
long currentDate,
|
||||
NodeAddress takerNodeAddress,
|
||||
NodeAddress makerNodeAddress,
|
||||
NodeAddress arbitratorNodeAddress) {
|
||||
NodeAddress takerNodeAddress,
|
||||
NodeAddress arbitratorNodeAddress,
|
||||
@Nullable String reserveTxHash,
|
||||
@Nullable String reserveTxHex,
|
||||
@Nullable String reserveTxKey,
|
||||
@Nullable String payoutAddress,
|
||||
@Nullable String makerSignature) {
|
||||
super(messageVersion, tradeId, uid);
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.pubKeyRing = pubKeyRing;
|
||||
this.paymentAccountPayload = paymentAccountPayload;
|
||||
this.tradeAmount = tradeAmount;
|
||||
this.tradePrice = tradePrice;
|
||||
this.txFee = txFee;
|
||||
this.tradeFee = tradeFee;
|
||||
this.payoutAddressString = payoutAddressString;
|
||||
this.accountId = accountId;
|
||||
this.tradeFeeTxId = tradeFeeTxId;
|
||||
this.paymentAccountId = paymentAccountId;
|
||||
this.paymentMethodId = paymentMethodId;
|
||||
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
|
||||
this.currentDate = currentDate;
|
||||
this.takerNodeAddress = takerNodeAddress;
|
||||
this.makerNodeAddress = makerNodeAddress;
|
||||
this.takerNodeAddress = takerNodeAddress;
|
||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||
this.reserveTxHash = reserveTxHash;
|
||||
this.reserveTxHex = reserveTxHex;
|
||||
this.reserveTxKey = reserveTxKey;
|
||||
this.payoutAddress = payoutAddress;
|
||||
this.makerSignature = makerSignature;
|
||||
}
|
||||
|
||||
|
||||
@ -109,19 +122,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setTakerNodeAddress(takerNodeAddress.toProtoMessage())
|
||||
.setMakerNodeAddress(makerNodeAddress.toProtoMessage())
|
||||
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
|
||||
.setTradeAmount(tradeAmount)
|
||||
.setTradePrice(tradePrice)
|
||||
.setTxFee(txFee)
|
||||
.setTradeFee(tradeFee)
|
||||
.setPayoutAddressString(payoutAddressString)
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage())
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.setAccountId(accountId)
|
||||
.setUid(uid);
|
||||
|
||||
Optional.ofNullable(tradeFeeTxId).ifPresent(e -> builder.setTradeFeeTxId(tradeFeeTxId));
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
|
||||
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
|
||||
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
||||
Optional.ofNullable(payoutAddress).ifPresent(e -> builder.setPayoutAddress(payoutAddress));
|
||||
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
|
||||
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
|
||||
builder.setCurrentDate(currentDate);
|
||||
|
||||
return getNetworkEnvelopeBuilder().setInitTradeRequest(builder).build();
|
||||
@ -135,19 +151,22 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
proto.getTradeAmount(),
|
||||
proto.getTradePrice(),
|
||||
proto.getTxFee(),
|
||||
proto.getTradeFee(),
|
||||
proto.getPayoutAddressString(),
|
||||
coreProtoResolver.fromProto(proto.getPaymentAccountPayload()),
|
||||
proto.getAccountId(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getTradeFeeTxId()),
|
||||
proto.getPaymentAccountId(),
|
||||
proto.getPaymentMethodId(),
|
||||
proto.getUid(),
|
||||
messageVersion,
|
||||
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
|
||||
proto.getCurrentDate(),
|
||||
NodeAddress.fromProto(proto.getTakerNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getMakerNodeAddress()),
|
||||
NodeAddress.fromProto(proto.getArbitratorNodeAddress()));
|
||||
NodeAddress.fromProto(proto.getTakerNodeAddress()),
|
||||
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
|
||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getPayoutAddress()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getMakerSignature()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -156,16 +175,19 @@ public final class InitTradeRequest extends TradeMessage implements DirectMessag
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n tradeAmount=" + tradeAmount +
|
||||
",\n tradePrice=" + tradePrice +
|
||||
",\n txFee=" + txFee +
|
||||
",\n takerFee=" + tradeFee +
|
||||
",\n payoutAddressString='" + payoutAddressString + '\'' +
|
||||
",\n tradeFee=" + tradeFee +
|
||||
",\n pubKeyRing=" + pubKeyRing +
|
||||
",\n paymentAccountPayload=" + paymentAccountPayload +
|
||||
",\n paymentAccountPayload='" + accountId + '\'' +
|
||||
",\n takerFeeTxId='" + tradeFeeTxId + '\'' +
|
||||
",\n accountId='" + accountId + '\'' +
|
||||
",\n paymentAccountId=" + paymentAccountId +
|
||||
",\n paymentMethodId=" + paymentMethodId +
|
||||
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
|
||||
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
|
||||
",\n currentDate=" + currentDate +
|
||||
",\n reserveTxHash=" + reserveTxHash +
|
||||
",\n reserveTxHex=" + reserveTxHex +
|
||||
",\n reserveTxKey=" + reserveTxKey +
|
||||
",\n payoutAddress=" + payoutAddress +
|
||||
",\n makerSignature=" + makerSignature +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
public String toString() {
|
||||
return "MultisigMessage {" +
|
||||
return "UpdateMultisigRequest {" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n pubKeyRing=" + pubKeyRing +
|
||||
",\n currentDate=" + currentDate +
|
||||
|
@ -89,7 +89,7 @@ public final class UpdateMultisigResponse extends TradeMessage implements Direct
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MultisigMessage {" +
|
||||
return "UpdateMultisigResponse {" +
|
||||
"\n senderNodeAddress=" + senderNodeAddress +
|
||||
",\n pubKeyRing=" + pubKeyRing +
|
||||
",\n currentDate=" + currentDate +
|
||||
|
@ -2,10 +2,19 @@ package bisq.core.trade.protocol;
|
||||
|
||||
import bisq.core.trade.ArbitratorTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.DepositRequest;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeRequestToMakerIfFromTaker;
|
||||
import bisq.core.trade.protocol.tasks.ProcessDepositRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx;
|
||||
import bisq.core.trade.protocol.tasks.ArbitratorSendsInitMultisigRequestsIfFundsReserved;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
|
||||
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||
import bisq.core.util.Validator;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
@ -15,73 +24,91 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class ArbitratorProtocol extends DisputeProtocol {
|
||||
|
||||
private final ArbitratorTrade arbitratorTrade;
|
||||
|
||||
public ArbitratorProtocol(ArbitratorTrade trade) {
|
||||
super(trade);
|
||||
this.arbitratorTrade = trade;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Incoming messages
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: new implementation for MakerProtocol
|
||||
// private void handle(InitTradeRequest message, NodeAddress peer) {
|
||||
// expect(phase(Trade.Phase.INIT)
|
||||
// .with(message)
|
||||
// .from(peer))
|
||||
// .setup(tasks(ProcessInitTradeRequest.class,
|
||||
// ApplyFilter.class,
|
||||
// VerifyPeersAccountAgeWitness.class,
|
||||
// MakerVerifyTakerFeePayment.class,
|
||||
// MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
|
||||
// MakerSendsReadyToFundMultisigResponse.class)
|
||||
// .withTimeout(30))
|
||||
// .executeTasks();
|
||||
// }
|
||||
|
||||
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(
|
||||
//ApplyFilter.class,
|
||||
ProcessInitTradeRequest.class))
|
||||
.executeTasks();
|
||||
processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set
|
||||
//processModel.setTempTradingPeerNodeAddress(peer);
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(peer))
|
||||
.setup(tasks(
|
||||
ApplyFilter.class,
|
||||
ProcessInitTradeRequest.class,
|
||||
ArbitratorProcessesReserveTx.class,
|
||||
ArbitratorSendsInitTradeRequestToMakerIfFromTaker.class,
|
||||
ArbitratorSendsInitMultisigRequestsIfFundsReserved.class))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("ArbitratorProtocol.handleInitMultisigRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessInitMultisigRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("ArbitratorProtocol.handleSignContractRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
public void handleDepositRequest(DepositRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("ArbitratorProtocol.handleDepositRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessDepositRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void handleTakeOfferRequest(InputsForDepositTxRequest message,
|
||||
// NodeAddress peer,
|
||||
// ErrorMessageHandler errorMessageHandler) {
|
||||
// expect(phase(Trade.Phase.INIT)
|
||||
// .with(message)
|
||||
// .from(peer))
|
||||
// .setup(tasks(
|
||||
// MakerProcessesInputsForDepositTxRequest.class,
|
||||
// ApplyFilter.class,
|
||||
// VerifyPeersAccountAgeWitness.class,
|
||||
// getVerifyPeersFeePaymentClass(),
|
||||
// MakerSetsLockTime.class,
|
||||
// MakerCreateAndSignContract.class,
|
||||
// BuyerAsMakerCreatesAndSignsDepositTx.class,
|
||||
// BuyerSetupDepositTxListener.class,
|
||||
// BuyerAsMakerSendsInputsForDepositTxResponse.class).
|
||||
// using(new TradeTaskRunner(trade,
|
||||
// () -> handleTaskRunnerSuccess(message),
|
||||
// errorMessage -> {
|
||||
// errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
// handleTaskRunnerFault(message, errorMessage);
|
||||
// }))
|
||||
// .withTimeout(30))
|
||||
// .executeTasks();
|
||||
// }
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Message dispatcher
|
||||
|
@ -20,27 +20,28 @@ package bisq.core.trade.protocol;
|
||||
import bisq.core.trade.BuyerAsMakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
|
||||
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
@ -73,6 +74,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
||||
// Incoming messages Take offer process
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
|
||||
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
.with(message)
|
||||
@ -138,74 +140,130 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
||||
.from(peer))
|
||||
.setup(tasks(
|
||||
ProcessInitTradeRequest.class,
|
||||
ApplyFilter.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
|
||||
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
|
||||
MakerSendsReadyToFundMultisigResponse.class).
|
||||
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
|
||||
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
|
||||
//MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this
|
||||
MakerRemovesOpenOffer.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
handleTaskRunnerSuccess(peer, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
handleTaskRunnerFault(peer, message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
|
||||
NodeAddress sender,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessInitMultisigRequest.class,
|
||||
SendSignContractRequestAfterMultisig.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
MakerSendsReadyToFundMultisigResponse.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDepositTxMessage(DepositTxMessage message,
|
||||
NodeAddress sender,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
// TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
MakerVerifyTakerDepositTx.class,
|
||||
MakerCreateAndSignContract.class,
|
||||
MakerCreateAndPublishDepositTx.class,
|
||||
MakerSetupDepositTxsListener.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> handleTaskRunnerSuccess(message),
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
})))
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), response);
|
||||
processModel.setTradeMessage(response);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(response)
|
||||
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessDepositResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, response);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, response, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(request)
|
||||
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessPaymentAccountPayloadRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
}
|
||||
|
@ -22,15 +22,22 @@ import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.BuyerAsTakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
|
||||
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
|
||||
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
|
||||
@ -41,43 +48,24 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.FundMultisig;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
// TODO (woodser): remove unused request handling
|
||||
@Slf4j
|
||||
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
|
||||
private ResultHandler takeOfferListener;
|
||||
private Timer initDepositTimer;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -87,7 +75,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
super(trade);
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
|
||||
trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
|
||||
|
||||
// TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase?
|
||||
}
|
||||
@ -107,8 +95,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
.from(trade.getTradingPeerNodeAddress()))
|
||||
.setup(tasks(
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerSendInitTradeRequests.class)
|
||||
TakerReservesTradeFunds.class,
|
||||
TakerSendsInitTradeRequestToArbitrator.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
@ -125,7 +113,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
.setup(tasks(TakerProcessesInputsForDepositTxResponse.class,
|
||||
ApplyFilter.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
TakerVerifyAndSignContract.class,
|
||||
//TakerVerifyAndSignContract.class,
|
||||
TakerPublishFeeTx.class,
|
||||
BuyerAsTakerSignsDepositTx.class,
|
||||
BuyerSetupDepositTxListener.class,
|
||||
@ -134,6 +122,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(DelayedPayoutTxSignatureRequest message, NodeAddress peer) {
|
||||
expect(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
.with(message)
|
||||
@ -196,188 +185,123 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MakerProtocol
|
||||
// TakerProtocol
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Incoming message handling
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
|
||||
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
|
||||
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
|
||||
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
|
||||
processModel.setTradeMessage(message);
|
||||
if (message.isMakerReadyToFundMultisig()) {
|
||||
createAndFundMultisig(message, takeOfferListener);
|
||||
} else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests
|
||||
reserveTrade(message, takeOfferListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
|
||||
System.out.println("BuyerAsTakerProtocol.reserveTrade()");
|
||||
|
||||
// define wallet listener which initiates multisig deposit when trade fee tx unlocked
|
||||
// TODO (woodser): this needs run for reserved trades when client is opened
|
||||
// TODO (woodser): test initiating multisig when maker offline
|
||||
MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet();
|
||||
MoneroWalletListener fundMultisigListener = new MoneroWalletListener() {
|
||||
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||
|
||||
// get updated offer fee tx
|
||||
MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId());
|
||||
|
||||
// check if tx is unlocked
|
||||
if (Boolean.FALSE.equals(feeTx.isLocked())) {
|
||||
System.out.println("TRADE FEE TX IS UNLOCKED!!!");
|
||||
|
||||
// stop listening to wallet
|
||||
wallet.removeListener(this);
|
||||
|
||||
// periodically request multisig deposit until successful
|
||||
Runnable requestMultisigDeposit = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler);
|
||||
else initDepositTimer.stop();
|
||||
}
|
||||
};
|
||||
UserThread.execute(requestMultisigDeposit);
|
||||
initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// run pipeline to publish trade fee tx
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
TakerCreateFeeTx.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
//TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx
|
||||
TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade?
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later?
|
||||
wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
|
||||
System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()");
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerSendReadyToFundMultisigRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
|
||||
System.out.println("TakerProtocolBase.createAndFundMultisig()");
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerVerifyAndSignContract.class,
|
||||
TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("TakerProtocolBase.handleMultisigMessage()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
.with(message)
|
||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessInitMultisigMessage.class)
|
||||
ProcessInitMultisigRequest.class,
|
||||
SendSignContractRequestAfterMultisig.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
System.out.println("handle multisig pipeline completed successfully!");
|
||||
handleTaskRunnerSuccess(message);
|
||||
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
|
||||
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
|
||||
fundMultisig(message, takeOfferListener);
|
||||
}
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
takeOfferListener.handleResult();
|
||||
})))
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
private void fundMultisig(InitMultisigMessage message, ResultHandler handler) {
|
||||
System.out.println("TakerProtocolBase.fundMultisig()");
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
FundMultisig.class). // will receive MultisigMessage in response
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
System.out.println("MULTISIG WALLET FUNDED!!!!");
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
TakerProcessesMakerDepositTxMessage.class,
|
||||
TakerSetupDepositTxsListener.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), response);
|
||||
processModel.setTradeMessage(response);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(response)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessDepositResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, response);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, response, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(request)
|
||||
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessPaymentAccountPayloadRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
}
|
||||
|
@ -24,12 +24,12 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.UpdateMultisigWithTradingPeer;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerCreateAndSignPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
@ -57,16 +57,15 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
||||
@Override
|
||||
protected void onInitialized() {
|
||||
super.onInitialized();
|
||||
// We get called the constructor with any possible state and phase. As we don't want to log an error for such
|
||||
// cases we use the alternative 'given' method instead of 'expect'.
|
||||
given(phase(Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
|
||||
given(phase(Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(BuyerEvent.STARTUP))
|
||||
.setup(tasks(BuyerSetupDepositTxListener.class))
|
||||
.setup(tasks(SetupDepositTxsListener.class))
|
||||
.executeTasks();
|
||||
|
||||
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
||||
.with(BuyerEvent.STARTUP))
|
||||
.setup(tasks(BuyerSetupPayoutTxListener.class))
|
||||
.setup(tasks(BuyerSetupPayoutTxListener.class)) // TODO (woodser): mirror deposit listener setup?
|
||||
.executeTasks();
|
||||
|
||||
given(anyPhase(Trade.Phase.FIAT_SENT, Trade.Phase.FIAT_RECEIVED)
|
||||
@ -166,10 +165,10 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
||||
BuyerProcessPayoutTxPublishedMessage.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(message);
|
||||
handleTaskRunnerSuccess(peer, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
handleTaskRunnerFault(peer, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED)
|
||||
.with(event)
|
||||
.preCondition(trade.getProcessModel().getTradingPeer().getMediatedPayoutTxSignature() == null,
|
||||
.preCondition(trade.getTradingPeer().getMediatedPayoutTxSignature() == null,
|
||||
() -> errorMessageHandler.handleErrorMessage("We have received already the signature from the peer."))
|
||||
.preCondition(trade.getPayoutTx() == null,
|
||||
() -> errorMessageHandler.handleErrorMessage("Payout tx is already published.")))
|
||||
|
@ -98,7 +98,7 @@ public class FluentProtocol {
|
||||
|
||||
NodeAddress peer = condition.getPeer();
|
||||
if (peer != null) {
|
||||
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer);
|
||||
tradeProtocol.processModel.setTempTradingPeerNodeAddress(peer); // TODO (woodser): node has multiple peers (arbitrator and maker or taker), but fluent protocol assumes only one
|
||||
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ public class FluentProtocol {
|
||||
tradeProtocol.processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
TradeTaskRunner taskRunner = setup.getTaskRunner(message, condition.getEvent());
|
||||
TradeTaskRunner taskRunner = setup.getTaskRunner(peer, message, condition.getEvent());
|
||||
taskRunner.addTasks(setup.getTasks());
|
||||
taskRunner.run();
|
||||
return this;
|
||||
@ -366,12 +366,12 @@ public class FluentProtocol {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TradeTaskRunner getTaskRunner(@Nullable TradeMessage message, @Nullable Event event) {
|
||||
public TradeTaskRunner getTaskRunner(NodeAddress sender, @Nullable TradeMessage message, @Nullable Event event) {
|
||||
if (taskRunner == null) {
|
||||
if (message != null) {
|
||||
taskRunner = new TradeTaskRunner(trade,
|
||||
() -> tradeProtocol.handleTaskRunnerSuccess(message),
|
||||
errorMessage -> tradeProtocol.handleTaskRunnerFault(message, errorMessage));
|
||||
() -> tradeProtocol.handleTaskRunnerSuccess(sender, message),
|
||||
errorMessage -> tradeProtocol.handleTaskRunnerFault(sender, message, errorMessage));
|
||||
} else if (event != null) {
|
||||
taskRunner = new TradeTaskRunner(trade,
|
||||
() -> tradeProtocol.handleTaskRunnerSuccess(event),
|
||||
|
@ -19,13 +19,11 @@ package bisq.core.trade.protocol;
|
||||
|
||||
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
public interface MakerProtocol {
|
||||
public interface MakerProtocol extends TraderProtocol {
|
||||
void handleInitTradeRequest(InitTradeRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
||||
void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
||||
}
|
||||
|
@ -22,10 +22,12 @@ import bisq.core.btc.model.RawTransactionInput;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.TradeWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.network.MessageState;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload.Direction;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
@ -60,7 +62,7 @@ import org.bitcoinj.core.Transaction;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@ -90,14 +92,6 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
transient private ProcessModelServiceProvider provider;
|
||||
transient private TradeManager tradeManager;
|
||||
transient private Offer offer;
|
||||
@Setter
|
||||
transient private Trade trade;
|
||||
|
||||
// Transient/Mutable
|
||||
@Getter
|
||||
transient private MoneroTxWallet takeOfferFeeTx;
|
||||
@Setter
|
||||
transient private TradeMessage tradeMessage;
|
||||
|
||||
// Added in v1.2.0
|
||||
@Setter
|
||||
@ -114,7 +108,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
transient private ObjectProperty<MessageState> depositTxMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
@Getter
|
||||
transient private Transaction depositTx;
|
||||
transient private Transaction depositTx; // TODO (woodser): remove and rename depositTxBtc with depositTx
|
||||
|
||||
// Persistable Immutable (private setter only used by PB method)
|
||||
private TradingPeer maker = new TradingPeer();
|
||||
@ -153,7 +147,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
// After successful verified we copy that over to the trade.tradingPeerAddress
|
||||
@Nullable
|
||||
@Setter
|
||||
private NodeAddress tempTradingPeerNodeAddress;
|
||||
private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely
|
||||
|
||||
// Added in v.1.1.6
|
||||
@Nullable
|
||||
@ -165,10 +159,33 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
private long sellerPayoutAmountFromMediation;
|
||||
|
||||
// Added for XMR integration
|
||||
@Getter
|
||||
transient private MoneroTxWallet takeOfferFeeTx; // TODO (woodser): remove
|
||||
@Setter
|
||||
transient private TradeMessage tradeMessage;
|
||||
@Getter
|
||||
@Setter
|
||||
private String makerSignature;
|
||||
@Getter
|
||||
@Setter
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String preparedMultisigHex;
|
||||
transient private MoneroTxWallet reserveTx;
|
||||
@Setter
|
||||
@Getter
|
||||
private String reserveTxHash;
|
||||
@Setter
|
||||
@Getter
|
||||
private List<String> frozenKeyImages = new ArrayList<>();
|
||||
@Getter
|
||||
@Setter
|
||||
transient private MoneroTxWallet depositTxXmr;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private String preparedMultisigHex; // TODO (woodser): ProcessModel shares many fields with TradingPeer; switch to trade getMaker(), getTaker(), getArbitrator(), getSelf(), with common TradingPeer object?
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
@ -180,19 +197,12 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean makerReadyToFundMultisig;
|
||||
private boolean makerReadyToFundMultisig; // TODO (woodser): remove
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean multisigDepositInitiated;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String makerPreparedDepositTxId;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String takerPreparedDepositTxId;
|
||||
@Nullable
|
||||
transient private MoneroTxWallet buyerSignedPayoutTx;
|
||||
|
||||
transient private MoneroTxWallet buyerSignedPayoutTx; // TODO (woodser): remove
|
||||
|
||||
|
||||
// We want to indicate the user the state of the message delivery of the
|
||||
@ -239,18 +249,20 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
.setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong)
|
||||
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
|
||||
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
|
||||
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
|
||||
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation)
|
||||
.addAllFrozenKeyImages(frozenKeyImages);
|
||||
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
|
||||
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage()));
|
||||
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage()));
|
||||
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
|
||||
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
|
||||
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
|
||||
Optional.ofNullable(makerPreparedDepositTxId).ifPresent(e -> builder.setMakerPreparedDepositTxId(makerPreparedDepositTxId));
|
||||
Optional.ofNullable(takerPreparedDepositTxId).ifPresent(e -> builder.setTakerPreparedDepositTxId(takerPreparedDepositTxId));
|
||||
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
|
||||
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
|
||||
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
|
||||
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
|
||||
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
||||
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
|
||||
@ -272,6 +284,8 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
|
||||
|
||||
// nullable
|
||||
processModel.setReserveTxHash(proto.getReserveTxHash());
|
||||
processModel.setFrozenKeyImages(proto.getFrozenKeyImagesList());
|
||||
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
|
||||
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
|
||||
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
|
||||
@ -282,13 +296,13 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
processModel.setMyMultiSigPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getMyMultiSigPubKey()));
|
||||
processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
|
||||
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
|
||||
processModel.setMakerSignature(proto.getMakerSignature());
|
||||
processModel.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
|
||||
processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
|
||||
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
||||
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
|
||||
processModel.setMakerReadyToFundMultisig(proto.getMakerReadyToFundMultisig());
|
||||
processModel.setMultisigDepositInitiated(proto.getMultisigDepositInitiated());
|
||||
processModel.setMakerPreparedDepositTxId(proto.getMakerPreparedDepositTxId());
|
||||
processModel.setTakerPreparedDepositTxId(proto.getTakerPreparedDepositTxId());
|
||||
|
||||
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
|
||||
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);
|
||||
@ -349,22 +363,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTradingPeer(TradingPeer peer) {
|
||||
if (trade == null) throw new RuntimeException("Cannot set trading peer because trade is null");
|
||||
else if (trade instanceof MakerTrade) taker = peer;
|
||||
else if (trade instanceof TakerTrade) maker = peer;
|
||||
else throw new RuntimeException("Must be maker or taker to set trading peer");
|
||||
}
|
||||
|
||||
public TradingPeer getTradingPeer() {
|
||||
if (trade == null) throw new RuntimeException("Cannot get trading peer because trade is null");
|
||||
else if (trade instanceof MakerTrade) return taker;
|
||||
else if (trade instanceof TakerTrade) return maker;
|
||||
else if (trade instanceof ArbitratorTrade) return null;
|
||||
else throw new RuntimeException("Unknown trade type: " + trade.getClass().getName());
|
||||
}
|
||||
|
||||
|
||||
void setDepositTxSentAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
@ -387,6 +386,10 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Delegates
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public XmrWalletService getXmrWalletService() {
|
||||
return provider.getXmrWalletService();
|
||||
}
|
||||
|
||||
public BtcWalletService getBtcWalletService() {
|
||||
return provider.getBtcWalletService();
|
||||
|
@ -21,21 +21,23 @@ package bisq.core.trade.protocol;
|
||||
import bisq.core.trade.SellerAsMakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigRequest;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
|
||||
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndPublishDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequest;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSendsReadyToFundMultisigResponse;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
@ -43,7 +45,6 @@ import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
@ -127,8 +128,10 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MakerProtocol TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
|
||||
// MakerProtocol
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance
|
||||
|
||||
@Override
|
||||
public void handleInitTradeRequest(InitTradeRequest message,
|
||||
@ -139,74 +142,130 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
||||
.from(peer))
|
||||
.setup(tasks(
|
||||
ProcessInitTradeRequest.class,
|
||||
ApplyFilter.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
MakerSendsInitTradeRequest.class, // TODO (woodser): contact arbitrator here? probably later when ready to create multisig
|
||||
MakerRemovesOpenOffer.class, // TODO (woodser): remove offer after taker pays trade fee or it needs to be reserved until deposit tx
|
||||
MakerSendsReadyToFundMultisigResponse.class).
|
||||
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
|
||||
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
|
||||
//MakerSendsInitTradeRequestIfUnreserved.class, // TODO (woodser): implement this
|
||||
MakerRemovesOpenOffer.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
handleTaskRunnerSuccess(peer, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
handleTaskRunnerFault(peer, message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigRequest message,
|
||||
NodeAddress sender,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessInitMultisigRequest.class,
|
||||
SendSignContractRequestAfterMultisig.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
MakerSendsReadyToFundMultisigResponse.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDepositTxMessage(DepositTxMessage message,
|
||||
NodeAddress sender,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
processModel.setTempTradingPeerNodeAddress(sender);
|
||||
|
||||
// TODO (woodser): MakerProcessesTakerDepositTxMessage.java which verifies deposit amount = fee + security deposit (+ trade amount), or that deposit is exact amount
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
MakerVerifyTakerDepositTx.class,
|
||||
MakerCreateAndSignContract.class,
|
||||
MakerCreateAndPublishDepositTx.class,
|
||||
MakerSetupDepositTxsListener.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> handleTaskRunnerSuccess(message),
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
})))
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), response);
|
||||
processModel.setTradeMessage(response);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(response)
|
||||
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessDepositResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, response);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, response, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(request)
|
||||
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessPaymentAccountPayloadRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
}
|
||||
|
@ -22,56 +22,45 @@ import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.SellerAsTakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.messages.DepositResponse;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InputsForDepositTxResponse;
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
||||
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.SignContractResponse;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
|
||||
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
|
||||
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.FundMultisig;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesMakerDepositTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitMultisigMessages;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendInitTradeRequests;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendReadyToFundMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
|
||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
// TODO (woodser): remove unused request handling
|
||||
@Slf4j
|
||||
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
|
||||
private ResultHandler takeOfferListener;
|
||||
private Timer initDepositTimer;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -80,7 +69,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
|
||||
super(trade);
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
|
||||
trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
|
||||
}
|
||||
|
||||
|
||||
@ -97,8 +86,8 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
.from(trade.getTradingPeerNodeAddress()))
|
||||
.setup(tasks(
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerSendInitTradeRequests.class)
|
||||
TakerReservesTradeFunds.class,
|
||||
TakerSendsInitTradeRequestToArbitrator.class)
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
@ -116,7 +105,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
TakerProcessesInputsForDepositTxResponse.class,
|
||||
ApplyFilter.class,
|
||||
VerifyPeersAccountAgeWitness.class,
|
||||
TakerVerifyAndSignContract.class,
|
||||
//TakerVerifyAndSignContract.class,
|
||||
TakerPublishFeeTx.class,
|
||||
SellerAsTakerSignsDepositTx.class,
|
||||
SellerCreatesDelayedPayoutTx.class,
|
||||
@ -176,182 +165,118 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
||||
|
||||
// TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Incoming message handling
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsTakerProtocol.handleMakerReadyToFundMultisigResponse()");
|
||||
System.out.println("Maker is ready to fund multisig: " + message.isMakerReadyToFundMultisig());
|
||||
processModel.setTempTradingPeerNodeAddress(peer); // TODO: verify this
|
||||
if (processModel.isMultisigDepositInitiated()) throw new RuntimeException("Taker has already initiated multisig deposit. This should not happen"); // TODO (woodser): proper error handling
|
||||
processModel.setTradeMessage(message);
|
||||
if (message.isMakerReadyToFundMultisig()) {
|
||||
createAndFundMultisig(message, takeOfferListener);
|
||||
} else if (trade.getTakerFeeTxId() == null && !trade.getState().equals(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX)) { // TODO (woodser): use processModel.isTradeFeeTxInitiated() like check above to avoid timing issues with subsequent requests
|
||||
reserveTrade(message, takeOfferListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void reserveTrade(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
|
||||
System.out.println("BuyerAsTakerProtocol.reserveTrade()");
|
||||
|
||||
// define wallet listener which initiates multisig deposit when trade fee tx unlocked
|
||||
// TODO (woodser): this needs run for reserved trades when client is opened
|
||||
// TODO (woodser): test initiating multisig when maker offline
|
||||
MoneroWallet wallet = processModel.getProvider().getXmrWalletService().getWallet();
|
||||
MoneroWalletListener fundMultisigListener = new MoneroWalletListener() {
|
||||
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||
|
||||
// get updated offer fee tx
|
||||
MoneroTxWallet feeTx = wallet.getTx(processModel.getTakeOfferFeeTxId());
|
||||
|
||||
// check if tx is unlocked
|
||||
if (Boolean.FALSE.equals(feeTx.isLocked())) {
|
||||
System.out.println("TRADE FEE TX IS UNLOCKED!!!");
|
||||
|
||||
// stop listening to wallet
|
||||
wallet.removeListener(this);
|
||||
|
||||
// periodically request multisig deposit until successful
|
||||
Runnable requestMultisigDeposit = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!processModel.isMultisigDepositInitiated()) sendMakerReadyToFundMultisigRequest(message, handler);
|
||||
else initDepositTimer.stop();
|
||||
}
|
||||
};
|
||||
UserThread.execute(requestMultisigDeposit);
|
||||
initDepositTimer = UserThread.runPeriodically(requestMultisigDeposit, 60);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// run pipeline to publish trade fee tx
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
TakerCreateFeeTx.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
//TakerVerifyAndSignContract.class, // TODO (woodser): no... create taker fee tx, send to maker which creates contract, returns, then taker verifies and signs contract, then publishes taker fee tx
|
||||
TakerPublishFeeTx.class) // TODO (woodser): need to notify maker/network of trade fee tx id to reserve trade?
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
if (handler != null) handler.handleResult(); // TODO (woodser): use handler to timeout initializing entire trade or remove use of handler and let gui indicate failure later?
|
||||
wallet.addListener(fundMultisigListener); // listen for trade fee tx to become available then initiate multisig deposit // TODO: put in pipeline
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
private void sendMakerReadyToFundMultisigRequest(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
|
||||
System.out.println("TakerProtocolBase.sendMakerReadyToFundMultisigRequest()");
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerSendReadyToFundMultisigRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
private void createAndFundMultisig(MakerReadyToFundMultisigResponse message, ResultHandler handler) {
|
||||
System.out.println("TakerProtocolBase.createAndFundMultisig()");
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
TakerVerifyAndSignContract.class,
|
||||
TakerSendInitMultisigMessages.class) // will receive MultisigMessage in response
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("TakerProtocolBase.handleMultisigMessage()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.TAKER_FEE_PUBLISHED)
|
||||
.with(message)
|
||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(request)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
ProcessInitMultisigMessage.class)
|
||||
ProcessInitMultisigRequest.class,
|
||||
SendSignContractRequestAfterMultisig.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
System.out.println("handle multisig pipeline completed successfully!");
|
||||
handleTaskRunnerSuccess(message);
|
||||
if (processModel.isMultisigSetupComplete() && !processModel.isMultisigDepositInitiated()) {
|
||||
processModel.setMultisigDepositInitiated(true); // ensure only funding multisig one time
|
||||
fundMultisig(message, takeOfferListener);
|
||||
}
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
takeOfferListener.handleResult();
|
||||
})))
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
private void fundMultisig(InitMultisigMessage message, ResultHandler handler) {
|
||||
System.out.println("TakerProtocolBase.fundMultisig()");
|
||||
expect(new FluentProtocol.Condition(trade))
|
||||
.setup(tasks(
|
||||
FundMultisig.class). // will receive MultisigMessage in response
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
System.out.println("MULTISIG WALLET FUNDED!!!!");
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleDepositTxMessage(DepositTxMessage message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("TakerProtocolBase.handleDepositTxMessage()");
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
TakerProcessesMakerDepositTxMessage.class,
|
||||
TakerSetupDepositTxsListener.class).
|
||||
using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
}))
|
||||
.withTimeout(30))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
expect(anyPhase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessSignContractResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, message);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, message, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), response);
|
||||
processModel.setTradeMessage(response);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(response)
|
||||
.from(sender))
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessDepositResponse.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
handleTaskRunnerSuccess(sender, response);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, response, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(request)
|
||||
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
|
||||
.setup(tasks(
|
||||
// TODO (woodser): validate request
|
||||
ProcessPaymentAccountPayloadRequest.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(sender, request);
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(sender, request, errorMessage);
|
||||
})))
|
||||
.executeTasks();
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ import bisq.core.trade.SellerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
@ -44,6 +46,16 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||
public SellerProtocol(SellerTrade trade) {
|
||||
super(trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialized() {
|
||||
super.onInitialized();
|
||||
|
||||
given(phase(Trade.Phase.DEPOSIT_PUBLISHED)
|
||||
.with(BuyerEvent.STARTUP))
|
||||
.setup(tasks(SetupDepositTxsListener.class))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -78,7 +90,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||
log.warn("We received a CounterCurrencyTransferStartedMessage but we have already created the payout tx " +
|
||||
"so we ignore the message. This can happen if the ACK message to the peer did not " +
|
||||
"arrive and the peer repeats sending us the message. We send another ACK msg.");
|
||||
sendAckMessage(message, true, null);
|
||||
sendAckMessage(peer, message, true, null);
|
||||
removeMailboxMessageAfterProcessing(message);
|
||||
}))
|
||||
.setup(tasks(
|
||||
|
@ -17,20 +17,11 @@
|
||||
|
||||
package bisq.core.trade.protocol;
|
||||
|
||||
import bisq.core.trade.messages.MakerReadyToFundMultisigResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
|
||||
public interface TakerProtocol {
|
||||
public interface TakerProtocol extends TraderProtocol {
|
||||
void onTakeOffer();
|
||||
|
||||
enum TakerEvent implements FluentProtocol.Event {
|
||||
TAKE_OFFER
|
||||
}
|
||||
|
||||
// TODO (woodser): update after rebase
|
||||
//åvoid takeAvailableOffer(ResultHandler handler);
|
||||
void handleMakerReadyToFundMultisigResponse(MakerReadyToFundMultisigResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
package bisq.core.trade.protocol;
|
||||
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
/**
|
||||
* Receives notifications of decrypted, verified trade messages.
|
||||
* Receives notifications of decrypted, verified trade and ack messages.
|
||||
*/
|
||||
public class TradeMessageListener {
|
||||
public class TradeListener {
|
||||
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) { }
|
||||
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { }
|
||||
}
|
@ -22,11 +22,10 @@ import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.DepositTxMessage;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.SignContractRequest;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigMessage;
|
||||
import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
|
||||
import bisq.core.util.Validator;
|
||||
|
||||
@ -70,7 +69,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
public TradeProtocol(Trade trade) {
|
||||
this.trade = trade;
|
||||
this.processModel = trade.getProcessModel();
|
||||
this.processModel.setTrade(trade); // TODO (woodser): added to explicitly set trade circular loop, keep?
|
||||
}
|
||||
|
||||
|
||||
@ -128,12 +126,14 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
if (networkEnvelope instanceof TradeMessage) {
|
||||
onTradeMessage((TradeMessage) networkEnvelope, peer);
|
||||
|
||||
// notify trade listeners
|
||||
// TODO (woodser): better way to register message notifications for trade?
|
||||
if (((TradeMessage) networkEnvelope).getTradeId().equals(processModel.getOfferId())) {
|
||||
trade.onVerifiedTradeMessage((TradeMessage) networkEnvelope, peer);
|
||||
}
|
||||
} else if (networkEnvelope instanceof AckMessage) {
|
||||
onAckMessage((AckMessage) networkEnvelope, peer);
|
||||
trade.onAckMessage((AckMessage) networkEnvelope, peer); // notify trade listeners
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,28 +205,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
|
||||
|
||||
public void handleMultisigMessage(InitMultisigMessage message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message, "handleMultisigMessage");
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
});
|
||||
taskRunner.addTasks(
|
||||
ProcessInitMultisigMessage.class
|
||||
);
|
||||
startTimeout(60); // TODO (woodser): what timeout to use? don't hardcode
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
public abstract void handleDepositTxMessage(DepositTxMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
|
||||
public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
|
||||
public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler);
|
||||
|
||||
// TODO (woodser): update to use fluent for consistency
|
||||
public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
||||
@ -236,11 +216,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
handleTaskRunnerSuccess(message, "handleUpdateMultisigRequest");
|
||||
handleTaskRunnerSuccess(peer, message, "handleUpdateMultisigRequest");
|
||||
},
|
||||
errorMessage -> {
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
handleTaskRunnerFault(message, errorMessage);
|
||||
handleTaskRunnerFault(peer, message, errorMessage);
|
||||
});
|
||||
taskRunner.addTasks(
|
||||
ProcessUpdateMultisigRequest.class
|
||||
@ -262,6 +242,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
if (!result.isValid()) {
|
||||
log.warn(result.getInfo());
|
||||
handleTaskRunnerFault(null,
|
||||
null,
|
||||
result.name(),
|
||||
result.getInfo());
|
||||
}
|
||||
@ -292,9 +273,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
// ACK msg
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO (woodser): support notifications of ack messages
|
||||
private void onAckMessage(AckMessage ackMessage, NodeAddress peer) {
|
||||
// We handle the ack for CounterCurrencyTransferStartedMessage and DepositTxAndDelayedPayoutTxMessage
|
||||
// as we support automatic re-send of the msg in case it was not ACKed after a certain time
|
||||
// TODO (woodser): add AckMessage for InitTradeRequest and support automatic re-send ?
|
||||
if (ackMessage.getSourceMsgClassName().equals(CounterCurrencyTransferStartedMessage.class.getSimpleName())) {
|
||||
processModel.setPaymentStartedAckMessage(ackMessage);
|
||||
} else if (ackMessage.getSourceMsgClassName().equals(DepositTxAndDelayedPayoutTxMessage.class.getSimpleName())) {
|
||||
@ -310,15 +293,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendAckMessage(TradeMessage message, boolean result, @Nullable String errorMessage) {
|
||||
protected void sendAckMessage(NodeAddress peer, TradeMessage message, boolean result, @Nullable String errorMessage) {
|
||||
|
||||
// TODO (woodser): remove trade.getTradingPeerNodeAddress() and processModel.getTempTradingPeerNodeAddress() if everything should be maker, taker, or arbitrator
|
||||
|
||||
// If there was an error during offer verification, the tradingPeerNodeAddress of the trade might not be set yet.
|
||||
// We can find the peer's node address in the processModel's tempTradingPeerNodeAddress in that case.
|
||||
NodeAddress peer = trade.getTradingPeerNodeAddress() != null ?
|
||||
trade.getTradingPeerNodeAddress() :
|
||||
processModel.getTempTradingPeerNodeAddress();
|
||||
|
||||
// get destination pub key ring
|
||||
// get peer's pub key ring
|
||||
PubKeyRing peersPubKeyRing = getPeersPubKeyRing(peer);
|
||||
if (peersPubKeyRing == null) {
|
||||
log.error("We cannot send the ACK message as peersPubKeyRing is null");
|
||||
@ -392,20 +371,20 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
// Task runner
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void handleTaskRunnerSuccess(TradeMessage message) {
|
||||
handleTaskRunnerSuccess(message, message.getClass().getSimpleName());
|
||||
protected void handleTaskRunnerSuccess(NodeAddress sender, TradeMessage message) {
|
||||
handleTaskRunnerSuccess(sender, message, message.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
protected void handleTaskRunnerSuccess(FluentProtocol.Event event) {
|
||||
handleTaskRunnerSuccess(null, event.name());
|
||||
handleTaskRunnerSuccess(null, null, event.name());
|
||||
}
|
||||
|
||||
protected void handleTaskRunnerFault(TradeMessage message, String errorMessage) {
|
||||
handleTaskRunnerFault(message, message.getClass().getSimpleName(), errorMessage);
|
||||
protected void handleTaskRunnerFault(NodeAddress sender, TradeMessage message, String errorMessage) {
|
||||
handleTaskRunnerFault(sender, message, message.getClass().getSimpleName(), errorMessage);
|
||||
}
|
||||
|
||||
protected void handleTaskRunnerFault(FluentProtocol.Event event, String errorMessage) {
|
||||
handleTaskRunnerFault(null, event.name(), errorMessage);
|
||||
handleTaskRunnerFault(null, null, event.name(), errorMessage);
|
||||
}
|
||||
|
||||
|
||||
@ -446,10 +425,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void handleTaskRunnerSuccess(@Nullable TradeMessage message, String source) {
|
||||
private void handleTaskRunnerSuccess(NodeAddress sender, @Nullable TradeMessage message, String source) {
|
||||
log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId());
|
||||
if (message != null) {
|
||||
sendAckMessage(message, true, null);
|
||||
sendAckMessage(sender, message, true, null);
|
||||
|
||||
// Once a taskRunner is completed we remove the mailbox message. To not remove it directly at the task
|
||||
// adds some resilience in case of minor errors, so after a restart the mailbox message can be applied
|
||||
@ -458,11 +437,11 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
}
|
||||
|
||||
void handleTaskRunnerFault(@Nullable TradeMessage message, String source, String errorMessage) {
|
||||
void handleTaskRunnerFault(NodeAddress ackReceiver, @Nullable TradeMessage message, String source, String errorMessage) {
|
||||
log.error("Task runner failed with error {}. Triggered from {}", errorMessage, source);
|
||||
|
||||
if (message != null) {
|
||||
sendAckMessage(message, false, errorMessage);
|
||||
sendAckMessage(ackReceiver, message, false, errorMessage);
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
|
@ -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
|
||||
private String accountId;
|
||||
@Nullable
|
||||
private String paymentAccountId;
|
||||
@Nullable
|
||||
private String paymentMethodId;
|
||||
@Nullable
|
||||
private byte[] paymentAccountPayloadHash;
|
||||
@Nullable
|
||||
private PaymentAccountPayload paymentAccountPayload;
|
||||
@Nullable
|
||||
private String payoutAddressString;
|
||||
@ -90,10 +96,24 @@ public final class TradingPeer implements PersistablePayload {
|
||||
|
||||
// Added for XMR integration
|
||||
@Nullable
|
||||
private String reserveTxHash;
|
||||
@Nullable
|
||||
private String reserveTxHex;
|
||||
@Nullable
|
||||
private String reserveTxKey;
|
||||
@Nullable
|
||||
private String preparedMultisigHex;
|
||||
@Nullable
|
||||
private String madeMultisigHex;
|
||||
@Nullable
|
||||
private String signedPayoutTxHex;
|
||||
|
||||
@Nullable
|
||||
private String depositTxHash;
|
||||
@Nullable
|
||||
private String depositTxHex;
|
||||
@Nullable
|
||||
private String depositTxKey;
|
||||
|
||||
public TradingPeer() {
|
||||
}
|
||||
|
||||
@ -102,6 +122,9 @@ public final class TradingPeer implements PersistablePayload {
|
||||
final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder()
|
||||
.setChangeOutputValue(changeOutputValue);
|
||||
Optional.ofNullable(accountId).ifPresent(builder::setAccountId);
|
||||
Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId);
|
||||
Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId);
|
||||
Optional.ofNullable(paymentAccountPayloadHash).ifPresent(e -> builder.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash)));
|
||||
Optional.ofNullable(paymentAccountPayload).ifPresent(e -> builder.setPaymentAccountPayload((protobuf.PaymentAccountPayload) e.toProtoMessage()));
|
||||
Optional.ofNullable(payoutAddressString).ifPresent(builder::setPayoutAddressString);
|
||||
Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson);
|
||||
@ -115,9 +138,15 @@ public final class TradingPeer implements PersistablePayload {
|
||||
Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e)));
|
||||
Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e)));
|
||||
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
|
||||
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
|
||||
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
|
||||
Optional.ofNullable(reserveTxKey).ifPresent(e -> builder.setReserveTxKey(reserveTxKey));
|
||||
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
|
||||
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
|
||||
Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
|
||||
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
|
||||
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
|
||||
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
||||
|
||||
builder.setCurrentDate(currentDate);
|
||||
return builder.build();
|
||||
@ -130,6 +159,9 @@ public final class TradingPeer implements PersistablePayload {
|
||||
TradingPeer tradingPeer = new TradingPeer();
|
||||
tradingPeer.setChangeOutputValue(proto.getChangeOutputValue());
|
||||
tradingPeer.setAccountId(ProtoUtil.stringOrNullFromProto(proto.getAccountId()));
|
||||
tradingPeer.setPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getPaymentAccountId()));
|
||||
tradingPeer.setPaymentMethodId(ProtoUtil.stringOrNullFromProto(proto.getPaymentMethodId()));
|
||||
tradingPeer.setPaymentAccountPayloadHash(proto.getPaymentAccountPayloadHash().toByteArray());
|
||||
tradingPeer.setPaymentAccountPayload(proto.hasPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getPaymentAccountPayload()) : null);
|
||||
tradingPeer.setPayoutAddressString(ProtoUtil.stringOrNullFromProto(proto.getPayoutAddressString()));
|
||||
tradingPeer.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()));
|
||||
@ -148,9 +180,15 @@ public final class TradingPeer implements PersistablePayload {
|
||||
tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature()));
|
||||
tradingPeer.setCurrentDate(proto.getCurrentDate());
|
||||
tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
|
||||
tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()));
|
||||
tradingPeer.setReserveTxHex(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()));
|
||||
tradingPeer.setReserveTxKey(ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()));
|
||||
tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
|
||||
tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
|
||||
tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()));
|
||||
tradingPeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
|
||||
tradingPeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
|
||||
tradingPeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
||||
return tradingPeer;
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,9 @@ package bisq.core.trade.protocol.tasks;
|
||||
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.trade.ArbitratorTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
@ -43,9 +44,7 @@ public class ApplyFilter extends TradeTask {
|
||||
runInterceptHook();
|
||||
|
||||
NodeAddress nodeAddress = checkNotNull(processModel.getTempTradingPeerNodeAddress());
|
||||
@Nullable
|
||||
PaymentAccountPayload paymentAccountPayload = processModel.getTradingPeer().getPaymentAccountPayload();
|
||||
|
||||
|
||||
FilterManager filterManager = processModel.getFilterManager();
|
||||
if (filterManager.isNodeAddressBanned(nodeAddress)) {
|
||||
failed("Other trader is banned by their node address.\n" +
|
||||
@ -59,9 +58,6 @@ public class ApplyFilter extends TradeTask {
|
||||
} else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) {
|
||||
failed("Payment method is banned.\n" +
|
||||
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
|
||||
} else if (paymentAccountPayload != null && filterManager.arePeersPaymentAccountDataBanned(paymentAccountPayload)) {
|
||||
failed("Other trader is banned by their trading account data.\n" +
|
||||
"paymentAccountPayload=" + paymentAccountPayload.getPaymentDetails());
|
||||
} else if (filterManager.requireUpdateToNewVersionForTrading()) {
|
||||
failed("Your version of Bisq is not compatible for trading anymore. " +
|
||||
"Please update to the latest Bisq version at https://bisq.network/downloads.");
|
||||
|
@ -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/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.taker;
|
||||
package bisq.core.trade.protocol.tasks;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import bisq.core.payment.payload.PaymentAccountPayload;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
import com.google.common.base.Charsets;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
/**
|
||||
* Arbitrator sends InitMultisigRequest to maker and taker if both reserve txs received.
|
||||
*/
|
||||
@Slf4j
|
||||
public class TakerSendInitMultisigMessages extends TradeTask {
|
||||
|
||||
public class ArbitratorSendsInitMultisigRequestsIfFundsReserved extends TradeTask {
|
||||
|
||||
private boolean takerAck;
|
||||
private boolean makerAck;
|
||||
private boolean arbitratorAck;
|
||||
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public TakerSendInitMultisigMessages(TaskRunner taskHandler, Trade trade) {
|
||||
public ArbitratorSendsInitMultisigRequestsIfFundsReserved(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@ -50,18 +51,23 @@ public class TakerSendInitMultisigMessages extends TradeTask {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
|
||||
// skip if arbitrator does not have maker reserve tx
|
||||
if (processModel.getMaker().getReserveTxHash() == null) {
|
||||
log.info("Arbitrator does not have maker reserve tx for offerId {}, waiting to receive before initializing multisig wallet", processModel.getOffer().getId());
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// create wallet for multisig
|
||||
// TODO (woodser): assert that wallet does not already exist
|
||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
|
||||
|
||||
MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId());
|
||||
|
||||
// prepare multisig
|
||||
String preparedHex = multisigWallet.prepareMultisig();
|
||||
processModel.setPreparedMultisigHex(preparedHex);
|
||||
System.out.println("Prepared multisig hex: " + preparedHex);
|
||||
|
||||
// create message to initialize trade
|
||||
InitMultisigMessage message = new InitMultisigMessage(
|
||||
// create message to initialize multisig
|
||||
InitMultisigRequest request = new InitMultisigRequest(
|
||||
processModel.getOffer().getId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getPubKeyRing(),
|
||||
@ -71,55 +77,55 @@ public class TakerSendInitMultisigMessages extends TradeTask {
|
||||
preparedHex,
|
||||
null);
|
||||
|
||||
// send request to arbitrator
|
||||
log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getArbitratorNodeAddress(),
|
||||
trade.getArbitratorPubKeyRing(),
|
||||
message,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
|
||||
arbitratorAck = true;
|
||||
checkComplete();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitratorNodeAddress(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// send request to maker
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getMakerNodeAddress());
|
||||
log.info("Send {} with offerId {} and uid {} to maker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getMakerNodeAddress(),
|
||||
trade.getMakerPubKeyRing(),
|
||||
message,
|
||||
request,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
|
||||
log.info("{} arrived at arbitrator: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid());
|
||||
makerAck = true;
|
||||
checkComplete();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getMakerNodeAddress(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// send request to taker
|
||||
log.info("Send {} with offerId {} and uid {} to taker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getTakerNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getTakerNodeAddress(),
|
||||
trade.getTakerPubKeyRing(),
|
||||
request,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid());
|
||||
takerAck = true;
|
||||
checkComplete();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getTakerNodeAddress(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkComplete() {
|
||||
if (makerAck && arbitratorAck) complete();
|
||||
if (makerAck && takerAck) complete();
|
||||
}
|
||||
}
|
@ -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.TakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.messages.InitMultisigMessage;
|
||||
import bisq.core.trade.messages.InitMultisigRequest;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
@ -46,7 +46,7 @@ import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroMultisigInitResult;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessInitMultisigMessage extends TradeTask {
|
||||
public class ProcessInitMultisigRequest extends TradeTask {
|
||||
|
||||
private boolean ack1 = false;
|
||||
private boolean ack2 = false;
|
||||
@ -54,7 +54,7 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
||||
MoneroWallet multisigWallet;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessInitMultisigMessage(TaskRunner taskHandler, Trade trade) {
|
||||
public ProcessInitMultisigRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@ -63,12 +63,12 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
||||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
InitMultisigMessage message = (InitMultisigMessage) processModel.getTradeMessage();
|
||||
checkNotNull(message);
|
||||
checkTradeId(processModel.getOfferId(), message);
|
||||
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
checkTradeId(processModel.getOfferId(), request);
|
||||
|
||||
System.out.println("PROCESS MULTISIG MESSAGE");
|
||||
System.out.println(message);
|
||||
System.out.println(request);
|
||||
// System.out.println("PROCESS MULTISIG MESSAGE TRADE");
|
||||
// System.out.println(trade);
|
||||
|
||||
@ -81,26 +81,26 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
||||
|
||||
// get peer multisig participant
|
||||
TradingPeer multisigParticipant;
|
||||
if (message.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker();
|
||||
else if (message.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker();
|
||||
else if (message.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator();
|
||||
if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) multisigParticipant = processModel.getMaker();
|
||||
else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) multisigParticipant = processModel.getTaker();
|
||||
else if (request.getSenderNodeAddress().equals(trade.getArbitratorNodeAddress())) multisigParticipant = processModel.getArbitrator();
|
||||
else throw new RuntimeException("Invalid sender to process init trade message: " + trade.getClass().getName());
|
||||
|
||||
// reconcile peer's established multisig hex with message
|
||||
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(message.getPreparedMultisigHex());
|
||||
else if (!multisigParticipant.getPreparedMultisigHex().equals(message.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + message.getPreparedMultisigHex());
|
||||
if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(message.getMadeMultisigHex());
|
||||
else if (!multisigParticipant.getMadeMultisigHex().equals(message.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages");
|
||||
|
||||
// get or create multisig wallet // TODO (woodser): ensure multisig wallet is created for first time
|
||||
multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
|
||||
|
||||
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.getPreparedMultisigHex());
|
||||
else if (!multisigParticipant.getPreparedMultisigHex().equals(request.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + request.getPreparedMultisigHex());
|
||||
if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(request.getMadeMultisigHex());
|
||||
else if (!multisigParticipant.getMadeMultisigHex().equals(request.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages");
|
||||
|
||||
// prepare multisig if applicable
|
||||
boolean updateParticipants = false;
|
||||
if (processModel.getPreparedMultisigHex() == null) {
|
||||
System.out.println("Preparing multisig wallet!");
|
||||
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId());
|
||||
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
||||
updateParticipants = true;
|
||||
} else {
|
||||
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
|
||||
}
|
||||
|
||||
// make multisig if applicable
|
||||
@ -145,38 +145,38 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
||||
}
|
||||
|
||||
if (peer1Address == null) throw new RuntimeException("Peer1 address is null");
|
||||
if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring");
|
||||
if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring is null");
|
||||
if (peer2Address == null) throw new RuntimeException("Peer2 address is null");
|
||||
if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring");
|
||||
if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring null");
|
||||
|
||||
// send to peer 1
|
||||
sendMultisigMessage(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
|
||||
sendInitMultisigRequest(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer1Address, message.getTradeId(), message.getUid());
|
||||
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer1Address, request.getTradeId(), request.getUid());
|
||||
ack1 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer1Address, errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer1Address, errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// send to peer 2
|
||||
sendMultisigMessage(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
|
||||
sendInitMultisigRequest(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: peer={}; offerId={}; uid={}", message.getClass().getSimpleName(), peer2Address, message.getTradeId(), message.getUid());
|
||||
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer2Address, request.getTradeId(), request.getUid());
|
||||
ack2 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), peer2Address, errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer2Address, errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
@ -204,10 +204,10 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
||||
return peers;
|
||||
}
|
||||
|
||||
private void sendMultisigMessage(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) {
|
||||
private void sendInitMultisigRequest(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) {
|
||||
|
||||
// create multisig message with current multisig hex
|
||||
InitMultisigMessage message = new InitMultisigMessage(
|
||||
InitMultisigRequest request = new InitMultisigRequest(
|
||||
processModel.getOffer().getId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getPubKeyRing(),
|
||||
@ -217,8 +217,8 @@ public class ProcessInitMultisigMessage extends TradeTask {
|
||||
processModel.getPreparedMultisigHex(),
|
||||
processModel.getMadeMultisigHex());
|
||||
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), recipient);
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, message, listener);
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient);
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener);
|
||||
}
|
||||
|
||||
private void completeAux() {
|
@ -19,18 +19,15 @@ package bisq.core.trade.protocol.tasks;
|
||||
|
||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||
import bisq.core.trade.ArbitratorTrade;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
import bisq.core.trade.messages.InitTradeRequest;
|
||||
import bisq.core.trade.protocol.TradingPeer;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
@ -49,82 +46,83 @@ public class ProcessInitTradeRequest extends TradeTask {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
||||
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
checkTradeId(processModel.getOfferId(), request);
|
||||
|
||||
System.out.println("PROCESS INIT TRADE REQUEST");
|
||||
System.out.println(request);
|
||||
|
||||
User user = checkNotNull(processModel.getUser(), "User must not be null");
|
||||
|
||||
// handle maker trade
|
||||
|
||||
// handle request as arbitrator
|
||||
TradingPeer multisigParticipant;
|
||||
if (trade instanceof MakerTrade) {
|
||||
|
||||
NodeAddress arbitratorNodeAddress = checkNotNull(request.getArbitratorNodeAddress(), "payDepositRequest.getMediatorNodeAddress() must not be null");
|
||||
Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(arbitratorNodeAddress), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // TODO (woodser): switch to arbitrator?
|
||||
|
||||
multisigParticipant = processModel.getTaker();
|
||||
trade.setTakerNodeAddress(request.getTakerNodeAddress());
|
||||
trade.setTakerPubKeyRing(request.getPubKeyRing());
|
||||
trade.setArbitratorNodeAddress(request.getArbitratorNodeAddress());
|
||||
trade.setArbitratorPubKeyRing(mediator.getPubKeyRing());
|
||||
if (trade instanceof ArbitratorTrade) {
|
||||
|
||||
// handle request from taker
|
||||
if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) {
|
||||
multisigParticipant = processModel.getTaker();
|
||||
if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree");
|
||||
if (trade.getTakerPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest");
|
||||
trade.setTakerPubKeyRing(request.getPubKeyRing());
|
||||
if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
|
||||
}
|
||||
|
||||
// handle request from maker
|
||||
else if (request.getSenderNodeAddress().equals(request.getMakerNodeAddress())) {
|
||||
multisigParticipant = processModel.getMaker();
|
||||
if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling, uninitialize trade for other takers
|
||||
if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing());
|
||||
else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
|
||||
trade.setMakerPubKeyRing(request.getPubKeyRing());
|
||||
} else {
|
||||
throw new RuntimeException("Sender is not trade's maker or taker");
|
||||
}
|
||||
}
|
||||
|
||||
// handle arbitrator trade
|
||||
else if (trade instanceof ArbitratorTrade) {
|
||||
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
|
||||
if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) {
|
||||
multisigParticipant = processModel.getMaker();
|
||||
if (!trade.getMakerNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling
|
||||
if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing());
|
||||
else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
|
||||
} else if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) {
|
||||
|
||||
// handle maker trade
|
||||
else if (trade instanceof MakerTrade) {
|
||||
multisigParticipant = processModel.getTaker();
|
||||
if (!trade.getTakerNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
|
||||
if (trade.getTakerPubKeyRing() == null) trade.setTakerPubKeyRing(request.getPubKeyRing());
|
||||
else if (!trade.getTakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
|
||||
} else {
|
||||
throw new RuntimeException("Sender is not trade's maker or taker");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName());
|
||||
trade.setTakerNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring
|
||||
trade.setTakerPubKeyRing(request.getPubKeyRing());
|
||||
}
|
||||
|
||||
// handle invalid trade type
|
||||
else {
|
||||
throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName());
|
||||
}
|
||||
|
||||
multisigParticipant.setPaymentAccountPayload(checkNotNull(request.getPaymentAccountPayload()));
|
||||
multisigParticipant.setPayoutAddressString(nonEmptyStringOf(request.getPayoutAddressString()));
|
||||
// set trading peer info
|
||||
if (multisigParticipant.getPaymentAccountId() == null) multisigParticipant.setPaymentAccountId(request.getPaymentAccountId());
|
||||
else if (multisigParticipant.getPaymentAccountId() != request.getPaymentAccountId()) throw new RuntimeException("Payment account id is different from previous");
|
||||
multisigParticipant.setPubKeyRing(checkNotNull(request.getPubKeyRing()));
|
||||
|
||||
multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId()));
|
||||
//trade.setTakerFeeTxId(nonEmptyStringOf(request.getTradeFeeTxId())); // TODO (woodser): no trade fee tx yet if creating multisig first
|
||||
|
||||
// Taker has to sign offerId (he cannot manipulate that - so we avoid to have a challenge protocol for passing the nonce we want to get signed)
|
||||
multisigParticipant.setPaymentMethodId(nonEmptyStringOf(request.getPaymentMethodId()));
|
||||
multisigParticipant.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
|
||||
multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId());
|
||||
multisigParticipant.setCurrentDate(request.getCurrentDate());
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
||||
// check trade price
|
||||
try {
|
||||
long takersTradePrice = request.getTradePrice();
|
||||
offer.checkTradePriceTolerance(takersTradePrice);
|
||||
trade.setTradePrice(takersTradePrice);
|
||||
long tradePrice = request.getTradePrice();
|
||||
offer.checkTradePriceTolerance(tradePrice);
|
||||
trade.setTradePrice(tradePrice);
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
failed(e.getMessage());
|
||||
} catch (Throwable e2) {
|
||||
failed(e2);
|
||||
}
|
||||
|
||||
// check trade amount
|
||||
checkArgument(request.getTradeAmount() > 0);
|
||||
trade.setTradeAmount(Coin.valueOf(request.getTradeAmount()));
|
||||
|
||||
// persist trade
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
@ -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();
|
||||
checkNotNull(request);
|
||||
checkTradeId(processModel.getOfferId(), request);
|
||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getOrCreateMultisigWallet(processModel.getTrade().getId());
|
||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
|
||||
|
||||
System.out.println("PROCESS UPDATE MULTISIG REQUEST");
|
||||
System.out.println(request);
|
||||
@ -85,13 +85,7 @@ public class ProcessUpdateMultisigRequest extends TradeTask {
|
||||
new Date().getTime(),
|
||||
updatedMultisigHex);
|
||||
|
||||
System.out.println("SENDING MESSAGE!!!!!!!");
|
||||
System.out.println(response);
|
||||
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid(), trade.getTradingPeerNodeAddress());
|
||||
System.out.println("GONNA BE BAD IF EITHER OF THESE ARE NULL");
|
||||
System.out.println(trade.getTradingPeerNodeAddress());
|
||||
System.out.println(trade.getTradingPeerPubKeyRing());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), response, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
|
@ -54,7 +54,7 @@ public class PublishTradeStatistics extends TradeTask {
|
||||
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
|
||||
}
|
||||
|
||||
NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress());
|
||||
NodeAddress mediatorNodeAddress = checkNotNull(trade.getArbitratorNodeAddress());
|
||||
// The first 4 chars are sufficient to identify a mediator.
|
||||
// For testing with regtest/localhost we use the full address as its localhost and would result in
|
||||
// same values for multiple mediators.
|
||||
@ -62,15 +62,15 @@ public class PublishTradeStatistics extends TradeTask {
|
||||
String address = networkNode instanceof TorNetworkNode ?
|
||||
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
||||
mediatorNodeAddress.getFullAddress();
|
||||
extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address);
|
||||
extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address);
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
|
||||
trade.getTradePrice(),
|
||||
trade.getTradeAmount(),
|
||||
trade.getDate(),
|
||||
trade.getMakerDepositTxId(),
|
||||
trade.getTakerDepositTxId(),
|
||||
trade.getMaker().getDepositTxHash(),
|
||||
trade.getTaker().getDepositTxHash(),
|
||||
extraDataMap);
|
||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user