diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 6e6eec11483..79ee123c2b2 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,6 +1,5 @@
-
\ No newline at end of file
diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java
index c5275945fdb..5763df44dbb 100644
--- a/common/src/main/java/bisq/common/app/Version.java
+++ b/common/src/main/java/bisq/common/app/Version.java
@@ -73,7 +73,7 @@ private static int getSubVersion(String version, int index) {
// The version no. for the objects sent over the network. A change will break the serialization of old objects.
// If objects are used for both network and database the network version is applied.
// VERSION = 0.5.0 -> P2P_NETWORK_VERSION = 1
- @SuppressWarnings("ConstantConditions")
+ // With version 1.2.0 we change to version 2 (new trade protocol)
public static final int P2P_NETWORK_VERSION = 1;
// The version no. of the serialized data stored to disc. A change will break the serialization of old objects.
@@ -84,7 +84,8 @@ private static int getSubVersion(String version, int index) {
// A taker will check the version of the offers to see if his version is compatible.
// Offers created with the old version will become invalid and have to be canceled.
// VERSION = 0.5.0 -> TRADE_PROTOCOL_VERSION = 1
- public static final int TRADE_PROTOCOL_VERSION = 1;
+ // Version 1.2.0 -> TRADE_PROTOCOL_VERSION = 2
+ public static final int TRADE_PROTOCOL_VERSION = 2;
private static int p2pMessageVersion;
public static final String BSQ_TX_VERSION = "1";
diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto
index 63385e522dc..e59d12e4170 100644
--- a/common/src/main/proto/pb.proto
+++ b/common/src/main/proto/pb.proto
@@ -36,9 +36,9 @@ message NetworkEnvelope {
CloseConnectionMessage close_connection_message = 15;
PrefixedSealedAndSignedMessage prefixed_sealed_and_signed_message = 16;
- PayDepositRequest pay_deposit_request = 17;
- PublishDepositTxRequest publish_deposit_tx_request = 18;
- DepositTxPublishedMessage deposit_tx_published_message = 19;
+ InputsForDepositTxRequest inputs_for_deposit_tx_request = 17;
+ InputsForDepositTxResponse inputs_for_deposit_tx_response = 18;
+ DepositTxMessage deposit_tx_message = 19;
CounterCurrencyTransferStartedMessage counter_currency_transfer_started_message = 20;
PayoutTxPublishedMessage payout_tx_published_message = 21;
@@ -70,6 +70,11 @@ message NetworkEnvelope {
BundleOfEnvelopes bundle_of_envelopes = 43;
MediatedPayoutTxSignatureMessage mediated_payout_tx_signature_message = 44;
MediatedPayoutTxPublishedMessage mediated_payout_tx_published_message = 45;
+
+ DelayedPayoutTxSignatureRequest delayed_payout_tx_signature_request = 46;
+ DelayedPayoutTxSignatureResponse delayed_payout_tx_signature_response = 47;
+ DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 48;
+ PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 49;
}
}
@@ -144,6 +149,7 @@ message OfferAvailabilityResponse {
string uid = 4;
NodeAddress arbitrator = 5;
NodeAddress mediator = 6;
+ NodeAddress refund_agent = 7;
}
message RefreshOfferMessage {
@@ -197,7 +203,7 @@ message PrefixedSealedAndSignedMessage {
// trade
-message PayDepositRequest {
+message InputsForDepositTxRequest {
string trade_id = 1;
NodeAddress sender_node_address = 2;
int64 trade_amount = 3;
@@ -221,9 +227,11 @@ message PayDepositRequest {
string uid = 21;
bytes account_age_witness_signature_of_offer_id = 22;
int64 current_date = 23;
+ repeated NodeAddress accepted_refund_agent_node_addresses = 24;
+ NodeAddress refund_agent_node_address = 25;
}
-message PublishDepositTxRequest {
+message InputsForDepositTxResponse {
string trade_id = 1;
PaymentAccountPayload maker_payment_account_payload = 2;
string maker_account_id = 3;
@@ -237,13 +245,42 @@ message PublishDepositTxRequest {
string uid = 11;
bytes account_age_witness_signature_of_prepared_deposit_tx = 12;
int64 current_date = 13;
+ int64 lock_time = 14;
}
-message DepositTxPublishedMessage {
- string trade_id = 1;
- bytes deposit_tx = 2;
+message DelayedPayoutTxSignatureRequest {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes delayed_payout_tx = 4;
+}
+
+message DelayedPayoutTxSignatureResponse {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes delayed_payout_tx_signature = 4;
+}
+
+message DepositTxAndDelayedPayoutTxMessage {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes deposit_tx = 4;
+ bytes delayed_payout_tx = 5;
+}
+
+message DepositTxMessage {
+ string uid = 1;
+ string trade_id = 2;
+ NodeAddress sender_node_address = 3;
+ bytes deposit_tx = 4;
+}
+
+message PeerPublishedDelayedPayoutTxMessage {
+ string uid = 1;
+ string trade_id = 2;
NodeAddress sender_node_address = 3;
- string uid = 4;
}
message CounterCurrencyTransferStartedMessage {
@@ -279,8 +316,8 @@ message MediatedPayoutTxPublishedMessage {
message MediatedPayoutTxSignatureMessage {
string uid = 1;
- bytes tx_signature = 2;
string trade_id = 3;
+ bytes tx_signature = 2;
NodeAddress sender_node_address = 4;
}
@@ -290,6 +327,7 @@ enum SupportType {
ARBITRATION = 0;
MEDIATION = 1;
TRADE = 2;
+ REFUND = 3;
}
message OpenNewDisputeMessage {
@@ -431,7 +469,7 @@ message Peer {
message PubKeyRing {
bytes signature_pub_key_bytes = 1;
bytes encryption_pub_key_bytes = 2;
- reserved 3; // WAS: string pgp_pub_key_as_pem = 3;
+ reserved 3; // WAS: string pgp_pub_key_as_pem = 3;
}
message SealedAndSigned {
@@ -457,6 +495,7 @@ message StoragePayload {
MailboxStoragePayload mailbox_storage_payload = 6;
OfferPayload offer_payload = 7;
TempProposalPayload temp_proposal_payload = 8;
+ RefundAgent refund_agent = 9;
}
}
@@ -550,6 +589,18 @@ message Mediator {
map extra_data = 9;
}
+message RefundAgent {
+ NodeAddress node_address = 1;
+ repeated string language_codes = 2;
+ int64 registration_date = 3;
+ string registration_signature = 4;
+ bytes registration_pub_key = 5;
+ PubKeyRing pub_key_ring = 6;
+ string email_address = 7;
+ string info = 8;
+ map extra_data = 9;
+}
+
message Filter {
repeated string banned_node_address = 1;
repeated string banned_offer_ids = 2;
@@ -568,6 +619,7 @@ message Filter {
string disable_dao_below_version = 15;
string disable_trade_below_version = 16;
repeated string mediators = 17;
+ repeated string refundAgents = 18;
}
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
@@ -708,6 +760,8 @@ message Dispute {
bool is_closed = 21;
DisputeResult dispute_result = 22;
string dispute_payout_tx_id = 23;
+ SupportType support_type = 24;
+ string mediators_dispute_result = 25;
}
message Attachment {
@@ -774,6 +828,8 @@ message Contract {
bytes maker_multi_sig_pub_key = 17;
bytes taker_multi_sig_pub_key = 18;
NodeAddress mediator_node_address = 19;
+ int64 lock_time = 20;
+ NodeAddress refund_agent_node_address = 21;
}
message RawTransactionInput {
@@ -793,6 +849,7 @@ enum AvailabilityResult {
NO_MEDIATORS = 7;
USER_IGNORED = 8;
MISSING_MANDATORY_CAPABILITY = 9;
+ NO_REFUND_AGENTS = 10;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -1074,6 +1131,7 @@ message PersistableEnvelope {
UnconfirmedBsqChangeOutputList unconfirmed_bsq_change_output_list = 27;
SignedWitnessStore signed_witness_store = 28;
MediationDisputeList mediation_dispute_list = 29;
+ RefundDisputeList refund_dispute_list = 30;
}
}
@@ -1198,6 +1256,7 @@ message OpenOffer {
State state = 2;
NodeAddress arbitrator_node_address = 3;
NodeAddress mediator_node_address = 4;
+ NodeAddress refund_agent_node_address = 5;
}
message Tradable {
@@ -1220,13 +1279,13 @@ message Trade {
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 5;
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 6;
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST = 7;
- TAKER_PUBLISHED_DEPOSIT_TX = 8;
- TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9;
- TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10;
- TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11;
- TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12;
- MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
- MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
+ SELLER_PUBLISHED_DEPOSIT_TX = 8;
+ SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 9;
+ SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 10;
+ SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 11;
+ SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 12;
+ BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
+ BUYER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN = 15;
BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED = 16;
BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG = 17;
@@ -1266,6 +1325,9 @@ message Trade {
MEDIATION_REQUESTED = 5;
MEDIATION_STARTED_BY_PEER = 6;
MEDIATION_CLOSED = 7;
+ REFUND_REQUESTED = 8;
+ REFUND_REQUEST_STARTED_BY_PEER = 9;
+ REFUND_REQUEST_CLOSED = 10;
}
enum TradePeriodState {
@@ -1305,6 +1367,11 @@ message Trade {
string counter_currency_tx_id = 28;
repeated ChatMessage chat_message = 29;
MediationResultState mediation_result_state = 30;
+ int64 lock_time = 31;
+ string delayed_payout_tx_id = 32;
+ NodeAddress refund_agent_node_address = 33;
+ PubKeyRing refund_agent_pub_key_ring = 34;
+ RefundResultState refund_result_state = 35;
}
message BuyerAsMakerTrade {
@@ -1330,8 +1397,8 @@ message ProcessModel {
PubKeyRing pub_key_ring = 4;
string take_offer_fee_tx_id = 5;
bytes payout_tx_signature = 6;
- repeated NodeAddress taker_accepted_arbitrator_node_addresses = 7;
- repeated NodeAddress taker_accepted_mediator_node_addresses = 8;
+ reserved 7; // Not used anymore
+ reserved 8; // Not used anymore
bytes prepared_deposit_tx = 9;
repeated RawTransactionInput raw_transaction_inputs = 10;
int64 change_output_value = 11;
@@ -1376,6 +1443,10 @@ message MediationDisputeList {
repeated Dispute dispute = 1;
}
+message RefundDisputeList {
+ repeated Dispute dispute = 1;
+}
+
enum MediationResultState {
PB_ERROR_MEDIATION_RESULT = 0;
UNDEFINED_MEDIATION_RESULT = 1;
@@ -1395,6 +1466,12 @@ enum MediationResultState {
PAYOUT_TX_SEEN_IN_NETWORK = 15;
}
+//todo
+enum RefundResultState {
+ PB_ERROR_REFUND_RESULT = 0;
+ UNDEFINED_REFUND_RESULT = 1;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
// Preferences
///////////////////////////////////////////////////////////////////////////////////////////
@@ -1454,6 +1531,7 @@ message PreferencesPayload {
double buyer_security_deposit_as_percent_for_crypto = 52;
int32 block_notify_port = 53;
int32 css_theme = 54;
+ bool tac_accepted_v120 = 55;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -1474,6 +1552,8 @@ message UserPayload {
Mediator registered_mediator = 11;
PriceAlertFilter price_alert_filter = 12;
repeated MarketAlertFilter market_alert_filters = 13;
+ repeated RefundAgent accepted_refund_agents = 14;
+ RefundAgent registered_refund_agent = 15;
}
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java
index 18763753fcf..897899cc223 100644
--- a/core/src/main/java/bisq/core/app/BisqSetup.java
+++ b/core/src/main/java/bisq/core/app/BisqSetup.java
@@ -49,6 +49,8 @@
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.RefundManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.support.traderchat.TraderChatManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.statistics.AssetTradeActivityCheck;
@@ -131,11 +133,13 @@ public interface BisqSetupCompleteListener {
private final PriceFeedService priceFeedService;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
+ private final RefundAgentManager refundAgentManager;
private final P2PService p2PService;
private final TradeManager tradeManager;
private final OpenOfferManager openOfferManager;
private final ArbitrationManager arbitrationManager;
private final MediationManager mediationManager;
+ private final RefundManager refundManager;
private final TraderChatManager traderChatManager;
private final Preferences preferences;
private final User user;
@@ -213,11 +217,13 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup,
PriceFeedService priceFeedService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
P2PService p2PService,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
ArbitrationManager arbitrationManager,
MediationManager mediationManager,
+ RefundManager refundManager,
TraderChatManager traderChatManager,
Preferences preferences,
User user,
@@ -257,11 +263,13 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup,
this.priceFeedService = priceFeedService;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
this.p2PService = p2PService;
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
+ this.refundManager = refundManager;
this.traderChatManager = traderChatManager;
this.preferences = preferences;
this.user = user;
@@ -428,10 +436,10 @@ private void maybeReSyncSPVChain() {
}
private void maybeShowTac() {
- if (!preferences.isTacAccepted() && !DevEnv.isDevMode()) {
+ if (!preferences.isTacAcceptedV120() && !DevEnv.isDevMode()) {
if (displayTacHandler != null)
displayTacHandler.accept(() -> {
- preferences.setTacAccepted(true);
+ preferences.setTacAcceptedV120(true);
step2();
});
} else {
@@ -618,6 +626,7 @@ private void initDomainServices() {
arbitrationManager.onAllServicesInitialized();
mediationManager.onAllServicesInitialized();
+ refundManager.onAllServicesInitialized();
traderChatManager.onAllServicesInitialized();
tradeManager.onAllServicesInitialized();
@@ -631,6 +640,7 @@ private void initDomainServices() {
arbitratorManager.onAllServicesInitialized();
mediatorManager.onAllServicesInitialized();
+ refundAgentManager.onAllServicesInitialized();
alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) ->
displayAlertIfPresent(newValue, false));
diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java
index 984211c451e..cf85da0b98d 100644
--- a/core/src/main/java/bisq/core/btc/Balances.java
+++ b/core/src/main/java/bisq/core/btc/Balances.java
@@ -22,6 +22,8 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
@@ -52,6 +54,7 @@ public class Balances {
private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
+ private final RefundManager refundManager;
@Getter
private final ObjectProperty availableBalance = new SimpleObjectProperty<>();
@@ -65,17 +68,20 @@ public Balances(TradeManager tradeManager,
BtcWalletService btcWalletService,
OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager,
- FailedTradesManager failedTradesManager) {
+ FailedTradesManager failedTradesManager,
+ RefundManager refundManager) {
this.tradeManager = tradeManager;
this.btcWalletService = btcWalletService;
this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
+ this.refundManager = refundManager;
}
public void onAllServicesInitialized() {
openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance());
tradeManager.getTradableList().addListener((ListChangeListener) change -> updateBalance());
+ refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updateBalance());
btcWalletService.addBalanceListener(new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
diff --git a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java
index 9a2e1c76ba5..c0fbfb5b682 100644
--- a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java
+++ b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java
@@ -126,6 +126,25 @@ private Tuple2 getEstimatedFeeAndTxSize(boolean isTaker,
return new Tuple2<>(txFee, size);
}
+ public Tuple2 getEstimatedFeeAndTxSize(Coin amount,
+ FeeService feeService,
+ BtcWalletService btcWalletService) {
+ Coin txFeePerByte = feeService.getTxFeePerByte();
+ // We start with min taker fee size of 260
+ int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
+ try {
+ estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, btcWalletService);
+ } catch (InsufficientMoneyException e) {
+ log.info("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +
+ "if the user pays from an external wallet. In that case we use an estimated tx size of {} bytes.", estimatedTxSize);
+ }
+
+ Coin txFee = txFeePerByte.multiply(estimatedTxSize);
+ log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {} Sat.", estimatedTxSize, txFee.value);
+
+ return new Tuple2<>(txFee, estimatedTxSize);
+ }
+
// We start with the initialEstimatedTxSize for a tx with 1 input (260) bytes and get from BitcoinJ a tx back which
// contains the required inputs to fund that tx (outputs + miner fee). The miner fee in that case is based on
// the assumption that we only need 1 input. Once we receive back the real tx size from the tx BitcoinJ has created
diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java
index 0e0ee6f0e9d..4841551cb9a 100644
--- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java
@@ -1114,4 +1114,55 @@ private SendRequest getSendRequestForMultipleAddresses(Set fromAddresses
protected boolean isDustAttackUtxo(TransactionOutput output) {
return output.getValue().value < preferences.getIgnoreDustThreshold();
}
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Refund payoutTx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public Transaction createRefundPayoutTx(Coin buyerAmount,
+ Coin sellerAmount,
+ Coin fee,
+ String buyerAddressString,
+ String sellerAddressString)
+ throws AddressFormatException, InsufficientMoneyException, WalletException, TransactionVerificationException {
+ Transaction tx = new Transaction(params);
+ Preconditions.checkArgument(buyerAmount.add(sellerAmount).isPositive(),
+ "The sellerAmount + buyerAmount must be positive.");
+ // buyerAmount can be 0
+ if (buyerAmount.isPositive()) {
+ Preconditions.checkArgument(Restrictions.isAboveDust(buyerAmount),
+ "The buyerAmount is too low (dust limit).");
+
+ tx.addOutput(buyerAmount, Address.fromBase58(params, buyerAddressString));
+ }
+ // sellerAmount can be 0
+ if (sellerAmount.isPositive()) {
+ Preconditions.checkArgument(Restrictions.isAboveDust(sellerAmount),
+ "The sellerAmount is too low (dust limit).");
+
+ tx.addOutput(sellerAmount, Address.fromBase58(params, sellerAddressString));
+ }
+
+ SendRequest sendRequest = SendRequest.forTx(tx);
+ sendRequest.fee = fee;
+ sendRequest.feePerKb = Coin.ZERO;
+ sendRequest.ensureMinRequiredFee = false;
+ sendRequest.aesKey = aesKey;
+ sendRequest.shuffleOutputs = false;
+ sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE),
+ preferences.getIgnoreDustThreshold());
+ sendRequest.changeAddress = getFreshAddressEntry().getAddress();
+
+ checkNotNull(wallet);
+ wallet.completeTx(sendRequest);
+
+ Transaction resultTx = sendRequest.tx;
+ checkWalletConsistency(wallet);
+ verifyTransaction(resultTx);
+
+ WalletService.printTx("createRefundPayoutTx", resultTx);
+
+ return resultTx;
+ }
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
index ac18d14e10a..d0fba89d717 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
@@ -72,46 +72,6 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-// TradeService handles all relevant transactions used in the trade process
-/*
- To maintain a consistent tx structure we use that structure:
- Always buyers in/outputs/keys first then sellers in/outputs/keys the arbitrators outputs/keys.
-
- Deposit tx:
- IN[0] buyer (mandatory) e.g. 0.1 BTC
- IN[...] optional additional buyer inputs (normally never used as we pay from trade fee tx and always have 1 output there)
- IN[...] seller (mandatory) e.g. 1.1001 BTC
- IN[...] optional additional seller inputs (normally never used as we pay from trade fee tx and always have 1 output there)
- OUT[0] Multisig output (include tx fee for payout tx) e.g. 1.2001
- OUT[1] OP_RETURN with hash of contract and 0 BTC amount
- OUT[...] optional buyer change (normally never used as we pay from trade fee tx and always have 1 output there)
- OUT[...] optional seller change (normally never used as we pay from trade fee tx and always have 1 output there)
- FEE tx fee 0.0001 BTC
-
- Payout tx:
- IN[0] Multisig output from deposit Tx (signed by buyer and trader)
- OUT[0] Buyer payout address
- OUT[1] Seller payout address
-
- We use 0 confirmation transactions to make the trade process practical from usability side.
- There is no risk for double spends as the deposit transaction would become invalid if any preceding transaction would have been double spent.
- If a preceding transaction in the chain will not make it into the same or earlier block as the deposit transaction the deposit transaction
- will be invalid as well.
- Though the deposit need 1 confirmation before the buyer starts the Fiat payment.
-
- We have that chain of transactions:
- 1. Deposit from external wallet to our trading wallet: Tx0 (0 conf)
- 2. Create offer (or take offer) fee payment from Tx0 output: tx1 (0 conf)
- 3. Deposit tx created with inputs from tx1 of both traders: Tx2 (here we wait for 1 conf)
-
- Fiat transaction will not start before we get at least 1 confirmation for the deposit tx, then we can proceed.
- 4. Payout tx with input from MS output and output to both traders: Tx3 (0 conf)
- 5. Withdrawal to external wallet from Tx3: Tx4 (0 conf)
-
- After the payout transaction we also don't have issues with 0 conf or if not both tx (payout, withdrawal) make it into a block.
- Worst case is to rebroadcast the transactions (TODO: is not implemented yet).
-
- */
public class TradeWalletService {
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
@@ -178,16 +138,7 @@ public Transaction createBtcTradingFeeTx(Address fundingAddress,
Coin txFee,
String feeReceiverAddresses,
boolean doBroadcast,
- @Nullable TxBroadcaster.Callback callback)
- throws InsufficientMoneyException, AddressFormatException {
- log.debug("fundingAddress {}", fundingAddress);
- log.debug("reservedForTradeAddress {}", reservedForTradeAddress);
- log.debug("changeAddress {}", changeAddress);
- log.info("reservedFundsForOffer {}", reservedFundsForOffer.toPlainString());
- log.debug("useSavingsWallet {}", useSavingsWallet);
- log.info("tradingFee {}", tradingFee.toPlainString());
- log.info("txFee {}", txFee.toPlainString());
- log.debug("feeReceiverAddresses {}", feeReceiverAddresses);
+ @Nullable TxBroadcaster.Callback callback) throws InsufficientMoneyException, AddressFormatException {
Transaction tradingFeeTx = new Transaction(params);
SendRequest sendRequest = null;
try {
@@ -220,17 +171,18 @@ public Transaction createBtcTradingFeeTx(Address fundingAddress,
wallet.completeTx(sendRequest);
WalletService.printTx("tradingFeeTx", tradingFeeTx);
- if (doBroadcast && callback != null)
+ if (doBroadcast && callback != null) {
broadcastTx(tradingFeeTx, callback);
+ }
return tradingFeeTx;
} catch (Throwable t) {
- if (wallet != null && sendRequest != null && sendRequest.coinSelector != null)
- log.warn("Balance = {}; CoinSelector = {}",
- wallet.getBalance(sendRequest.coinSelector),
- sendRequest.coinSelector);
+ if (wallet != null && sendRequest != null && sendRequest.coinSelector != null) {
+ log.warn("Balance = {}; CoinSelector = {}", wallet.getBalance(sendRequest.coinSelector), sendRequest.coinSelector);
+ }
- log.warn("createBtcTradingFeeTx failed: tradingFeeTx={}, txOutputs={}", tradingFeeTx.toString(), tradingFeeTx.getOutputs());
+ log.warn("createBtcTradingFeeTx failed: tradingFeeTx={}, txOutputs={}", tradingFeeTx.toString(),
+ tradingFeeTx.getOutputs());
throw t;
}
}
@@ -241,17 +193,8 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
Address changeAddress,
Coin reservedFundsForOffer,
boolean useSavingsWallet,
- Coin txFee) throws
- TransactionVerificationException, WalletException,
- InsufficientMoneyException, AddressFormatException {
-
- log.debug("preparedBsqTx {}", preparedBsqTx);
- log.debug("fundingAddress {}", fundingAddress);
- log.debug("changeAddress {}", changeAddress);
- log.debug("reservedFundsForOffer {}", reservedFundsForOffer.toPlainString());
- log.debug("useSavingsWallet {}", useSavingsWallet);
- log.debug("txFee {}", txFee.toPlainString());
-
+ Coin txFee)
+ throws TransactionVerificationException, WalletException, InsufficientMoneyException, AddressFormatException {
// preparedBsqTx has following structure:
// inputs [1-n] BSQ inputs
// outputs [0-1] BSQ change output
@@ -306,7 +249,8 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
// Sign all BTC inputs
for (int i = preparedBsqTxInputsSize; i < resultTx.getInputs().size(); i++) {
TransactionInput txIn = resultTx.getInputs().get(i);
- checkArgument(txIn.getConnectedOutput() != null && txIn.getConnectedOutput().isMine(wallet),
+ checkArgument(txIn.getConnectedOutput() != null &&
+ txIn.getConnectedOutput().isMine(wallet),
"txIn.getConnectedOutput() is not in our wallet. That must not happen.");
WalletService.signTransactionInput(wallet, aesKey, resultTx, txIn, i);
WalletService.checkScriptSig(resultTx, txIn, i);
@@ -321,9 +265,10 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Trade
+ // Deposit tx
///////////////////////////////////////////////////////////////////////////////////////////
+
// We construct the deposit transaction in the way that the buyer is always the first entry (inputs, outputs, MS keys) and then the seller.
// In the creation of the deposit tx the taker/maker roles are the determining roles instead of buyer/seller.
// In the payout tx is is the buyer/seller role. We keep the buyer/seller ordering over all transactions to not get confusion with ordering,
@@ -341,18 +286,10 @@ public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx,
* @return A data container holding the inputs, the output value and address
* @throws TransactionVerificationException
*/
- public InputsAndChangeOutput takerCreatesDepositsTxInputs(Transaction takeOfferFeeTx,
- Coin inputAmount,
- Coin txFee,
- Address takersAddress) throws
- TransactionVerificationException {
- if (log.isDebugEnabled()) {
- log.debug("takerCreatesDepositsTxInputs called");
- log.debug("inputAmount {}", inputAmount.toFriendlyString());
- log.debug("txFee {}", txFee.toFriendlyString());
- log.debug("takersAddress {}", takersAddress.toString());
- }
-
+ public InputsAndChangeOutput takerCreatesDepositTxInputs(Transaction takeOfferFeeTx,
+ Coin inputAmount,
+ Coin txFee)
+ throws TransactionVerificationException {
// We add the mining fee 2 times to the deposit tx:
// 1. Will be spent when publishing the deposit tx (paid by buyer)
// 2. Will be added to the MS amount, so when publishing the payout tx the fee is already there and the outputs are not changed by fee reduction
@@ -390,14 +327,13 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee)
//WalletService.printTx("dummyTX", dummyTX);
- List rawTransactionInputList = dummyTX.getInputs().stream()
- .map(e -> {
- checkNotNull(e.getConnectedOutput(), "e.getConnectedOutput() must not be null");
- checkNotNull(e.getConnectedOutput().getParentTransaction(), "e.getConnectedOutput().getParentTransaction() must not be null");
- checkNotNull(e.getValue(), "e.getValue() must not be null");
- return getRawInputFromTransactionInput(e);
- })
- .collect(Collectors.toList());
+ List rawTransactionInputList = dummyTX.getInputs().stream().map(e -> {
+ checkNotNull(e.getConnectedOutput(), "e.getConnectedOutput() must not be null");
+ checkNotNull(e.getConnectedOutput().getParentTransaction(),
+ "e.getConnectedOutput().getParentTransaction() must not be null");
+ checkNotNull(e.getValue(), "e.getValue() must not be null");
+ return getRawInputFromTransactionInput(e);
+ }).collect(Collectors.toList());
// TODO changeOutputValue and changeOutputAddress is not used as taker spends exact amount from fee tx.
@@ -408,6 +344,54 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee)
return new InputsAndChangeOutput(new ArrayList<>(rawTransactionInputList), 0, null);
}
+ public PreparedDepositTxAndMakerInputs sellerAsMakerCreatesDepositTx(byte[] contractHash,
+ Coin makerInputAmount,
+ Coin msOutputAmount,
+ List takerRawTransactionInputs,
+ long takerChangeOutputValue,
+ @Nullable String takerChangeAddressString,
+ Address makerAddress,
+ Address makerChangeAddress,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
+ return makerCreatesDepositTx(false,
+ contractHash,
+ makerInputAmount,
+ msOutputAmount,
+ takerRawTransactionInputs,
+ takerChangeOutputValue,
+ takerChangeAddressString,
+ makerAddress,
+ makerChangeAddress,
+ buyerPubKey,
+ sellerPubKey);
+ }
+
+ public PreparedDepositTxAndMakerInputs buyerAsMakerCreatesAndSignsDepositTx(byte[] contractHash,
+ Coin makerInputAmount,
+ Coin msOutputAmount,
+ List takerRawTransactionInputs,
+ long takerChangeOutputValue,
+ @Nullable String takerChangeAddressString,
+ Address makerAddress,
+ Address makerChangeAddress,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
+ return makerCreatesDepositTx(true,
+ contractHash,
+ makerInputAmount,
+ msOutputAmount,
+ takerRawTransactionInputs,
+ takerChangeOutputValue,
+ takerChangeAddressString,
+ makerAddress,
+ makerChangeAddress,
+ buyerPubKey,
+ sellerPubKey);
+ }
+
/**
* The maker creates the deposit transaction using the takers input(s) and optional output and signs his input(s).
*
@@ -422,38 +406,23 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee)
* @param makerChangeAddress The maker's change address.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
* @return A data container holding the serialized transaction and the maker raw inputs
* @throws SigningException
* @throws TransactionVerificationException
* @throws WalletException
*/
- public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean makerIsBuyer,
- byte[] contractHash,
- Coin makerInputAmount,
- Coin msOutputAmount,
- List takerRawTransactionInputs,
- long takerChangeOutputValue,
- @Nullable String takerChangeAddressString,
- Address makerAddress,
- Address makerChangeAddress,
- byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ private PreparedDepositTxAndMakerInputs makerCreatesDepositTx(boolean makerIsBuyer,
+ byte[] contractHash,
+ Coin makerInputAmount,
+ Coin msOutputAmount,
+ List takerRawTransactionInputs,
+ long takerChangeOutputValue,
+ @Nullable String takerChangeAddressString,
+ Address makerAddress,
+ Address makerChangeAddress,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
- log.debug("makerCreatesAndSignsDepositTx called");
- log.debug("makerIsBuyer {}", makerIsBuyer);
- log.debug("makerInputAmount {}", makerInputAmount.toFriendlyString());
- log.debug("msOutputAmount {}", msOutputAmount.toFriendlyString());
- log.debug("takerRawInputs {}", takerRawTransactionInputs.toString());
- log.debug("takerChangeOutputValue {}", takerChangeOutputValue);
- log.debug("takerChangeAddressString {}", takerChangeAddressString);
- log.debug("makerAddress {}", makerAddress);
- log.debug("makerChangeAddress {}", makerChangeAddress);
- log.debug("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey));
- log.debug("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey));
- log.debug("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey));
-
checkArgument(!takerRawTransactionInputs.isEmpty());
// First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx.
@@ -461,7 +430,7 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
Transaction dummyTx = new Transaction(params);
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, new ECKey().toAddress(params));
dummyTx.addOutput(dummyOutput);
- addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress, Coin.ZERO);
+ addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress);
// Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
List makerInputs = dummyTx.getInputs();
TransactionOutput makerOutput = null;
@@ -470,8 +439,9 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
checkArgument(dummyTx.getOutputs().size() < 3, "dummyTx.getOutputs().size() >= 3");
// Only save change outputs, the dummy output is ignored (that's why we start with index 1)
- if (dummyTx.getOutputs().size() > 1)
+ if (dummyTx.getOutputs().size() > 1) {
makerOutput = dummyTx.getOutput(1);
+ }
// Now we construct the real deposit tx
Transaction preparedDepositTx = new Transaction(params);
@@ -505,10 +475,11 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
// Add MultiSig output
- Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
// Tx fee for deposit tx will be paid by buyer.
- TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount, p2SHMultiSigOutputScript.getProgram());
+ TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount,
+ p2SHMultiSigOutputScript.getProgram());
preparedDepositTx.addOutput(p2SHMultiSigOutput);
// We add the hash ot OP_RETURN with a 0 amount output
@@ -517,31 +488,35 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
preparedDepositTx.addOutput(contractHashOutput);
TransactionOutput takerTransactionOutput = null;
- if (takerChangeOutputValue > 0 && takerChangeAddressString != null)
+ if (takerChangeOutputValue > 0 && takerChangeAddressString != null) {
takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue),
Address.fromBase58(params, takerChangeAddressString));
+ }
if (makerIsBuyer) {
// Add optional buyer outputs
- if (makerOutput != null)
+ if (makerOutput != null) {
preparedDepositTx.addOutput(makerOutput);
+ }
// Add optional seller outputs
- if (takerTransactionOutput != null)
+ if (takerTransactionOutput != null) {
preparedDepositTx.addOutput(takerTransactionOutput);
+ }
} else {
// taker is buyer role
// Add optional seller outputs
- if (takerTransactionOutput != null)
+ if (takerTransactionOutput != null) {
preparedDepositTx.addOutput(takerTransactionOutput);
+ }
// Add optional buyer outputs
- if (makerOutput != null)
+ if (makerOutput != null) {
preparedDepositTx.addOutput(makerOutput);
+ }
}
- // Sign inputs
int start = makerIsBuyer ? 0 : takerRawTransactionInputs.size();
int end = makerIsBuyer ? makerInputs.size() : preparedDepositTx.getInputs().size();
for (int i = start; i < end; i++) {
@@ -550,8 +525,7 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
WalletService.checkScriptSig(preparedDepositTx, input, i);
}
- WalletService.printTx("prepared depositTx", preparedDepositTx);
-
+ WalletService.printTx("makerCreatesDepositTx", preparedDepositTx);
WalletService.verifyTransaction(preparedDepositTx);
return new PreparedDepositTxAndMakerInputs(makerRawTransactionInputs, preparedDepositTx.bitcoinSerialize());
@@ -567,40 +541,28 @@ public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean mak
* @param sellerInputs The connected outputs for all inputs of the seller.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
- * @param callback Callback when transaction is broadcasted.
* @throws SigningException
* @throws TransactionVerificationException
* @throws WalletException
*/
- public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
- byte[] contractHash,
- byte[] makersDepositTxSerialized,
- List buyerInputs,
- List sellerInputs,
- byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey,
- TxBroadcaster.Callback callback) throws SigningException, TransactionVerificationException,
- WalletException {
+ public Transaction takerSignsDepositTx(boolean takerIsSeller,
+ byte[] contractHash,
+ byte[] makersDepositTxSerialized,
+ List buyerInputs,
+ List sellerInputs,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws SigningException, TransactionVerificationException, WalletException {
Transaction makersDepositTx = new Transaction(params, makersDepositTxSerialized);
- log.debug("signAndPublishDepositTx called");
- log.debug("takerIsSeller {}", takerIsSeller);
- log.debug("makersDepositTx {}", makersDepositTx.toString());
- log.debug("buyerConnectedOutputsForAllInputs {}", buyerInputs.toString());
- log.debug("sellerConnectedOutputsForAllInputs {}", sellerInputs.toString());
- log.debug("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.debug("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.debug("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
checkArgument(!buyerInputs.isEmpty());
checkArgument(!sellerInputs.isEmpty());
// Check if maker's Multisig script is identical to the takers
- Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
- if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript))
+ Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
+ if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript)) {
throw new TransactionVerificationException("Maker's p2SHMultiSigOutputScript does not match to takers p2SHMultiSigOutputScript");
+ }
// The outpoints are not available from the serialized makersDepositTx, so we cannot use that tx directly, but we use it to construct a new
// depositTx
@@ -609,22 +571,29 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
if (takerIsSeller) {
// Add buyer inputs and apply signature
// We grab the signature from the makersDepositTx and apply it to the new tx input
- for (int i = 0; i < buyerInputs.size(); i++)
- depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), buyerInputs.get(i)));
+ for (int i = 0; i < buyerInputs.size(); i++) {
+ TransactionInput transactionInput = makersDepositTx.getInputs().get(i);
+ depositTx.addInput(getTransactionInput(depositTx, getMakersScriptSigProgram(transactionInput), buyerInputs.get(i)));
+ }
// Add seller inputs
- for (RawTransactionInput rawTransactionInput : sellerInputs)
+ for (RawTransactionInput rawTransactionInput : sellerInputs) {
depositTx.addInput(getTransactionInput(depositTx, new byte[]{}, rawTransactionInput));
+ }
} else {
// taker is buyer
// Add buyer inputs and apply signature
- for (RawTransactionInput rawTransactionInput : buyerInputs)
+ for (RawTransactionInput rawTransactionInput : buyerInputs) {
depositTx.addInput(getTransactionInput(depositTx, new byte[]{}, rawTransactionInput));
+ }
// Add seller inputs
// We grab the signature from the makersDepositTx and apply it to the new tx input
- for (int i = buyerInputs.size(), k = 0; i < makersDepositTx.getInputs().size(); i++, k++)
- depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), sellerInputs.get(k)));
+ for (int i = buyerInputs.size(), k = 0; i < makersDepositTx.getInputs().size(); i++, k++) {
+ TransactionInput transactionInput = makersDepositTx.getInputs().get(i);
+ // We get the deposit tx unsigned if maker is seller
+ depositTx.addInput(getTransactionInput(depositTx, new byte[]{}, sellerInputs.get(k)));
+ }
}
// Check if OP_RETURN output with contract hash matches the one from the maker
@@ -633,12 +602,13 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
log.debug("contractHashOutput {}", contractHashOutput);
TransactionOutput makersContractHashOutput = makersDepositTx.getOutputs().get(1);
log.debug("makersContractHashOutput {}", makersContractHashOutput);
- if (!makersContractHashOutput.getScriptPubKey().equals(contractHashOutput.getScriptPubKey()))
+ if (!makersContractHashOutput.getScriptPubKey().equals(contractHashOutput.getScriptPubKey())) {
throw new TransactionVerificationException("Maker's transaction output for the contract hash is not matching takers version.");
+ }
// Add all outputs from makersDepositTx to depositTx
makersDepositTx.getOutputs().forEach(depositTx::addOutput);
- //WalletService.printTx("makersDepositTx", makersDepositTx);
+ WalletService.printTx("makersDepositTx", makersDepositTx);
// Sign inputs
int start = takerIsSeller ? buyerInputs.size() : 0;
@@ -649,17 +619,113 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
WalletService.checkScriptSig(depositTx, input, i);
}
- WalletService.printTx("depositTx", depositTx);
+ WalletService.printTx("takerSignsDepositTx", depositTx);
WalletService.verifyTransaction(depositTx);
WalletService.checkWalletConsistency(wallet);
- broadcastTx(depositTx, callback);
-
return depositTx;
}
+ public void sellerAsMakerFinalizesDepositTx(Transaction myDepositTx, Transaction takersDepositTx, int numTakersInputs)
+ throws TransactionVerificationException, AddressFormatException {
+
+ // We add takers signature from his inputs and add it to out tx which was already signed earlier.
+ for (int i = 0; i < numTakersInputs; i++) {
+ TransactionInput input = takersDepositTx.getInput(i);
+ Script scriptSig = input.getScriptSig();
+ myDepositTx.getInput(i).setScriptSig(scriptSig);
+ }
+
+ WalletService.printTx("sellerAsMakerFinalizesDepositTx", myDepositTx);
+ WalletService.verifyTransaction(myDepositTx);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Delayed payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public Transaction createDelayedUnsignedPayoutTx(Transaction depositTx,
+ String donationAddressString,
+ Coin minerFee,
+ long lockTime)
+ throws AddressFormatException, TransactionVerificationException {
+ TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
+ Transaction delayedPayoutTx = new Transaction(params);
+ delayedPayoutTx.addInput(p2SHMultiSigOutput);
+ applyLockTime(lockTime, delayedPayoutTx);
+ Coin outputAmount = depositTx.getOutputSum().subtract(minerFee);
+ delayedPayoutTx.addOutput(outputAmount, Address.fromBase58(params, donationAddressString));
+ WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx);
+ WalletService.verifyTransaction(delayedPayoutTx);
+ return delayedPayoutTx;
+ }
+
+ public byte[] signDelayedPayoutTx(Transaction delayedPayoutTx,
+ DeterministicKey myMultiSigKeyPair,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey)
+ throws AddressFormatException, TransactionVerificationException {
+
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
+ Sha256Hash sigHash = delayedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
+ checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
+ if (myMultiSigKeyPair.isEncrypted()) {
+ checkNotNull(aesKey);
+ }
+
+ ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
+ WalletService.printTx("delayedPayoutTx for sig creation", delayedPayoutTx);
+ WalletService.verifyTransaction(delayedPayoutTx);
+ return mySignature.encodeToDER();
+ }
+
+ public Transaction finalizeDelayedPayoutTx(Transaction delayedPayoutTx,
+ byte[] buyerPubKey,
+ byte[] sellerPubKey,
+ byte[] buyerSignature,
+ byte[] sellerSignature)
+ throws AddressFormatException, TransactionVerificationException, WalletException {
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
+ ECKey.ECDSASignature buyerECDSASignature = ECKey.ECDSASignature.decodeFromDER(buyerSignature);
+ ECKey.ECDSASignature sellerECDSASignature = ECKey.ECDSASignature.decodeFromDER(sellerSignature);
+ TransactionSignature buyerTxSig = new TransactionSignature(buyerECDSASignature, Transaction.SigHash.ALL, false);
+ TransactionSignature sellerTxSig = new TransactionSignature(sellerECDSASignature, Transaction.SigHash.ALL, false);
+ Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
+ TransactionInput input = delayedPayoutTx.getInput(0);
+ input.setScriptSig(inputScript);
+ WalletService.printTx("finalizeDelayedPayoutTx", delayedPayoutTx);
+ WalletService.verifyTransaction(delayedPayoutTx);
+ WalletService.checkWalletConsistency(wallet);
+ WalletService.checkScriptSig(delayedPayoutTx, input, 0);
+ checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
+ input.verify(input.getConnectedOutput());
+ return delayedPayoutTx;
+ }
+
+ public boolean verifiesDepositTxAndDelayedPayoutTx(Transaction depositTx,
+ Transaction delayedPayoutTx) {
+ // todo add more checks
+ if (delayedPayoutTx.getLockTime() == 0) {
+ log.error("Time lock is not set");
+ return false;
+ }
+
+ if (delayedPayoutTx.getInputs().stream().noneMatch(e -> e.getSequenceNumber() == TransactionInput.NO_SEQUENCE - 1)) {
+ log.error("Sequence number must be 0xFFFFFFFE");
+ return false;
+ }
+
+ return true;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Standard payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
/**
* Seller signs payout transaction, buyer has not signed yet.
*
@@ -671,7 +737,6 @@ public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller,
* @param multiSigKeyPair DeterministicKey for MultiSig from seller
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
* @return DER encoded canonical signature
* @throws AddressFormatException
* @throws TransactionVerificationException
@@ -683,38 +748,21 @@ public byte[] buyerSignsPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException {
- log.trace("sellerSignsPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey HEX=" + ECKey.fromPublicOnly(buyerPubKey).getPublicKeyAsHex());
- log.info("sellerPubKey HEX=" + ECKey.fromPublicOnly(sellerPubKey).getPublicKeyAsHex());
- log.info("arbitratorPubKey HEX=" + ECKey.fromPublicOnly(arbitratorPubKey).getPublicKeyAsHex());
- Transaction preparedPayoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction preparedPayoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount,
+ buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
- if (multiSigKeyPair.isEncrypted())
+ if (multiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
+ }
ECKey.ECDSASignature buyerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
WalletService.printTx("prepared payoutTx", preparedPayoutTx);
-
WalletService.verifyTransaction(preparedPayoutTx);
-
return buyerSignature.encodeToDER();
}
@@ -731,7 +779,6 @@ public byte[] buyerSignsPayoutTx(Transaction depositTx,
* @param multiSigKeyPair Buyer's keypair for MultiSig
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
- * @param arbitratorPubKey The public key of the arbitrator.
* @return The payout transaction
* @throws AddressFormatException
* @throws TransactionVerificationException
@@ -745,49 +792,27 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
- log.trace("buyerSignsAndFinalizesPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerSignature r {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).r.toString());
- log.trace("buyerSignature s {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).s.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.info("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.info("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
- Transaction payoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction payoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
- if (multiSigKeyPair.isEncrypted())
+ if (multiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
-
+ }
ECKey.ECDSASignature sellerSignature = multiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
Transaction.SigHash.ALL, false);
TransactionSignature sellerTxSig = new TransactionSignature(sellerSignature, Transaction.SigHash.ALL, false);
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (arbitrator, seller, buyer)
- Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
-
+ Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig),
+ redeemScript);
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
@@ -798,7 +823,7 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Mediation
+ // Mediated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////
public byte[] signMediatedPayoutTx(Transaction depositTx,
@@ -808,38 +833,20 @@ public byte[] signMediatedPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey myMultiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException {
- log.trace("signMediatedPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.trace("buyerPubKey HEX=" + ECKey.fromPublicOnly(buyerPubKey).getPublicKeyAsHex());
- log.trace("sellerPubKey HEX=" + ECKey.fromPublicOnly(sellerPubKey).getPublicKeyAsHex());
- log.trace("arbitratorPubKey HEX=" + ECKey.fromPublicOnly(arbitratorPubKey).getPublicKeyAsHex());
- Transaction preparedPayoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction preparedPayoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
- if (myMultiSigKeyPair.isEncrypted())
+ if (myMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
+ }
ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
WalletService.printTx("prepared mediated payoutTx for sig creation", preparedPayoutTx);
-
WalletService.verifyTransaction(preparedPayoutTx);
-
return mySignature.encodeToDER();
}
@@ -852,47 +859,22 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
- byte[] sellerPubKey,
- byte[] arbitratorPubKey)
+ byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
- log.trace("finalizeMediatedPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerSignature r {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).r.toString());
- log.trace("buyerSignature s {}", ECKey.ECDSASignature.decodeFromDER(buyerSignature).s.toString());
- log.trace("sellerSignature r {}", ECKey.ECDSASignature.decodeFromDER(sellerSignature).r.toString());
- log.trace("sellerSignature s {}", ECKey.ECDSASignature.decodeFromDER(sellerSignature).s.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerPayoutAddressString {}", buyerPayoutAddressString);
- log.trace("sellerPayoutAddressString {}", sellerPayoutAddressString);
- log.trace("multiSigKeyPair (not displayed for security reasons)");
- log.trace("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.trace("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.trace("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
- Transaction payoutTx = createPayoutTx(depositTx,
- buyerPayoutAmount,
- sellerPayoutAmount,
- buyerPayoutAddressString,
- sellerPayoutAddressString);
+ Transaction payoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
-
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
Transaction.SigHash.ALL, false);
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature),
Transaction.SigHash.ALL, false);
-
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (arbitrator, seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
-
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("mediated payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
@@ -903,7 +885,7 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Arbitration
+ // Arbitrated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////
/**
@@ -933,39 +915,27 @@ public byte[] arbitratorSignsDisputedPayoutTx(byte[] depositTxSerialized,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException {
Transaction depositTx = new Transaction(params, depositTxSerialized);
- log.trace("signDisputedPayoutTx called");
- log.trace("depositTx {}", depositTx.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerAddressString {}", buyerAddressString);
- log.trace("sellerAddressString {}", sellerAddressString);
- log.trace("arbitratorKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.info("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.info("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
// Our MS is index 0
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
Transaction preparedPayoutTx = new Transaction(params);
preparedPayoutTx.addInput(p2SHMultiSigOutput);
- if (buyerPayoutAmount.isGreaterThan(Coin.ZERO))
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
preparedPayoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
- if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
preparedPayoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
+ }
// take care of sorting!
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(arbitratorKeyPair, "arbitratorKeyPair must not be null");
- if (arbitratorKeyPair.isEncrypted())
+ if (arbitratorKeyPair.isEncrypted()) {
checkNotNull(aesKey);
-
+ }
ECKey.ECDSASignature arbitratorSignature = arbitratorKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
WalletService.verifyTransaction(preparedPayoutTx);
-
- //WalletService.printTx("preparedPayoutTx", preparedPayoutTx);
-
+ WalletService.printTx("preparedPayoutTx", preparedPayoutTx);
return arbitratorSignature.encodeToDER();
}
@@ -999,47 +969,33 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
Transaction depositTx = new Transaction(params, depositTxSerialized);
-
- log.trace("signAndFinalizeDisputedPayoutTx called");
- log.trace("depositTx {}", depositTx);
- log.trace("arbitratorSignature r {}", ECKey.ECDSASignature.decodeFromDER(arbitratorSignature).r.toString());
- log.trace("arbitratorSignature s {}", ECKey.ECDSASignature.decodeFromDER(arbitratorSignature).s.toString());
- log.trace("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.trace("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.trace("buyerAddressString {}", buyerAddressString);
- log.trace("sellerAddressString {}", sellerAddressString);
- log.trace("tradersMultiSigKeyPair (not displayed for security reasons)");
- log.info("buyerPubKey {}", ECKey.fromPublicOnly(buyerPubKey).toString());
- log.info("sellerPubKey {}", ECKey.fromPublicOnly(sellerPubKey).toString());
- log.info("arbitratorPubKey {}", ECKey.fromPublicOnly(arbitratorPubKey).toString());
-
-
TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0);
Transaction payoutTx = new Transaction(params);
payoutTx.addInput(p2SHMultiSigOutput);
- if (buyerPayoutAmount.isGreaterThan(Coin.ZERO))
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
- if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
+ }
// take care of sorting!
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(tradersMultiSigKeyPair, "tradersMultiSigKeyPair must not be null");
- if (tradersMultiSigKeyPair.isEncrypted())
+ if (tradersMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
+ }
ECKey.ECDSASignature tradersSignature = tradersMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
-
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
TransactionSignature arbitratorTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(arbitratorSignature),
Transaction.SigHash.ALL, false);
// Take care of order of signatures. See comment below at getMultiSigRedeemScript (sort order needed here: arbitrator, seller, buyer)
- Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(arbitratorTxSig, tradersTxSig), redeemScript);
+ Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(arbitratorTxSig, tradersTxSig),
+ redeemScript);
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("disputed payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
@@ -1049,49 +1005,38 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Emergency payoutTx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
// Emergency payout tool. Used only in cased when the payput from the arbitrator does not work because some data
// in the trade/dispute are messed up.
// We keep here arbitratorPayoutAmount just in case (requires cooperation from peer anyway)
- public Transaction emergencySignAndPublishPayoutTx(String depositTxHex,
- Coin buyerPayoutAmount,
- Coin sellerPayoutAmount,
- Coin arbitratorPayoutAmount,
- Coin txFee,
- String buyerAddressString,
- String sellerAddressString,
- String arbitratorAddressString,
- @Nullable String buyerPrivateKeyAsHex,
- @Nullable String sellerPrivateKeyAsHex,
- String arbitratorPrivateKeyAsHex,
- String buyerPubKeyAsHex,
- String sellerPubKeyAsHex,
- String arbitratorPubKeyAsHex,
- String P2SHMultiSigOutputScript,
- TxBroadcaster.Callback callback)
+ public Transaction emergencySignAndPublishPayoutTxFrom2of3MultiSig(String depositTxHex,
+ Coin buyerPayoutAmount,
+ Coin sellerPayoutAmount,
+ Coin arbitratorPayoutAmount,
+ Coin txFee,
+ String buyerAddressString,
+ String sellerAddressString,
+ String arbitratorAddressString,
+ @Nullable String buyerPrivateKeyAsHex,
+ @Nullable String sellerPrivateKeyAsHex,
+ String arbitratorPrivateKeyAsHex,
+ String buyerPubKeyAsHex,
+ String sellerPubKeyAsHex,
+ String arbitratorPubKeyAsHex,
+ TxBroadcaster.Callback callback)
throws AddressFormatException, TransactionVerificationException, WalletException {
- log.info("signAndPublishPayoutTx called");
- log.info("depositTxHex {}", depositTxHex);
- log.info("buyerPayoutAmount {}", buyerPayoutAmount.toFriendlyString());
- log.info("sellerPayoutAmount {}", sellerPayoutAmount.toFriendlyString());
- log.info("arbitratorPayoutAmount {}", arbitratorPayoutAmount.toFriendlyString());
- log.info("buyerAddressString {}", buyerAddressString);
- log.info("sellerAddressString {}", sellerAddressString);
- log.info("arbitratorAddressString {}", arbitratorAddressString);
- log.info("buyerPrivateKeyAsHex (not displayed for security reasons)");
- log.info("sellerPrivateKeyAsHex (not displayed for security reasons)");
- log.info("arbitratorPrivateKeyAsHex (not displayed for security reasons)");
- log.info("buyerPubKeyAsHex {}", buyerPubKeyAsHex);
- log.info("sellerPubKeyAsHex {}", sellerPubKeyAsHex);
- log.info("arbitratorPubKeyAsHex {}", arbitratorPubKeyAsHex);
- log.info("P2SHMultiSigOutputScript {}", P2SHMultiSigOutputScript);
-
- checkNotNull((buyerPrivateKeyAsHex != null || sellerPrivateKeyAsHex != null), "either buyerPrivateKeyAsHex or sellerPrivateKeyAsHex must not be null");
+ checkNotNull((buyerPrivateKeyAsHex != null || sellerPrivateKeyAsHex != null),
+ "either buyerPrivateKeyAsHex or sellerPrivateKeyAsHex must not be null");
byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
- final byte[] arbitratorPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(arbitratorPubKeyAsHex)).getPubKey();
+ byte[] arbitratorPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(arbitratorPubKeyAsHex)).getPubKey();
- Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script p2SHMultiSigOutputScript = get2of3MultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(arbitratorPayoutAmount).add(txFee);
TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
@@ -1102,15 +1047,18 @@ public Transaction emergencySignAndPublishPayoutTx(String depositTxHex,
Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex);
payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
- if (buyerPayoutAmount.isGreaterThan(Coin.ZERO))
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
- if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
- if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO))
+ }
+ if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO)) {
payoutTx.addOutput(arbitratorPayoutAmount, Address.fromBase58(params, arbitratorAddressString));
+ }
// take care of sorting!
- Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
+ Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature tradersSignature;
@@ -1124,24 +1072,79 @@ public Transaction emergencySignAndPublishPayoutTx(String depositTxHex,
checkNotNull(sellerPrivateKey, "sellerPrivateKey must not be null");
tradersSignature = sellerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
}
- final ECKey key = ECKey.fromPrivate(Utils.HEX.decode(arbitratorPrivateKeyAsHex));
+ ECKey key = ECKey.fromPrivate(Utils.HEX.decode(arbitratorPrivateKeyAsHex));
checkNotNull(key, "key must not be null");
ECKey.ECDSASignature arbitratorSignature = key.sign(sigHash, aesKey).toCanonicalised();
-
TransactionSignature tradersTxSig = new TransactionSignature(tradersSignature, Transaction.SigHash.ALL, false);
TransactionSignature arbitratorTxSig = new TransactionSignature(arbitratorSignature, Transaction.SigHash.ALL, false);
// Take care of order of signatures. See comment below at getMultiSigRedeemScript (sort order needed here: arbitrator, seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(arbitratorTxSig, tradersTxSig), redeemScript);
+
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
-
WalletService.printTx("payoutTx", payoutTx);
-
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
-
broadcastTx(payoutTx, callback, 20);
+ return payoutTx;
+ }
+
+ //todo add window tool for usage
+ public Transaction emergencySignAndPublishPayoutTxFrom2of2MultiSig(String depositTxHex,
+ Coin buyerPayoutAmount,
+ Coin sellerPayoutAmount,
+ Coin txFee,
+ String buyerAddressString,
+ String sellerAddressString,
+ String buyerPrivateKeyAsHex,
+ String sellerPrivateKeyAsHex,
+ String buyerPubKeyAsHex,
+ String sellerPubKeyAsHex,
+ TxBroadcaster.Callback callback)
+ throws AddressFormatException, TransactionVerificationException, WalletException {
+ byte[] buyerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(buyerPubKeyAsHex)).getPubKey();
+ byte[] sellerPubKey = ECKey.fromPublicOnly(Utils.HEX.decode(sellerPubKeyAsHex)).getPubKey();
+
+ Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey);
+
+ Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(txFee);
+ TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram());
+ Transaction depositTx = new Transaction(params);
+ depositTx.addOutput(p2SHMultiSigOutput);
+
+ Transaction payoutTx = new Transaction(params);
+ Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex);
+ payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput));
+
+ if (buyerPayoutAmount.isGreaterThan(Coin.ZERO)) {
+ payoutTx.addOutput(buyerPayoutAmount, Address.fromBase58(params, buyerAddressString));
+ }
+ if (sellerPayoutAmount.isGreaterThan(Coin.ZERO)) {
+ payoutTx.addOutput(sellerPayoutAmount, Address.fromBase58(params, sellerAddressString));
+ }
+
+ // take care of sorting!
+ Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
+ Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
+
+ ECKey buyerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(buyerPrivateKeyAsHex));
+ checkNotNull(buyerPrivateKey, "key must not be null");
+ ECKey.ECDSASignature buyerECDSASignature = buyerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
+ ECKey sellerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(sellerPrivateKeyAsHex));
+ checkNotNull(sellerPrivateKey, "key must not be null");
+ ECKey.ECDSASignature sellerECDSASignature = sellerPrivateKey.sign(sigHash, aesKey).toCanonicalised();
+
+ TransactionSignature buyerTxSig = new TransactionSignature(buyerECDSASignature, Transaction.SigHash.ALL, false);
+ TransactionSignature sellerTxSig = new TransactionSignature(sellerECDSASignature, Transaction.SigHash.ALL, false);
+ Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
+
+ TransactionInput input = payoutTx.getInput(0);
+ input.setScriptSig(inputScript);
+ WalletService.printTx("payoutTx", payoutTx);
+ WalletService.verifyTransaction(payoutTx);
+ WalletService.checkWalletConsistency(wallet);
+ broadcastTx(payoutTx, callback, 20);
return payoutTx;
}
@@ -1172,12 +1175,13 @@ public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback, int tim
*/
public Transaction addTxToWallet(Transaction transaction) throws VerificationException {
// We need to recreate the transaction otherwise we get a null pointer...
- Transaction result = new Transaction(params, transaction.bitcoinSerialize());
- result.getConfidence(Context.get()).setSource(TransactionConfidence.Source.SELF);
+ Transaction tx = new Transaction(params, transaction.bitcoinSerialize());
+ tx.getConfidence(Context.get()).setSource(TransactionConfidence.Source.SELF);
- if (wallet != null)
- wallet.receivePending(result, null, true);
- return result;
+ if (wallet != null) {
+ wallet.receivePending(tx, null, true);
+ }
+ return tx;
}
/**
@@ -1190,8 +1194,9 @@ public Transaction addTxToWallet(byte[] serializedTransaction) throws Verificati
Transaction transaction = new Transaction(params, serializedTransaction);
transaction.getConfidence(Context.get()).setSource(TransactionConfidence.Source.NETWORK);
- if (wallet != null)
+ if (wallet != null) {
wallet.receivePending(transaction, null, true);
+ }
return transaction;
}
@@ -1219,31 +1224,31 @@ public Transaction getClonedTransaction(Transaction tx) {
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
- @NotNull
private RawTransactionInput getRawInputFromTransactionInput(@NotNull TransactionInput input) {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
- checkNotNull(input.getConnectedOutput().getParentTransaction(), "input.getConnectedOutput().getParentTransaction() must not be null");
+ checkNotNull(input.getConnectedOutput().getParentTransaction(),
+ "input.getConnectedOutput().getParentTransaction() must not be null");
checkNotNull(input.getValue(), "input.getValue() must not be null");
- return new RawTransactionInput(input.getOutpoint().getIndex(), input.getConnectedOutput().getParentTransaction().bitcoinSerialize(), input.getValue().value);
+ return new RawTransactionInput(input.getOutpoint().getIndex(),
+ input.getConnectedOutput().getParentTransaction().bitcoinSerialize(),
+ input.getValue().value);
}
- private byte[] getScriptProgram(Transaction makersDepositTx, int i) throws TransactionVerificationException {
- byte[] scriptProgram = makersDepositTx.getInputs().get(i).getScriptSig().getProgram();
- if (scriptProgram.length == 0)
+ private byte[] getMakersScriptSigProgram(TransactionInput transactionInput) throws TransactionVerificationException {
+ byte[] scriptProgram = transactionInput.getScriptSig().getProgram();
+ if (scriptProgram.length == 0) {
throw new TransactionVerificationException("Inputs from maker not signed.");
+ }
return scriptProgram;
}
- @NotNull
private TransactionInput getTransactionInput(Transaction depositTx,
byte[] scriptProgram,
RawTransactionInput rawTransactionInput) {
- return new TransactionInput(params,
- depositTx,
- scriptProgram,
- new TransactionOutPoint(params, rawTransactionInput.index, new Transaction(params, rawTransactionInput.parentTransaction)),
+ return new TransactionInput(params, depositTx, scriptProgram, new TransactionOutPoint(params,
+ rawTransactionInput.index, new Transaction(params, rawTransactionInput.parentTransaction)),
Coin.valueOf(rawTransactionInput.value));
}
@@ -1257,7 +1262,7 @@ private TransactionInput getTransactionInput(Transaction depositTx,
// Furthermore the executed list is reversed to the provided.
// Best practice is to provide the list sorted by the least probable successful candidates first (arbitrator is first -> will be last in execution loop, so
// avoiding unneeded expensive ECKey.verify calls)
- private Script getMultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
+ private Script get2of3MultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
ECKey buyerKey = ECKey.fromPublicOnly(buyerPubKey);
ECKey sellerKey = ECKey.fromPublicOnly(sellerPubKey);
ECKey arbitratorKey = ECKey.fromPublicOnly(arbitratorPubKey);
@@ -1266,8 +1271,20 @@ private Script getMultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey,
return ScriptBuilder.createMultiSigOutputScript(2, keys);
}
- private Script getP2SHMultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
- return ScriptBuilder.createP2SHOutputScript(getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey));
+ private Script get2of2MultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubKey) {
+ ECKey buyerKey = ECKey.fromPublicOnly(buyerPubKey);
+ ECKey sellerKey = ECKey.fromPublicOnly(sellerPubKey);
+ // Take care of sorting! Need to reverse to the order we use normally (buyer, seller)
+ List keys = ImmutableList.of(sellerKey, buyerKey);
+ return ScriptBuilder.createMultiSigOutputScript(2, keys);
+ }
+
+ private Script get2of3MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) {
+ return ScriptBuilder.createP2SHOutputScript(get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey));
+ }
+
+ private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey) {
+ return ScriptBuilder.createP2SHOutputScript(get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey));
}
private Transaction createPayoutTx(Transaction depositTx,
@@ -1288,9 +1305,11 @@ private void signInput(Transaction transaction, TransactionInput input, int inpu
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
checkNotNull(wallet);
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
- checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" + input.getOutpoint().toString());
- if (sigKey.isEncrypted())
+ checkNotNull(sigKey, "signInput: sigKey must not be null. input.getOutpoint()=" +
+ input.getOutpoint().toString());
+ if (sigKey.isEncrypted()) {
checkNotNull(aesKey);
+ }
Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature signature = sigKey.sign(hash, aesKey);
TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false);
@@ -1305,8 +1324,7 @@ private void signInput(Transaction transaction, TransactionInput input, int inpu
private void addAvailableInputsAndChangeOutputs(Transaction transaction,
Address address,
- Address changeAddress,
- Coin txFee) throws WalletException {
+ Address changeAddress) throws WalletException {
SendRequest sendRequest = null;
try {
// Lets let the framework do the work to find the right inputs
@@ -1314,7 +1332,7 @@ private void addAvailableInputsAndChangeOutputs(Transaction transaction,
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
// We use a fixed fee
- sendRequest.fee = txFee;
+ sendRequest.fee = Coin.ZERO;
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation)
@@ -1327,10 +1345,18 @@ private void addAvailableInputsAndChangeOutputs(Transaction transaction,
checkNotNull(wallet, "wallet must not be null");
wallet.completeTx(sendRequest);
} catch (Throwable t) {
- if (sendRequest != null && sendRequest.tx != null)
- log.warn("addAvailableInputsAndChangeOutputs: sendRequest.tx={}, sendRequest.tx.getOutputs()={}", sendRequest.tx, sendRequest.tx.getOutputs());
+ if (sendRequest != null && sendRequest.tx != null) {
+ log.warn("addAvailableInputsAndChangeOutputs: sendRequest.tx={}, sendRequest.tx.getOutputs()={}",
+ sendRequest.tx, sendRequest.tx.getOutputs());
+ }
throw new WalletException(t);
}
}
+
+ private void applyLockTime(long lockTime, Transaction tx) {
+ checkArgument(!tx.getInputs().isEmpty(), "The tx must have inputs. tx={}", tx);
+ tx.getInputs().forEach(input -> input.setSequenceNumber(TransactionInput.NO_SEQUENCE - 1));
+ tx.setLockTime(lockTime);
+ }
}
diff --git a/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java b/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java
index 0576024a736..23d38dd13c1 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TxBroadcaster.java
@@ -104,12 +104,12 @@ public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction t
}
// We decided the least risky scenario is to commit the tx to the wallet and broadcast it later.
- // If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have commited the tx to both bsq and btc
+ // If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have committed the tx to both bsq and btc
// wallets so the next line causes no effect.
// If it's a btc tx, the next line adds the tx to the wallet.
wallet.maybeCommitTx(tx);
- Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback() {
+ Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Transaction result) {
// We expect that there is still a timeout in our map, otherwise the timeout got triggered
@@ -119,7 +119,7 @@ public void onSuccess(@Nullable Transaction result) {
// before the caller is finished.
UserThread.execute(() -> callback.onSuccess(tx));
} else {
- log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout.", txId);
+ log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout. txId={}", txId);
}
}
diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java
index 8d92fa17839..134dbf52b10 100644
--- a/core/src/main/java/bisq/core/filter/Filter.java
+++ b/core/src/main/java/bisq/core/filter/Filter.java
@@ -98,6 +98,10 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {
@Nullable
private final List mediators;
+ // added in v1.2.0
+ @Nullable
+ private final List refundAgents;
+
public Filter(List bannedOfferIds,
List bannedNodeAddress,
List bannedPaymentAccounts,
@@ -111,7 +115,8 @@ public Filter(List bannedOfferIds,
boolean disableDao,
@Nullable String disableDaoBelowVersion,
@Nullable String disableTradeBelowVersion,
- @Nullable List mediators) {
+ @Nullable List mediators,
+ @Nullable List refundAgents) {
this.bannedOfferIds = bannedOfferIds;
this.bannedNodeAddress = bannedNodeAddress;
this.bannedPaymentAccounts = bannedPaymentAccounts;
@@ -126,6 +131,7 @@ public Filter(List bannedOfferIds,
this.disableDaoBelowVersion = disableDaoBelowVersion;
this.disableTradeBelowVersion = disableTradeBelowVersion;
this.mediators = mediators;
+ this.refundAgents = refundAgents;
}
@@ -150,7 +156,8 @@ public Filter(List bannedOfferIds,
String signatureAsBase64,
byte[] ownerPubKeyBytes,
@Nullable Map extraDataMap,
- @Nullable List mediators) {
+ @Nullable List mediators,
+ @Nullable List refundAgents) {
this(bannedOfferIds,
bannedNodeAddress,
bannedPaymentAccounts,
@@ -164,7 +171,8 @@ public Filter(List bannedOfferIds,
disableDao,
disableDaoBelowVersion,
disableTradeBelowVersion,
- mediators);
+ mediators,
+ refundAgents);
this.signatureAsBase64 = signatureAsBase64;
this.ownerPubKeyBytes = ownerPubKeyBytes;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
@@ -198,6 +206,7 @@ public protobuf.StoragePayload toProtoMessage() {
Optional.ofNullable(disableTradeBelowVersion).ifPresent(builder::setDisableTradeBelowVersion);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
Optional.ofNullable(mediators).ifPresent(builder::addAllMediators);
+ Optional.ofNullable(refundAgents).ifPresent(builder::addAllRefundAgents);
return protobuf.StoragePayload.newBuilder().setFilter(builder).build();
}
@@ -221,7 +230,8 @@ public static Filter fromProto(protobuf.Filter proto) {
proto.getSignatureAsBase64(),
proto.getOwnerPubKeyBytes().toByteArray(),
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
- CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()));
+ CollectionUtils.isEmpty(proto.getMediatorsList()) ? null : new ArrayList<>(proto.getMediatorsList()),
+ CollectionUtils.isEmpty(proto.getRefundAgentsList()) ? null : new ArrayList<>(proto.getRefundAgentsList()));
}
diff --git a/core/src/main/java/bisq/core/offer/AvailabilityResult.java b/core/src/main/java/bisq/core/offer/AvailabilityResult.java
index 95895687037..2d3d749ff24 100644
--- a/core/src/main/java/bisq/core/offer/AvailabilityResult.java
+++ b/core/src/main/java/bisq/core/offer/AvailabilityResult.java
@@ -26,5 +26,6 @@ public enum AvailabilityResult {
NO_ARBITRATORS,
NO_MEDIATORS,
USER_IGNORED,
- MISSING_MANDATORY_CAPABILITY
+ MISSING_MANDATORY_CAPABILITY,
+ NO_REFUND_AGENTS
}
diff --git a/core/src/main/java/bisq/core/offer/OfferBookService.java b/core/src/main/java/bisq/core/offer/OfferBookService.java
index d99955d5327..5ebe152453c 100644
--- a/core/src/main/java/bisq/core/offer/OfferBookService.java
+++ b/core/src/main/java/bisq/core/offer/OfferBookService.java
@@ -28,7 +28,6 @@
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.common.UserThread;
-import bisq.common.app.Capability;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.storage.JsonFileManager;
@@ -93,10 +92,8 @@ public void onAdded(ProtectedStorageEntry data) {
if (data.getProtectedStoragePayload() instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) data.getProtectedStoragePayload();
Offer offer = new Offer(offerPayload);
- if (showOffer(offer)) {
- offer.setPriceFeedService(priceFeedService);
- listener.onAdded(offer);
- }
+ offer.setPriceFeedService(priceFeedService);
+ listener.onAdded(offer);
}
});
}
@@ -135,11 +132,6 @@ public void onRemoved(Offer offer) {
}
}
- private boolean showOffer(Offer offer) {
- return !OfferRestrictions.requiresUpdate() ||
- OfferRestrictions.hasOfferMandatoryCapability(offer, Capability.MEDIATION);
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// API
@@ -208,7 +200,6 @@ public List getOffers() {
offer.setPriceFeedService(priceFeedService);
return offer;
})
- .filter(this::showOffer)
.collect(Collectors.toList());
}
diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java
index bffe39c8890..a8c9406d682 100644
--- a/core/src/main/java/bisq/core/offer/OfferUtil.java
+++ b/core/src/main/java/bisq/core/offer/OfferUtil.java
@@ -32,7 +32,6 @@
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.user.Preferences;
-import bisq.core.util.BSFormatter;
import bisq.core.util.BsqFormatter;
import bisq.core.util.CoinUtil;
@@ -326,7 +325,8 @@ public static Optional getFeeInUserFiatCurrency(Coin makerFee, boolean i
public static Map getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService,
ReferralIdService referralIdService,
PaymentAccount paymentAccount,
- String currencyCode) {
+ String currencyCode,
+ Preferences preferences) {
Map extraDataMap = new HashMap<>();
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
String myWitnessHashAsHex = accountAgeWitnessService.getMyWitnessHashAsHex(paymentAccount.getPaymentAccountPayload());
diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java
index 2193b30752c..ced8cce9834 100644
--- a/core/src/main/java/bisq/core/offer/OpenOffer.java
+++ b/core/src/main/java/bisq/core/offer/OpenOffer.java
@@ -65,6 +65,12 @@ public enum State {
@Nullable
private NodeAddress mediatorNodeAddress;
+ // Added v1.2.0
+ @Getter
+ @Setter
+ @Nullable
+ private NodeAddress refundAgentNodeAddress;
+
transient private Storage> storage;
public OpenOffer(Offer offer, Storage> storage) {
@@ -80,11 +86,13 @@ public OpenOffer(Offer offer, Storage> storage) {
private OpenOffer(Offer offer,
State state,
@Nullable NodeAddress arbitratorNodeAddress,
- @Nullable NodeAddress mediatorNodeAddress) {
+ @Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress) {
this.offer = offer;
this.state = state;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
if (this.state == State.RESERVED)
setState(State.AVAILABLE);
@@ -98,6 +106,7 @@ public protobuf.Tradable toProtoMessage() {
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
Optional.ofNullable(mediatorNodeAddress).ifPresent(nodeAddress -> builder.setMediatorNodeAddress(nodeAddress.toProtoMessage()));
+ Optional.ofNullable(refundAgentNodeAddress).ifPresent(nodeAddress -> builder.setRefundAgentNodeAddress(nodeAddress.toProtoMessage()));
return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
}
@@ -106,7 +115,8 @@ public static Tradable fromProto(protobuf.OpenOffer proto) {
return 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.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
}
@@ -175,6 +185,7 @@ public String toString() {
",\n state=" + state +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
index 6754512c6e1..f0226ee6a52 100644
--- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
@@ -29,6 +29,7 @@
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
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.closed.ClosedTradableManager;
import bisq.core.trade.handlers.TransactionResultHandler;
@@ -51,6 +52,7 @@
import bisq.common.UserThread;
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
+import bisq.common.app.Version;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import bisq.common.handlers.ErrorMessageHandler;
@@ -104,6 +106,7 @@ 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 Storage> openOfferTradableListStorage;
private final Map offersToBeEdited = new HashMap<>();
private boolean stopped;
@@ -129,6 +132,7 @@ public OpenOfferManager(KeyRing keyRing,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
Storage> storage) {
this.keyRing = keyRing;
this.user = user;
@@ -143,6 +147,7 @@ public OpenOfferManager(KeyRing keyRing,
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
openOfferTradableListStorage = storage;
@@ -577,6 +582,7 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
AvailabilityResult availabilityResult;
NodeAddress arbitratorNodeAddress = null;
NodeAddress mediatorNodeAddress = null;
+ NodeAddress refundAgentNodeAddress = null;
if (openOfferOptional.isPresent()) {
OpenOffer openOffer = openOfferOptional.get();
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
@@ -584,41 +590,29 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
availabilityResult = AvailabilityResult.AVAILABLE;
- List acceptedArbitrators = user.getAcceptedArbitratorAddresses();
- if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) {
- arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager).getNodeAddress();
- openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
-
- mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
- openOffer.setMediatorNodeAddress(mediatorNodeAddress);
- Capabilities supportedCapabilities = request.getSupportedCapabilities();
- if (!OfferRestrictions.requiresUpdate() ||
- (supportedCapabilities != null &&
- Capabilities.hasMandatoryCapability(supportedCapabilities, Capability.MEDIATION))) {
- 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());
- } 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());
- availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
- }
- } else {
- log.warn("Taker has not mandatory capability MEDIATION");
- // Because an old peer has not AvailabilityResult.MISSING_MANDATORY_CAPABILITY and we
- // have not set the UNDEFINED fallback in AvailabilityResult the user will get a null value.
- availabilityResult = AvailabilityResult.MISSING_MANDATORY_CAPABILITY;
- }
- } else {
- log.warn("acceptedArbitrators is null or empty: acceptedArbitrators=" + acceptedArbitrators);
- availabilityResult = AvailabilityResult.NO_ARBITRATORS;
+ arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager).getNodeAddress();
+ openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
+
+ mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress();
+ openOffer.setMediatorNodeAddress(mediatorNodeAddress);
+
+ refundAgentNodeAddress = DisputeAgentSelection.getLeastUsedRefundAgent(tradeStatisticsManager, refundAgentManager).getNodeAddress();
+ openOffer.setRefundAgentNodeAddress(refundAgentNodeAddress);
+
+ 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());
+ } 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());
+ availabilityResult = AvailabilityResult.UNKNOWN_FAILURE;
}
} else {
availabilityResult = AvailabilityResult.USER_IGNORED;
@@ -634,7 +628,8 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
availabilityResult,
arbitratorNodeAddress,
- mediatorNodeAddress);
+ mediatorNodeAddress,
+ refundAgentNodeAddress);
log.info("Send {} with offerId {} and uid {} to peer {}",
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
offerAvailabilityResponse.getUid(), peer);
@@ -722,7 +717,8 @@ private void maybeUpdatePersistedOffers() {
// We added CAPABILITIES with entry for Capability.MEDIATION in v1.1.6 and want to rewrite a
// persisted offer after the user has updated to 1.1.6 so their offer will be accepted by the network.
- if (!OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION)) {
+ if (originalOfferPayload.getProtocolVersion() < Version.TRADE_PROTOCOL_VERSION ||
+ !OfferRestrictions.hasOfferMandatoryCapability(originalOffer, Capability.MEDIATION)) {
// We rewrite our offer with the additional capabilities entry
Map originalExtraDataMap = originalOfferPayload.getExtraDataMap();
@@ -735,6 +731,9 @@ private void maybeUpdatePersistedOffers() {
// We overwrite any entry with our current capabilities
updatedExtraDataMap.put(OfferPayload.CAPABILITIES, Capabilities.app.toStringList());
+ // We update the trade protocol version
+ int protocolVersion = Version.TRADE_PROTOCOL_VERSION;
+
OfferPayload updatedPayload = new OfferPayload(originalOfferPayload.getId(),
originalOfferPayload.getDate(),
originalOfferPayload.getOwnerNodeAddress(),
@@ -772,7 +771,7 @@ private void maybeUpdatePersistedOffers() {
originalOfferPayload.isPrivateOffer(),
originalOfferPayload.getHashOfChallenge(),
updatedExtraDataMap,
- originalOfferPayload.getProtocolVersion());
+ protocolVersion);
// Save states from original data to use the for updated
Offer.State originalOfferState = originalOffer.getState();
diff --git a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
index bce44e6cbc6..2639261d6f4 100644
--- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
+++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java
@@ -57,6 +57,13 @@ public static T getLeastUsedMediator(TradeStatisticsMan
TradeStatistics2.MEDIATOR_ADDRESS);
}
+ public static T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
+ DisputeAgentManager disputeAgentManager) {
+ return getLeastUsedDisputeAgent(tradeStatisticsManager,
+ disputeAgentManager,
+ TradeStatistics2.REFUND_AGENT_ADDRESS);
+ }
+
private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager disputeAgentManager,
String extraMapKey) {
diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java
index 2b55c18813a..aad97d33f42 100644
--- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java
+++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java
@@ -60,6 +60,12 @@ public class OfferAvailabilityModel implements Model {
@Getter
private NodeAddress selectedMediator;
+ // Added in v1.2.0
+ @Nullable
+ @Setter
+ @Getter
+ private NodeAddress selectedRefundAgent;
+
public OfferAvailabilityModel(Offer offer,
PubKeyRing pubKeyRing,
diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
index 1690ddc34ae..6862661b391 100644
--- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
+++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java
@@ -57,12 +57,16 @@ protected void run() {
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.getLeastUsedMediator(model.getTradeStatisticsManager(), model.getMediatorManager()).getNodeAddress();
}
model.setSelectedMediator(mediator);
+
+ model.setSelectedRefundAgent(offerAvailabilityResponse.getRefundAgent());
+
complete();
} catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" +
diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java
index f71713c7a01..1f5161f6f2e 100644
--- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java
+++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java
@@ -49,17 +49,23 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable
private final NodeAddress mediator;
+ // Added v1.2.0
+ @Nullable
+ private final NodeAddress refundAgent;
+
public OfferAvailabilityResponse(String offerId,
AvailabilityResult availabilityResult,
NodeAddress arbitrator,
- NodeAddress mediator) {
+ NodeAddress mediator,
+ NodeAddress refundAgent) {
this(offerId,
availabilityResult,
Capabilities.app,
Version.getP2PMessageVersion(),
UUID.randomUUID().toString(),
arbitrator,
- mediator);
+ mediator,
+ refundAgent);
}
@@ -73,12 +79,14 @@ private OfferAvailabilityResponse(String offerId,
int messageVersion,
@Nullable String uid,
NodeAddress arbitrator,
- @Nullable NodeAddress mediator) {
+ @Nullable NodeAddress mediator,
+ @Nullable NodeAddress refundAgent) {
super(messageVersion, offerId, uid);
this.availabilityResult = availabilityResult;
this.supportedCapabilities = supportedCapabilities;
this.arbitrator = arbitrator;
this.mediator = mediator;
+ this.refundAgent = refundAgent;
}
@Override
@@ -91,6 +99,7 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
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()));
return getNetworkEnvelopeBuilder()
.setOfferAvailabilityResponse(builder)
@@ -104,6 +113,7 @@ public static OfferAvailabilityResponse fromProto(protobuf.OfferAvailabilityResp
messageVersion,
proto.getUid().isEmpty() ? null : proto.getUid(),
NodeAddress.fromProto(proto.getArbitrator()),
- proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null);
+ proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null,
+ proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null);
}
}
diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
index 9d84408420b..91791a5ba39 100644
--- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
@@ -44,14 +44,19 @@
import bisq.core.support.dispute.messages.DisputeResultMessage;
import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
-import bisq.core.trade.messages.PayDepositRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
-import bisq.core.trade.messages.PublishDepositTxRequest;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.statistics.TradeStatistics;
import bisq.network.p2p.AckMessage;
@@ -89,7 +94,6 @@
@Slf4j
@Singleton
public class CoreNetworkProtoResolver extends CoreProtoResolver implements NetworkProtoResolver {
-
@Inject
public CoreNetworkProtoResolver() {
}
@@ -134,16 +138,28 @@ public NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf
case PREFIXED_SEALED_AND_SIGNED_MESSAGE:
return PrefixedSealedAndSignedMessage.fromProto(proto.getPrefixedSealedAndSignedMessage(), messageVersion);
- case PAY_DEPOSIT_REQUEST:
- return PayDepositRequest.fromProto(proto.getPayDepositRequest(), this, messageVersion);
- case DEPOSIT_TX_PUBLISHED_MESSAGE:
- return DepositTxPublishedMessage.fromProto(proto.getDepositTxPublishedMessage(), messageVersion);
- case PUBLISH_DEPOSIT_TX_REQUEST:
- return PublishDepositTxRequest.fromProto(proto.getPublishDepositTxRequest(), this, messageVersion);
+ // trade protocol messages
+ case INPUTS_FOR_DEPOSIT_TX_REQUEST:
+ return InputsForDepositTxRequest.fromProto(proto.getInputsForDepositTxRequest(), this, messageVersion);
+ case INPUTS_FOR_DEPOSIT_TX_RESPONSE:
+ return InputsForDepositTxResponse.fromProto(proto.getInputsForDepositTxResponse(), this, messageVersion);
+ case DEPOSIT_TX_MESSAGE:
+ return DepositTxMessage.fromProto(proto.getDepositTxMessage(), messageVersion);
+ case DELAYED_PAYOUT_TX_SIGNATURE_REQUEST:
+ return DelayedPayoutTxSignatureRequest.fromProto(proto.getDelayedPayoutTxSignatureRequest(), messageVersion);
+ case DELAYED_PAYOUT_TX_SIGNATURE_RESPONSE:
+ return DelayedPayoutTxSignatureResponse.fromProto(proto.getDelayedPayoutTxSignatureResponse(), messageVersion);
+ case DEPOSIT_TX_AND_DELAYED_PAYOUT_TX_MESSAGE:
+ return DepositTxAndDelayedPayoutTxMessage.fromProto(proto.getDepositTxAndDelayedPayoutTxMessage(), messageVersion);
+
case COUNTER_CURRENCY_TRANSFER_STARTED_MESSAGE:
return CounterCurrencyTransferStartedMessage.fromProto(proto.getCounterCurrencyTransferStartedMessage(), messageVersion);
+
case PAYOUT_TX_PUBLISHED_MESSAGE:
return PayoutTxPublishedMessage.fromProto(proto.getPayoutTxPublishedMessage(), messageVersion);
+ case PEER_PUBLISHED_DELAYED_PAYOUT_TX_MESSAGE:
+ return PeerPublishedDelayedPayoutTxMessage.fromProto(proto.getPeerPublishedDelayedPayoutTxMessage(), messageVersion);
+
case MEDIATED_PAYOUT_TX_SIGNATURE_MESSAGE:
return MediatedPayoutTxSignatureMessage.fromProto(proto.getMediatedPayoutTxSignatureMessage(), messageVersion);
case MEDIATED_PAYOUT_TX_PUBLISHED_MESSAGE:
@@ -236,6 +252,8 @@ public NetworkPayload fromProto(protobuf.StoragePayload proto) {
return Arbitrator.fromProto(proto.getArbitrator());
case MEDIATOR:
return Mediator.fromProto(proto.getMediator());
+ case REFUND_AGENT:
+ return RefundAgent.fromProto(proto.getRefundAgent());
case FILTER:
return Filter.fromProto(proto.getFilter());
case TRADE_STATISTICS:
diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
index 796572b0c3f..321b236408f 100644
--- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java
@@ -37,6 +37,7 @@
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.ArbitrationDisputeList;
import bisq.core.support.dispute.mediation.MediationDisputeList;
+import bisq.core.support.dispute.refund.RefundDisputeList;
import bisq.core.trade.TradableList;
import bisq.core.trade.statistics.TradeStatistics2Store;
import bisq.core.user.PreferencesPayload;
@@ -110,6 +111,10 @@ public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) {
return MediationDisputeList.fromProto(proto.getMediationDisputeList(),
this,
new Storage<>(storageDir, this, corruptedDatabaseFilesHandler));
+ case REFUND_DISPUTE_LIST:
+ return RefundDisputeList.fromProto(proto.getRefundDisputeList(),
+ this,
+ new Storage<>(storageDir, this, corruptedDatabaseFilesHandler));
case PREFERENCES_PAYLOAD:
return PreferencesPayload.fromProto(proto.getPreferencesPayload(), this);
case USER_PAYLOAD:
diff --git a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
index 1a068e82a8a..f5a0e39c6b3 100644
--- a/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
+++ b/core/src/main/java/bisq/core/setup/CorePersistedDataHost.java
@@ -29,6 +29,7 @@
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.dispute.arbitration.ArbitrationDisputeListService;
import bisq.core.support.dispute.mediation.MediationDisputeListService;
+import bisq.core.support.dispute.refund.RefundDisputeListService;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
@@ -63,6 +64,7 @@ public static List getPersistedDataHosts(Injector injector) {
persistedDataHosts.add(injector.getInstance(FailedTradesManager.class));
persistedDataHosts.add(injector.getInstance(ArbitrationDisputeListService.class));
persistedDataHosts.add(injector.getInstance(MediationDisputeListService.class));
+ persistedDataHosts.add(injector.getInstance(RefundDisputeListService.class));
persistedDataHosts.add(injector.getInstance(P2PService.class));
if (injector.getInstance(Key.get(Boolean.class, Names.named(DaoOptionKeys.DAO_ACTIVATED)))) {
diff --git a/core/src/main/java/bisq/core/support/SupportType.java b/core/src/main/java/bisq/core/support/SupportType.java
index 4d13c7848ec..cd10cc024ff 100644
--- a/core/src/main/java/bisq/core/support/SupportType.java
+++ b/core/src/main/java/bisq/core/support/SupportType.java
@@ -22,7 +22,8 @@
public enum SupportType {
ARBITRATION, // Need to be at index 0 to be the fall back for old clients
MEDIATION,
- TRADE;
+ TRADE,
+ REFUND;
public static SupportType fromProto(
protobuf.SupportType type) {
diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java
index 3348bbf963f..abe76911ada 100644
--- a/core/src/main/java/bisq/core/support/dispute/Dispute.java
+++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java
@@ -48,6 +48,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@@ -80,7 +81,7 @@ public final class Dispute implements NetworkPayload {
private final String makerContractSignature;
@Nullable
private final String takerContractSignature;
- private final PubKeyRing agentPubKeyRing; // arbitrator or mediator
+ private final PubKeyRing agentPubKeyRing; // dispute agent
private final boolean isSupportTicket;
private final ObservableList chatMessages = FXCollections.observableArrayList();
private BooleanProperty isClosedProperty = new SimpleBooleanProperty();
@@ -92,6 +93,13 @@ public final class Dispute implements NetworkPayload {
transient private Storage extends DisputeList> storage;
+ // Added v1.2.0
+ private SupportType supportType;
+ // Only used at refundAgent so that he knows how the mediator resolved the case
+ @Setter
+ @Nullable
+ private String mediatorsDisputeResult;
+
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@@ -114,7 +122,8 @@ public Dispute(Storage extends DisputeList> storage,
@Nullable String makerContractSignature,
@Nullable String takerContractSignature,
PubKeyRing agentPubKeyRing,
- boolean isSupportTicket) {
+ boolean isSupportTicket,
+ SupportType supportType) {
this(tradeId,
traderId,
disputeOpenerIsBuyer,
@@ -131,7 +140,8 @@ public Dispute(Storage extends DisputeList> storage,
makerContractSignature,
takerContractSignature,
agentPubKeyRing,
- isSupportTicket);
+ isSupportTicket,
+ supportType);
this.storage = storage;
openingDate = new Date().getTime();
}
@@ -157,7 +167,8 @@ public Dispute(String tradeId,
@Nullable String makerContractSignature,
@Nullable String takerContractSignature,
PubKeyRing agentPubKeyRing,
- boolean isSupportTicket) {
+ boolean isSupportTicket,
+ SupportType supportType) {
this.tradeId = tradeId;
this.traderId = traderId;
this.disputeOpenerIsBuyer = disputeOpenerIsBuyer;
@@ -175,6 +186,7 @@ public Dispute(String tradeId,
this.takerContractSignature = takerContractSignature;
this.agentPubKeyRing = agentPubKeyRing;
this.isSupportTicket = isSupportTicket;
+ this.supportType = supportType;
id = tradeId + "_" + traderId;
}
@@ -210,6 +222,8 @@ public protobuf.Dispute toProtoMessage() {
Optional.ofNullable(makerContractSignature).ifPresent(builder::setMakerContractSignature);
Optional.ofNullable(takerContractSignature).ifPresent(builder::setTakerContractSignature);
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));
return builder.build();
}
@@ -230,7 +244,8 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr
ProtoUtil.stringOrNullFromProto(proto.getMakerContractSignature()),
ProtoUtil.stringOrNullFromProto(proto.getTakerContractSignature()),
PubKeyRing.fromProto(proto.getAgentPubKeyRing()),
- proto.getIsSupportTicket());
+ proto.getIsSupportTicket(),
+ SupportType.fromProto(proto.getSupportType()));
dispute.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
@@ -241,6 +256,7 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr
if (proto.hasDisputeResult())
dispute.disputeResultProperty.set(DisputeResult.fromProto(proto.getDisputeResult()));
dispute.disputePayoutTxId = ProtoUtil.stringOrNullFromProto(proto.getDisputePayoutTxId());
+ dispute.setMediatorsDisputeResult(proto.getMediatorsDisputeResult());
return dispute;
}
@@ -258,10 +274,6 @@ public void addAndPersistChatMessage(ChatMessage chatMessage) {
}
}
- public boolean isMediationDispute() {
- return !chatMessages.isEmpty() && chatMessages.get(0).getSupportType() == SupportType.MEDIATION;
- }
-
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
index 7049dbcc431..e7ae907394d 100644
--- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java
@@ -60,7 +60,7 @@
@Slf4j
public abstract class DisputeManager> extends SupportManager {
protected final TradeWalletService tradeWalletService;
- protected final BtcWalletService walletService;
+ protected final BtcWalletService btcWalletService;
protected final TradeManager tradeManager;
protected final ClosedTradableManager closedTradableManager;
protected final OpenOfferManager openOfferManager;
@@ -74,7 +74,7 @@ public abstract class DisputeManager findOwnDispute(String tradeId) {
// Message handler
///////////////////////////////////////////////////////////////////////////////////////////
- // arbitrator receives that from trader who opens dispute
+ // dispute agent receives that from trader who opens dispute
protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -270,15 +272,29 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
}
// We use the ChatMessage not the openNewDisputeMessage for the ACK
- ObservableList messages = openNewDisputeMessage.getDispute().getChatMessages();
+ ObservableList messages = dispute.getChatMessages();
if (!messages.isEmpty()) {
- ChatMessage msg = messages.get(0);
+ ChatMessage chatMessage = messages.get(0);
PubKeyRing sendersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getBuyerPubKeyRing() : contractFromOpener.getSellerPubKeyRing();
- sendAckMessage(msg, sendersPubKeyRing, errorMessage == null, errorMessage);
+ sendAckMessage(chatMessage, sendersPubKeyRing, errorMessage == null, errorMessage);
+ }
+
+ // In case of refundAgent we add a message with the mediatorsDisputeSummary. Only visible for refundAgent.
+ if (dispute.getMediatorsDisputeResult() != null) {
+ String mediatorsDisputeResult = Res.get("support.mediatorsDisputeSummary", dispute.getMediatorsDisputeResult());
+ ChatMessage mediatorsDisputeResultMessage = new ChatMessage(
+ getSupportType(),
+ dispute.getTradeId(),
+ pubKeyRing.hashCode(),
+ false,
+ mediatorsDisputeResult,
+ p2PService.getAddress());
+ mediatorsDisputeResultMessage.setSystemMessage(true);
+ dispute.addAndPersistChatMessage(mediatorsDisputeResultMessage);
}
}
- // not dispute requester receives that from arbitrator
+ // not dispute requester receives that from dispute agent
protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -345,17 +361,18 @@ public void sendOpenNewDisputeMessage(Dispute dispute,
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent() || reOpen) {
- String disputeInfo = getDisputeInfo(dispute.isMediationDispute());
+ String disputeInfo = getDisputeInfo(dispute);
String sysMsg = dispute.isSupportTicket() ?
Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION)
: Res.get("support.youOpenedDispute", disputeInfo, Version.VERSION);
+ String message = Res.get("support.systemMsg", sysMsg);
ChatMessage chatMessage = new ChatMessage(
getSupportType(),
dispute.getTradeId(),
pubKeyRing.hashCode(),
false,
- Res.get("support.systemMsg", sysMsg),
+ message,
p2PService.getAddress());
chatMessage.setSystemMessage(true);
dispute.addAndPersistChatMessage(chatMessage);
@@ -368,11 +385,14 @@ public void sendOpenNewDisputeMessage(Dispute dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType());
- log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, " +
- "chatMessage.uid={}",
- openNewDisputeMessage.getClass().getSimpleName(), agentNodeAddress,
- openNewDisputeMessage.getTradeId(), openNewDisputeMessage.getUid(),
+
+ log.info("Send {} to peer {}. tradeId={}, openNewDisputeMessage.uid={}, chatMessage.uid={}",
+ openNewDisputeMessage.getClass().getSimpleName(),
+ agentNodeAddress,
+ openNewDisputeMessage.getTradeId(),
+ openNewDisputeMessage.getUid(),
chatMessage.getUid());
+
p2PService.sendEncryptedMailboxMessage(agentNodeAddress,
dispute.getAgentPubKeyRing(),
openNewDisputeMessage,
@@ -432,7 +452,7 @@ public void onFault(String errorMessage) {
}
}
- // arbitrator sends that to trading peer when he received openDispute request
+ // dispute agent sends that to trading peer when he received openDispute request
private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
Contract contractFromOpener,
PubKeyRing pubKeyRing) {
@@ -459,10 +479,11 @@ private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
disputeFromOpener.getMakerContractSignature(),
disputeFromOpener.getTakerContractSignature(),
disputeFromOpener.getAgentPubKeyRing(),
- disputeFromOpener.isSupportTicket());
+ disputeFromOpener.isSupportTicket(),
+ disputeFromOpener.getSupportType());
Optional storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
- String disputeInfo = getDisputeInfo(dispute.isMediationDispute());
+ String disputeInfo = getDisputeInfo(dispute);
String sysMsg = dispute.isSupportTicket() ?
Res.get("support.peerOpenedTicket", disputeInfo)
: Res.get("support.peerOpenedDispute", disputeInfo);
@@ -485,11 +506,12 @@ private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
p2PService.getAddress(),
UUID.randomUUID().toString(),
getSupportType());
- log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, " +
- "chatMessage.uid={}",
+
+ log.info("Send {} to peer {}. tradeId={}, peerOpenedDisputeMessage.uid={}, chatMessage.uid={}",
peerOpenedDisputeMessage.getClass().getSimpleName(), peersNodeAddress,
peerOpenedDisputeMessage.getTradeId(), peerOpenedDisputeMessage.getUid(),
chatMessage.getUid());
+
p2PService.sendEncryptedMailboxMessage(peersNodeAddress,
peersPubKeyRing,
peerOpenedDisputeMessage,
@@ -546,7 +568,7 @@ public void onFault(String errorMessage) {
}
}
- // arbitrator send result to trader
+ // dispute agent send result to trader
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String text) {
T disputeList = getDisputeList();
if (disputeList == null) {
@@ -690,12 +712,4 @@ public Optional findDispute(String tradeId) {
.filter(e -> e.getTradeId().equals(tradeId))
.findAny();
}
-
- private String getDisputeInfo(boolean isMediationDispute) {
- String role = isMediationDispute ? Res.get("shared.mediator").toLowerCase() :
- Res.get("shared.arbitrator2").toLowerCase();
- String link = isMediationDispute ? "https://docs.bisq.network/trading-rules.html#mediation" :
- "https://bisq.network/docs/exchange/arbitration-system";
- return Res.get("support.initialInfo", role, role, link);
- }
}
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
index cbf859e2a24..3dd3b779fd8 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java
@@ -24,6 +24,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.SupportType;
@@ -140,6 +141,13 @@ public void cleanupDisputes() {
disputeListService.cleanupDisputes(tradeId -> tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.DISPUTE_CLOSED));
}
+ @Override
+ protected String getDisputeInfo(Dispute dispute) {
+ String role = Res.get("shared.arbitrator2").toLowerCase();
+ String link = "https://bisq.network/docs/exchange/arbitration-system";
+ return Res.get("support.initialInfo", role, role, link);
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Message handler
@@ -152,7 +160,7 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
ChatMessage chatMessage = disputeResult.getChatMessage();
checkNotNull(chatMessage, "chatMessage must not be null");
if (Arrays.equals(disputeResult.getArbitratorPubKey(),
- walletService.getArbitratorAddressEntry().getPubKey())) {
+ btcWalletService.getArbitratorAddressEntry().getPubKey())) {
log.error("Arbitrator received disputeResultMessage. That must never happen.");
return;
}
@@ -230,7 +238,7 @@ else if (publisher == DisputeResult.Winner.SELLER)
if (payoutTx == null) {
if (dispute.getDepositTxSerialized() != null) {
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
- DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
+ DeterministicKey multiSigKeyPair = btcWalletService.getMultiSigKeyPair(tradeId, multiSigPubKey);
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(
dispute.getDepositTxSerialized(),
disputeResult.getArbitratorSignature(),
diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
index 705a17ff5ef..7ad44e76509 100644
--- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
+++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java
@@ -20,6 +20,7 @@
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.SupportType;
@@ -46,15 +47,12 @@
import bisq.common.crypto.PubKeyRing;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.Date;
-import java.util.GregorianCalendar;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
@@ -66,14 +64,6 @@
@Singleton
public final class MediationManager extends DisputeManager {
- // The date when mediation is activated
- private static final Date MEDIATION_ACTIVATED_DATE = Utilities.getUTCDate(2019, GregorianCalendar.SEPTEMBER, 26);
-
- public static boolean isMediationActivated() {
- return new Date().after(MEDIATION_ACTIVATED_DATE);
- }
-
-
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@@ -141,6 +131,13 @@ public void cleanupDisputes() {
});
}
+ @Override
+ protected String getDisputeInfo(Dispute dispute) {
+ String role = Res.get("shared.mediator").toLowerCase();
+ String link = "https://docs.bisq.network/trading-rules.html#mediation";
+ return Res.get("support.initialInfo", role, role, link);
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Message handler
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java
new file mode 100644
index 00000000000..5b2ba07b5c1
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java
@@ -0,0 +1,83 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.proto.CoreProtoResolver;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeList;
+
+import bisq.common.proto.ProtoUtil;
+import bisq.common.storage.Storage;
+
+import com.google.protobuf.Message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ToString
+/*
+ * Holds a List of refund dispute objects.
+ *
+ * Calls to the List are delegated because this class intercepts the add/remove calls so changes
+ * can be saved to disc.
+ */
+public final class RefundDisputeList extends DisputeList {
+
+ RefundDisputeList(Storage storage) {
+ super(storage);
+ }
+
+ @Override
+ public void readPersisted() {
+ // We need to use DisputeList as file name to not lose existing disputes which are stored in the DisputeList file
+ RefundDisputeList persisted = storage.initAndGetPersisted(this, "RefundDisputeList", 50);
+ if (persisted != null) {
+ list.addAll(persisted.getList());
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private RefundDisputeList(Storage storage, List list) {
+ super(storage, list);
+ }
+
+ @Override
+ public Message toProtoMessage() {
+ return protobuf.PersistableEnvelope.newBuilder().setRefundDisputeList(protobuf.RefundDisputeList.newBuilder()
+ .addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list)))).build();
+ }
+
+ public static RefundDisputeList fromProto(protobuf.RefundDisputeList proto,
+ CoreProtoResolver coreProtoResolver,
+ Storage storage) {
+ List list = proto.getDisputeList().stream()
+ .map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver))
+ .collect(Collectors.toList());
+ list.forEach(e -> e.setStorage(storage));
+ return new RefundDisputeList(storage, list);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java
new file mode 100644
index 00000000000..afdac5c9f3b
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.support.dispute.DisputeListService;
+
+import bisq.common.storage.Storage;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public final class RefundDisputeListService extends DisputeListService {
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public RefundDisputeListService(Storage storage) {
+ super(storage);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implement template methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected RefundDisputeList getConcreteDisputeList() {
+ return new RefundDisputeList(storage);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
new file mode 100644
index 00000000000..34dd6711383
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java
@@ -0,0 +1,206 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.btc.setup.WalletsSetup;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.locale.Res;
+import bisq.core.offer.OpenOffer;
+import bisq.core.offer.OpenOfferManager;
+import bisq.core.support.SupportType;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeManager;
+import bisq.core.support.dispute.DisputeResult;
+import bisq.core.support.dispute.messages.DisputeResultMessage;
+import bisq.core.support.dispute.messages.OpenNewDisputeMessage;
+import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
+import bisq.core.support.messages.ChatMessage;
+import bisq.core.support.messages.SupportMessage;
+import bisq.core.trade.Trade;
+import bisq.core.trade.TradeManager;
+import bisq.core.trade.closed.ClosedTradableManager;
+
+import bisq.network.p2p.AckMessageSourceType;
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.P2PService;
+
+import bisq.common.Timer;
+import bisq.common.UserThread;
+import bisq.common.crypto.PubKeyRing;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.Optional;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+@Singleton
+public final class RefundManager extends DisputeManager {
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public RefundManager(P2PService p2PService,
+ TradeWalletService tradeWalletService,
+ BtcWalletService walletService,
+ WalletsSetup walletsSetup,
+ TradeManager tradeManager,
+ ClosedTradableManager closedTradableManager,
+ OpenOfferManager openOfferManager,
+ PubKeyRing pubKeyRing,
+ RefundDisputeListService refundDisputeListService) {
+ super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
+ openOfferManager, pubKeyRing, refundDisputeListService);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implement template methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public SupportType getSupportType() {
+ return SupportType.REFUND;
+ }
+
+ @Override
+ public void dispatchMessage(SupportMessage message) {
+ if (canProcessMessage(message)) {
+ log.info("Received {} with tradeId {} and uid {}",
+ message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
+
+ if (message instanceof OpenNewDisputeMessage) {
+ onOpenNewDisputeMessage((OpenNewDisputeMessage) message);
+ } else if (message instanceof PeerOpenedDisputeMessage) {
+ onPeerOpenedDisputeMessage((PeerOpenedDisputeMessage) message);
+ } else if (message instanceof ChatMessage) {
+ onChatMessage((ChatMessage) message);
+ } else if (message instanceof DisputeResultMessage) {
+ onDisputeResultMessage((DisputeResultMessage) message);
+ } else {
+ log.warn("Unsupported message at dispatchMessage. message={}", message);
+ }
+ }
+ }
+
+ @Override
+ protected Trade.DisputeState getDisputeState_StartedByPeer() {
+ return Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER;
+ }
+
+ @Override
+ protected AckMessageSourceType getAckMessageSourceType() {
+ return AckMessageSourceType.REFUND_MESSAGE;
+ }
+
+ @Override
+ public void cleanupDisputes() {
+ disputeListService.cleanupDisputes(tradeId -> tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED));
+ }
+
+ @Override
+ protected String getDisputeInfo(Dispute dispute) {
+ String role = Res.get("shared.refundAgent").toLowerCase();
+ String link = "https://bisq.network/docs/exchange/refundAgent"; //todo create link
+ return Res.get("support.initialInfo", role, role, link);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Message handler
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ // We get that message at both peers. The dispute object is in context of the trader
+ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
+ DisputeResult disputeResult = disputeResultMessage.getDisputeResult();
+ String tradeId = disputeResult.getTradeId();
+ ChatMessage chatMessage = disputeResult.getChatMessage();
+ checkNotNull(chatMessage, "chatMessage must not be null");
+ Optional disputeOptional = findDispute(disputeResult);
+ String uid = disputeResultMessage.getUid();
+ if (!disputeOptional.isPresent()) {
+ log.warn("We got a dispute result msg but we don't have a matching dispute. " +
+ "That might happen when we get the disputeResultMessage before the dispute was created. " +
+ "We try again after 2 sec. to apply the disputeResultMessage. TradeId = " + tradeId);
+ if (!delayMsgMap.containsKey(uid)) {
+ // We delay 2 sec. to be sure the comm. msg gets added first
+ Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2);
+ delayMsgMap.put(uid, timer);
+ } else {
+ log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " +
+ "That should never happen. TradeId = " + tradeId);
+ }
+ return;
+ }
+
+ Dispute dispute = disputeOptional.get();
+ cleanupRetryMap(uid);
+ if (!dispute.getChatMessages().contains(chatMessage)) {
+ dispute.addAndPersistChatMessage(chatMessage);
+ } else {
+ log.warn("We got a dispute mail msg what we have already stored. TradeId = " + chatMessage.getTradeId());
+ }
+ dispute.setIsClosed(true);
+
+ if (dispute.disputeResultProperty().get() != null) {
+ log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " +
+ "again because the first close did not succeed. TradeId = " + tradeId);
+ }
+
+ dispute.setDisputeResult(disputeResult);
+
+ Optional tradeOptional = tradeManager.getTradeById(tradeId);
+ if (tradeOptional.isPresent()) {
+ Trade trade = tradeOptional.get();
+ if (trade.getDisputeState() == Trade.DisputeState.REFUND_REQUESTED ||
+ trade.getDisputeState() == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
+ trade.setDisputeState(Trade.DisputeState.REFUND_REQUEST_CLOSED);
+ }
+ } else {
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
+ openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
+ }
+ sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
+
+ // set state after payout as we call swapTradeEntryToAvailableEntry
+ if (tradeManager.getTradeById(tradeId).isPresent()) {
+ tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED);
+ } else {
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
+ openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // API
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public NodeAddress getAgentNodeAddress(Dispute dispute) {
+ return dispute.getContract().getRefundAgentNodeAddress();
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundResultState.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundResultState.java
new file mode 100644
index 00000000000..1664b733bc4
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundResultState.java
@@ -0,0 +1,33 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.common.proto.ProtoUtil;
+
+// todo
+public enum RefundResultState {
+ UNDEFINED_REFUND_RESULT;
+
+ public static RefundResultState fromProto(protobuf.RefundResultState refundResultState) {
+ return ProtoUtil.enumFromProto(RefundResultState.class, refundResultState.name());
+ }
+
+ public static protobuf.RefundResultState toProtoMessage(RefundResultState refundResultState) {
+ return protobuf.RefundResultState.valueOf(refundResultState.name());
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundSession.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundSession.java
new file mode 100644
index 00000000000..b5e9d7e5cc9
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundSession.java
@@ -0,0 +1,33 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund;
+
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeSession;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+@Slf4j
+public class RefundSession extends DisputeSession {
+
+ public RefundSession(@Nullable Dispute dispute, boolean isTrader) {
+ super(dispute, isTrader);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgent.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgent.java
new file mode 100644
index 00000000000..e4a4a47d643
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgent.java
@@ -0,0 +1,109 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund.refundagent;
+
+import bisq.core.support.dispute.agent.DisputeAgent;
+
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.crypto.PubKeyRing;
+import bisq.common.proto.ProtoUtil;
+
+import com.google.protobuf.ByteString;
+
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+@EqualsAndHashCode(callSuper = true)
+@Slf4j
+@Getter
+public final class RefundAgent extends DisputeAgent {
+
+ public RefundAgent(NodeAddress nodeAddress,
+ PubKeyRing pubKeyRing,
+ List languageCodes,
+ long registrationDate,
+ byte[] registrationPubKey,
+ String registrationSignature,
+ @Nullable String emailAddress,
+ @Nullable String info,
+ @Nullable Map extraDataMap) {
+
+ super(nodeAddress,
+ pubKeyRing,
+ languageCodes,
+ registrationDate,
+ registrationPubKey,
+ registrationSignature,
+ emailAddress,
+ info,
+ extraDataMap);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public protobuf.StoragePayload toProtoMessage() {
+ protobuf.RefundAgent.Builder builder = protobuf.RefundAgent.newBuilder()
+ .setNodeAddress(nodeAddress.toProtoMessage())
+ .setPubKeyRing(pubKeyRing.toProtoMessage())
+ .addAllLanguageCodes(languageCodes)
+ .setRegistrationDate(registrationDate)
+ .setRegistrationPubKey(ByteString.copyFrom(registrationPubKey))
+ .setRegistrationSignature(registrationSignature);
+ Optional.ofNullable(emailAddress).ifPresent(builder::setEmailAddress);
+ Optional.ofNullable(info).ifPresent(builder::setInfo);
+ Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
+ return protobuf.StoragePayload.newBuilder().setRefundAgent(builder).build();
+ }
+
+ public static RefundAgent fromProto(protobuf.RefundAgent proto) {
+ return new RefundAgent(NodeAddress.fromProto(proto.getNodeAddress()),
+ PubKeyRing.fromProto(proto.getPubKeyRing()),
+ new ArrayList<>(proto.getLanguageCodesList()),
+ proto.getRegistrationDate(),
+ proto.getRegistrationPubKey().toByteArray(),
+ proto.getRegistrationSignature(),
+ ProtoUtil.stringOrNullFromProto(proto.getEmailAddress()),
+ ProtoUtil.stringOrNullFromProto(proto.getInfo()),
+ CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap());
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // API
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ @Override
+ public String toString() {
+ return "RefundAgent{} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java
new file mode 100644
index 00000000000..d13560f3dfc
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java
@@ -0,0 +1,105 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund.refundagent;
+
+import bisq.core.app.AppOptionKeys;
+import bisq.core.filter.FilterManager;
+import bisq.core.support.dispute.agent.DisputeAgentManager;
+import bisq.core.user.User;
+
+import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
+
+import bisq.common.crypto.KeyRing;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Singleton
+public class RefundAgentManager extends DisputeAgentManager {
+
+ @Inject
+ public RefundAgentManager(KeyRing keyRing,
+ RefundAgentService refundAgentService,
+ User user,
+ FilterManager filterManager,
+ @Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
+ super(keyRing, refundAgentService, user, filterManager, useDevPrivilegeKeys);
+ }
+
+ @Override
+ protected List getPubKeyList() {
+ return List.of("02a25798e256b800d7ea71c31098ac9a47cb20892176afdfeb051f5ded382d44af",
+ "0360455d3cffe00ef73cc1284c84eedacc8c5c3374c43f4aac8ffb95f5130b9ef5",
+ "03b0513afbb531bc4551b379eba027feddd33c92b5990fd477b0fa6eff90a5b7db",
+ "03533fd75fda29c351298e50b8ea696656dcb8ce4e263d10618c6901a50450bf0e",
+ "028124436482aa4c61a4bc4097d60c80b09f4285413be3b023a37a0164cbd5d818",
+ "0384fcf883116d8e9469720ed7808cc4141f6dc6a5ed23d76dd48f2f5f255590d7",
+ "029bd318ecee4e212ff06a4396770d600d72e9e0c6532142a428bdb401491e9721",
+ "02e375b4b24d0a858953f7f94666667554d41f78000b9c8a301294223688b29011",
+ "0232c088ae7c070de89d2b6c8d485b34bf0e3b2a964a2c6622f39ca501260c23f7",
+ "033e047f74f2aa1ce41e8c85731f97ab83d448d65dc8518ab3df4474a5d53a3d19",
+ "02f52a8cf373c8cbddb318e523b7f111168bf753fdfb6f8aa81f88c950ede3a5ce",
+ "039784029922c54bcd0f0e7f14530f586053a5f4e596e86b3474cd7404657088ae",
+ "037969f9d5ab2cc609104c6e61323df55428f8f108c11aab7c7b5f953081d39304",
+ "031bd37475b8c5615ac46d6816e791c59d806d72a0bc6739ae94e5fe4545c7f8a6",
+ "021bb92c636feacf5b082313eb071a63dfcd26501a48b3cd248e35438e5afb7daf");
+
+
+ }
+
+ @Override
+ protected boolean isExpectedInstance(ProtectedStorageEntry data) {
+ return data.getProtectedStoragePayload() instanceof RefundAgent;
+ }
+
+ @Override
+ protected void addAcceptedDisputeAgentToUser(RefundAgent disputeAgent) {
+ user.addAcceptedRefundAgent(disputeAgent);
+ }
+
+ @Override
+ protected void removeAcceptedDisputeAgentFromUser(ProtectedStorageEntry data) {
+ user.removeAcceptedRefundAgent((RefundAgent) data.getProtectedStoragePayload());
+ }
+
+ @Override
+ protected List getAcceptedDisputeAgentsFromUser() {
+ return user.getAcceptedRefundAgents();
+ }
+
+ @Override
+ protected void clearAcceptedDisputeAgentsAtUser() {
+ user.clearAcceptedRefundAgents();
+ }
+
+ @Override
+ protected RefundAgent getRegisteredDisputeAgentFromUser() {
+ return user.getRegisteredRefundAgent();
+ }
+
+ @Override
+ protected void setRegisteredDisputeAgentAtUser(RefundAgent disputeAgent) {
+ user.setRegisteredRefundAgent(disputeAgent);
+ }
+}
diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentService.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentService.java
new file mode 100644
index 00000000000..ab67223e984
--- /dev/null
+++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentService.java
@@ -0,0 +1,61 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.support.dispute.refund.refundagent;
+
+import bisq.core.filter.FilterManager;
+import bisq.core.support.dispute.agent.DisputeAgentService;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.P2PService;
+
+import com.google.inject.Singleton;
+
+import javax.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Singleton
+public class RefundAgentService extends DisputeAgentService {
+ @Inject
+ public RefundAgentService(P2PService p2PService, FilterManager filterManager) {
+ super(p2PService, filterManager);
+ }
+
+ @Override
+ protected Set getDisputeAgentSet(List bannedDisputeAgents) {
+ return p2PService.getDataMap().values().stream()
+ .filter(data -> data.getProtectedStoragePayload() instanceof RefundAgent)
+ .map(data -> (RefundAgent) data.getProtectedStoragePayload())
+ .filter(a -> bannedDisputeAgents == null ||
+ !bannedDisputeAgents.contains(a.getNodeAddress().getFullAddress()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ protected List getDisputeAgentsFromFilter() {
+ return filterManager.getFilter() != null ? filterManager.getFilter().getRefundAgents() : new ArrayList<>();
+ }
+
+ public Map getRefundAgents() {
+ return super.getDisputeAgents();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
index 17eeca7a7a4..e6cecc1e4f9 100644
--- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java
@@ -20,7 +20,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.BuyerAsMakerProtocol;
import bisq.core.trade.protocol.MakerProtocol;
@@ -48,6 +48,7 @@ public BuyerAsMakerTrade(Offer offer,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -56,6 +57,7 @@ public BuyerAsMakerTrade(Offer offer,
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -84,6 +86,7 @@ public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradePro
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService);
@@ -107,7 +110,7 @@ protected void createTradeProtocol() {
}
@Override
- public void handleTakeOfferRequest(TradeMessage message,
+ public void handleTakeOfferRequest(InputsForDepositTxRequest message,
NodeAddress taker,
ErrorMessageHandler errorMessageHandler) {
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(message, taker, errorMessageHandler);
diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
index c4c2c1e7768..d9b5f86c290 100644
--- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java
@@ -51,6 +51,7 @@ public BuyerAsTakerTrade(Offer offer,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -62,6 +63,7 @@ public BuyerAsTakerTrade(Offer offer,
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -94,6 +96,7 @@ public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradePro
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService),
proto,
diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java
index 4c534b30f05..4112252e6d6 100644
--- a/core/src/main/java/bisq/core/trade/BuyerTrade.java
+++ b/core/src/main/java/bisq/core/trade/BuyerTrade.java
@@ -47,6 +47,7 @@ public abstract class BuyerTrade extends Trade {
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -58,6 +59,7 @@ public abstract class BuyerTrade extends Trade {
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -68,6 +70,7 @@ public abstract class BuyerTrade extends Trade {
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -76,6 +79,7 @@ public abstract class BuyerTrade extends Trade {
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/Contract.java
index 6ec2e573949..43bd345813b 100644
--- a/core/src/main/java/bisq/core/trade/Contract.java
+++ b/core/src/main/java/bisq/core/trade/Contract.java
@@ -73,6 +73,10 @@ public final class Contract implements NetworkPayload {
@JsonExclude
private final byte[] takerMultiSigPubKey;
+ // Added in v1.2.0
+ private long lockTime;
+ private final NodeAddress refundAgentNodeAddress;
+
public Contract(OfferPayload offerPayload,
long tradeAmount,
long tradePrice,
@@ -91,7 +95,9 @@ public Contract(OfferPayload offerPayload,
String makerPayoutAddressString,
String takerPayoutAddressString,
byte[] makerMultiSigPubKey,
- byte[] takerMultiSigPubKey) {
+ byte[] takerMultiSigPubKey,
+ long lockTime,
+ NodeAddress refundAgentNodeAddress) {
this.offerPayload = offerPayload;
this.tradeAmount = tradeAmount;
this.tradePrice = tradePrice;
@@ -111,6 +117,8 @@ public Contract(OfferPayload offerPayload,
this.takerPayoutAddressString = takerPayoutAddressString;
this.makerMultiSigPubKey = makerMultiSigPubKey;
this.takerMultiSigPubKey = takerMultiSigPubKey;
+ this.lockTime = lockTime;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
String makerPaymentMethodId = makerPaymentAccountPayload.getPaymentMethodId();
String takerPaymentMethodId = takerPaymentAccountPayload.getPaymentMethodId();
@@ -128,7 +136,6 @@ public Contract(OfferPayload offerPayload,
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- @Nullable
public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver coreProtoResolver) {
return new Contract(OfferPayload.fromProto(proto.getOfferPayload()),
proto.getTradeAmount(),
@@ -148,7 +155,9 @@ public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver core
proto.getMakerPayoutAddressString(),
proto.getTakerPayoutAddressString(),
proto.getMakerMultiSigPubKey().toByteArray(),
- proto.getTakerMultiSigPubKey().toByteArray());
+ proto.getTakerMultiSigPubKey().toByteArray(),
+ proto.getLockTime(),
+ NodeAddress.fromProto(proto.getRefundAgentNodeAddress()));
}
@Override
@@ -173,6 +182,8 @@ public protobuf.Contract toProtoMessage() {
.setTakerPayoutAddressString(takerPayoutAddressString)
.setMakerMultiSigPubKey(ByteString.copyFrom(makerMultiSigPubKey))
.setTakerMultiSigPubKey(ByteString.copyFrom(takerMultiSigPubKey))
+ .setLockTime(lockTime)
+ .setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
.build();
}
@@ -291,6 +302,7 @@ public String toString() {
",\n sellerNodeAddress=" + sellerNodeAddress +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n takerAccountId='" + takerAccountId + '\'' +
@@ -304,6 +316,7 @@ public String toString() {
",\n takerMultiSigPubKey=" + Utilities.bytesAsHexString(takerMultiSigPubKey) +
",\n buyerMultiSigPubKey=" + Utilities.bytesAsHexString(getBuyerMultiSigPubKey()) +
",\n sellerMultiSigPubKey=" + Utilities.bytesAsHexString(getSellerMultiSigPubKey()) +
+ ",\n lockTime=" + lockTime +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/trade/MakerTrade.java b/core/src/main/java/bisq/core/trade/MakerTrade.java
index ffb5b672892..5a2fd7dd8d3 100644
--- a/core/src/main/java/bisq/core/trade/MakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/MakerTrade.java
@@ -17,12 +17,12 @@
package bisq.core.trade;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface MakerTrade {
- void handleTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler);
+ void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress peerNodeAddress, ErrorMessageHandler errorMessageHandler);
}
diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
index 08fb2f46ca2..5e9b5883f9d 100644
--- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java
@@ -20,7 +20,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.Offer;
import bisq.core.proto.CoreProtoResolver;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.MakerProtocol;
import bisq.core.trade.protocol.SellerAsMakerProtocol;
@@ -48,9 +48,18 @@ public SellerAsMakerTrade(Offer offer,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
- super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, mediatorNodeAddress, storage, btcWalletService);
+ super(offer,
+ txFee,
+ takerFee,
+ isCurrencyForTakerFeeBtc,
+ arbitratorNodeAddress,
+ mediatorNodeAddress,
+ refundAgentNodeAddress,
+ storage,
+ btcWalletService);
}
@@ -78,6 +87,7 @@ public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeP
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService);
@@ -101,7 +111,7 @@ protected void createTradeProtocol() {
}
@Override
- public void handleTakeOfferRequest(TradeMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
+ public void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler) {
((MakerProtocol) tradeProtocol).handleTakeOfferRequest(message, taker, errorMessageHandler);
}
}
diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
index 0d4aeb6b847..79debd8137c 100644
--- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java
@@ -51,6 +51,7 @@ public SellerAsTakerTrade(Offer offer,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -62,6 +63,7 @@ public SellerAsTakerTrade(Offer offer,
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -94,6 +96,7 @@ public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeP
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null,
+ proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null,
storage,
btcWalletService),
proto,
diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java
index 629dc5adb52..68eed3a1e08 100644
--- a/core/src/main/java/bisq/core/trade/SellerTrade.java
+++ b/core/src/main/java/bisq/core/trade/SellerTrade.java
@@ -46,6 +46,7 @@ public abstract class SellerTrade extends Trade {
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -57,6 +58,7 @@ public abstract class SellerTrade extends Trade {
tradingPeerNodeAddress,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
@@ -67,6 +69,7 @@ public abstract class SellerTrade extends Trade {
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer,
@@ -75,6 +78,7 @@ public abstract class SellerTrade extends Trade {
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
}
diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java
index a86ea93fa7b..938b225e437 100644
--- a/core/src/main/java/bisq/core/trade/Trade.java
+++ b/core/src/main/java/bisq/core/trade/Trade.java
@@ -21,6 +21,7 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.monetary.Price;
@@ -34,6 +35,8 @@
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.RefundResultState;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.TradeProtocol;
@@ -114,7 +117,7 @@ public enum State {
// maker perspective
MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
- MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
+ MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED), //todo remove
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.TAKER_FEE_PUBLISHED),
// taker perspective
@@ -122,21 +125,21 @@ public enum State {
// #################### Phase DEPOSIT_PAID
- TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
+ SELLER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PUBLISHED),
// DEPOSIT_TX_PUBLISHED_MSG
- // taker perspective
- TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ // seller perspective
+ SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- // maker perspective
- MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
+ // buyer perspective
+ BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PUBLISHED),
- // Alternatively the maker could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
- MAKER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
+ // Alternatively the buyer could have seen the deposit tx earlier before he received the DEPOSIT_TX_PUBLISHED_MSG
+ BUYER_SAW_DEPOSIT_TX_IN_NETWORK(Phase.DEPOSIT_PUBLISHED),
// #################### Phase DEPOSIT_CONFIRMED
@@ -221,7 +224,12 @@ public enum DisputeState {
// mediation
MEDIATION_REQUESTED,
MEDIATION_STARTED_BY_PEER,
- MEDIATION_CLOSED;
+ MEDIATION_CLOSED,
+
+ // refund
+ REFUND_REQUESTED,
+ REFUND_REQUEST_STARTED_BY_PEER,
+ REFUND_REQUEST_CLOSED;
public static Trade.DisputeState fromProto(protobuf.Trade.DisputeState disputeState) {
return ProtoUtil.enumFromProto(Trade.DisputeState.class, disputeState.name());
@@ -368,9 +376,14 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
@Getter
transient protected TradeProtocol tradeProtocol;
@Nullable
- transient private Transaction payoutTx;
- @Nullable
transient private Transaction depositTx;
+
+ // Added in v1.2.0
+ @Nullable
+ transient private Transaction delayedPayoutTx;
+
+ @Nullable
+ transient private Transaction payoutTx;
@Nullable
transient private Coin tradeAmount;
@@ -378,12 +391,33 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
transient private ObjectProperty tradeVolumeProperty;
final transient private Set decryptedMessageWithPubKeySet = new HashSet<>();
- //Added in v1.1.6
+ // Added in v1.1.6
@Getter
@Nullable
private MediationResultState mediationResultState = MediationResultState.UNDEFINED_MEDIATION_RESULT;
transient final private ObjectProperty mediationResultStateProperty = new SimpleObjectProperty<>(mediationResultState);
+ // Added in v1.2.0
+ @Getter
+ @Setter
+ private long lockTime;
+ @Nullable
+ @Getter
+ @Setter
+ private String delayedPayoutTxId;
+ @Nullable
+ @Getter
+ @Setter
+ private NodeAddress refundAgentNodeAddress;
+ @Nullable
+ @Getter
+ @Setter
+ private PubKeyRing refundAgentPubKeyRing;
+ @Getter
+ @Nullable
+ private RefundResultState refundResultState = RefundResultState.UNDEFINED_REFUND_RESULT;
+ transient final private ObjectProperty refundResultStateProperty = new SimpleObjectProperty<>(refundResultState);
+
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
@@ -396,6 +430,7 @@ protected Trade(Offer offer,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
this.offer = offer;
@@ -406,6 +441,7 @@ protected Trade(Offer offer,
this.btcWalletService = btcWalletService;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
txFeeAsLong = txFee.value;
takerFeeAsLong = takerFee.value;
@@ -425,6 +461,7 @@ protected Trade(Offer offer,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
@Nullable NodeAddress mediatorNodeAddress,
+ @Nullable NodeAddress refundAgentNodeAddress,
Storage extends TradableList> storage,
BtcWalletService btcWalletService) {
@@ -434,6 +471,7 @@ protected Trade(Offer offer,
isCurrencyForTakerFeeBtc,
arbitratorNodeAddress,
mediatorNodeAddress,
+ refundAgentNodeAddress,
storage,
btcWalletService);
this.tradePrice = tradePrice;
@@ -463,7 +501,8 @@ public Message toProtoMessage() {
.setTradePeriodState(Trade.TradePeriodState.toProtoMessage(tradePeriodState))
.addAllChatMessage(chatMessages.stream()
.map(msg -> msg.toProtoNetworkEnvelope().getChatMessage())
- .collect(Collectors.toList()));
+ .collect(Collectors.toList()))
+ .setLockTime(lockTime);
Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId);
Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId);
@@ -476,13 +515,17 @@ public Message toProtoMessage() {
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)));
+ Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState)));
+ Optional.ofNullable(delayedPayoutTxId).ifPresent(e -> builder.setDelayedPayoutTxId(delayedPayoutTxId));
return builder.build();
}
@@ -502,13 +545,19 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv
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()));
+ trade.setRefundResultState(RefundResultState.fromProto(proto.getRefundResultState()));
+ String delayedPayoutTxId = proto.getDelayedPayoutTxId();
+ trade.setDelayedPayoutTxId(delayedPayoutTxId.isEmpty() ? null : delayedPayoutTxId);
+ trade.setLockTime(proto.getLockTime());
trade.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
@@ -531,6 +580,7 @@ public void init(P2PService p2PService,
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
+ DaoFacade daoFacade,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
ReferralIdService referralIdService,
@@ -540,6 +590,7 @@ public void init(P2PService p2PService,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@@ -550,6 +601,7 @@ public void init(P2PService p2PService,
btcWalletService,
bsqWalletService,
tradeWalletService,
+ daoFacade,
referralIdService,
user,
filterManager,
@@ -557,6 +609,7 @@ public void init(P2PService p2PService,
tradeStatisticsManager,
arbitratorManager,
mediatorManager,
+ refundAgentManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
@@ -572,6 +625,11 @@ public void init(P2PService p2PService,
persist();
});
+ refundAgentManager.getDisputeAgentByNodeAddress(refundAgentNodeAddress).ifPresent(refundAgent -> {
+ refundAgentPubKeyRing = refundAgent.getPubKeyRing();
+ persist();
+ });
+
createTradeProtocol();
// If we have already received a msg we apply it.
@@ -590,10 +648,10 @@ public void init(P2PService p2PService,
// The deserialized tx has not actual confidence data, so we need to get the fresh one from the wallet.
void updateDepositTxFromWallet() {
if (getDepositTx() != null)
- setDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getHash()));
+ applyDepositTx(processModel.getTradeWalletService().getWalletTx(getDepositTx().getHash()));
}
- public void setDepositTx(Transaction tx) {
+ public void applyDepositTx(Transaction tx) {
log.debug("setDepositTx " + tx);
this.depositTx = tx;
depositTxId = depositTx.getHashAsString();
@@ -608,6 +666,19 @@ public Transaction getDepositTx() {
return depositTx;
}
+ public void applyDelayedPayoutTx(Transaction delayedPayoutTx) {
+ this.delayedPayoutTx = delayedPayoutTx;
+ delayedPayoutTxId = delayedPayoutTx.getHashAsString();
+ persist();
+ }
+
+ @Nullable
+ public Transaction getDelayedPayoutTx() {
+ if (delayedPayoutTx == null)
+ delayedPayoutTx = delayedPayoutTxId != null ? btcWalletService.getTransaction(delayedPayoutTxId) : null;
+ return delayedPayoutTx;
+ }
+
// We don't need to persist the msg as if we dont apply it it will not be removed from the P2P network and we
// will received it again at next startup. Such might happen in edge cases when the user shuts down after we
// received the msb but before the init is called.
@@ -703,6 +774,14 @@ public void setMediationResultState(MediationResultState mediationResultState) {
persist();
}
+ public void setRefundResultState(RefundResultState refundResultState) {
+ boolean changed = this.refundResultState != refundResultState;
+ this.refundResultState = refundResultState;
+ refundResultStateProperty.set(refundResultState);
+ if (changed)
+ persist();
+ }
+
public void setTradePeriodState(TradePeriodState tradePeriodState) {
boolean changed = this.tradePeriodState != tradePeriodState;
@@ -821,7 +900,12 @@ public boolean isDepositPublished() {
}
public boolean isFundsLockedIn() {
- return isDepositPublished() && !isPayoutPublished() && disputeState != DisputeState.DISPUTE_CLOSED;
+ return isDepositPublished() &&
+ !isPayoutPublished() &&
+ disputeState != DisputeState.DISPUTE_CLOSED &&
+ disputeState != DisputeState.REFUND_REQUESTED &&
+ disputeState != DisputeState.REFUND_REQUEST_STARTED_BY_PEER &&
+ disputeState != DisputeState.REFUND_REQUEST_CLOSED;
}
public boolean isDepositConfirmed() {
@@ -832,7 +916,6 @@ public boolean isFiatSent() {
return getState().getPhase().ordinal() >= Phase.FIAT_SENT.ordinal();
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isFiatReceived() {
return getState().getPhase().ordinal() >= Phase.FIAT_RECEIVED.ordinal();
}
@@ -861,6 +944,10 @@ public ReadOnlyObjectProperty mediationResultStateProperty
return mediationResultStateProperty;
}
+ public ReadOnlyObjectProperty refundResultStateProperty() {
+ return refundResultStateProperty;
+ }
+
public ReadOnlyObjectProperty tradePeriodStateProperty() {
return tradePeriodStateProperty;
}
@@ -987,7 +1074,7 @@ public String toString() {
",\n isCurrencyForTakerFeeBtc=" + isCurrencyForTakerFeeBtc +
",\n txFeeAsLong=" + txFeeAsLong +
",\n takerFeeAsLong=" + takerFeeAsLong +
- ",\n takeOfferDate=" + getTakeOfferDate() +
+ ",\n takeOfferDate=" + takeOfferDate +
",\n processModel=" + processModel +
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
",\n depositTxId='" + depositTxId + '\'' +
@@ -1004,10 +1091,14 @@ public String toString() {
",\n takerContractSignature='" + takerContractSignature + '\'' +
",\n makerContractSignature='" + makerContractSignature + '\'' +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
- ",\n mediatorNodeAddress=" + mediatorNodeAddress +
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
+ ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
+ ",\n mediatorNodeAddress=" + mediatorNodeAddress +
+ ",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
",\n errorMessage='" + errorMessage + '\'' +
+ ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
+ ",\n chatMessages=" + chatMessages +
",\n txFee=" + txFee +
",\n takerFee=" + takerFee +
",\n storage=" + storage +
@@ -1018,15 +1109,21 @@ public String toString() {
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
",\n errorMessageProperty=" + errorMessageProperty +
",\n tradeProtocol=" + tradeProtocol +
- ",\n payoutTx=" + payoutTx +
",\n depositTx=" + depositTx +
+ ",\n delayedPayoutTx=" + delayedPayoutTx +
+ ",\n payoutTx=" + payoutTx +
",\n tradeAmount=" + tradeAmount +
",\n tradeAmountProperty=" + tradeAmountProperty +
",\n tradeVolumeProperty=" + tradeVolumeProperty +
",\n decryptedMessageWithPubKeySet=" + decryptedMessageWithPubKeySet +
- ",\n arbitratorPubKeyRing=" + arbitratorPubKeyRing +
- ",\n mediatorPubKeyRing=" + mediatorPubKeyRing +
- ",\n chatMessages=" + chatMessages +
+ ",\n mediationResultState=" + mediationResultState +
+ ",\n mediationResultStateProperty=" + mediationResultStateProperty +
+ ",\n lockTime=" + lockTime +
+ ",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
+ ",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing +
+ ",\n refundResultState=" + refundResultState +
+ ",\n refundResultStateProperty=" + refundResultStateProperty +
"\n}";
}
}
diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java
index d3e57398885..86cc5a7ed32 100644
--- a/core/src/main/java/bisq/core/trade/TradeManager.java
+++ b/core/src/main/java/bisq/core/trade/TradeManager.java
@@ -19,10 +19,13 @@
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.exceptions.AddressEntryException;
+import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
@@ -32,10 +35,12 @@
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.handlers.TradeResultHandler;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
@@ -47,6 +52,7 @@
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
+import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.ClockWatcher;
import bisq.common.UserThread;
@@ -82,6 +88,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -116,6 +123,8 @@ public class TradeManager implements PersistedDataHost {
private final AccountAgeWitnessService accountAgeWitnessService;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
+ private final RefundAgentManager refundAgentManager;
+ private final DaoFacade daoFacade;
private final ClockWatcher clockWatcher;
private final Storage> tradableListStorage;
@@ -150,6 +159,8 @@ public TradeManager(User user,
AccountAgeWitnessService accountAgeWitnessService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
+ DaoFacade daoFacade,
ClockWatcher clockWatcher,
Storage> storage) {
this.user = user;
@@ -168,6 +179,8 @@ public TradeManager(User user,
this.accountAgeWitnessService = accountAgeWitnessService;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
+ this.daoFacade = daoFacade;
this.clockWatcher = clockWatcher;
tradableListStorage = storage;
@@ -176,8 +189,8 @@ public TradeManager(User user,
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
// Handler for incoming initial network_messages from taker
- if (networkEnvelope instanceof PayDepositRequest) {
- handlePayDepositRequest((PayDepositRequest) networkEnvelope, peerNodeAddress);
+ if (networkEnvelope instanceof InputsForDepositTxRequest) {
+ handlePayDepositRequest((InputsForDepositTxRequest) networkEnvelope, peerNodeAddress);
}
});
@@ -307,18 +320,18 @@ private void cleanUpAddressEntries() {
});
}
- private void handlePayDepositRequest(PayDepositRequest payDepositRequest, NodeAddress peer) {
+ private void handlePayDepositRequest(InputsForDepositTxRequest inputsForDepositTxRequest, NodeAddress peer) {
log.info("Received PayDepositRequest from {} with tradeId {} and uid {}",
- peer, payDepositRequest.getTradeId(), payDepositRequest.getUid());
+ peer, inputsForDepositTxRequest.getTradeId(), inputsForDepositTxRequest.getUid());
try {
- Validator.nonEmptyStringOf(payDepositRequest.getTradeId());
+ Validator.nonEmptyStringOf(inputsForDepositTxRequest.getTradeId());
} catch (Throwable t) {
- log.warn("Invalid requestDepositTxInputsMessage " + payDepositRequest.toString());
+ log.warn("Invalid requestDepositTxInputsMessage " + inputsForDepositTxRequest.toString());
return;
}
- Optional openOfferOptional = openOfferManager.getOpenOfferById(payDepositRequest.getTradeId());
+ Optional openOfferOptional = openOfferManager.getOpenOfferById(inputsForDepositTxRequest.getTradeId());
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) {
OpenOffer openOffer = openOfferOptional.get();
Offer offer = openOffer.getOffer();
@@ -326,26 +339,28 @@ private void handlePayDepositRequest(PayDepositRequest payDepositRequest, NodeAd
Trade trade;
if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer,
- Coin.valueOf(payDepositRequest.getTxFee()),
- Coin.valueOf(payDepositRequest.getTakerFee()),
- payDepositRequest.isCurrencyForTakerFeeBtc(),
+ Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
+ Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
+ inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
openOffer.getMediatorNodeAddress(),
+ openOffer.getRefundAgentNodeAddress(),
tradableListStorage,
btcWalletService);
else
trade = new SellerAsMakerTrade(offer,
- Coin.valueOf(payDepositRequest.getTxFee()),
- Coin.valueOf(payDepositRequest.getTakerFee()),
- payDepositRequest.isCurrencyForTakerFeeBtc(),
+ Coin.valueOf(inputsForDepositTxRequest.getTxFee()),
+ Coin.valueOf(inputsForDepositTxRequest.getTakerFee()),
+ inputsForDepositTxRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
openOffer.getMediatorNodeAddress(),
+ openOffer.getRefundAgentNodeAddress(),
tradableListStorage,
btcWalletService);
initTrade(trade, trade.getProcessModel().isUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTradeAsLong());
tradableList.add(trade);
- ((MakerTrade) trade).handleTakeOfferRequest(payDepositRequest, peer, errorMessage -> {
+ ((MakerTrade) trade).handleTakeOfferRequest(inputsForDepositTxRequest, peer, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
});
@@ -362,6 +377,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo
btcWalletService,
bsqWalletService,
tradeWalletService,
+ daoFacade,
this,
openOfferManager,
referralIdService,
@@ -371,6 +387,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo
tradeStatisticsManager,
arbitratorManager,
mediatorManager,
+ refundAgentManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
@@ -453,6 +470,7 @@ private void createTrade(Coin amount,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
+ model.getSelectedRefundAgent(),
tradableListStorage,
btcWalletService);
else
@@ -465,6 +483,7 @@ private void createTrade(Coin amount,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
+ model.getSelectedRefundAgent(),
tradableListStorage,
btcWalletService);
@@ -567,6 +586,69 @@ public void closeDisputedTrade(String tradeId, Trade.DisputeState disputeState)
}
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Publish delayed payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public void publishDelayedPayoutTx(String tradeId,
+ ResultHandler resultHandler,
+ ErrorMessageHandler errorMessageHandler) {
+ getTradeById(tradeId).ifPresent(trade -> {
+ Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
+ if (delayedPayoutTx != null) {
+ // We have spent the funds from the deposit tx with the delayedPayoutTx
+ btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
+ // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that
+
+ tradeWalletService.addTxToWallet(delayedPayoutTx);
+ tradeWalletService.broadcastTx(delayedPayoutTx, new TxBroadcaster.Callback() {
+ @Override
+ public void onSuccess(Transaction transaction) {
+ log.info("publishDelayedPayoutTx onSuccess " + transaction);
+ NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
+ PeerPublishedDelayedPayoutTxMessage msg = new PeerPublishedDelayedPayoutTxMessage(UUID.randomUUID().toString(),
+ tradeId,
+ tradingPeerNodeAddress);
+ p2PService.sendEncryptedMailboxMessage(
+ tradingPeerNodeAddress,
+ trade.getProcessModel().getTradingPeer().getPubKeyRing(),
+ msg,
+ new SendMailboxMessageListener() {
+ @Override
+ public void onArrived() {
+ resultHandler.handleResult();
+ log.info("SendMailboxMessageListener onArrived tradeId={} at peer {}",
+ tradeId, tradingPeerNodeAddress);
+ }
+
+ @Override
+ public void onStoredInMailbox() {
+ resultHandler.handleResult();
+ log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {}",
+ tradeId, tradingPeerNodeAddress);
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("SendMailboxMessageListener onFault tradeId={} at peer {}",
+ tradeId, tradingPeerNodeAddress);
+ errorMessageHandler.handleErrorMessage(errorMessage);
+ }
+ }
+ );
+ }
+
+ @Override
+ public void onFailure(TxBroadcastException exception) {
+ log.error("publishDelayedPayoutTx onFailure", exception);
+ errorMessageHandler.handleErrorMessage(exception.toString());
+ }
+ });
+ }
+ });
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java
new file mode 100644
index 00000000000..0d1182001b4
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureRequest.java
@@ -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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.DirectMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+import bisq.common.util.Utilities;
+
+import com.google.protobuf.ByteString;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class DelayedPayoutTxSignatureRequest extends TradeMessage implements DirectMessage {
+ private final NodeAddress senderNodeAddress;
+ private final byte[] delayedPayoutTx;
+
+ public DelayedPayoutTxSignatureRequest(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTx) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress,
+ delayedPayoutTx);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private DelayedPayoutTxSignatureRequest(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTx) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.delayedPayoutTx = delayedPayoutTx;
+ }
+
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ return getNetworkEnvelopeBuilder()
+ .setDelayedPayoutTxSignatureRequest(protobuf.DelayedPayoutTxSignatureRequest.newBuilder()
+ .setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
+ .build();
+ }
+
+ public static DelayedPayoutTxSignatureRequest fromProto(protobuf.DelayedPayoutTxSignatureRequest proto, int messageVersion) {
+ return new DelayedPayoutTxSignatureRequest(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDelayedPayoutTx().toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "DelayedPayoutTxSignatureRequest{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java
new file mode 100644
index 00000000000..639d5edb72a
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/DelayedPayoutTxSignatureResponse.java
@@ -0,0 +1,90 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.DirectMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+import bisq.common.util.Utilities;
+
+import com.google.protobuf.ByteString;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class DelayedPayoutTxSignatureResponse extends TradeMessage implements DirectMessage {
+ private final NodeAddress senderNodeAddress;
+ private final byte[] delayedPayoutTxSignature;
+
+ public DelayedPayoutTxSignatureResponse(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTxSignature) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress,
+ delayedPayoutTxSignature);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private DelayedPayoutTxSignatureResponse(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] delayedPayoutTxSignature) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.delayedPayoutTxSignature = delayedPayoutTxSignature;
+ }
+
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ return getNetworkEnvelopeBuilder()
+ .setDelayedPayoutTxSignatureResponse(protobuf.DelayedPayoutTxSignatureResponse.newBuilder()
+ .setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setDelayedPayoutTxSignature(ByteString.copyFrom(delayedPayoutTxSignature))
+ )
+ .build();
+ }
+
+ public static DelayedPayoutTxSignatureResponse fromProto(protobuf.DelayedPayoutTxSignatureResponse proto, int messageVersion) {
+ return new DelayedPayoutTxSignatureResponse(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDelayedPayoutTxSignature().toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "DelayedPayoutTxSignatureResponse{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n delayedPayoutTxSignature=" + Utilities.bytesAsHexString(delayedPayoutTxSignature) +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java
new file mode 100644
index 00000000000..6d90bb1f2de
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/DepositTxAndDelayedPayoutTxMessage.java
@@ -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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+import bisq.common.util.Utilities;
+
+import com.google.protobuf.ByteString;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+// It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance
+// in case of network issues and as the message does not trigger further protocol execution.
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class DepositTxAndDelayedPayoutTxMessage extends TradeMessage implements MailboxMessage {
+ private final NodeAddress senderNodeAddress;
+ private final byte[] depositTx;
+ private final byte[] delayedPayoutTx;
+
+ public DepositTxAndDelayedPayoutTxMessage(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx,
+ byte[] delayedPayoutTx) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress,
+ depositTx,
+ delayedPayoutTx);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private DepositTxAndDelayedPayoutTxMessage(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx,
+ byte[] delayedPayoutTx) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ this.depositTx = depositTx;
+ this.delayedPayoutTx = delayedPayoutTx;
+ }
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ return getNetworkEnvelopeBuilder()
+ .setDepositTxAndDelayedPayoutTxMessage(protobuf.DepositTxAndDelayedPayoutTxMessage.newBuilder()
+ .setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
+ .setDepositTx(ByteString.copyFrom(depositTx))
+ .setDelayedPayoutTx(ByteString.copyFrom(delayedPayoutTx)))
+ .build();
+ }
+
+ public static DepositTxAndDelayedPayoutTxMessage fromProto(protobuf.DepositTxAndDelayedPayoutTxMessage proto, int messageVersion) {
+ return new DepositTxAndDelayedPayoutTxMessage(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDepositTx().toByteArray(),
+ proto.getDelayedPayoutTx().toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "DepositTxAndDelayedPayoutTxMessage{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
+ ",\n delayedPayoutTx=" + Utilities.bytesAsHexString(delayedPayoutTx) +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
similarity index 58%
rename from core/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java
rename to core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
index e5fd07021e0..4350afd2266 100644
--- a/core/src/main/java/bisq/core/trade/messages/DepositTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/DepositTxMessage.java
@@ -17,7 +17,7 @@
package bisq.core.trade.messages;
-import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
@@ -28,64 +28,63 @@
import lombok.EqualsAndHashCode;
import lombok.Value;
+// It is the last message in the take offer phase. We use MailboxMessage instead of DirectMessage to add more tolerance
+// in case of network issues and as the message does not trigger further protocol execution.
@EqualsAndHashCode(callSuper = true)
@Value
-public final class DepositTxPublishedMessage extends TradeMessage implements MailboxMessage {
- private final byte[] depositTx;
+public final class DepositTxMessage extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
+ private final byte[] depositTx;
- public DepositTxPublishedMessage(String tradeId,
- byte[] depositTx,
- NodeAddress senderNodeAddress,
- String uid) {
- this(tradeId,
- depositTx,
- senderNodeAddress,
+ public DepositTxMessage(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx) {
+ this(Version.getP2PMessageVersion(),
uid,
- Version.getP2PMessageVersion());
+ tradeId,
+ senderNodeAddress,
+ depositTx);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- private DepositTxPublishedMessage(String tradeId,
- byte[] depositTx,
- NodeAddress senderNodeAddress,
- String uid,
- int messageVersion) {
+ private DepositTxMessage(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress,
+ byte[] depositTx) {
super(messageVersion, tradeId, uid);
- this.depositTx = depositTx;
this.senderNodeAddress = senderNodeAddress;
+ this.depositTx = depositTx;
}
-
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
- .setDepositTxPublishedMessage(protobuf.DepositTxPublishedMessage.newBuilder()
+ .setDepositTxMessage(protobuf.DepositTxMessage.newBuilder()
+ .setUid(uid)
.setTradeId(tradeId)
- .setDepositTx(ByteString.copyFrom(depositTx))
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setUid(uid))
+ .setDepositTx(ByteString.copyFrom(depositTx)))
.build();
}
- public static DepositTxPublishedMessage fromProto(protobuf.DepositTxPublishedMessage proto, int messageVersion) {
- return new DepositTxPublishedMessage(proto.getTradeId(),
- proto.getDepositTx().toByteArray(),
- NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ public static DepositTxMessage fromProto(protobuf.DepositTxMessage proto, int messageVersion) {
+ return new DepositTxMessage(messageVersion,
proto.getUid(),
- messageVersion);
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()),
+ proto.getDepositTx().toByteArray());
}
-
@Override
public String toString() {
- return "DepositTxPublishedMessage{" +
- "\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
- ",\n senderNodeAddress=" + senderNodeAddress +
- ",\n uid='" + uid + '\'' +
+ return "DepositTxMessage{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ ",\n depositTx=" + Utilities.bytesAsHexString(depositTx) +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/PayDepositRequest.java b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java
similarity index 72%
rename from core/src/main/java/bisq/core/trade/messages/PayDepositRequest.java
rename to core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java
index 05447f26a65..defe3165080 100644
--- a/core/src/main/java/bisq/core/trade/messages/PayDepositRequest.java
+++ b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java
@@ -21,6 +21,7 @@
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
+import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.PubKeyRing;
@@ -29,7 +30,6 @@
import com.google.protobuf.ByteString;
-import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -41,7 +41,7 @@
@EqualsAndHashCode(callSuper = true)
@Value
-public final class PayDepositRequest extends TradeMessage {
+public final class InputsForDepositTxRequest extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress;
private final long tradeAmount;
private final long tradePrice;
@@ -60,38 +60,42 @@ public final class PayDepositRequest extends TradeMessage {
private final String takerFeeTxId;
private final List acceptedArbitratorNodeAddresses;
private final List acceptedMediatorNodeAddresses;
+ private final List acceptedRefundAgentNodeAddresses;
private final NodeAddress arbitratorNodeAddress;
private final NodeAddress mediatorNodeAddress;
+ private final NodeAddress refundAgentNodeAddress;
// added in v 0.6. can be null if we trade with an older peer
@Nullable
private final byte[] accountAgeWitnessSignatureOfOfferId;
private final long currentDate;
- public PayDepositRequest(String tradeId,
- NodeAddress senderNodeAddress,
- long tradeAmount,
- long tradePrice,
- long txFee,
- long takerFee,
- boolean isCurrencyForTakerFeeBtc,
- List rawTransactionInputs,
- long changeOutputValue,
- @Nullable String changeOutputAddress,
- byte[] takerMultiSigPubKey,
- String takerPayoutAddressString,
- PubKeyRing takerPubKeyRing,
- PaymentAccountPayload takerPaymentAccountPayload,
- String takerAccountId,
- String takerFeeTxId,
- List acceptedArbitratorNodeAddresses,
- List acceptedMediatorNodeAddresses,
- NodeAddress arbitratorNodeAddress,
- NodeAddress mediatorNodeAddress,
- String uid,
- int messageVersion,
- @Nullable byte[] accountAgeWitnessSignatureOfOfferId,
- long currentDate) {
+ public InputsForDepositTxRequest(String tradeId,
+ NodeAddress senderNodeAddress,
+ long tradeAmount,
+ long tradePrice,
+ long txFee,
+ long takerFee,
+ boolean isCurrencyForTakerFeeBtc,
+ List rawTransactionInputs,
+ long changeOutputValue,
+ @Nullable String changeOutputAddress,
+ byte[] takerMultiSigPubKey,
+ String takerPayoutAddressString,
+ PubKeyRing takerPubKeyRing,
+ PaymentAccountPayload takerPaymentAccountPayload,
+ String takerAccountId,
+ String takerFeeTxId,
+ List acceptedArbitratorNodeAddresses,
+ List acceptedMediatorNodeAddresses,
+ List acceptedRefundAgentNodeAddresses,
+ NodeAddress arbitratorNodeAddress,
+ NodeAddress mediatorNodeAddress,
+ NodeAddress refundAgentNodeAddress,
+ String uid,
+ int messageVersion,
+ @Nullable byte[] accountAgeWitnessSignatureOfOfferId,
+ long currentDate) {
super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress;
this.tradeAmount = tradeAmount;
@@ -110,8 +114,10 @@ public PayDepositRequest(String tradeId,
this.takerFeeTxId = takerFeeTxId;
this.acceptedArbitratorNodeAddresses = acceptedArbitratorNodeAddresses;
this.acceptedMediatorNodeAddresses = acceptedMediatorNodeAddresses;
+ this.acceptedRefundAgentNodeAddresses = acceptedRefundAgentNodeAddresses;
this.arbitratorNodeAddress = arbitratorNodeAddress;
this.mediatorNodeAddress = mediatorNodeAddress;
+ this.refundAgentNodeAddress = refundAgentNodeAddress;
this.accountAgeWitnessSignatureOfOfferId = accountAgeWitnessSignatureOfOfferId;
this.currentDate = currentDate;
}
@@ -123,7 +129,7 @@ public PayDepositRequest(String tradeId,
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
- protobuf.PayDepositRequest.Builder builder = protobuf.PayDepositRequest.newBuilder()
+ protobuf.InputsForDepositTxRequest.Builder builder = protobuf.InputsForDepositTxRequest.newBuilder()
.setTradeId(tradeId)
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setTradeAmount(tradeAmount)
@@ -144,20 +150,23 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
.addAllAcceptedMediatorNodeAddresses(acceptedMediatorNodeAddresses.stream()
.map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
+ .addAllAcceptedRefundAgentNodeAddresses(acceptedRefundAgentNodeAddresses.stream()
+ .map(NodeAddress::toProtoMessage).collect(Collectors.toList()))
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())
.setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage())
+ .setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage())
.setUid(uid);
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(accountAgeWitnessSignatureOfOfferId).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfOfferId(ByteString.copyFrom(e)));
builder.setCurrentDate(currentDate);
- return getNetworkEnvelopeBuilder().setPayDepositRequest(builder).build();
+ return getNetworkEnvelopeBuilder().setInputsForDepositTxRequest(builder).build();
}
- public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
- CoreProtoResolver coreProtoResolver,
- int messageVersion) {
+ public static InputsForDepositTxRequest fromProto(protobuf.InputsForDepositTxRequest proto,
+ CoreProtoResolver coreProtoResolver,
+ int messageVersion) {
List rawTransactionInputs = proto.getRawTransactionInputsList().stream()
.map(rawTransactionInput -> new RawTransactionInput(rawTransactionInput.getIndex(),
rawTransactionInput.getParentTransaction().toByteArray(), rawTransactionInput.getValue()))
@@ -166,8 +175,10 @@ public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
.map(NodeAddress::fromProto).collect(Collectors.toList());
List acceptedMediatorNodeAddresses = proto.getAcceptedMediatorNodeAddressesList().stream()
.map(NodeAddress::fromProto).collect(Collectors.toList());
+ List acceptedRefundAgentNodeAddresses = proto.getAcceptedRefundAgentNodeAddressesList().stream()
+ .map(NodeAddress::fromProto).collect(Collectors.toList());
- return new PayDepositRequest(proto.getTradeId(),
+ return new InputsForDepositTxRequest(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()),
proto.getTradeAmount(),
proto.getTradePrice(),
@@ -185,8 +196,10 @@ public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
proto.getTakerFeeTxId(),
acceptedArbitratorNodeAddresses,
acceptedMediatorNodeAddresses,
+ acceptedRefundAgentNodeAddresses,
NodeAddress.fromProto(proto.getArbitratorNodeAddress()),
NodeAddress.fromProto(proto.getMediatorNodeAddress()),
+ NodeAddress.fromProto(proto.getRefundAgentNodeAddress()),
proto.getUid(),
messageVersion,
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfOfferId()),
@@ -195,7 +208,7 @@ public static PayDepositRequest fromProto(protobuf.PayDepositRequest proto,
@Override
public String toString() {
- return "PayDepositRequest{" +
+ return "InputsForDepositTxRequest{" +
"\n senderNodeAddress=" + senderNodeAddress +
",\n tradeAmount=" + tradeAmount +
",\n tradePrice=" + tradePrice +
@@ -213,11 +226,12 @@ public String toString() {
",\n takerFeeTxId='" + takerFeeTxId + '\'' +
",\n acceptedArbitratorNodeAddresses=" + acceptedArbitratorNodeAddresses +
",\n acceptedMediatorNodeAddresses=" + acceptedMediatorNodeAddresses +
+ ",\n acceptedRefundAgentNodeAddresses=" + acceptedRefundAgentNodeAddresses +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress +
",\n mediatorNodeAddress=" + mediatorNodeAddress +
- ",\n uid='" + uid + '\'' +
+ ",\n refundAgentNodeAddress=" + refundAgentNodeAddress +
",\n accountAgeWitnessSignatureOfOfferId=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfOfferId) +
- ",\n currentDate=" + new Date(currentDate) +
+ ",\n currentDate=" + currentDate +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java
similarity index 69%
rename from core/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java
rename to core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java
index 7e115979fbf..994be36e5bb 100644
--- a/core/src/main/java/bisq/core/trade/messages/PublishDepositTxRequest.java
+++ b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxResponse.java
@@ -21,7 +21,7 @@
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver;
-import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
@@ -40,12 +40,9 @@
import javax.annotation.Nullable;
-// We use a MailboxMessage here because the taker has paid already the trade fee and it could be that
-// we lost connection to him but we are complete on our side. So even if the peer is offline he can
-// continue later to complete the deposit tx.
@EqualsAndHashCode(callSuper = true)
@Value
-public final class PublishDepositTxRequest extends TradeMessage implements MailboxMessage {
+public final class InputsForDepositTxResponse extends TradeMessage implements DirectMessage {
private final PaymentAccountPayload makerPaymentAccountPayload;
private final String makerAccountId;
private final byte[] makerMultiSigPubKey;
@@ -60,20 +57,22 @@ public final class PublishDepositTxRequest extends TradeMessage implements Mailb
@Nullable
private final byte[] accountAgeWitnessSignatureOfPreparedDepositTx;
private final long currentDate;
-
- public PublishDepositTxRequest(String tradeId,
- PaymentAccountPayload makerPaymentAccountPayload,
- String makerAccountId,
- byte[] makerMultiSigPubKey,
- String makerContractAsJson,
- String makerContractSignature,
- String makerPayoutAddressString,
- byte[] preparedDepositTx,
- List makerInputs,
- NodeAddress senderNodeAddress,
- String uid,
- @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
- long currentDate) {
+ private final long lockTime;
+
+ public InputsForDepositTxResponse(String tradeId,
+ PaymentAccountPayload makerPaymentAccountPayload,
+ String makerAccountId,
+ byte[] makerMultiSigPubKey,
+ String makerContractAsJson,
+ String makerContractSignature,
+ String makerPayoutAddressString,
+ byte[] preparedDepositTx,
+ List makerInputs,
+ NodeAddress senderNodeAddress,
+ String uid,
+ @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
+ long currentDate,
+ long lockTime) {
this(tradeId,
makerPaymentAccountPayload,
makerAccountId,
@@ -87,7 +86,8 @@ public PublishDepositTxRequest(String tradeId,
uid,
Version.getP2PMessageVersion(),
accountAgeWitnessSignatureOfPreparedDepositTx,
- currentDate);
+ currentDate,
+ lockTime);
}
@@ -95,20 +95,21 @@ public PublishDepositTxRequest(String tradeId,
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
- private PublishDepositTxRequest(String tradeId,
- PaymentAccountPayload makerPaymentAccountPayload,
- String makerAccountId,
- byte[] makerMultiSigPubKey,
- String makerContractAsJson,
- String makerContractSignature,
- String makerPayoutAddressString,
- byte[] preparedDepositTx,
- List makerInputs,
- NodeAddress senderNodeAddress,
- String uid,
- int messageVersion,
- @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
- long currentDate) {
+ private InputsForDepositTxResponse(String tradeId,
+ PaymentAccountPayload makerPaymentAccountPayload,
+ String makerAccountId,
+ byte[] makerMultiSigPubKey,
+ String makerContractAsJson,
+ String makerContractSignature,
+ String makerPayoutAddressString,
+ byte[] preparedDepositTx,
+ List makerInputs,
+ NodeAddress senderNodeAddress,
+ String uid,
+ int messageVersion,
+ @Nullable byte[] accountAgeWitnessSignatureOfPreparedDepositTx,
+ long currentDate,
+ long lockTime) {
super(messageVersion, tradeId, uid);
this.makerPaymentAccountPayload = makerPaymentAccountPayload;
this.makerAccountId = makerAccountId;
@@ -121,11 +122,12 @@ private PublishDepositTxRequest(String tradeId,
this.senderNodeAddress = senderNodeAddress;
this.accountAgeWitnessSignatureOfPreparedDepositTx = accountAgeWitnessSignatureOfPreparedDepositTx;
this.currentDate = currentDate;
+ this.lockTime = lockTime;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
- final protobuf.PublishDepositTxRequest.Builder builder = protobuf.PublishDepositTxRequest.newBuilder()
+ final protobuf.InputsForDepositTxResponse.Builder builder = protobuf.InputsForDepositTxResponse.newBuilder()
.setTradeId(tradeId)
.setMakerPaymentAccountPayload((protobuf.PaymentAccountPayload) makerPaymentAccountPayload.toProtoMessage())
.setMakerAccountId(makerAccountId)
@@ -136,22 +138,23 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx))
.addAllMakerInputs(makerInputs.stream().map(RawTransactionInput::toProtoMessage).collect(Collectors.toList()))
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
- .setUid(uid);
+ .setUid(uid)
+ .setLockTime(lockTime);
Optional.ofNullable(accountAgeWitnessSignatureOfPreparedDepositTx).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfPreparedDepositTx(ByteString.copyFrom(e)));
builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder()
- .setPublishDepositTxRequest(builder)
+ .setInputsForDepositTxResponse(builder)
.build();
}
- public static PublishDepositTxRequest fromProto(protobuf.PublishDepositTxRequest proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
+ public static InputsForDepositTxResponse fromProto(protobuf.InputsForDepositTxResponse proto, CoreProtoResolver coreProtoResolver, int messageVersion) {
List makerInputs = proto.getMakerInputsList().stream()
.map(RawTransactionInput::fromProto)
.collect(Collectors.toList());
- return new PublishDepositTxRequest(proto.getTradeId(),
+ return new InputsForDepositTxResponse(proto.getTradeId(),
coreProtoResolver.fromProto(proto.getMakerPaymentAccountPayload()),
proto.getMakerAccountId(),
proto.getMakerMultiSigPubKey().toByteArray(),
@@ -164,13 +167,14 @@ public static PublishDepositTxRequest fromProto(protobuf.PublishDepositTxRequest
proto.getUid(),
messageVersion,
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfPreparedDepositTx()),
- proto.getCurrentDate());
+ proto.getCurrentDate(),
+ proto.getLockTime());
}
@Override
public String toString() {
- return "PublishDepositTxRequest{" +
+ return "InputsForDepositTxResponse{" +
"\n makerPaymentAccountPayload=" + makerPaymentAccountPayload +
",\n makerAccountId='" + makerAccountId + '\'' +
",\n makerMultiSigPubKey=" + Utilities.bytesAsHexString(makerMultiSigPubKey) +
@@ -183,6 +187,7 @@ public String toString() {
",\n uid='" + uid + '\'' +
",\n accountAgeWitnessSignatureOfPreparedDepositTx=" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfPreparedDepositTx) +
",\n currentDate=" + new Date(currentDate) +
+ ",\n lockTime=" + lockTime +
"\n} " + super.toString();
}
}
diff --git a/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java
new file mode 100644
index 00000000000..9447f9494f9
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/messages/PeerPublishedDelayedPayoutTxMessage.java
@@ -0,0 +1,77 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.messages;
+
+import bisq.network.p2p.MailboxMessage;
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.app.Version;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+
+@EqualsAndHashCode(callSuper = true)
+@Value
+public final class PeerPublishedDelayedPayoutTxMessage extends TradeMessage implements MailboxMessage {
+ private final NodeAddress senderNodeAddress;
+
+ public PeerPublishedDelayedPayoutTxMessage(String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress) {
+ this(Version.getP2PMessageVersion(),
+ uid,
+ tradeId,
+ senderNodeAddress);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO BUFFER
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private PeerPublishedDelayedPayoutTxMessage(int messageVersion,
+ String uid,
+ String tradeId,
+ NodeAddress senderNodeAddress) {
+ super(messageVersion, tradeId, uid);
+ this.senderNodeAddress = senderNodeAddress;
+ }
+
+ @Override
+ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
+ final protobuf.PeerPublishedDelayedPayoutTxMessage.Builder builder = protobuf.PeerPublishedDelayedPayoutTxMessage.newBuilder();
+ builder.setUid(uid)
+ .setTradeId(tradeId)
+ .setSenderNodeAddress(senderNodeAddress.toProtoMessage());
+ return getNetworkEnvelopeBuilder().setPeerPublishedDelayedPayoutTxMessage(builder).build();
+ }
+
+ public static PeerPublishedDelayedPayoutTxMessage fromProto(protobuf.PeerPublishedDelayedPayoutTxMessage proto, int messageVersion) {
+ return new PeerPublishedDelayedPayoutTxMessage(messageVersion,
+ proto.getUid(),
+ proto.getTradeId(),
+ NodeAddress.fromProto(proto.getSenderNodeAddress()));
+ }
+
+ @Override
+ public String toString() {
+ return "PeerPublishedDelayedPayoutTxMessage{" +
+ "\n senderNodeAddress=" + senderNodeAddress +
+ "\n} " + super.toString();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/messages/TradeMessage.java b/core/src/main/java/bisq/core/trade/messages/TradeMessage.java
index 5b386e2e1e5..e90cbb02657 100644
--- a/core/src/main/java/bisq/core/trade/messages/TradeMessage.java
+++ b/core/src/main/java/bisq/core/trade/messages/TradeMessage.java
@@ -17,7 +17,6 @@
package bisq.core.trade.messages;
-import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.UidMessage;
import bisq.common.proto.network.NetworkEnvelope;
@@ -29,7 +28,7 @@
@EqualsAndHashCode(callSuper = true)
@Getter
@ToString
-public abstract class TradeMessage extends NetworkEnvelope implements DirectMessage, UidMessage {
+public abstract class TradeMessage extends NetworkEnvelope implements UidMessage {
protected final String tradeId;
protected final String uid;
diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
index dd68fd16f01..236e6d47b09 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java
@@ -19,38 +19,40 @@
import bisq.core.trade.BuyerAsMakerTrade;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
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.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
+import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
+import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.util.Validator;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkArgument;
-
@Slf4j
public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol, MakerProtocol {
private final BuyerAsMakerTrade buyerAsMakerTrade;
@@ -68,10 +70,10 @@ public BuyerAsMakerProtocol(BuyerAsMakerTrade trade) {
Trade.Phase phase = trade.getState().getPhase();
if (phase == Trade.Phase.TAKER_FEE_PUBLISHED) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
- () -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
+ () -> handleTaskRunnerSuccess("BuyerSetupDepositTxListener"),
this::handleTaskRunnerFault);
- taskRunner.addTasks(MakerSetupDepositTxListener.class);
+ taskRunner.addTasks(BuyerSetupDepositTxListener.class);
taskRunner.run();
} else if (trade.isFiatSent() && !trade.isPayoutPublished()) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
@@ -89,23 +91,13 @@ public BuyerAsMakerProtocol(BuyerAsMakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- MailboxMessage mailboxMessage = (MailboxMessage) networkEnvelope;
- NodeAddress peerNodeAddress = mailboxMessage.getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof DepositTxPublishedMessage)
- handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof PayoutTxPublishedMessage)
- handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
- else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
+ handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -115,11 +107,10 @@ else if (tradeMessage instanceof PayoutTxPublishedMessage)
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void handleTakeOfferRequest(TradeMessage tradeMessage,
+ public void handleTakeOfferRequest(InputsForDepositTxRequest tradeMessage,
NodeAddress peerNodeAddress,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), tradeMessage);
- checkArgument(tradeMessage instanceof PayDepositRequest);
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
@@ -130,15 +121,16 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
handleTaskRunnerFault(errorMessage);
});
taskRunner.addTasks(
- MakerProcessPayDepositRequest.class,
+ MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
MakerVerifyTakerAccount.class,
VerifyPeersAccountAgeWitness.class,
MakerVerifyTakerFeePayment.class,
+ MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
BuyerAsMakerCreatesAndSignsDepositTx.class,
- MakerSetupDepositTxListener.class,
- MakerSendPublishDepositTxRequest.class
+ BuyerSetupDepositTxListener.class,
+ BuyerAsMakerSendsInputsForDepositTxResponse.class
);
// We don't use a timeout here because if the DepositTxPublishedMessage does not arrive we
// get the deposit tx set at MakerSetupDepositTxListener once it is seen in the bitcoin network
@@ -150,19 +142,35 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- private void handle(DepositTxPublishedMessage tradeMessage, NodeAddress peerNodeAddress) {
+ private void handle(DelayedPayoutTxSignatureRequest tradeMessage, NodeAddress peerNodeAddress) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
() -> {
- handleTaskRunnerSuccess(tradeMessage, "handle DepositTxPublishedMessage");
+ handleTaskRunnerSuccess(tradeMessage, "handle DelayedPayoutTxSignatureRequest");
},
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- MakerProcessDepositTxPublishedMessage.class,
- MakerVerifyTakerAccount.class,
- MakerVerifyTakerFeePayment.class,
+ BuyerProcessDelayedPayoutTxSignatureRequest.class,
+ BuyerSignsDelayedPayoutTx.class,
+ BuyerSendsDelayedPayoutTxSignatureResponse.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DepositTxAndDelayedPayoutTxMessage tradeMessage, NodeAddress peerNodeAddress) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
+ () -> {
+ handleTaskRunnerSuccess(tradeMessage, "handle DepositTxAndDelayedPayoutTxMessage");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+ taskRunner.addTasks(
+ BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
+ BuyerVerifiesDelayedPayoutTx.class,
PublishTradeStatistics.class
);
taskRunner.run();
@@ -191,7 +199,7 @@ public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandle
ApplyFilter.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
- BuyerAsMakerSignPayoutTx.class,
+ BuyerSignPayoutTx.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
BuyerSetupPayoutTxListener.class
);
@@ -232,8 +240,10 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof DepositTxPublishedMessage) {
- handle((DepositTxPublishedMessage) tradeMessage, sender);
+ if (tradeMessage instanceof DelayedPayoutTxSignatureRequest) {
+ handle((DelayedPayoutTxSignatureRequest) tradeMessage, sender);
+ } else if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
index 36b868cd90a..6047236501b 100644
--- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java
@@ -18,38 +18,47 @@
package bisq.core.trade.protocol;
+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.DepositTxAndDelayedPayoutTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
-import bisq.core.trade.messages.PublishDepositTxRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
-import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignAndPublishDepositTx;
+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.CreateTakerFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerProcessPublishDepositTxRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol, TakerProtocol {
private final BuyerAsTakerTrade buyerAsTakerTrade;
@@ -64,9 +73,18 @@ public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
this.buyerAsTakerTrade = trade;
- processModel.getTradingPeer().setPubKeyRing(trade.getOffer().getPubKeyRing());
+ Offer offer = checkNotNull(trade.getOffer());
+ processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
+
+ Trade.Phase phase = trade.getState().getPhase();
+ if (phase == Trade.Phase.TAKER_FEE_PUBLISHED) {
+ TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
+ () -> handleTaskRunnerSuccess("BuyerSetupDepositTxListener"),
+ this::handleTaskRunnerFault);
- if (trade.isFiatSent() && !trade.isPayoutPublished()) {
+ taskRunner.addTasks(BuyerSetupDepositTxListener.class);
+ taskRunner.run();
+ } else if (trade.isFiatSent() && !trade.isPayoutPublished()) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess("BuyerSetupPayoutTxListener"),
this::handleTaskRunnerFault);
@@ -82,22 +100,13 @@ public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- final NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest)
- handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof PayoutTxPublishedMessage) {
- handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
- } else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
+ handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -117,7 +126,7 @@ public void takeAvailableOffer() {
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.class,
- TakerSendPayDepositRequest.class
+ TakerSendInputsForDepositTxRequest.class
);
//TODO if peer does get an error he does not respond and all we get is the timeout now knowing why it failed.
@@ -131,7 +140,7 @@ public void takeAvailableOffer() {
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
+ private void handle(InputsForDepositTxResponse tradeMessage, NodeAddress sender) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -142,15 +151,50 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
},
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
VerifyPeersAccountAgeWitness.class,
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
- BuyerAsTakerSignAndPublishDepositTx.class,
- TakerSendDepositTxPublishedMessage.class,
+ BuyerAsTakerSignsDepositTx.class,
+ BuyerSetupDepositTxListener.class,
+ BuyerAsTakerSendsDepositTxMessage.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DelayedPayoutTxSignatureRequest tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
+ () -> {
+ handleTaskRunnerSuccess(tradeMessage, "handle DelayedPayoutTxSignatureRequest");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+ taskRunner.addTasks(
+ BuyerProcessDelayedPayoutTxSignatureRequest.class,
+ BuyerSignsDelayedPayoutTx.class,
+ BuyerSendsDelayedPayoutTxSignatureResponse.class
+ );
+ taskRunner.run();
+ }
+
+
+ private void handle(DepositTxAndDelayedPayoutTxMessage tradeMessage, NodeAddress peerNodeAddress) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(peerNodeAddress);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
+ () -> {
+ handleTaskRunnerSuccess(tradeMessage, "handle DepositTxAndDelayedPayoutTxMessage");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+ taskRunner.addTasks(
+ BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
+ BuyerVerifiesDelayedPayoutTx.class,
PublishTradeStatistics.class
);
taskRunner.run();
@@ -180,7 +224,7 @@ public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandle
ApplyFilter.class,
TakerVerifyMakerAccount.class,
TakerVerifyMakerFeePayment.class,
- BuyerAsMakerSignPayoutTx.class,
+ BuyerSignPayoutTx.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
BuyerSetupPayoutTxListener.class
);
@@ -221,8 +265,12 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest) {
- handle((PublishDepositTxRequest) tradeMessage, sender);
+ if (tradeMessage instanceof InputsForDepositTxResponse) {
+ handle((InputsForDepositTxResponse) tradeMessage, sender);
+ } else if (tradeMessage instanceof DelayedPayoutTxSignatureRequest) {
+ handle((DelayedPayoutTxSignatureRequest) tradeMessage, sender);
+ } else if (tradeMessage instanceof DepositTxAndDelayedPayoutTxMessage) {
+ handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
index a95c78bfeb2..d460289e3fc 100644
--- a/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/MakerProtocol.java
@@ -18,12 +18,12 @@
package bisq.core.trade.protocol;
-import bisq.core.trade.messages.TradeMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface MakerProtocol {
- void handleTakeOfferRequest(TradeMessage message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
+ void handleTakeOfferRequest(InputsForDepositTxRequest message, NodeAddress taker, ErrorMessageHandler errorMessageHandler);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
index f2b180926aa..f5678586379 100644
--- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
+++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java
@@ -22,6 +22,7 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.network.MessageState;
import bisq.core.offer.Offer;
@@ -31,6 +32,7 @@
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
@@ -69,6 +71,10 @@
import javax.annotation.Nullable;
+// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
+// persist them.
+//todo clean up older fields as well to make most transient
+
@Getter
@Slf4j
public class ProcessModel implements Model, PersistablePayload {
@@ -78,6 +84,7 @@ public class ProcessModel implements Model, PersistablePayload {
transient private BtcWalletService btcWalletService;
transient private BsqWalletService bsqWalletService;
transient private TradeWalletService tradeWalletService;
+ transient private DaoFacade daoFacade;
transient private Offer offer;
transient private User user;
transient private FilterManager filterManager;
@@ -85,6 +92,7 @@ public class ProcessModel implements Model, PersistablePayload {
transient private TradeStatisticsManager tradeStatisticsManager;
transient private ArbitratorManager arbitratorManager;
transient private MediatorManager mediatorManager;
+ transient private RefundAgentManager refundAgentManager;
transient private KeyRing keyRing;
transient private P2PService p2PService;
transient private ReferralIdService referralIdService;
@@ -96,32 +104,27 @@ public class ProcessModel implements Model, PersistablePayload {
@Setter
transient private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
-
- // Persistable Immutable (only set by PB)
+ // Added in v1.2.0
@Setter
+ @Nullable
+ transient private byte[] delayedPayoutTxSignature;
+
+
+ // Persistable Immutable (private setter only used by PB method)
private TradingPeer tradingPeer = new TradingPeer();
- @Setter
private String offerId;
- @Setter
private String accountId;
- @Setter
private PubKeyRing pubKeyRing;
// Persistable Mutable
@Nullable
- @Setter
+ @Setter()
private String takeOfferFeeTxId;
@Nullable
@Setter
private byte[] payoutTxSignature;
@Nullable
@Setter
- private List takerAcceptedArbitratorNodeAddresses;
- @Nullable
- @Setter
- private List takerAcceptedMediatorNodeAddresses;
- @Nullable
- @Setter
private byte[] preparedDepositTx;
@Nullable
@Setter
@@ -182,14 +185,13 @@ public protobuf.ProcessModel toProtoMessage() {
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId);
Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
- Optional.ofNullable(takerAcceptedArbitratorNodeAddresses).ifPresent(e -> builder.addAllTakerAcceptedArbitratorNodeAddresses(ProtoUtil.collectionToProto(takerAcceptedArbitratorNodeAddresses)));
- Optional.ofNullable(takerAcceptedMediatorNodeAddresses).ifPresent(e -> builder.addAllTakerAcceptedMediatorNodeAddresses(ProtoUtil.collectionToProto(takerAcceptedMediatorNodeAddresses)));
Optional.ofNullable(preparedDepositTx).ifPresent(e -> builder.setPreparedDepositTx(ByteString.copyFrom(preparedDepositTx)));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs)));
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(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
+
return builder.build();
}
@@ -208,14 +210,6 @@ public static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol
// nullable
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
- List takerAcceptedArbitratorNodeAddresses = proto.getTakerAcceptedArbitratorNodeAddressesList().isEmpty() ?
- null : proto.getTakerAcceptedArbitratorNodeAddressesList().stream()
- .map(NodeAddress::fromProto).collect(Collectors.toList());
- List takerAcceptedMediatorNodeAddresses = proto.getTakerAcceptedMediatorNodeAddressesList().isEmpty() ?
- null : proto.getTakerAcceptedMediatorNodeAddressesList().stream()
- .map(NodeAddress::fromProto).collect(Collectors.toList());
- processModel.setTakerAcceptedArbitratorNodeAddresses(takerAcceptedArbitratorNodeAddresses);
- processModel.setTakerAcceptedMediatorNodeAddresses(takerAcceptedMediatorNodeAddresses);
processModel.setPreparedDepositTx(ProtoUtil.byteArrayOrNullFromProto(proto.getPreparedDepositTx()));
List rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ?
null : proto.getRawTransactionInputsList().stream()
@@ -243,6 +237,7 @@ public void onAllServicesInitialized(Offer offer,
BtcWalletService walletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
+ DaoFacade daoFacade,
ReferralIdService referralIdService,
User user,
FilterManager filterManager,
@@ -250,6 +245,7 @@ public void onAllServicesInitialized(Offer offer,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@@ -259,6 +255,7 @@ public void onAllServicesInitialized(Offer offer,
this.btcWalletService = walletService;
this.bsqWalletService = bsqWalletService;
this.tradeWalletService = tradeWalletService;
+ this.daoFacade = daoFacade;
this.referralIdService = referralIdService;
this.user = user;
this.filterManager = filterManager;
@@ -266,6 +263,7 @@ public void onAllServicesInitialized(Offer offer,
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
+ this.refundAgentManager = refundAgentManager;
this.keyRing = keyRing;
this.p2PService = p2PService;
this.useSavingsWallet = useSavingsWallet;
@@ -339,4 +337,20 @@ public void setPaymentStartedAckMessage(AckMessage ackMessage) {
public void setPaymentStartedMessageState(MessageState paymentStartedMessageStateProperty) {
this.paymentStartedMessageStateProperty.set(paymentStartedMessageStateProperty);
}
+
+ private void setTradingPeer(TradingPeer tradingPeer) {
+ this.tradingPeer = tradingPeer;
+ }
+
+ private void setOfferId(String offerId) {
+ this.offerId = offerId;
+ }
+
+ private void setAccountId(String accountId) {
+ this.accountId = accountId;
+ }
+
+ private void setPubKeyRing(PubKeyRing pubKeyRing) {
+ this.pubKeyRing = pubKeyRing;
+ }
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
index 60894b26336..3b3c94ebc8a 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java
@@ -21,38 +21,43 @@
import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
+import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
+import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerVerifiesPeersAccountAge;
-import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesAndSignsDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
+import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.util.Validator;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
-import static com.google.common.base.Preconditions.checkArgument;
-
@Slf4j
public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtocol, MakerProtocol {
private final SellerAsMakerTrade sellerAsMakerTrade;
@@ -73,7 +78,6 @@ public SellerAsMakerProtocol(SellerAsMakerTrade trade) {
() -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
this::handleTaskRunnerFault);
- taskRunner.addTasks(MakerSetupDepositTxListener.class);
taskRunner.run();
}
}
@@ -84,23 +88,11 @@ public SellerAsMakerProtocol(SellerAsMakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
-
- if (tradeMessage instanceof DepositTxPublishedMessage)
- handle((DepositTxPublishedMessage) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
- handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
- else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
+ handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -110,11 +102,10 @@ else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void handleTakeOfferRequest(TradeMessage tradeMessage,
+ public void handleTakeOfferRequest(InputsForDepositTxRequest tradeMessage,
NodeAddress sender,
ErrorMessageHandler errorMessageHandler) {
Validator.checkTradeId(processModel.getOfferId(), tradeMessage);
- checkArgument(tradeMessage instanceof PayDepositRequest);
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -126,20 +117,18 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
});
taskRunner.addTasks(
- MakerProcessPayDepositRequest.class,
+ MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
MakerVerifyTakerAccount.class,
VerifyPeersAccountAgeWitness.class,
SellerVerifiesPeersAccountAge.class,
MakerVerifyTakerFeePayment.class,
+ MakerSetsLockTime.class,
MakerCreateAndSignContract.class,
- SellerAsMakerCreatesAndSignsDepositTx.class,
- MakerSetupDepositTxListener.class,
- MakerSendPublishDepositTxRequest.class
+ SellerAsMakerCreatesUnsignedDepositTx.class,
+ SellerAsMakerSendsInputsForDepositTxResponse.class
);
- // We don't start a timeout because if we don't receive the peers DepositTxPublishedMessage we still
- // will get set the deposit tx in MakerSetupDepositTxListener once seen in the network
taskRunner.run();
}
@@ -148,7 +137,7 @@ public void handleTakeOfferRequest(TradeMessage tradeMessage,
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- protected void handle(DepositTxPublishedMessage tradeMessage, NodeAddress sender) {
+ protected void handle(DepositTxMessage tradeMessage, NodeAddress sender) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -159,10 +148,32 @@ protected void handle(DepositTxPublishedMessage tradeMessage, NodeAddress sender
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- MakerProcessDepositTxPublishedMessage.class,
- PublishTradeStatistics.class,
- MakerVerifyTakerAccount.class,
- MakerVerifyTakerFeePayment.class
+ SellerAsMakerProcessDepositTxMessage.class,
+ SellerAsMakerFinalizesDepositTx.class,
+ SellerCreatesDelayedPayoutTx.class,
+ SellerSendDelayedPayoutTxSignatureRequest.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DelayedPayoutTxSignatureResponse tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade,
+ () -> {
+ stopTimeout();
+ handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+
+ taskRunner.addTasks(
+ SellerProcessDelayedPayoutTxSignatureResponse.class,
+ SellerSignsDelayedPayoutTx.class,
+ SellerFinalizesDelayedPayoutTx.class,
+ SellerPublishesDepositTx.class,
+ SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
+ PublishTradeStatistics.class
);
taskRunner.run();
}
@@ -255,8 +266,10 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof DepositTxPublishedMessage) {
- handle((DepositTxPublishedMessage) tradeMessage, sender);
+ if (tradeMessage instanceof DepositTxMessage) {
+ handle((DepositTxMessage) tradeMessage, sender);
+ } else if (tradeMessage instanceof DelayedPayoutTxSignatureResponse) {
+ handle((DelayedPayoutTxSignatureResponse) tradeMessage, sender);
} else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
handle((CounterCurrencyTransferStartedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
index d3071b1c8b6..2b1cff8a916 100644
--- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java
@@ -18,39 +18,47 @@
package bisq.core.trade.protocol;
+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.PublishDepositTxRequest;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerVerifiesPeersAccountAge;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
-import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignAndPublishDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerProcessPublishDepositTxRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
-import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
-import bisq.common.proto.network.NetworkEnvelope;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtocol, TakerProtocol {
private final SellerAsTakerTrade sellerAsTakerTrade;
@@ -65,7 +73,8 @@ public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
this.sellerAsTakerTrade = trade;
- processModel.getTradingPeer().setPubKeyRing(trade.getOffer().getPubKeyRing());
+ Offer offer = checkNotNull(trade.getOffer());
+ processModel.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
}
@@ -74,22 +83,11 @@ public SellerAsTakerProtocol(SellerAsTakerTrade trade) {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade) {
- this.trade = trade;
-
- if (networkEnvelope instanceof MailboxMessage) {
- NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof TradeMessage) {
- TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
- log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
- tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest)
- handle((PublishDepositTxRequest) tradeMessage, peerNodeAddress);
- else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage)
- handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
- else
- log.error("We received an unhandled tradeMessage" + tradeMessage.toString());
- }
+ public void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ super.doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
+
+ if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
+ handle((CounterCurrencyTransferStartedMessage) tradeMessage, peerNodeAddress);
}
}
@@ -109,11 +107,9 @@ public void takeAvailableOffer() {
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
SellerAsTakerCreatesDepositTxInputs.class,
- TakerSendPayDepositRequest.class
+ TakerSendInputsForDepositTxRequest.class
);
- //TODO if peer does get an error he does not respond and all we get is the timeout now knowing why it failed.
- // We should add an error message the peer sends us in such cases.
startTimeout();
taskRunner.run();
}
@@ -123,7 +119,7 @@ public void takeAvailableOffer() {
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
- private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
+ private void handle(InputsForDepositTxResponse tradeMessage, NodeAddress sender) {
processModel.setTradeMessage(tradeMessage);
processModel.setTempTradingPeerNodeAddress(sender);
@@ -135,7 +131,7 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
VerifyPeersAccountAgeWitness.class,
@@ -143,8 +139,30 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
- SellerAsTakerSignAndPublishDepositTx.class,
- TakerSendDepositTxPublishedMessage.class,
+ SellerAsTakerSignsDepositTx.class,
+ SellerCreatesDelayedPayoutTx.class,
+ SellerSendDelayedPayoutTxSignatureRequest.class
+ );
+ taskRunner.run();
+ }
+
+ private void handle(DelayedPayoutTxSignatureResponse tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade,
+ () -> {
+ stopTimeout();
+ handleTaskRunnerSuccess(tradeMessage, "PublishDepositTxRequest");
+ },
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+
+ taskRunner.addTasks(
+ SellerProcessDelayedPayoutTxSignatureResponse.class,
+ SellerSignsDelayedPayoutTx.class,
+ SellerFinalizesDelayedPayoutTx.class,
+ SellerPublishesDepositTx.class,
+ SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
PublishTradeStatistics.class
);
taskRunner.run();
@@ -238,8 +256,10 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
log.info("Received {} from {} with tradeId {} and uid {}",
tradeMessage.getClass().getSimpleName(), sender, tradeMessage.getTradeId(), tradeMessage.getUid());
- if (tradeMessage instanceof PublishDepositTxRequest) {
- handle((PublishDepositTxRequest) tradeMessage, sender);
+ if (tradeMessage instanceof InputsForDepositTxResponse) {
+ handle((InputsForDepositTxResponse) tradeMessage, sender);
+ } else if (tradeMessage instanceof DelayedPayoutTxSignatureResponse) {
+ handle((DelayedPayoutTxSignatureResponse) tradeMessage, sender);
} else if (tradeMessage instanceof CounterCurrencyTransferStartedMessage) {
handle((CounterCurrencyTransferStartedMessage) tradeMessage, sender);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
index eee4da8371d..cc2a59c3187 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java
@@ -21,11 +21,13 @@
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
+import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.FinalizeMediatedPayoutTx;
import bisq.core.trade.protocol.tasks.mediation.ProcessMediatedPayoutSignatureMessage;
@@ -59,6 +61,7 @@
import javax.annotation.Nullable;
import static bisq.core.util.Validator.nonEmptyStringOf;
+import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public abstract class TradeProtocol {
@@ -80,13 +83,13 @@ public TradeProtocol(Trade trade) {
PublicKey signaturePubKey = decryptedMessageWithPubKey.getSignaturePubKey();
if (tradingPeerPubKeyRing != null && signaturePubKey.equals(tradingPeerPubKeyRing.getSignaturePubKey())) {
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
- log.trace("handleNewMessage: message = {} from {}", networkEnvelope.getClass().getSimpleName(), peersNodeAddress);
if (networkEnvelope instanceof TradeMessage) {
TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
nonEmptyStringOf(tradeMessage.getTradeId());
- if (tradeMessage.getTradeId().equals(processModel.getOfferId()))
+ if (tradeMessage.getTradeId().equals(processModel.getOfferId())) {
doHandleDecryptedMessage(tradeMessage, peersNodeAddress);
+ }
} else if (networkEnvelope instanceof AckMessage) {
AckMessage ackMessage = (AckMessage) networkEnvelope;
if (ackMessage.getSourceType() == AckMessageSourceType.TRADE_MESSAGE &&
@@ -110,7 +113,7 @@ public TradeProtocol(Trade trade) {
stateChangeListener = (observable, oldValue, newValue) -> {
if (newValue.getPhase() == Trade.Phase.TAKER_FEE_PUBLISHED && trade instanceof MakerTrade)
- processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
+ processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
};
trade.stateProperty().addListener(stateChangeListener);
}
@@ -206,6 +209,26 @@ protected void handle(MediatedPayoutTxPublishedMessage tradeMessage, NodeAddress
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Peer has published the delayed payout tx
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private void handle(PeerPublishedDelayedPayoutTxMessage tradeMessage, NodeAddress sender) {
+ processModel.setTradeMessage(tradeMessage);
+ processModel.setTempTradingPeerNodeAddress(sender);
+
+ TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
+ () -> handleTaskRunnerSuccess(tradeMessage, "PeerPublishedDelayedPayoutTxMessage"),
+ errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
+
+ taskRunner.addTasks(
+ //todo
+ ProcessPeerPublishedDelayedPayoutTxMessage.class
+ );
+ taskRunner.run();
+ }
+
+
///////////////////////////////////////////////////////////////////////////////////////////
// Dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@@ -215,6 +238,8 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
handle((MediatedPayoutTxSignatureMessage) tradeMessage, sender);
} else if (tradeMessage instanceof MediatedPayoutTxPublishedMessage) {
handle((MediatedPayoutTxPublishedMessage) tradeMessage, sender);
+ } else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
+ handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, sender);
}
}
@@ -225,10 +250,6 @@ protected void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress s
public void completed() {
cleanup();
-
- // We only removed earlier the listener here, but then we migth have dangling trades after faults...
- // so lets remove it at cleanup
- //processModel.getP2PService().removeDecryptedDirectMessageListener(decryptedDirectMessageListener);
}
private void cleanup() {
@@ -241,28 +262,33 @@ private void cleanup() {
public void applyMailboxMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Trade trade) {
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
- log.debug("applyMailboxMessage {}", networkEnvelope);
if (processModel.getTradingPeer().getPubKeyRing() != null &&
decryptedMessageWithPubKey.getSignaturePubKey().equals(processModel.getTradingPeer().getPubKeyRing().getSignaturePubKey())) {
processModel.setDecryptedMessageWithPubKey(decryptedMessageWithPubKey);
- doApplyMailboxMessage(networkEnvelope, trade);
- // This is just a quick fix for the missing handling of the mediation MailboxMessages.
- // With the new trade protocol that will be refactored further with using doApplyMailboxMessage...
if (networkEnvelope instanceof MailboxMessage && networkEnvelope instanceof TradeMessage) {
- NodeAddress sender = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
- if (networkEnvelope instanceof MediatedPayoutTxSignatureMessage) {
- handle((MediatedPayoutTxSignatureMessage) networkEnvelope, sender);
- } else if (networkEnvelope instanceof MediatedPayoutTxPublishedMessage) {
- handle((MediatedPayoutTxPublishedMessage) networkEnvelope, sender);
- }
+ this.trade = trade;
+ TradeMessage tradeMessage = (TradeMessage) networkEnvelope;
+ NodeAddress peerNodeAddress = ((MailboxMessage) networkEnvelope).getSenderNodeAddress();
+ doApplyMailboxTradeMessage(tradeMessage, peerNodeAddress);
}
} else {
log.error("SignaturePubKey in message does not match the SignaturePubKey we have stored to that trading peer.");
}
}
- protected abstract void doApplyMailboxMessage(NetworkEnvelope networkEnvelope, Trade trade);
+ protected void doApplyMailboxTradeMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress) {
+ log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}",
+ tradeMessage.getClass().getSimpleName(), peerNodeAddress, tradeMessage.getTradeId(), tradeMessage.getUid());
+
+ if (tradeMessage instanceof MediatedPayoutTxSignatureMessage) {
+ handle((MediatedPayoutTxSignatureMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof MediatedPayoutTxPublishedMessage) {
+ handle((MediatedPayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
+ } else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
+ handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
+ }
+ }
protected void startTimeout() {
stopTimeout();
@@ -318,7 +344,7 @@ private void sendAckMessage(@Nullable TradeMessage tradeMessage, boolean result,
sourceUid = ((MailboxMessage) tradeMessage).getUid();
} else {
// For direct msg we don't have a mandatory uid so we need to cast to get it
- if (tradeMessage instanceof PayDepositRequest) {
+ if (tradeMessage instanceof InputsForDepositTxRequest) {
sourceUid = tradeMessage.getUid();
}
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
index 94398caf791..3a2af777055 100644
--- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
+++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java
@@ -38,10 +38,23 @@
import javax.annotation.Nullable;
+// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
+// persist them.
+//todo clean up older fields as well to make most transient
@Slf4j
@Getter
@Setter
public final class TradingPeer implements PersistablePayload {
+ // Transient/Mutable
+ // Added in v1.2.0
+ @Setter
+ @Nullable
+ transient private byte[] delayedPayoutTxSignature;
+ @Setter
+ @Nullable
+ transient private byte[] preparedDepositTx;
+
+ // Persistable mutable
@Nullable
private String accountId;
@Nullable
@@ -75,6 +88,7 @@ public final class TradingPeer implements PersistablePayload {
@Nullable
private byte[] mediatedPayoutTxSignature;
+
public TradingPeer() {
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
new file mode 100644
index 00000000000..d1efcb140bd
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
@@ -0,0 +1,63 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
+import bisq.core.util.Validator;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public ProcessPeerPublishedDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ PeerPublishedDelayedPayoutTxMessage message = (PeerPublishedDelayedPayoutTxMessage) processModel.getTradeMessage();
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ checkNotNull(message);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+ processModel.removeMailboxMessageAfterProcessing(trade);
+
+ // We add the tx to our wallet.
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ processModel.getTradeWalletService().addTxToWallet(delayedPayoutTx);
+
+ // todo trade.setState
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
index 61dbe0ff531..20b85539398 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SetupPayoutTxListener.java
@@ -54,10 +54,10 @@ protected void run() {
runInterceptHook();
if (!trade.isPayoutPublished()) {
BtcWalletService walletService = processModel.getBtcWalletService();
- final String id = processModel.getOffer().getId();
+ String id = processModel.getOffer().getId();
Address address = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT).getAddress();
- final TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
+ TransactionConfidence confidence = walletService.getConfidenceForAddress(address);
if (isInNetwork(confidence)) {
applyConfidence(confidence);
} else {
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java
new file mode 100644
index 00000000000..1d2f0f35292
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDelayedPayoutTxSignatureRequest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.TradeTask;
+import bisq.core.util.Validator;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class BuyerProcessDelayedPayoutTxSignatureRequest extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerProcessDelayedPayoutTxSignatureRequest(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ DelayedPayoutTxSignatureRequest message = (DelayedPayoutTxSignatureRequest) processModel.getTradeMessage();
+ checkNotNull(message);
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ byte[] delayedPayoutTxAsBytes = checkNotNull(message.getDelayedPayoutTx());
+ Transaction delayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes);
+ trade.applyDelayedPayoutTx(delayedPayoutTx);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessDepositTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java
similarity index 65%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessDepositTxPublishedMessage.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java
index c000c49e3f5..28e3b0a5049 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessDepositTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java
@@ -15,12 +15,12 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.maker;
+package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.util.Validator;
@@ -34,9 +34,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class MakerProcessDepositTxPublishedMessage extends TradeTask {
+public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
@SuppressWarnings({"unused"})
- public MakerProcessDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
+ public BuyerProcessDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -44,18 +44,22 @@ public MakerProcessDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade
protected void run() {
try {
runInterceptHook();
- log.debug("current trade state " + trade.getState());
- DepositTxPublishedMessage message = (DepositTxPublishedMessage) processModel.getTradeMessage();
- Validator.checkTradeId(processModel.getOfferId(), message);
+ DepositTxAndDelayedPayoutTxMessage message = (DepositTxAndDelayedPayoutTxMessage) processModel.getTradeMessage();
checkNotNull(message);
+ Validator.checkTradeId(processModel.getOfferId(), message);
checkArgument(message.getDepositTx() != null);
// To access tx confidence we need to add that tx into our wallet.
- Transaction txFromSerializedTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx());
+ Transaction depositTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx());
// update with full tx
- Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(txFromSerializedTx);
- trade.setDepositTx(walletTx);
- BtcWalletService.printTx("depositTx received from peer", walletTx);
+ Transaction depositTxWalletTx = processModel.getTradeWalletService().addTxToWallet(depositTx);
+ trade.applyDepositTx(depositTxWalletTx);
+ BtcWalletService.printTx("depositTx received from peer", depositTxWalletTx);
+
+ // To access tx confidence we need to add that tx into our wallet.
+ Transaction delayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDelayedPayoutTx());
+ trade.applyDelayedPayoutTx(delayedPayoutTx);
+ BtcWalletService.printTx("delayedPayoutTx received from peer", delayedPayoutTx);
// update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
@@ -63,8 +67,8 @@ protected void run() {
processModel.removeMailboxMessageAfterProcessing(trade);
// If we got already the confirmation we don't want to apply an earlier state
- if (trade.getState() != Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK)
- trade.setState(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
+ if (trade.getState() != Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK)
+ trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java
new file mode 100644
index 00000000000..887c416a546
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendsDelayedPayoutTxSignatureResponse.java
@@ -0,0 +1,85 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.SendDirectMessageListener;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class BuyerSendsDelayedPayoutTxSignatureResponse extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerSendsDelayedPayoutTxSignatureResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ byte[] delayedPayoutTxSignature = checkNotNull(processModel.getDelayedPayoutTxSignature());
+ DelayedPayoutTxSignatureResponse message = new DelayedPayoutTxSignatureResponse(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ delayedPayoutTxSignature);
+
+ // todo trade.setState
+
+ NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ processModel.getP2PService().sendEncryptedDirectMessage(
+ peersNodeAddress,
+ processModel.getTradingPeer().getPubKeyRing(),
+ message,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ // todo trade.setState
+ complete();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
+ // todo trade.setState
+ appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
+ failed(errorMessage);
+ }
+ }
+ );
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxListener.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
similarity index 94%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxListener.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
index a858124c5a1..454761b4aec 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetupDepositTxListener.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSetupDepositTxListener.java
@@ -15,7 +15,7 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.maker;
+package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.listeners.AddressConfidenceListener;
import bisq.core.btc.model.AddressEntry;
@@ -39,13 +39,13 @@
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
-public class MakerSetupDepositTxListener extends TradeTask {
+public class BuyerSetupDepositTxListener extends TradeTask {
// Use instance fields to not get eaten up by the GC
private Subscription tradeStateSubscription;
private AddressConfidenceListener confidenceListener;
@SuppressWarnings({"unused"})
- public MakerSetupDepositTxListener(TaskRunner taskHandler, Trade trade) {
+ public BuyerSetupDepositTxListener(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -94,9 +94,9 @@ public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
private void applyConfidence(TransactionConfidence confidence) {
if (trade.getDepositTx() == null) {
Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
- trade.setDepositTx(walletTx);
+ trade.applyDepositTx(walletTx);
BtcWalletService.printTx("depositTx received from network", walletTx);
- trade.setState(Trade.State.MAKER_SAW_DEPOSIT_TX_IN_NETWORK);
+ trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK);
} else {
log.info("We got the deposit tx already set from MakerProcessDepositTxPublishedMessage. tradeId={}, state={}", trade.getId(), trade.getState());
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSignPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java
similarity index 89%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSignPayoutTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java
index 4140ce364dd..b90a91ec22d 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSignPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignPayoutTx.java
@@ -15,7 +15,7 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.buyer_as_maker;
+package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
@@ -38,10 +38,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class BuyerAsMakerSignPayoutTx extends TradeTask {
+public class BuyerSignPayoutTx extends TradeTask {
@SuppressWarnings({"unused"})
- public BuyerAsMakerSignPayoutTx(TaskRunner taskHandler, Trade trade) {
+ public BuyerSignPayoutTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -69,7 +69,7 @@ protected void run() {
checkArgument(Arrays.equals(buyerMultiSigPubKey,
walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+ byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
byte[] payoutTxSignature = processModel.getTradeWalletService().buyerSignsPayoutTx(
trade.getDepositTx(),
@@ -79,8 +79,7 @@ protected void run() {
sellerPayoutAddressString,
buyerMultiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey());
+ sellerMultiSigPubKey);
processModel.setPayoutTxSignature(payoutTxSignature);
complete();
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java
new file mode 100644
index 00000000000..8fa17901d6d
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java
@@ -0,0 +1,68 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer;
+
+import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.crypto.DeterministicKey;
+
+import java.util.Arrays;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class BuyerSignsDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerSignsDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ BtcWalletService btcWalletService = processModel.getBtcWalletService();
+ String id = processModel.getOffer().getId();
+
+ byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey();
+ DeterministicKey myMultiSigKeyPair = btcWalletService.getMultiSigKeyPair(id, buyerMultiSigPubKey);
+
+ checkArgument(Arrays.equals(buyerMultiSigPubKey,
+ btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
+ "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
+ byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+ byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(delayedPayoutTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey);
+ processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
new file mode 100644
index 00000000000..3ec30a67786
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class BuyerVerifiesDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerVerifiesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction depositTx = checkNotNull(trade.getDepositTx());
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ if (processModel.getTradeWalletService().verifiesDepositTxAndDelayedPayoutTx(depositTx, delayedPayoutTx)) {
+ complete();
+ } else {
+ failed("DelayedPayoutTx is not spending correctly depositTx");
+ }
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java
index d49995560b2..f7d2e8c8176 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerCreatesAndSignsDepositTx.java
@@ -57,52 +57,40 @@ protected void run() {
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
TradingPeer tradingPeer = processModel.getTradingPeer();
- final Offer offer = trade.getOffer();
+ Offer offer = checkNotNull(trade.getOffer());
// params
- final boolean makerIsBuyer = true;
-
- final byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
log.debug("\n\n------------------------------------------------------------\n"
+ "Contract as json\n"
+ trade.getContractAsJson()
+ "\n------------------------------------------------------------\n");
- final Coin makerInputAmount = offer.getBuyerSecurityDeposit();
+ Coin makerInputAmount = offer.getBuyerSecurityDeposit();
Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get();
makerMultiSigAddressEntry.setCoinLockedInMultiSig(makerInputAmount);
walletService.saveAddressEntryList();
- final Coin msOutputAmount = makerInputAmount
+ Coin msOutputAmount = makerInputAmount
.add(trade.getTxFee())
.add(offer.getSellerSecurityDeposit())
.add(trade.getTradeAmount());
- final List takerRawTransactionInputs = tradingPeer.getRawTransactionInputs();
-
- final long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
-
- final String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
-
- final Address makerAddress = walletService.getOrCreateAddressEntry(id,
- AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
-
- final Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
-
- final byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
+ List takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
+ long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
+ String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
+ Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
+ Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
+ byte[] buyerPubKey = processModel.getMyMultiSigPubKey();
+ byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
checkArgument(Arrays.equals(buyerPubKey,
makerMultiSigAddressEntry.getPubKey()),
"buyerPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] sellerPubKey = tradingPeer.getMultiSigPubKey();
-
- final byte[] arbitratorBtcPubKey = trade.getArbitratorBtcPubKey();
-
- PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().makerCreatesAndSignsDepositTx(
- makerIsBuyer,
+ PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().buyerAsMakerCreatesAndSignsDepositTx(
contractHash,
makerInputAmount,
msOutputAmount,
@@ -112,8 +100,7 @@ protected void run() {
makerAddress,
makerChangeAddress,
buyerPubKey,
- sellerPubKey,
- arbitratorBtcPubKey);
+ sellerPubKey);
processModel.setPreparedDepositTx(result.depositTransaction);
processModel.setRawTransactionInputs(result.rawMakerInputs);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java
new file mode 100644
index 00000000000..029a8213dcd
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_maker/BuyerAsMakerSendsInputsForDepositTxResponse.java
@@ -0,0 +1,38 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class BuyerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInputsForDepositTxResponse {
+ @SuppressWarnings({"unused"})
+ public BuyerAsMakerSendsInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected byte[] getPreparedDepositTx() {
+ return processModel.getPreparedDepositTx();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java
index 79b18574dd9..ad0efc70bf6 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerCreatesDepositTxInputs.java
@@ -17,19 +17,18 @@
package bisq.core.trade.protocol.tasks.buyer_as_taker;
-import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.InputsAndChangeOutput;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class BuyerAsTakerCreatesDepositTxInputs extends TradeTask {
@@ -47,15 +46,15 @@ protected void run() {
Coin bsqTakerFee = trade.isCurrencyForTakerFeeBtc() ? Coin.ZERO : trade.getTakerFee();
Coin txFee = trade.getTxFee();
- Coin takerInputAmount = trade.getOffer().getBuyerSecurityDeposit().add(txFee).add(txFee).subtract(bsqTakerFee);
- BtcWalletService walletService = processModel.getBtcWalletService();
- Address takersAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(),
- AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
- InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
+ Coin takerInputAmount = checkNotNull(trade.getOffer()).getBuyerSecurityDeposit()
+ .add(txFee)
+ .add(txFee)
+ .subtract(bsqTakerFee);
+ Coin fee = txFee.subtract(bsqTakerFee);
+ InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs(
processModel.getTakeOfferFeeTx(),
takerInputAmount,
- txFee.subtract(bsqTakerFee),
- takersAddress);
+ fee);
processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java
new file mode 100644
index 00000000000..f0391f91ebf
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSendsDepositTxMessage.java
@@ -0,0 +1,87 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.buyer_as_taker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.SendDirectMessageListener;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class BuyerAsTakerSendsDepositTxMessage extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public BuyerAsTakerSendsDepositTxMessage(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ if (trade.getDepositTx() != null) {
+ DepositTxMessage message = new DepositTxMessage(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ trade.getDepositTx().bitcoinSerialize());
+
+ // todo trade.setState
+
+ NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ processModel.getP2PService().sendEncryptedDirectMessage(
+ peersNodeAddress,
+ processModel.getTradingPeer().getPubKeyRing(),
+ message,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ // todo trade.setState
+ complete();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
+
+ // todo trade.setState
+ appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
+ failed();
+ }
+ }
+ );
+ } else {
+ log.error("trade.getDepositTx() = " + trade.getDepositTx());
+ failed("DepositTx is null");
+ }
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignAndPublishDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java
similarity index 55%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignAndPublishDepositTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java
index 125e347578c..f5cdcb37606 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignAndPublishDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java
@@ -17,11 +17,9 @@
package bisq.core.trade.protocol.tasks.buyer_as_taker;
-import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.BtcWalletService;
-import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.trade.protocol.tasks.TradeTask;
@@ -42,10 +40,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class BuyerAsTakerSignAndPublishDepositTx extends TradeTask {
+public class BuyerAsTakerSignsDepositTx extends TradeTask {
@SuppressWarnings({"unused"})
- public BuyerAsTakerSignAndPublishDepositTx(TaskRunner taskHandler, Trade trade) {
+ public BuyerAsTakerSignsDepositTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -60,7 +58,7 @@ protected void run() {
+ "\n------------------------------------------------------------\n");
- byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
List buyerInputs = checkNotNull(processModel.getRawTransactionInputs(), "buyerInputs must not be null");
BtcWalletService walletService = processModel.getBtcWalletService();
@@ -79,49 +77,19 @@ protected void run() {
checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()),
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
+ List sellerInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
+ byte[] sellerMultiSigPubKey = tradingPeer.getMultiSigPubKey();
+ Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx(
false,
contractHash,
processModel.getPreparedDepositTx(),
buyerInputs,
- tradingPeer.getRawTransactionInputs(),
+ sellerInputs,
buyerMultiSigPubKey,
- tradingPeer.getMultiSigPubKey(),
- trade.getArbitratorBtcPubKey(),
- new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- if (!completed) {
- // We set the depositTx before we change the state as the state change triggers code
- // which expected the tx to be available. That case will usually never happen as the
- // callback is called after the method call has returned but in some test scenarios
- // with regtest we run into such issues, thus fixing it to make it more stict seems
- // reasonable.
- trade.setDepositTx(transaction);
- log.trace("takerSignsAndPublishesDepositTx succeeded " + transaction);
- trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
- walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
-
- complete();
- } else {
- log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
- }
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- if (!completed) {
- failed(exception);
- } else {
- log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
- }
- }
- });
- if (trade.getDepositTx() == null) {
- // We set the deposit tx in case we get the onFailure called. We cannot set it in the onFailure
- // callback as the tx is returned by the method call where the callback is used as an argument.
- trade.setDepositTx(depositTx);
- }
+ sellerMultiSigPubKey);
+ trade.applyDepositTx(depositTx);
+
+ complete();
} catch (Throwable t) {
failed(t);
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java
index 417a4be432c..9f963e0afac 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerCreateAndSignContract.java
@@ -55,7 +55,7 @@ protected void run() {
TradingPeer taker = processModel.getTradingPeer();
PaymentAccountPayload makerPaymentAccountPayload = processModel.getPaymentAccountPayload(trade);
checkNotNull(makerPaymentAccountPayload, "makerPaymentAccountPayload must not be null");
- PaymentAccountPayload takerPaymentAccountPayload = taker.getPaymentAccountPayload();
+ PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(taker.getPaymentAccountPayload());
boolean isBuyerMakerAndSellerTaker = trade instanceof BuyerAsMakerTrade;
NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ?
@@ -91,7 +91,9 @@ protected void run() {
takerAddressEntry.getAddressString(),
taker.getPayoutAddressString(),
makerMultiSigPubKey,
- taker.getMultiSigPubKey()
+ taker.getMultiSigPubKey(),
+ trade.getLockTime(),
+ trade.getRefundAgentNodeAddress()
);
String contractAsJson = Utilities.objectToJson(contract);
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessPayDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java
similarity index 63%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessPayDepositRequest.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java
index 3a8088905ce..d449accd164 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessPayDepositRequest.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java
@@ -22,7 +22,7 @@
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.user.User;
@@ -43,9 +43,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class MakerProcessPayDepositRequest extends TradeTask {
+public class MakerProcessesInputsForDepositTxRequest extends TradeTask {
@SuppressWarnings({"unused"})
- public MakerProcessPayDepositRequest(TaskRunner taskHandler, Trade trade) {
+ public MakerProcessesInputsForDepositTxRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -54,37 +54,35 @@ protected void run() {
try {
runInterceptHook();
log.debug("current trade state " + trade.getState());
- PayDepositRequest payDepositRequest = (PayDepositRequest) processModel.getTradeMessage();
- checkNotNull(payDepositRequest);
- checkTradeId(processModel.getOfferId(), payDepositRequest);
+ InputsForDepositTxRequest inputsForDepositTxRequest = (InputsForDepositTxRequest) processModel.getTradeMessage();
+ checkNotNull(inputsForDepositTxRequest);
+ checkTradeId(processModel.getOfferId(), inputsForDepositTxRequest);
final TradingPeer tradingPeer = processModel.getTradingPeer();
- tradingPeer.setPaymentAccountPayload(checkNotNull(payDepositRequest.getTakerPaymentAccountPayload()));
- tradingPeer.setRawTransactionInputs(checkNotNull(payDepositRequest.getRawTransactionInputs()));
- checkArgument(payDepositRequest.getRawTransactionInputs().size() > 0);
-
- tradingPeer.setChangeOutputValue(payDepositRequest.getChangeOutputValue());
- tradingPeer.setChangeOutputAddress(payDepositRequest.getChangeOutputAddress());
-
- tradingPeer.setMultiSigPubKey(checkNotNull(payDepositRequest.getTakerMultiSigPubKey()));
- tradingPeer.setPayoutAddressString(nonEmptyStringOf(payDepositRequest.getTakerPayoutAddressString()));
- tradingPeer.setPubKeyRing(checkNotNull(payDepositRequest.getTakerPubKeyRing()));
-
- tradingPeer.setAccountId(nonEmptyStringOf(payDepositRequest.getTakerAccountId()));
- trade.setTakerFeeTxId(nonEmptyStringOf(payDepositRequest.getTakerFeeTxId()));
- processModel.setTakerAcceptedArbitratorNodeAddresses(checkNotNull(payDepositRequest.getAcceptedArbitratorNodeAddresses()));
- processModel.setTakerAcceptedMediatorNodeAddresses(checkNotNull(payDepositRequest.getAcceptedMediatorNodeAddresses()));
- if (payDepositRequest.getAcceptedArbitratorNodeAddresses().isEmpty())
+ tradingPeer.setPaymentAccountPayload(checkNotNull(inputsForDepositTxRequest.getTakerPaymentAccountPayload()));
+ tradingPeer.setRawTransactionInputs(checkNotNull(inputsForDepositTxRequest.getRawTransactionInputs()));
+ checkArgument(inputsForDepositTxRequest.getRawTransactionInputs().size() > 0);
+
+ tradingPeer.setChangeOutputValue(inputsForDepositTxRequest.getChangeOutputValue());
+ tradingPeer.setChangeOutputAddress(inputsForDepositTxRequest.getChangeOutputAddress());
+
+ tradingPeer.setMultiSigPubKey(checkNotNull(inputsForDepositTxRequest.getTakerMultiSigPubKey()));
+ tradingPeer.setPayoutAddressString(nonEmptyStringOf(inputsForDepositTxRequest.getTakerPayoutAddressString()));
+ tradingPeer.setPubKeyRing(checkNotNull(inputsForDepositTxRequest.getTakerPubKeyRing()));
+
+ tradingPeer.setAccountId(nonEmptyStringOf(inputsForDepositTxRequest.getTakerAccountId()));
+ trade.setTakerFeeTxId(nonEmptyStringOf(inputsForDepositTxRequest.getTakerFeeTxId()));
+ if (inputsForDepositTxRequest.getAcceptedArbitratorNodeAddresses().isEmpty())
failed("acceptedArbitratorNodeAddresses must not be empty");
// 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)
tradingPeer.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
- tradingPeer.setAccountAgeWitnessSignature(payDepositRequest.getAccountAgeWitnessSignatureOfOfferId());
- tradingPeer.setCurrentDate(payDepositRequest.getCurrentDate());
+ tradingPeer.setAccountAgeWitnessSignature(inputsForDepositTxRequest.getAccountAgeWitnessSignatureOfOfferId());
+ tradingPeer.setCurrentDate(inputsForDepositTxRequest.getCurrentDate());
User user = checkNotNull(processModel.getUser(), "User must not be null");
- NodeAddress arbitratorNodeAddress = checkNotNull(payDepositRequest.getArbitratorNodeAddress(),
+ NodeAddress arbitratorNodeAddress = checkNotNull(inputsForDepositTxRequest.getArbitratorNodeAddress(),
"payDepositRequest.getArbitratorNodeAddress() must not be null");
trade.setArbitratorNodeAddress(arbitratorNodeAddress);
Arbitrator arbitrator = checkNotNull(user.getAcceptedArbitratorByAddress(arbitratorNodeAddress),
@@ -94,7 +92,7 @@ protected void run() {
trade.setArbitratorPubKeyRing(checkNotNull(arbitrator.getPubKeyRing(),
"arbitrator.getPubKeyRing() must not be null"));
- NodeAddress mediatorNodeAddress = checkNotNull(payDepositRequest.getMediatorNodeAddress(),
+ NodeAddress mediatorNodeAddress = checkNotNull(inputsForDepositTxRequest.getMediatorNodeAddress(),
"payDepositRequest.getMediatorNodeAddress() must not be null");
trade.setMediatorNodeAddress(mediatorNodeAddress);
Mediator mediator = checkNotNull(user.getAcceptedMediatorByAddress(mediatorNodeAddress),
@@ -104,7 +102,7 @@ protected void run() {
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
try {
- long takersTradePrice = payDepositRequest.getTradePrice();
+ long takersTradePrice = inputsForDepositTxRequest.getTradePrice();
offer.checkTradePriceTolerance(takersTradePrice);
trade.setTradePrice(takersTradePrice);
} catch (TradePriceOutOfToleranceException e) {
@@ -113,15 +111,13 @@ protected void run() {
failed(e2);
}
- checkArgument(payDepositRequest.getTradeAmount() > 0);
- trade.setTradeAmount(Coin.valueOf(payDepositRequest.getTradeAmount()));
+ checkArgument(inputsForDepositTxRequest.getTradeAmount() > 0);
+ trade.setTradeAmount(Coin.valueOf(inputsForDepositTxRequest.getTradeAmount()));
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
trade.persist();
- processModel.removeMailboxMessageAfterProcessing(trade);
-
complete();
} catch (Throwable t) {
failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java
similarity index 79%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java
index 8bb029f140f..2cd81721ee7 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendPublishDepositTxRequest.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInputsForDepositTxResponse.java
@@ -21,11 +21,11 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PublishDepositTxRequest;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.NodeAddress;
-import bisq.network.p2p.SendMailboxMessageListener;
+import bisq.network.p2p.SendDirectMessageListener;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner;
@@ -41,12 +41,14 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class MakerSendPublishDepositTxRequest extends TradeTask {
+public abstract class MakerSendsInputsForDepositTxResponse extends TradeTask {
@SuppressWarnings({"unused"})
- public MakerSendPublishDepositTxRequest(TaskRunner taskHandler, Trade trade) {
+ public MakerSendsInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
+ protected abstract byte[] getPreparedDepositTx();
+
@Override
protected void run() {
try {
@@ -62,15 +64,16 @@ protected void run() {
addressEntryOptional.get().getPubKey()),
"makerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] preparedDepositTx = processModel.getPreparedDepositTx();
+ byte[] preparedDepositTx = getPreparedDepositTx();
// Maker has to use preparedDepositTx as nonce.
// He cannot manipulate the preparedDepositTx - so we avoid to have a challenge protocol for passing the nonce we want to get signed.
- final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null");
+ PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade),
+ "processModel.getPaymentAccountPayload(trade) must not be null");
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), preparedDepositTx);
- PublishDepositTxRequest message = new PublishDepositTxRequest(
+ InputsForDepositTxResponse message = new InputsForDepositTxResponse(
processModel.getOfferId(),
paymentAccountPayload,
processModel.getAccountId(),
@@ -83,18 +86,19 @@ protected void run() {
processModel.getMyNodeAddress(),
UUID.randomUUID().toString(),
sig,
- new Date().getTime());
+ new Date().getTime(),
+ trade.getLockTime());
trade.setState(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST);
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- processModel.getP2PService().sendEncryptedMailboxMessage(
+ processModel.getP2PService().sendEncryptedDirectMessage(
peersNodeAddress,
processModel.getTradingPeer().getPubKeyRing(),
message,
- new SendMailboxMessageListener() {
+ new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
@@ -103,14 +107,6 @@ public void onArrived() {
complete();
}
- @Override
- public void onStoredInMailbox() {
- log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
- message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- trade.setState(Trade.State.MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST);
- complete();
- }
-
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java
new file mode 100644
index 00000000000..bea1d452e3c
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import java.util.Random;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MakerSetsLockTime extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public MakerSetsLockTime(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ // 20-30 days
+ int delay = 144 * 20 + new Random().nextInt(144 * 10);
+ long lockTime = processModel.getBtcWalletService().getBestChainHeight() + delay;
+ log.info("lockTime={}, delay={}", lockTime, delay);
+ trade.setLockTime(lockTime);
+ //todo for dev testing
+ trade.setLockTime(processModel.getBtcWalletService().getBestChainHeight() + 5);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java
index b88a39ff05e..96d8db5b4ec 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/FinalizeMediatedPayoutTx.java
@@ -104,8 +104,7 @@ protected void run() {
sellerPayoutAddressString,
multiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey()
+ sellerMultiSigPubKey
);
trade.setPayoutTx(transaction);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java
index 59c27e83177..5a48240f7d4 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/SignMediatedPayoutTx.java
@@ -97,8 +97,7 @@ protected void run() {
sellerPayoutAddressString,
myMultiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey());
+ sellerMultiSigPubKey);
processModel.setMediatedPayoutTxSignature(mediatedPayoutTxSignature);
complete();
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java
new file mode 100644
index 00000000000..437dbaed0fa
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java
@@ -0,0 +1,65 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.dao.governance.param.Param;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerCreatesDelayedPayoutTx extends TradeTask {
+
+ @SuppressWarnings({"unused"})
+ public SellerCreatesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ String donationAddressString = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS);
+ Coin minerFee = trade.getTxFee();
+ TradeWalletService tradeWalletService = processModel.getTradeWalletService();
+ Transaction depositTx = checkNotNull(trade.getDepositTx());
+
+ long lockTime = trade.getLockTime();
+ Transaction unsignedDelayedPayoutTx = tradeWalletService.createDelayedUnsignedPayoutTx(depositTx,
+ donationAddressString,
+ minerFee,
+ lockTime);
+
+ trade.applyDelayedPayoutTx(unsignedDelayedPayoutTx);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java
new file mode 100644
index 00000000000..67583a95981
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.java
@@ -0,0 +1,74 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import java.util.Arrays;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerFinalizesDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerFinalizesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction unsignedDelayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ BtcWalletService btcWalletService = processModel.getBtcWalletService();
+ String id = processModel.getOffer().getId();
+
+ byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+ byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
+ checkArgument(Arrays.equals(sellerMultiSigPubKey,
+ btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
+ "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
+
+ byte[] buyerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature();
+ byte[] sellerSignature = processModel.getDelayedPayoutTxSignature();
+
+ Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeDelayedPayoutTx(unsignedDelayedPayoutTx,
+ buyerMultiSigPubKey,
+ sellerMultiSigPubKey,
+ buyerSignature,
+ sellerSignature);
+
+ trade.applyDelayedPayoutTx(signedDelayedPayoutTx);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java
new file mode 100644
index 00000000000..e2e3d7cd37f
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessDelayedPayoutTxSignatureResponse.java
@@ -0,0 +1,59 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.util.Validator.checkTradeId;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerProcessDelayedPayoutTxSignatureResponse extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerProcessDelayedPayoutTxSignatureResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ DelayedPayoutTxSignatureResponse delayedPayoutTxSignatureResponse = (DelayedPayoutTxSignatureResponse) processModel.getTradeMessage();
+ checkNotNull(delayedPayoutTxSignatureResponse);
+ checkTradeId(processModel.getOfferId(), delayedPayoutTxSignatureResponse);
+
+ byte[] delayedPayoutTxSignature = checkNotNull(delayedPayoutTxSignatureResponse.getDelayedPayoutTxSignature());
+ processModel.getTradingPeer().setDelayedPayoutTxSignature(delayedPayoutTxSignature);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+
+ // todo trade.setState
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java
new file mode 100644
index 00000000000..be9b3fc1bd3
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerPublishesDepositTx.java
@@ -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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.btc.exceptions.TxBroadcastException;
+import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.trade.Contract;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SellerPublishesDepositTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerPublishesDepositTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ processModel.getTradeWalletService().broadcastTx(trade.getDepositTx(),
+ new TxBroadcaster.Callback() {
+ @Override
+ public void onSuccess(Transaction transaction) {
+ if (!completed) {
+ trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX);
+
+ processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
+
+ complete();
+ } else {
+ log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
+ }
+ }
+
+ @Override
+ public void onFailure(TxBroadcastException exception) {
+ if (!completed) {
+ failed(exception);
+ } else {
+ log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
+ }
+ }
+ });
+ } catch (Throwable t) {
+ Contract contract = trade.getContract();
+ if (contract != null)
+ contract.printDiff(processModel.getTradingPeer().getContractAsJson());
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java
new file mode 100644
index 00000000000..8b6fc402ee6
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java
@@ -0,0 +1,87 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.network.p2p.NodeAddress;
+import bisq.network.p2p.SendDirectMessageListener;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerSendDelayedPayoutTxSignatureRequest extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerSendDelayedPayoutTxSignatureRequest(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx(), "trade.getDelayedPayoutTx() must not be null");
+ DelayedPayoutTxSignatureRequest message = new DelayedPayoutTxSignatureRequest(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ delayedPayoutTx.bitcoinSerialize());
+
+ // todo trade.setState
+
+ NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
+ log.info("Send {} to peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ processModel.getP2PService().sendEncryptedDirectMessage(
+ peersNodeAddress,
+ processModel.getTradingPeer().getPubKeyRing(),
+ message,
+ new SendDirectMessageListener() {
+ @Override
+ public void onArrived() {
+ log.info("{} arrived at peer {}. tradeId={}, uid={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
+ // todo trade.setState
+ complete();
+ }
+
+ @Override
+ public void onFault(String errorMessage) {
+ log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
+ message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
+ // todo trade.setState
+ appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
+ failed();
+ }
+ }
+ );
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java
similarity index 73%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java
index ce9034b98dc..077bca13636 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendDepositTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendsDepositTxAndDelayedPayoutTxMessage.java
@@ -15,10 +15,10 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.taker;
+package bisq.core.trade.protocol.tasks.seller;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.DepositTxPublishedMessage;
+import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.NodeAddress;
@@ -26,14 +26,18 @@
import bisq.common.taskrunner.TaskRunner;
+import org.bitcoinj.core.Transaction;
+
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
-public class TakerSendDepositTxPublishedMessage extends TradeTask {
+public class SellerSendsDepositTxAndDelayedPayoutTxMessage extends TradeTask {
@SuppressWarnings({"unused"})
- public TakerSendDepositTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
+ public SellerSendsDepositTxAndDelayedPayoutTxMessage(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -42,12 +46,14 @@ protected void run() {
try {
runInterceptHook();
if (trade.getDepositTx() != null) {
- final String id = processModel.getOfferId();
- DepositTxPublishedMessage message = new DepositTxPublishedMessage(processModel.getOfferId(),
- trade.getDepositTx().bitcoinSerialize(),
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ Transaction depositTx = checkNotNull(trade.getDepositTx());
+ DepositTxAndDelayedPayoutTxMessage message = new DepositTxAndDelayedPayoutTxMessage(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
processModel.getMyNodeAddress(),
- UUID.randomUUID().toString());
- trade.setState(Trade.State.TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
+ depositTx.bitcoinSerialize(),
+ delayedPayoutTx.bitcoinSerialize());
+ trade.setState(Trade.State.SELLER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
@@ -61,7 +67,7 @@ protected void run() {
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- trade.setState(Trade.State.TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG);
+ trade.setState(Trade.State.SELLER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG);
complete();
}
@@ -69,7 +75,8 @@ public void onArrived() {
public void onStoredInMailbox() {
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
- trade.setState(Trade.State.TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG);
+
+ trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG);
complete();
}
@@ -77,7 +84,7 @@ public void onStoredInMailbox() {
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
- trade.setState(Trade.State.TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG);
+ trade.setState(Trade.State.SELLER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed();
}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java
index 80b3e4c6340..d6868db4238 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignAndFinalizePayoutTx.java
@@ -60,7 +60,7 @@ protected void run() {
final byte[] buyerSignature = tradingPeer.getSignature();
- Coin buyerPayoutAmount = offer.getBuyerSecurityDeposit().add(trade.getTradeAmount());
+ Coin buyerPayoutAmount = checkNotNull(offer.getBuyerSecurityDeposit()).add(trade.getTradeAmount());
Coin sellerPayoutAmount = offer.getSellerSecurityDeposit();
final String buyerPayoutAddressString = tradingPeer.getPayoutAddressString();
@@ -78,7 +78,7 @@ protected void run() {
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(id, sellerMultiSigPubKey);
Transaction transaction = processModel.getTradeWalletService().sellerSignsAndFinalizesPayoutTx(
- trade.getDepositTx(),
+ checkNotNull(trade.getDepositTx()),
buyerSignature,
buyerPayoutAmount,
sellerPayoutAmount,
@@ -86,8 +86,7 @@ protected void run() {
sellerPayoutAddressString,
multiSigKeyPair,
buyerMultiSigPubKey,
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey()
+ sellerMultiSigPubKey
);
trade.setPayoutTx(transaction);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java
new file mode 100644
index 00000000000..6947ba908c2
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java
@@ -0,0 +1,73 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller;
+
+import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.crypto.DeterministicKey;
+
+import java.util.Arrays;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerSignsDelayedPayoutTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerSignsDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
+ BtcWalletService btcWalletService = processModel.getBtcWalletService();
+ String id = processModel.getOffer().getId();
+
+ byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey();
+ DeterministicKey myMultiSigKeyPair = btcWalletService.getMultiSigKeyPair(id, sellerMultiSigPubKey);
+
+ checkArgument(Arrays.equals(sellerMultiSigPubKey,
+ btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()),
+ "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
+ byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey();
+
+ byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(delayedPayoutTx,
+ myMultiSigKeyPair,
+ buyerMultiSigPubKey,
+ sellerMultiSigPubKey);
+
+ processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java
similarity index 72%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsDepositTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java
index 28f3005163d..3f7cbdbef02 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesAndSignsDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerCreatesUnsignedDepositTx.java
@@ -42,9 +42,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class SellerAsMakerCreatesAndSignsDepositTx extends TradeTask {
+public class SellerAsMakerCreatesUnsignedDepositTx extends TradeTask {
@SuppressWarnings({"unused"})
- public SellerAsMakerCreatesAndSignsDepositTx(TaskRunner taskHandler, Trade trade) {
+ public SellerAsMakerCreatesUnsignedDepositTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -57,44 +57,39 @@ protected void run() {
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
TradingPeer tradingPeer = processModel.getTradingPeer();
- final Offer offer = trade.getOffer();
+ Offer offer = checkNotNull(trade.getOffer());
// params
- final boolean makerIsBuyer = false;
-
- final byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
log.debug("\n\n------------------------------------------------------------\n"
+ "Contract as json\n"
+ trade.getContractAsJson()
+ "\n------------------------------------------------------------\n");
- final Coin makerInputAmount = offer.getSellerSecurityDeposit().add(trade.getTradeAmount());
+ Coin makerInputAmount = offer.getSellerSecurityDeposit().add(trade.getTradeAmount());
Optional addressEntryOptional = walletService.getAddressEntry(id, AddressEntry.Context.MULTI_SIG);
checkArgument(addressEntryOptional.isPresent(), "addressEntryOptional must be present");
AddressEntry makerMultiSigAddressEntry = addressEntryOptional.get();
makerMultiSigAddressEntry.setCoinLockedInMultiSig(makerInputAmount);
walletService.saveAddressEntryList();
- final Coin msOutputAmount = makerInputAmount
+ Coin msOutputAmount = makerInputAmount
.add(trade.getTxFee())
.add(offer.getBuyerSecurityDeposit());
- final List takerRawTransactionInputs = tradingPeer.getRawTransactionInputs();
- final long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
- final String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
- final Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
- final Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
- final byte[] buyerPubKey = tradingPeer.getMultiSigPubKey();
- final byte[] sellerPubKey = processModel.getMyMultiSigPubKey();
+ List takerRawTransactionInputs = checkNotNull(tradingPeer.getRawTransactionInputs());
+ long takerChangeOutputValue = tradingPeer.getChangeOutputValue();
+ String takerChangeAddressString = tradingPeer.getChangeOutputAddress();
+ Address makerAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
+ Address makerChangeAddress = walletService.getFreshAddressEntry().getAddress();
+ byte[] buyerPubKey = tradingPeer.getMultiSigPubKey();
+ byte[] sellerPubKey = processModel.getMyMultiSigPubKey();
checkArgument(Arrays.equals(sellerPubKey,
makerMultiSigAddressEntry.getPubKey()),
"sellerPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
- final byte[] arbitratorBtcPubKey = trade.getArbitratorBtcPubKey();
-
- PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().makerCreatesAndSignsDepositTx(
- makerIsBuyer,
+ PreparedDepositTxAndMakerInputs result = processModel.getTradeWalletService().sellerAsMakerCreatesDepositTx(
contractHash,
makerInputAmount,
msOutputAmount,
@@ -104,8 +99,7 @@ protected void run() {
makerAddress,
makerChangeAddress,
buyerPubKey,
- sellerPubKey,
- arbitratorBtcPubKey);
+ sellerPubKey);
processModel.setPreparedDepositTx(result.depositTransaction);
processModel.setRawTransactionInputs(result.rawMakerInputs);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java
new file mode 100644
index 00000000000..ba490a92f3d
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerFinalizesDepositTx.java
@@ -0,0 +1,57 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerAsMakerFinalizesDepositTx extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerAsMakerFinalizesDepositTx(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+
+ byte[] takersRawPreparedDepositTx = checkNotNull(processModel.getTradingPeer().getPreparedDepositTx());
+ byte[] myRawPreparedDepositTx = checkNotNull(processModel.getPreparedDepositTx());
+ Transaction takersDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(takersRawPreparedDepositTx);
+ Transaction myDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(myRawPreparedDepositTx);
+ int numTakersInputs = checkNotNull(processModel.getTradingPeer().getRawTransactionInputs()).size();
+ processModel.getTradeWalletService().sellerAsMakerFinalizesDepositTx(myDepositTx, takersDepositTx, numTakersInputs);
+
+ trade.applyDepositTx(myDepositTx);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java
new file mode 100644
index 00000000000..b5962c989fa
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerProcessDepositTxMessage.java
@@ -0,0 +1,56 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.DepositTxMessage;
+import bisq.core.trade.protocol.tasks.TradeTask;
+import bisq.core.util.Validator;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class SellerAsMakerProcessDepositTxMessage extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public SellerAsMakerProcessDepositTxMessage(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ log.debug("current trade state " + trade.getState());
+ DepositTxMessage message = (DepositTxMessage) processModel.getTradeMessage();
+ Validator.checkTradeId(processModel.getOfferId(), message);
+ checkNotNull(message);
+ checkNotNull(message.getDepositTx());
+
+ processModel.getTradingPeer().setPreparedDepositTx(message.getDepositTx());
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java
new file mode 100644
index 00000000000..f1fb5162809
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_maker/SellerAsMakerSendsInputsForDepositTxResponse.java
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.seller_as_maker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.protocol.tasks.maker.MakerSendsInputsForDepositTxResponse;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.script.Script;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SellerAsMakerSendsInputsForDepositTxResponse extends MakerSendsInputsForDepositTxResponse {
+ @SuppressWarnings({"unused"})
+ public SellerAsMakerSendsInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected byte[] getPreparedDepositTx() {
+ Transaction preparedDepositTx = processModel.getBtcWalletService().getTxFromSerializedTx(processModel.getPreparedDepositTx());
+ preparedDepositTx.getInputs().forEach(input -> {
+ // Remove signature before sending to peer as we don't want to risk that buyer could publish deposit tx
+ // before we have received his signature for the delayed payout tx.
+ input.setScriptSig(new Script(new byte[]{}));
+ });
+ return preparedDepositTx.bitcoinSerialize();
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java
index 6298250566e..9b4b88a0ba4 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerCreatesDepositTxInputs.java
@@ -17,19 +17,18 @@
package bisq.core.trade.protocol.tasks.seller_as_taker;
-import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.InputsAndChangeOutput;
-import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
-import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
@Slf4j
public class SellerAsTakerCreatesDepositTxInputs extends TradeTask {
@SuppressWarnings({"unused"})
@@ -43,17 +42,14 @@ protected void run() {
runInterceptHook();
if (trade.getTradeAmount() != null) {
Coin txFee = trade.getTxFee();
- Coin takerInputAmount = trade.getOffer().getSellerSecurityDeposit()
- .add(txFee).add(txFee).add(trade.getTradeAmount());
-
- BtcWalletService walletService = processModel.getBtcWalletService();
- Address takersAddress = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(),
- AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
- InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(
+ Coin takerInputAmount = checkNotNull(trade.getOffer()).getSellerSecurityDeposit()
+ .add(txFee)
+ .add(txFee)
+ .add(trade.getTradeAmount());
+ InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositTxInputs(
processModel.getTakeOfferFeeTx(),
takerInputAmount,
- txFee,
- takersAddress);
+ txFee);
processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignAndPublishDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java
similarity index 56%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignAndPublishDepositTx.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java
index 9bce2944109..6a27e1221c8 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignAndPublishDepositTx.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller_as_taker/SellerAsTakerSignsDepositTx.java
@@ -17,11 +17,9 @@
package bisq.core.trade.protocol.tasks.seller_as_taker;
-import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.BtcWalletService;
-import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
@@ -43,9 +41,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class SellerAsTakerSignAndPublishDepositTx extends TradeTask {
+public class SellerAsTakerSignsDepositTx extends TradeTask {
@SuppressWarnings({"unused"})
- public SellerAsTakerSignAndPublishDepositTx(TaskRunner taskHandler, Trade trade) {
+ public SellerAsTakerSignsDepositTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -58,7 +56,7 @@ protected void run() {
+ trade.getContractAsJson()
+ "\n------------------------------------------------------------\n");
- byte[] contractHash = Hash.getSha256Hash(trade.getContractAsJson());
+ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson()));
trade.setContractHash(contractHash);
List sellerInputs = checkNotNull(processModel.getRawTransactionInputs(), "sellerInputs must not be null");
@@ -80,51 +78,20 @@ protected void run() {
TradingPeer tradingPeer = processModel.getTradingPeer();
- Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
+ Transaction depositTx = processModel.getTradeWalletService().takerSignsDepositTx(
true,
contractHash,
processModel.getPreparedDepositTx(),
- tradingPeer.getRawTransactionInputs(),
+ checkNotNull(tradingPeer.getRawTransactionInputs()),
sellerInputs,
tradingPeer.getMultiSigPubKey(),
- sellerMultiSigPubKey,
- trade.getArbitratorBtcPubKey(),
- new TxBroadcaster.Callback() {
- @Override
- public void onSuccess(Transaction transaction) {
- if (!completed) {
- // We set the depositTx before we change the state as the state change triggers code
- // which expected the tx to be available. That case will usually never happen as the
- // callback is called after the method call has returned but in some test scenarios
- // with regtest we run into such issues, thus fixing it to make it more stict seems
- // reasonable.
- trade.setDepositTx(transaction);
- log.trace("takerSignsAndPublishesDepositTx succeeded " + transaction);
- trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
- walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
-
- complete();
- } else {
- log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
- }
- }
-
- @Override
- public void onFailure(TxBroadcastException exception) {
- if (!completed) {
- failed(exception);
- } else {
- log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
- }
- }
- });
- if (trade.getDepositTx() == null) {
- // We set the deposit tx in case we get the onFailure called. We cannot set it in the onFailure
- // callback as the tx is returned by the method call where the callback is used as an argument.
- trade.setDepositTx(depositTx);
- }
+ sellerMultiSigPubKey);
+
+ trade.applyDepositTx(depositTx);
+
+ complete();
} catch (Throwable t) {
- final Contract contract = trade.getContract();
+ Contract contract = trade.getContract();
if (contract != null)
contract.printDiff(processModel.getTradingPeer().getContractAsJson());
failed(t);
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessPublishDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessPublishDepositTxRequest.java
deleted file mode 100644
index 0160397d04e..00000000000
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessPublishDepositTxRequest.java
+++ /dev/null
@@ -1,79 +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 .
- */
-
-package bisq.core.trade.protocol.tasks.taker;
-
-import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PublishDepositTxRequest;
-import bisq.core.trade.protocol.TradingPeer;
-import bisq.core.trade.protocol.tasks.TradeTask;
-
-import bisq.common.taskrunner.TaskRunner;
-
-import lombok.extern.slf4j.Slf4j;
-
-import static bisq.core.util.Validator.checkTradeId;
-import static bisq.core.util.Validator.nonEmptyStringOf;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-@Slf4j
-public class TakerProcessPublishDepositTxRequest extends TradeTask {
- @SuppressWarnings({"unused"})
- public TakerProcessPublishDepositTxRequest(TaskRunner taskHandler, Trade trade) {
- super(taskHandler, trade);
- }
-
- @Override
- protected void run() {
- try {
- runInterceptHook();
- log.debug("current trade state " + trade.getState());
- PublishDepositTxRequest publishDepositTxRequest = (PublishDepositTxRequest) processModel.getTradeMessage();
- checkTradeId(processModel.getOfferId(), publishDepositTxRequest);
- checkNotNull(publishDepositTxRequest);
-
- final TradingPeer tradingPeer = processModel.getTradingPeer();
- tradingPeer.setPaymentAccountPayload(checkNotNull(publishDepositTxRequest.getMakerPaymentAccountPayload()));
- tradingPeer.setAccountId(nonEmptyStringOf(publishDepositTxRequest.getMakerAccountId()));
- tradingPeer.setMultiSigPubKey(checkNotNull(publishDepositTxRequest.getMakerMultiSigPubKey()));
- tradingPeer.setContractAsJson(nonEmptyStringOf(publishDepositTxRequest.getMakerContractAsJson()));
- tradingPeer.setContractSignature(nonEmptyStringOf(publishDepositTxRequest.getMakerContractSignature()));
- tradingPeer.setPayoutAddressString(nonEmptyStringOf(publishDepositTxRequest.getMakerPayoutAddressString()));
- tradingPeer.setRawTransactionInputs(checkNotNull(publishDepositTxRequest.getMakerInputs()));
- final byte[] preparedDepositTx = publishDepositTxRequest.getPreparedDepositTx();
- processModel.setPreparedDepositTx(checkNotNull(preparedDepositTx));
-
- // Maker has to sign preparedDepositTx. He cannot manipulate the preparedDepositTx - so we avoid to have a
- // challenge protocol for passing the nonce we want to get signed.
- tradingPeer.setAccountAgeWitnessNonce(publishDepositTxRequest.getPreparedDepositTx());
- tradingPeer.setAccountAgeWitnessSignature(publishDepositTxRequest.getAccountAgeWitnessSignatureOfPreparedDepositTx());
-
- tradingPeer.setCurrentDate(publishDepositTxRequest.getCurrentDate());
-
- checkArgument(publishDepositTxRequest.getMakerInputs().size() > 0);
-
- // update to the latest peer address of our peer if the message is correct
- trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
- trade.setState(Trade.State.TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST);
-
- complete();
- } catch (Throwable t) {
- failed(t);
- }
- }
-}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java
new file mode 100644
index 00000000000..ba1b80b5308
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerProcessesInputsForDepositTxResponse.java
@@ -0,0 +1,84 @@
+/*
+ * 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 .
+ */
+
+package bisq.core.trade.protocol.tasks.taker;
+
+import bisq.core.trade.Trade;
+import bisq.core.trade.messages.InputsForDepositTxResponse;
+import bisq.core.trade.protocol.TradingPeer;
+import bisq.core.trade.protocol.tasks.TradeTask;
+
+import bisq.common.taskrunner.TaskRunner;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.util.Validator.checkTradeId;
+import static bisq.core.util.Validator.nonEmptyStringOf;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class TakerProcessesInputsForDepositTxResponse extends TradeTask {
+ @SuppressWarnings({"unused"})
+ public TakerProcessesInputsForDepositTxResponse(TaskRunner taskHandler, Trade trade) {
+ super(taskHandler, trade);
+ }
+
+ @Override
+ protected void run() {
+ try {
+ runInterceptHook();
+ log.debug("current trade state " + trade.getState());
+ InputsForDepositTxResponse inputsForDepositTxResponse = (InputsForDepositTxResponse) processModel.getTradeMessage();
+ checkTradeId(processModel.getOfferId(), inputsForDepositTxResponse);
+ checkNotNull(inputsForDepositTxResponse);
+
+ TradingPeer tradingPeer = processModel.getTradingPeer();
+ tradingPeer.setPaymentAccountPayload(checkNotNull(inputsForDepositTxResponse.getMakerPaymentAccountPayload()));
+ tradingPeer.setAccountId(nonEmptyStringOf(inputsForDepositTxResponse.getMakerAccountId()));
+ tradingPeer.setMultiSigPubKey(checkNotNull(inputsForDepositTxResponse.getMakerMultiSigPubKey()));
+ tradingPeer.setContractAsJson(nonEmptyStringOf(inputsForDepositTxResponse.getMakerContractAsJson()));
+ tradingPeer.setContractSignature(nonEmptyStringOf(inputsForDepositTxResponse.getMakerContractSignature()));
+ tradingPeer.setPayoutAddressString(nonEmptyStringOf(inputsForDepositTxResponse.getMakerPayoutAddressString()));
+ tradingPeer.setRawTransactionInputs(checkNotNull(inputsForDepositTxResponse.getMakerInputs()));
+ byte[] preparedDepositTx = inputsForDepositTxResponse.getPreparedDepositTx();
+ processModel.setPreparedDepositTx(checkNotNull(preparedDepositTx));
+ long lockTime = inputsForDepositTxResponse.getLockTime();
+ //todo for dev testing deactivated
+ //checkArgument(lockTime >= processModel.getBtcWalletService().getBestChainHeight() + 144 * 20);
+ trade.setLockTime(lockTime);
+ log.info("lockTime={}, delay={}", lockTime, (processModel.getBtcWalletService().getBestChainHeight() - lockTime));
+
+ // Maker has to sign preparedDepositTx. He cannot manipulate the preparedDepositTx - so we avoid to have a
+ // challenge protocol for passing the nonce we want to get signed.
+ tradingPeer.setAccountAgeWitnessNonce(inputsForDepositTxResponse.getPreparedDepositTx());
+ tradingPeer.setAccountAgeWitnessSignature(inputsForDepositTxResponse.getAccountAgeWitnessSignatureOfPreparedDepositTx());
+
+ tradingPeer.setCurrentDate(inputsForDepositTxResponse.getCurrentDate());
+
+ checkArgument(inputsForDepositTxResponse.getMakerInputs().size() > 0);
+
+ // update to the latest peer address of our peer if the message is correct
+ trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
+ trade.setState(Trade.State.TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST);
+
+ complete();
+ } catch (Throwable t) {
+ failed(t);
+ }
+ }
+}
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java
similarity index 87%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java
index 58f8591d47d..f9d0651383a 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendPayDepositRequest.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendInputsForDepositTxRequest.java
@@ -21,7 +21,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade;
-import bisq.core.trade.messages.PayDepositRequest;
+import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.user.User;
@@ -45,9 +45,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
-public class TakerSendPayDepositRequest extends TradeTask {
+public class TakerSendInputsForDepositTxRequest extends TradeTask {
@SuppressWarnings({"unused"})
- public TakerSendPayDepositRequest(TaskRunner taskHandler, Trade trade) {
+ public TakerSendInputsForDepositTxRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -61,8 +61,10 @@ protected void run() {
checkNotNull(user, "User must not be null");
final List acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses();
final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses();
- checkNotNull(acceptedArbitratorAddresses, "acceptedArbitratorAddresses must not be null");
+ final List acceptedRefundAgentAddresses = user.getAcceptedRefundAgentAddresses();
+ // We don't check for arbitrators as they should vanish soon
checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null");
+ // We also don't check for refund agents yet as we don't want to restict us too much. They are not mandatory.
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
@@ -85,7 +87,7 @@ protected void run() {
final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null");
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8));
- PayDepositRequest message = new PayDepositRequest(
+ InputsForDepositTxRequest message = new InputsForDepositTxRequest(
offerId,
processModel.getMyNodeAddress(),
trade.getTradeAmount().value,
@@ -102,10 +104,12 @@ protected void run() {
paymentAccountPayload,
processModel.getAccountId(),
trade.getTakerFeeTxId(),
- new ArrayList<>(acceptedArbitratorAddresses),
+ acceptedArbitratorAddresses == null ? new ArrayList<>() : new ArrayList<>(acceptedArbitratorAddresses),
new ArrayList<>(acceptedMediatorAddresses),
+ acceptedRefundAgentAddresses == null ? new ArrayList<>() : new ArrayList<>(acceptedRefundAgentAddresses),
trade.getArbitratorNodeAddress(),
trade.getMediatorNodeAddress(),
+ trade.getRefundAgentNodeAddress(),
UUID.randomUUID().toString(),
Version.getP2PMessageVersion(),
sig,
diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java
index 879a9ac6e9b..fb4ddc662ef 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerVerifyAndSignContract.java
@@ -56,8 +56,8 @@ protected void run() {
checkNotNull(trade.getTakerFeeTxId(), "TakeOfferFeeTxId must not be null");
TradingPeer maker = processModel.getTradingPeer();
- PaymentAccountPayload makerPaymentAccountPayload = maker.getPaymentAccountPayload();
- PaymentAccountPayload takerPaymentAccountPayload = processModel.getPaymentAccountPayload(trade);
+ PaymentAccountPayload makerPaymentAccountPayload = checkNotNull(maker.getPaymentAccountPayload());
+ PaymentAccountPayload takerPaymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade));
boolean isBuyerMakerAndSellerTaker = trade instanceof SellerAsTakerTrade;
NodeAddress buyerNodeAddress = isBuyerMakerAndSellerTaker ? processModel.getTempTradingPeerNodeAddress() : processModel.getMyNodeAddress();
@@ -97,7 +97,9 @@ protected void run() {
maker.getPayoutAddressString(),
takerPayoutAddressString,
maker.getMultiSigPubKey(),
- takerMultiSigPubKey
+ takerMultiSigPubKey,
+ trade.getLockTime(),
+ trade.getRefundAgentNodeAddress()
);
String contractAsJson = Utilities.objectToJson(contract);
log.trace("Contract as json:{}", contractAsJson);
diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
index 1210de64c86..2cd22ad92b4 100644
--- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
+++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java
@@ -63,6 +63,7 @@
public final class TradeStatistics2 implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope {
public static final String ARBITRATOR_ADDRESS = "arbAddr";
public static final String MEDIATOR_ADDRESS = "medAddr";
+ public static final String REFUND_AGENT_ADDRESS = "refAddr";
private final OfferPayload.Direction direction;
private final String baseCurrency;
diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java
index 4d575029d24..6109da9d71e 100644
--- a/core/src/main/java/bisq/core/user/Preferences.java
+++ b/core/src/main/java/bisq/core/user/Preferences.java
@@ -394,6 +394,11 @@ public void setTacAccepted(boolean tacAccepted) {
persist();
}
+ public void setTacAcceptedV120(boolean tacAccepted) {
+ prefPayload.setTacAcceptedV120(tacAccepted);
+ persist();
+ }
+
private void persist() {
if (initialReadDone)
storage.queueUpForSave(prefPayload);
@@ -951,5 +956,7 @@ private interface ExcludesDelegateMethods {
String getRpcPw();
int getBlockNotifyPort();
+
+ void setTacAcceptedV120(boolean tacAccepted);
}
}
diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java
index 998378b61da..1cddc321787 100644
--- a/core/src/main/java/bisq/core/user/PreferencesPayload.java
+++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java
@@ -125,6 +125,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private int ignoreDustThreshold = 600;
private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(new CryptoCurrencyAccount());
private int blockNotifyPort;
+ private boolean tacAcceptedV120;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -185,7 +186,8 @@ public Message toProtoMessage() {
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
.setIgnoreDustThreshold(ignoreDustThreshold)
.setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto)
- .setBlockNotifyPort(blockNotifyPort);
+ .setBlockNotifyPort(blockNotifyPort)
+ .setTacAcceptedV120(tacAcceptedV120);
Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode);
@@ -271,7 +273,8 @@ public static PersistableEnvelope fromProto(protobuf.PreferencesPayload proto, C
proto.getBuyerSecurityDepositAsPercent(),
proto.getIgnoreDustThreshold(),
proto.getBuyerSecurityDepositAsPercentForCrypto(),
- proto.getBlockNotifyPort());
+ proto.getBlockNotifyPort(),
+ proto.getTacAcceptedV120());
}
}
diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java
index d197a802c98..cc5a75dc707 100644
--- a/core/src/main/java/bisq/core/user/User.java
+++ b/core/src/main/java/bisq/core/user/User.java
@@ -26,6 +26,7 @@
import bisq.core.payment.PaymentAccount;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.mediator.Mediator;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.network.p2p.NodeAddress;
@@ -44,7 +45,6 @@
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -126,13 +126,6 @@ public void persist() {
// API
///////////////////////////////////////////////////////////////////////////////////////////
- /* public Optional getPaymentAccountForCurrency(TradeCurrency tradeCurrency) {
- return getPaymentAccounts().stream()
- .flatMap(e -> e.getTradeCurrencies().stream())
- .filter(e -> e.equals(tradeCurrency))
- .findFirst();
- }*/
-
@Nullable
public Arbitrator getAcceptedArbitratorByAddress(NodeAddress nodeAddress) {
final List acceptedArbitrators = userPayload.getAcceptedArbitrators();
@@ -159,6 +152,19 @@ public Mediator getAcceptedMediatorByAddress(NodeAddress nodeAddress) {
}
}
+ @Nullable
+ public RefundAgent getAcceptedRefundAgentByAddress(NodeAddress nodeAddress) {
+ final List acceptedRefundAgents = userPayload.getAcceptedRefundAgents();
+ if (acceptedRefundAgents != null) {
+ Optional refundAgentOptional = acceptedRefundAgents.stream()
+ .filter(e -> e.getNodeAddress().equals(nodeAddress))
+ .findFirst();
+ return refundAgentOptional.orElse(null);
+ } else {
+ return null;
+ }
+ }
+
@Nullable
public PaymentAccount findFirstPaymentAccountWithCurrency(TradeCurrency tradeCurrency) {
if (userPayload.getPaymentAccounts() != null) {
@@ -174,21 +180,6 @@ public PaymentAccount findFirstPaymentAccountWithCurrency(TradeCurrency tradeCur
}
}
- public boolean hasMatchingLanguage(Arbitrator arbitrator) {
- final List codes = userPayload.getAcceptedLanguageLocaleCodes();
- if (arbitrator != null && codes != null) {
- for (String acceptedCode : codes) {
- for (String itemCode : arbitrator.getLanguageCodes()) {
- if (acceptedCode.equals(itemCode))
- return true;
- }
- }
- return false;
- } else {
- return false;
- }
- }
-
public boolean hasPaymentAccountForCurrency(TradeCurrency tradeCurrency) {
return findFirstPaymentAccountWithCurrency(tradeCurrency) != null;
}
@@ -222,10 +213,10 @@ public void removePaymentAccount(PaymentAccount paymentAccount) {
persist();
}
- public boolean addAcceptedLanguageLocale(String localeCode) {
- final List codes = userPayload.getAcceptedLanguageLocaleCodes();
- if (codes != null && !codes.contains(localeCode)) {
- boolean changed = codes.add(localeCode);
+ public boolean addAcceptedArbitrator(Arbitrator arbitrator) {
+ final List arbitrators = userPayload.getAcceptedArbitrators();
+ if (arbitrators != null && !arbitrators.contains(arbitrator) && !isMyOwnRegisteredArbitrator(arbitrator)) {
+ boolean changed = arbitrators.add(arbitrator);
if (changed)
persist();
return changed;
@@ -234,23 +225,18 @@ public boolean addAcceptedLanguageLocale(String localeCode) {
}
}
- public boolean removeAcceptedLanguageLocale(String languageLocaleCode) {
- boolean changed = userPayload.getAcceptedLanguageLocaleCodes() != null &&
- userPayload.getAcceptedLanguageLocaleCodes().remove(languageLocaleCode);
- if (changed)
- persist();
- return changed;
- }
-
- public boolean addAcceptedArbitrator(Arbitrator arbitrator) {
- final List arbitrators = userPayload.getAcceptedArbitrators();
- if (arbitrators != null && !arbitrators.contains(arbitrator) && !isMyOwnRegisteredArbitrator(arbitrator)) {
- boolean changed = arbitrators.add(arbitrator);
+ public void removeAcceptedArbitrator(Arbitrator arbitrator) {
+ if (userPayload.getAcceptedArbitrators() != null) {
+ boolean changed = userPayload.getAcceptedArbitrators().remove(arbitrator);
if (changed)
persist();
- return changed;
- } else {
- return false;
+ }
+ }
+
+ public void clearAcceptedArbitrators() {
+ if (userPayload.getAcceptedArbitrators() != null) {
+ userPayload.getAcceptedArbitrators().clear();
+ persist();
}
}
@@ -266,33 +252,44 @@ public boolean addAcceptedMediator(Mediator mediator) {
}
}
-
- public void removeAcceptedArbitrator(Arbitrator arbitrator) {
- if (userPayload.getAcceptedArbitrators() != null) {
- boolean changed = userPayload.getAcceptedArbitrators().remove(arbitrator);
+ public void removeAcceptedMediator(Mediator mediator) {
+ if (userPayload.getAcceptedMediators() != null) {
+ boolean changed = userPayload.getAcceptedMediators().remove(mediator);
if (changed)
persist();
}
}
- public void clearAcceptedArbitrators() {
- if (userPayload.getAcceptedArbitrators() != null) {
- userPayload.getAcceptedArbitrators().clear();
+ public void clearAcceptedMediators() {
+ if (userPayload.getAcceptedMediators() != null) {
+ userPayload.getAcceptedMediators().clear();
persist();
}
}
- public void removeAcceptedMediator(Mediator mediator) {
- if (userPayload.getAcceptedMediators() != null) {
- boolean changed = userPayload.getAcceptedMediators().remove(mediator);
+ public boolean addAcceptedRefundAgent(RefundAgent refundAgent) {
+ final List refundAgents = userPayload.getAcceptedRefundAgents();
+ if (refundAgents != null && !refundAgents.contains(refundAgent) && !isMyOwnRegisteredRefundAgent(refundAgent)) {
+ boolean changed = refundAgents.add(refundAgent);
if (changed)
persist();
+ return changed;
+ } else {
+ return false;
}
}
- public void clearAcceptedMediators() {
- if (userPayload.getAcceptedMediators() != null) {
- userPayload.getAcceptedMediators().clear();
+ public void removeAcceptedRefundAgent(RefundAgent refundAgent) {
+ if (userPayload.getAcceptedRefundAgents() != null) {
+ boolean changed = userPayload.getAcceptedRefundAgents().remove(refundAgent);
+ if (changed)
+ persist();
+ }
+ }
+
+ public void clearAcceptedRefundAgents() {
+ if (userPayload.getAcceptedRefundAgents() != null) {
+ userPayload.getAcceptedRefundAgents().clear();
persist();
}
}
@@ -317,6 +314,11 @@ public void setRegisteredMediator(@Nullable Mediator mediator) {
persist();
}
+ public void setRegisteredRefundAgent(@Nullable RefundAgent refundAgent) {
+ userPayload.setRegisteredRefundAgent(refundAgent);
+ persist();
+ }
+
public void setDevelopersFilter(@Nullable Filter developersFilter) {
userPayload.setDevelopersFilter(developersFilter);
persist();
@@ -395,20 +397,41 @@ public Mediator getRegisteredMediator() {
return userPayload.getRegisteredMediator();
}
+ @Nullable
+ public RefundAgent getRegisteredRefundAgent() {
+ return userPayload.getRegisteredRefundAgent();
+ }
+
+
//TODO
@Nullable
public List getAcceptedArbitrators() {
return userPayload.getAcceptedArbitrators();
}
+ @Nullable
+ public List getAcceptedMediators() {
+ return userPayload.getAcceptedMediators();
+ }
+
+ @Nullable
+ public List getAcceptedRefundAgents() {
+ return userPayload.getAcceptedRefundAgents();
+ }
+
@Nullable
public List getAcceptedArbitratorAddresses() {
return userPayload.getAcceptedArbitrators() != null ? userPayload.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList()) : null;
}
@Nullable
- public List getAcceptedMediators() {
- return userPayload.getAcceptedMediators();
+ public List getAcceptedMediatorAddresses() {
+ return userPayload.getAcceptedMediators() != null ? userPayload.getAcceptedMediators().stream().map(Mediator::getNodeAddress).collect(Collectors.toList()) : null;
+ }
+
+ @Nullable
+ public List getAcceptedRefundAgentAddresses() {
+ return userPayload.getAcceptedRefundAgents() != null ? userPayload.getAcceptedRefundAgents().stream().map(RefundAgent::getNodeAddress).collect(Collectors.toList()) : null;
}
public boolean hasAcceptedArbitrators() {
@@ -419,13 +442,8 @@ public boolean hasAcceptedMediators() {
return getAcceptedMediators() != null && !getAcceptedMediators().isEmpty();
}
- @Nullable
- public List getAcceptedMediatorAddresses() {
- return userPayload.getAcceptedMediators() != null ? userPayload.getAcceptedMediators().stream().map(Mediator::getNodeAddress).collect(Collectors.toList()) : null;
- }
-
- public List getAcceptedLanguageLocaleCodes() {
- return userPayload.getAcceptedLanguageLocaleCodes() != null ? userPayload.getAcceptedLanguageLocaleCodes() : new ArrayList<>();
+ public boolean hasAcceptedRefundAgents() {
+ return getAcceptedRefundAgents() != null && !getAcceptedRefundAgents().isEmpty();
}
@Nullable
@@ -451,6 +469,10 @@ public boolean isMyOwnRegisteredMediator(Mediator mediator) {
return mediator.equals(userPayload.getRegisteredMediator());
}
+ public boolean isMyOwnRegisteredRefundAgent(RefundAgent refundAgent) {
+ return refundAgent.equals(userPayload.getRegisteredRefundAgent());
+ }
+
public List getMarketAlertFilters() {
return userPayload.getMarketAlertFilters();
}
diff --git a/core/src/main/java/bisq/core/user/UserPayload.java b/core/src/main/java/bisq/core/user/UserPayload.java
index 878a711df90..1edd8f37da3 100644
--- a/core/src/main/java/bisq/core/user/UserPayload.java
+++ b/core/src/main/java/bisq/core/user/UserPayload.java
@@ -25,6 +25,7 @@
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.mediation.mediator.Mediator;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.common.proto.ProtoUtil;
import bisq.common.proto.persistable.PersistableEnvelope;
@@ -73,6 +74,12 @@ public class UserPayload implements PersistableEnvelope {
@Nullable
private List marketAlertFilters = new ArrayList<>();
+ // Added v1.2.0
+ @Nullable
+ private RefundAgent registeredRefundAgent;
+ @Nullable
+ private List acceptedRefundAgents = new ArrayList<>();
+
public UserPayload() {
}
@@ -105,6 +112,12 @@ public protobuf.PersistableEnvelope toProtoMessage() {
Optional.ofNullable(priceAlertFilter).ifPresent(priceAlertFilter -> builder.setPriceAlertFilter(priceAlertFilter.toProtoMessage()));
Optional.ofNullable(marketAlertFilters)
.ifPresent(e -> builder.addAllMarketAlertFilters(ProtoUtil.collectionToProto(marketAlertFilters)));
+
+ Optional.ofNullable(registeredRefundAgent)
+ .ifPresent(registeredRefundAgent -> builder.setRegisteredRefundAgent(registeredRefundAgent.toProtoMessage().getRefundAgent()));
+ Optional.ofNullable(acceptedRefundAgents)
+ .ifPresent(e -> builder.addAllAcceptedRefundAgents(ProtoUtil.collectionToProto(acceptedRefundAgents,
+ message -> ((protobuf.StoragePayload) message).getRefundAgent())));
return protobuf.PersistableEnvelope.newBuilder().setUserPayload(builder).build();
}
@@ -130,6 +143,11 @@ public static UserPayload fromProto(protobuf.UserPayload proto, CoreProtoResolve
PriceAlertFilter.fromProto(proto.getPriceAlertFilter()),
proto.getMarketAlertFiltersList().isEmpty() ? new ArrayList<>() : new ArrayList<>(proto.getMarketAlertFiltersList().stream()
.map(e -> MarketAlertFilter.fromProto(e, coreProtoResolver))
- .collect(Collectors.toSet())));
+ .collect(Collectors.toSet())),
+ proto.hasRegisteredRefundAgent() ? RefundAgent.fromProto(proto.getRegisteredRefundAgent()) : null,
+ proto.getAcceptedRefundAgentsList().isEmpty() ? new ArrayList<>() : new ArrayList<>(proto.getAcceptedRefundAgentsList().stream()
+ .map(RefundAgent::fromProto)
+ .collect(Collectors.toList()))
+ );
}
}
diff --git a/core/src/main/java/bisq/core/util/BSFormatter.java b/core/src/main/java/bisq/core/util/BSFormatter.java
index 98ed6f0d5ab..00eb5e23a56 100644
--- a/core/src/main/java/bisq/core/util/BSFormatter.java
+++ b/core/src/main/java/bisq/core/util/BSFormatter.java
@@ -43,6 +43,7 @@
import java.text.DateFormat;
import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
import java.math.BigDecimal;
@@ -356,6 +357,13 @@ public static String formatDateTime(Date date, DateFormat dateFormatter, DateFor
}
}
+ public static String getDateFromBlockHeight(long blockHeight) {
+ long now = new Date().getTime();
+ SimpleDateFormat dateFormatter = new SimpleDateFormat("dd MMM", Locale.getDefault());
+ SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm", Locale.getDefault());
+ return BSFormatter.formatDateTime(new Date(now + blockHeight * 10 * 60 * 1000L), dateFormatter, timeFormatter);
+ }
+
public static String formatToPercentWithSymbol(double value) {
return formatToPercent(value) + "%";
}
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index 4fe68119f48..58616494141 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -210,6 +210,7 @@ shared.selectedArbitrator=Selected arbitrator
shared.selectedMediator=Selected mediator
shared.mediator=Mediator
shared.arbitrator2=Arbitrator
+shared.refundAgent=Refund agent
####################################################################
# UI views
@@ -754,6 +755,8 @@ portfolio.pending.openSupportTicket.msg=Please use this function only in emergen
\"Open support\" or \"Open dispute\" button.\n\nWhen you open a support ticket the trade will be interrupted and \
handled by a mediator or arbitrator.
+portfolio.pending.timeLockNotOver=You have to wait until ≈{0} ({1} more blocks) before you can open an arbitration dispute.
+
portfolio.pending.notification=Notification
portfolio.pending.support.headline.getHelp=Need help?
@@ -767,15 +770,12 @@ portfolio.pending.support.button.getHelp=Get support
portfolio.pending.support.popup.info=If your issue with the trade remains unsolved, you can open a support \
ticket to request help from a mediator. If you have not received the payment, please wait until the trade period is over.\n\n\
Are you sure you want to open a support ticket?
-portfolio.pending.support.popup.info.arbitrator=If your issue with the trade remains unsolved, you can open a support \
- ticket to request help from an arbitrator. If you have not received the payment, please wait until the trade period is over.\n\n\
- Are you sure you want to open a support ticket?
portfolio.pending.support.popup.button=Open support ticket
portfolio.pending.support.headline.halfPeriodOver=Check payment
portfolio.pending.support.headline.periodOver=Trade period is over
-portfolio.pending.arbitrationRequested=Arbitration requested
portfolio.pending.mediationRequested=Mediation requested
+portfolio.pending.refundRequested=Refund requested
portfolio.pending.openSupport=Open support ticket
portfolio.pending.supportTicketOpened=Support ticket opened
portfolio.pending.requestSupport=Request support
@@ -806,10 +806,14 @@ portfolio.pending.mediationResult.popup.info=The mediator has suggested the foll
You can accept or reject this suggested payout.\n\n\
By accepting it, you sign the proposed payout transaction. \
If your trade peer also accepts and signs, the payout will be completed, and the trade is closed.\n\n\
- If one or both parties reject the suggestion, a dispute with an arbitrator will be opened. \
- The arbitrator will investigate the case again and do a payout based on their findings.\n\n\
- Please note that arbitrators are not always online and may take longer to respond than mediators. \
- It can take up to 5 business days for them to respond to messages.
+ If one or both parties reject the suggestion, they have to wait until {2} (block {3}) and can afterwards open a \
+ second dispute round with an arbitrator who will investigate the case again and do a payout based on their findings.\n\n\
+ If the arbitrator comes to the same conclusion for the payout as the mediator the trader who opened the arbitration \
+ request will lose part of their payout to cover the costs for the arbitrator's effort. Finding a consensus with the \
+ other trader about the mediator's suggestion is the preferred model and requesting arbitration should be only used if \
+ the other peer is not reacting or if a trader is convinced that the mediator did not made a fair payout suggestion.\n\n\
+ Please read about the details about the new arbitration model at:\n\
+ https://docs.bisq.network/trading-rules.html#arbitration
portfolio.pending.mediationResult.popup.openArbitration=Reject and request arbitration
portfolio.closed.completed=Completed
@@ -878,6 +882,8 @@ funds.tx.multiSigDeposit=Multisig deposit: {0}
funds.tx.multiSigPayout=Multisig payout: {0}
funds.tx.disputePayout=Dispute payout: {0}
funds.tx.disputeLost=Lost dispute case: {0}
+funds.tx.collateralForRefund=Collateral for refund: {0}
+funds.tx.refund=Refund from arbitration: {0}
funds.tx.unknown=Unknown reason: {0}
funds.tx.noFundsFromDispute=No refund from dispute
funds.tx.receivedFunds=Received funds
@@ -905,6 +911,7 @@ funds.tx.dustAttackTx.popup=This transaction is sending a very small BTC amount
support.tab.mediation.support=Mediation
support.tab.arbitration.support=Arbitration
+support.tab.refund.support=Refund
support.tab.ArbitratorsSupportTickets={0}'s tickets
support.filter=Filter list
support.filter.prompt=Enter trade ID, date, onion address or account data
@@ -973,6 +980,8 @@ support.youOpenedTicket=You opened a request for support.\n\n{0}\n\nBisq version
support.youOpenedDispute=You opened a request for a dispute.\n\n{0}\n\nBisq version: {1}
support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}
support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}
+support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0}
+support.mediatorsAddress=Mediator''s node address: {0}
####################################################################
@@ -1104,6 +1113,7 @@ setting.about.subsystems.val=Network version: {0}; P2P message version: {1}; Loc
account.tab.arbitratorRegistration=Arbitrator registration
account.tab.mediatorRegistration=Mediator registration
+account.tab.refundAgentRegistration=Refund agent registration
account.tab.account=Account
account.info.headline=Welcome to your Bisq Account
account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\n\
@@ -2206,6 +2216,14 @@ Summary notes:\n{3}
disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\n\
Open ongoing trade and accept or reject the suggested mediation
disputeSummaryWindow.close.closePeer=You need to close also the trading peers ticket!
+disputeSummaryWindow.close.txDetails.headline=Publish refund transaction
+disputeSummaryWindow.close.txDetails.buyer=Buyer receives {0} on address: {1}\n
+disputeSummaryWindow.close.txDetails.seller=Seller receives {0} on address: {1}\n
+disputeSummaryWindow.close.txDetails=Spending: {0}\n\
+ {1}{2}\
+ Transaction fee: {3} ({4} satoshis/byte)\n\
+ Transaction size: {5} Kb\n\n\
+ Are you sure you want to publish that transaction?
emptyWalletWindow.headline={0} emergency wallet tool
emptyWalletWindow.info=Please use that only in emergency case if you cannot access your fund from the UI.\n\n\
@@ -2232,6 +2250,7 @@ filterWindow.bannedCurrencies=Filtered currency codes (comma sep.)
filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.)
filterWindow.arbitrators=Filtered arbitrators (comma sep. onion addresses)
filterWindow.mediators=Filtered mediators (comma sep. onion addresses)
+filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses)
filterWindow.seedNode=Filtered seed nodes (comma sep. onion addresses)
filterWindow.priceRelayNode=Filtered price relay nodes (comma sep. onion addresses)
filterWindow.btcNode=Filtered Bitcoin nodes (comma sep. addresses + port)
diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java
index 9fcca9a4def..5fead0386be 100644
--- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java
+++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java
@@ -40,7 +40,7 @@ public void testStartEditOfferForActiveOffer() {
final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService,
null, null, null, offerBookService,
null, null, null,
- null, null, null,
+ null, null, null, null,
new Storage>(null, null, corruptedDatabaseFilesHandler));
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
@@ -76,7 +76,7 @@ public void testStartEditOfferForDeactivatedOffer() {
final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService,
null, null, null, offerBookService,
null, null, null,
- null, null, null,
+ null, null, null, null,
new Storage>(null, null, corruptedDatabaseFilesHandler));
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
@@ -104,7 +104,7 @@ public void testStartEditOfferForOfferThatIsCurrentlyEdited() {
final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService,
null, null, null, offerBookService,
null, null, null,
- null, null, null,
+ null, null, null, null,
new Storage>(null, null, corruptedDatabaseFilesHandler));
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java
index 9062f5209cf..96616e5afdf 100644
--- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java
+++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java
@@ -57,6 +57,7 @@ public void testRoundtripFull() {
"string",
new byte[]{10, 0, 0},
null,
+ Lists.newArrayList(),
Lists.newArrayList()));
vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock());
vo.setRegisteredMediator(MediatorTest.getMediatorMock());
diff --git a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java
index 0c9dc8c8e22..e13272a1d1c 100644
--- a/desktop/src/main/java/bisq/desktop/main/account/AccountView.java
+++ b/desktop/src/main/java/bisq/desktop/main/account/AccountView.java
@@ -32,6 +32,7 @@
import bisq.desktop.main.account.content.seedwords.SeedWordsView;
import bisq.desktop.main.account.register.arbitrator.ArbitratorRegistrationView;
import bisq.desktop.main.account.register.mediator.MediatorRegistrationView;
+import bisq.desktop.main.account.register.refundagent.RefundAgentRegistrationView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.core.locale.Res;
@@ -73,8 +74,10 @@ public class AccountView extends ActivatableView {
private Tab selectedTab;
private Tab arbitratorRegistrationTab;
private Tab mediatorRegistrationTab;
+ private Tab refundAgentRegistrationTab;
private ArbitratorRegistrationView arbitratorRegistrationView;
private MediatorRegistrationView mediatorRegistrationView;
+ private RefundAgentRegistrationView refundAgentRegistrationView;
private Scene scene;
private EventHandler keyEventEventHandler;
private ListChangeListener tabListChangeListener;
@@ -103,6 +106,8 @@ public void initialize() {
navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
} else if (mediatorRegistrationTab == null && viewPath.get(2).equals(MediatorRegistrationView.class)) {
navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
+ } else if (refundAgentRegistrationTab == null && viewPath.get(2).equals(RefundAgentRegistrationView.class)) {
+ navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
} else {
loadView(viewPath.tip());
}
@@ -116,6 +121,9 @@ public void initialize() {
if (mediatorRegistrationTab != null) {
root.getTabs().remove(mediatorRegistrationTab);
}
+ if (refundAgentRegistrationTab != null) {
+ root.getTabs().remove(refundAgentRegistrationTab);
+ }
arbitratorRegistrationTab = new Tab(Res.get("account.tab.arbitratorRegistration").toUpperCase());
arbitratorRegistrationTab.setClosable(true);
root.getTabs().add(arbitratorRegistrationTab);
@@ -124,10 +132,24 @@ public void initialize() {
if (arbitratorRegistrationTab != null) {
root.getTabs().remove(arbitratorRegistrationTab);
}
+ if (refundAgentRegistrationTab != null) {
+ root.getTabs().remove(refundAgentRegistrationTab);
+ }
mediatorRegistrationTab = new Tab(Res.get("account.tab.mediatorRegistration").toUpperCase());
mediatorRegistrationTab.setClosable(true);
root.getTabs().add(mediatorRegistrationTab);
navigation.navigateTo(MainView.class, AccountView.class, MediatorRegistrationView.class);
+ } else if (Utilities.isAltOrCtrlPressed(KeyCode.N, event) && refundAgentRegistrationTab == null) {
+ if (arbitratorRegistrationTab != null) {
+ root.getTabs().remove(arbitratorRegistrationTab);
+ }
+ if (mediatorRegistrationTab != null) {
+ root.getTabs().remove(mediatorRegistrationTab);
+ }
+ refundAgentRegistrationTab = new Tab(Res.get("account.tab.refundAgentRegistration").toUpperCase());
+ refundAgentRegistrationTab.setClosable(true);
+ root.getTabs().add(refundAgentRegistrationTab);
+ navigation.navigateTo(MainView.class, AccountView.class, RefundAgentRegistrationView.class);
}
};
@@ -136,6 +158,8 @@ public void initialize() {
navigation.navigateTo(MainView.class, AccountView.class, ArbitratorRegistrationView.class);
} else if (mediatorRegistrationTab != null && selectedTab != mediatorRegistrationTab) {
navigation.navigateTo(MainView.class, AccountView.class, MediatorRegistrationView.class);
+ } else if (refundAgentRegistrationTab != null && selectedTab != refundAgentRegistrationTab) {
+ navigation.navigateTo(MainView.class, AccountView.class, RefundAgentRegistrationView.class);
} else if (newValue == fiatAccountsTab && selectedTab != fiatAccountsTab) {
navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
} else if (newValue == altcoinAccountsTab && selectedTab != altcoinAccountsTab) {
@@ -159,6 +183,9 @@ public void initialize() {
if (removedTabs.size() == 1 && removedTabs.get(0).equals(mediatorRegistrationTab))
onMediatorRegistrationTabRemoved();
+
+ if (removedTabs.size() == 1 && removedTabs.get(0).equals(refundAgentRegistrationTab))
+ onRefundAgentRegistrationTabRemoved();
};
}
@@ -172,6 +199,11 @@ private void onMediatorRegistrationTabRemoved() {
navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
}
+ private void onRefundAgentRegistrationTabRemoved() {
+ refundAgentRegistrationTab = null;
+ navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
+ }
+
@Override
protected void activate() {
navigation.addListener(navigationListener);
@@ -188,6 +220,8 @@ protected void activate() {
navigation.navigateTo(MainView.class, AccountView.class, ArbitratorRegistrationView.class);
else if (mediatorRegistrationTab != null)
navigation.navigateTo(MainView.class, AccountView.class, MediatorRegistrationView.class);
+ else if (refundAgentRegistrationTab != null)
+ navigation.navigateTo(MainView.class, AccountView.class, RefundAgentRegistrationView.class);
else if (root.getSelectionModel().getSelectedItem() == fiatAccountsTab)
navigation.navigateTo(MainView.class, AccountView.class, FiatAccountsView.class);
else if (root.getSelectionModel().getSelectedItem() == altcoinAccountsTab)
@@ -240,6 +274,12 @@ private void loadView(Class extends View> viewClass) {
mediatorRegistrationView = (MediatorRegistrationView) view;
mediatorRegistrationView.onTabSelection(true);
}
+ } else if (view instanceof RefundAgentRegistrationView) {
+ if (refundAgentRegistrationTab != null) {
+ selectedTab = refundAgentRegistrationTab;
+ refundAgentRegistrationView = (RefundAgentRegistrationView) view;
+ refundAgentRegistrationView.onTabSelection(true);
+ }
} else if (view instanceof FiatAccountsView) {
selectedTab = fiatAccountsTab;
} else if (view instanceof AltCoinAccountsView) {
diff --git a/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationView.fxml b/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationView.fxml
new file mode 100644
index 00000000000..3ca8ce3c3b2
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationView.fxml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationView.java b/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationView.java
new file mode 100644
index 00000000000..af42e6599d0
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationView.java
@@ -0,0 +1,45 @@
+/*
+ * 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 .
+ */
+
+package bisq.desktop.main.account.register.refundagent;
+
+
+import bisq.desktop.common.view.FxmlView;
+import bisq.desktop.main.account.register.AgentRegistrationView;
+
+import bisq.core.app.AppOptionKeys;
+import bisq.core.locale.Res;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
+
+import com.google.inject.name.Named;
+
+import javax.inject.Inject;
+
+@FxmlView
+public class RefundAgentRegistrationView extends AgentRegistrationView {
+
+ @Inject
+ public RefundAgentRegistrationView(RefundAgentRegistrationViewModel model,
+ @Named(AppOptionKeys.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
+ super(model, useDevPrivilegeKeys);
+ }
+
+ @Override
+ protected String getRole() {
+ return Res.get("shared.refundAgent");
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationViewModel.java b/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationViewModel.java
new file mode 100644
index 00000000000..f4b60f98bbe
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/account/register/refundagent/RefundAgentRegistrationViewModel.java
@@ -0,0 +1,68 @@
+/*
+ * 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 .
+ */
+
+package bisq.desktop.main.account.register.refundagent;
+
+
+import bisq.desktop.main.account.register.AgentRegistrationViewModel;
+
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.support.dispute.refund.refundagent.RefundAgent;
+import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
+import bisq.core.user.User;
+
+import bisq.network.p2p.P2PService;
+
+import bisq.common.crypto.KeyRing;
+
+import com.google.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+public class RefundAgentRegistrationViewModel extends AgentRegistrationViewModel {
+
+ @Inject
+ public RefundAgentRegistrationViewModel(RefundAgentManager arbitratorManager,
+ User user,
+ P2PService p2PService,
+ BtcWalletService walletService,
+ KeyRing keyRing) {
+ super(arbitratorManager, user, p2PService, walletService, keyRing);
+ }
+
+ @Override
+ protected RefundAgent getDisputeAgent(String registrationSignature,
+ String emailAddress) {
+ return new RefundAgent(
+ p2PService.getAddress(),
+ keyRing.getPubKeyRing(),
+ new ArrayList<>(languageCodes),
+ new Date().getTime(),
+ registrationKey.getPubKey(),
+ registrationSignature,
+ emailAddress,
+ null,
+ null
+ );
+ }
+
+ @Override
+ protected RefundAgent getRegisteredDisputeAgentFromUser() {
+ return user.getRegisteredRefundAgent();
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java
index 7bd2b24707c..7f9008b256f 100644
--- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java
+++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java
@@ -29,29 +29,27 @@
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendCounterCurrencyTransferStartedMessage;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.buyer.BuyerSetupPayoutTxListener;
+import bisq.core.trade.protocol.tasks.buyer.BuyerSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerCreatesAndSignsDepositTx;
-import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSignPayoutTx;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
-import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignAndPublishDepositTx;
+import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerCreateAndSignContract;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
-import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
+import bisq.core.trade.protocol.tasks.maker.MakerProcessesInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
+import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
-import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesAndSignsDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesUnsignedDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
-import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignAndPublishDepositTx;
+import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.CreateTakerFeeTx;
-import bisq.core.trade.protocol.tasks.taker.TakerProcessPublishDepositTxRequest;
-import bisq.core.trade.protocol.tasks.taker.TakerSendDepositTxPublishedMessage;
-import bisq.core.trade.protocol.tasks.taker.TakerSendPayDepositRequest;
+import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
+import bisq.core.trade.protocol.tasks.taker.TakerSendInputsForDepositTxRequest;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyAndSignContract;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerAccount;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
@@ -76,6 +74,7 @@
import static bisq.desktop.util.FormBuilder.addTopLabelComboBox;
+// Not maintained anymore with new trade protocol, but leave it...If used needs to be adopted to current protocol.
@FxmlView
public class DebugView extends InitializableView {
@@ -105,16 +104,14 @@ public void initialize() {
addGroup("BuyerAsMakerProtocol",
FXCollections.observableArrayList(Arrays.asList(
- MakerProcessPayDepositRequest.class,
+ MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
MakerCreateAndSignContract.class,
BuyerAsMakerCreatesAndSignsDepositTx.class,
- MakerSetupDepositTxListener.class,
- MakerSendPublishDepositTxRequest.class,
+ BuyerSetupDepositTxListener.class,
- MakerProcessDepositTxPublishedMessage.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
PublishTradeStatistics.class,
@@ -122,7 +119,7 @@ public void initialize() {
ApplyFilter.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
- BuyerAsMakerSignPayoutTx.class,
+ BuyerSignPayoutTx.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
BuyerSetupPayoutTxListener.class)
));
@@ -132,15 +129,15 @@ public void initialize() {
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
SellerAsTakerCreatesDepositTxInputs.class,
- TakerSendPayDepositRequest.class,
+ TakerSendInputsForDepositTxRequest.class,
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
- SellerAsTakerSignAndPublishDepositTx.class,
- TakerSendDepositTxPublishedMessage.class,
+ SellerAsTakerSignsDepositTx.class,
+ SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerProcessCounterCurrencyTransferStartedMessage.class,
TakerVerifyMakerAccount.class,
@@ -159,35 +156,33 @@ public void initialize() {
TakerVerifyMakerFeePayment.class,
CreateTakerFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.class,
- TakerSendPayDepositRequest.class,
+ TakerSendInputsForDepositTxRequest.class,
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
TakerVerifyMakerFeePayment.class,
TakerVerifyAndSignContract.class,
- BuyerAsTakerSignAndPublishDepositTx.class,
- TakerSendDepositTxPublishedMessage.class,
+ BuyerAsTakerSignsDepositTx.class,
+ SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
TakerVerifyMakerFeePayment.class,
- BuyerAsMakerSignPayoutTx.class,
+ BuyerSignPayoutTx.class,
BuyerSendCounterCurrencyTransferStartedMessage.class,
BuyerSetupPayoutTxListener.class)
));
addGroup("SellerAsMakerProtocol",
FXCollections.observableArrayList(Arrays.asList(
- MakerProcessPayDepositRequest.class,
+ MakerProcessesInputsForDepositTxRequest.class,
ApplyFilter.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
MakerCreateAndSignContract.class,
- SellerAsMakerCreatesAndSignsDepositTx.class,
- MakerSetupDepositTxListener.class,
- MakerSendPublishDepositTxRequest.class,
+ SellerAsMakerCreatesUnsignedDepositTx.class,
+ BuyerSetupDepositTxListener.class,
- MakerProcessDepositTxPublishedMessage.class,
PublishTradeStatistics.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java
index 4f2e44fc9a4..049b8efe6b4 100644
--- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java
+++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/DisplayedTransactionsFactory.java
@@ -30,7 +30,8 @@ public class DisplayedTransactionsFactory {
private final TransactionAwareTradableFactory transactionAwareTradableFactory;
@Inject
- DisplayedTransactionsFactory(BtcWalletService btcWalletService, TradableRepository tradableRepository,
+ DisplayedTransactionsFactory(BtcWalletService btcWalletService,
+ TradableRepository tradableRepository,
TransactionListItemFactory transactionListItemFactory,
TransactionAwareTradableFactory transactionAwareTradableFactory) {
this.btcWalletService = btcWalletService;
diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java
index 691901fc1b1..f81fece8374 100644
--- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java
+++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java
@@ -38,8 +38,10 @@ public class TradableRepository {
private final FailedTradesManager failedTradesManager;
@Inject
- TradableRepository(OpenOfferManager openOfferManager, TradeManager tradeManager,
- ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager) {
+ TradableRepository(OpenOfferManager openOfferManager,
+ TradeManager tradeManager,
+ ClosedTradableManager closedTradableManager,
+ FailedTradesManager failedTradesManager) {
this.openOfferManager = openOfferManager;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java
index 490bbafb52c..86b7699e2e1 100644
--- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java
+++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTradableFactory.java
@@ -17,28 +17,46 @@
package bisq.desktop.main.funds.transactions;
-import bisq.core.support.dispute.arbitration.ArbitrationManager;
+import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.offer.OpenOffer;
+import bisq.core.support.dispute.arbitration.ArbitrationManager;
+import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
+import bisq.common.crypto.PubKeyRing;
+
import javax.inject.Inject;
import javax.inject.Singleton;
+
@Singleton
public class TransactionAwareTradableFactory {
private final ArbitrationManager arbitrationManager;
+ private final RefundManager refundManager;
+ private final BtcWalletService btcWalletService;
+ private final PubKeyRing pubKeyRing;
@Inject
- TransactionAwareTradableFactory(ArbitrationManager arbitrationManager) {
+ TransactionAwareTradableFactory(ArbitrationManager arbitrationManager,
+ RefundManager refundManager,
+ BtcWalletService btcWalletService,
+ PubKeyRing pubKeyRing) {
this.arbitrationManager = arbitrationManager;
+ this.refundManager = refundManager;
+ this.btcWalletService = btcWalletService;
+ this.pubKeyRing = pubKeyRing;
}
TransactionAwareTradable create(Tradable delegate) {
if (delegate instanceof OpenOffer) {
return new TransactionAwareOpenOffer((OpenOffer) delegate);
} else if (delegate instanceof Trade) {
- return new TransactionAwareTrade((Trade) delegate, arbitrationManager);
+ return new TransactionAwareTrade((Trade) delegate,
+ arbitrationManager,
+ refundManager,
+ btcWalletService,
+ pubKeyRing);
} else {
return new DummyTransactionAwareTradable(delegate);
}
diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java
index 07c4bd7d801..8f66ee1f140 100644
--- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java
+++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionAwareTrade.java
@@ -17,63 +17,89 @@
package bisq.desktop.main.funds.transactions;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.offer.Offer;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
-import bisq.core.offer.Offer;
+import bisq.core.support.dispute.refund.RefundManager;
+import bisq.core.trade.Contract;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
+import bisq.common.crypto.PubKeyRing;
+
+import org.bitcoinj.core.Address;
import org.bitcoinj.core.Transaction;
import javafx.collections.ObservableList;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import lombok.extern.slf4j.Slf4j;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+
+@Slf4j
class TransactionAwareTrade implements TransactionAwareTradable {
- private final Trade delegate;
+ private final Trade trade;
private final ArbitrationManager arbitrationManager;
-
- TransactionAwareTrade(Trade delegate, ArbitrationManager arbitrationManager) {
- this.delegate = delegate;
+ private final RefundManager refundManager;
+ private final BtcWalletService btcWalletService;
+ private final PubKeyRing pubKeyRing;
+
+ TransactionAwareTrade(Trade trade,
+ ArbitrationManager arbitrationManager,
+ RefundManager refundManager,
+ BtcWalletService btcWalletService,
+ PubKeyRing pubKeyRing) {
+ this.trade = trade;
this.arbitrationManager = arbitrationManager;
+ this.refundManager = refundManager;
+ this.btcWalletService = btcWalletService;
+ this.pubKeyRing = pubKeyRing;
}
@Override
public boolean isRelatedToTransaction(Transaction transaction) {
String txId = transaction.getHashAsString();
- boolean isTakerOfferFeeTx = txId.equals(delegate.getTakerFeeTxId());
+ boolean isTakerOfferFeeTx = txId.equals(trade.getTakerFeeTxId());
boolean isOfferFeeTx = isOfferFeeTx(txId);
boolean isDepositTx = isDepositTx(txId);
boolean isPayoutTx = isPayoutTx(txId);
boolean isDisputedPayoutTx = isDisputedPayoutTx(txId);
+ boolean isDelayedPayoutTx = isDelayedPayoutTx(txId);
+ boolean isRefundPayoutTx = isRefundPayoutTx(txId);
- return isTakerOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx || isDisputedPayoutTx;
+ return isTakerOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx ||
+ isDisputedPayoutTx || isDelayedPayoutTx || isRefundPayoutTx;
}
private boolean isPayoutTx(String txId) {
- return Optional.ofNullable(delegate.getPayoutTx())
+ return Optional.ofNullable(trade.getPayoutTx())
.map(Transaction::getHashAsString)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isDepositTx(String txId) {
- return Optional.ofNullable(delegate.getDepositTx())
+ return Optional.ofNullable(trade.getDepositTx())
.map(Transaction::getHashAsString)
.map(hash -> hash.equals(txId))
.orElse(false);
}
private boolean isOfferFeeTx(String txId) {
- return Optional.ofNullable(delegate.getOffer())
+ return Optional.ofNullable(trade.getOffer())
.map(Offer::getOfferFeePaymentTxId)
.map(paymentTxId -> paymentTxId.equals(txId))
.orElse(false);
}
private boolean isDisputedPayoutTx(String txId) {
- String delegateId = delegate.getId();
+ String delegateId = trade.getId();
ObservableList disputes = arbitrationManager.getDisputesAsObservableList();
return disputes.stream()
@@ -88,8 +114,60 @@ private boolean isDisputedPayoutTx(String txId) {
});
}
+ private boolean isDelayedPayoutTx(String txId) {
+ String delegateId = trade.getId();
+
+ ObservableList disputes = refundManager.getDisputesAsObservableList();
+ return disputes.stream()
+ .anyMatch(dispute -> {
+ Transaction delayedPayoutTx = trade.getDelayedPayoutTx();
+ if (delayedPayoutTx != null) {
+ boolean isDelayedPayoutTx = txId.equals(delayedPayoutTx.getHashAsString());
+ String disputeTradeId = dispute.getTradeId();
+ boolean isDisputeRelatedToThis = delegateId.equals(disputeTradeId);
+ return isDelayedPayoutTx && isDisputeRelatedToThis;
+ } else {
+ return false;
+ }
+ });
+ }
+
+ private boolean isRefundPayoutTx(String txId) {
+ String tradeId = trade.getId();
+ ObservableList disputes = refundManager.getDisputesAsObservableList();
+ AtomicBoolean isRefundTx = new AtomicBoolean(false);
+ AtomicBoolean isDisputeRelatedToThis = new AtomicBoolean(false);
+ disputes.forEach(dispute -> {
+ String disputeTradeId = dispute.getTradeId();
+ isDisputeRelatedToThis.set(tradeId.equals(disputeTradeId));
+ if (isDisputeRelatedToThis.get()) {
+ Transaction tx = btcWalletService.getTransaction(txId);
+ if (tx != null) {
+ tx.getOutputs().forEach(txo -> {
+ if (btcWalletService.isTransactionOutputMine(txo)) {
+ try {
+ Address receiverAddress = txo.getAddressFromP2PKHScript(btcWalletService.getParams());
+ Contract contract = checkNotNull(trade.getContract());
+ String myPayoutAddressString = contract.isMyRoleBuyer(pubKeyRing) ?
+ contract.getBuyerPayoutAddressString() :
+ contract.getSellerPayoutAddressString();
+ if (receiverAddress != null && myPayoutAddressString.equals(receiverAddress.toString())) {
+ isRefundTx.set(true);
+ }
+ } catch (Throwable ignore) {
+ }
+
+ }
+ });
+ }
+ }
+ });
+
+ return isRefundTx.get() && isDisputeRelatedToThis.get();
+ }
+
@Override
public Tradable asTradable() {
- return delegate;
+ return trade;
}
}
diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java
index e95974989d1..92fa004c420 100644
--- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java
+++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionListItemFactory.java
@@ -24,6 +24,8 @@
import bisq.core.user.Preferences;
import bisq.core.util.BSFormatter;
+import bisq.common.crypto.PubKeyRing;
+
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
@@ -33,20 +35,27 @@
import javax.annotation.Nullable;
+
@Singleton
public class TransactionListItemFactory {
private final BtcWalletService btcWalletService;
private final BsqWalletService bsqWalletService;
private final DaoFacade daoFacade;
+ private final PubKeyRing pubKeyRing;
private final BSFormatter formatter;
private final Preferences preferences;
@Inject
- TransactionListItemFactory(BtcWalletService btcWalletService, BsqWalletService bsqWalletService,
- DaoFacade daoFacade, BSFormatter formatter, Preferences preferences) {
+ TransactionListItemFactory(BtcWalletService btcWalletService,
+ BsqWalletService bsqWalletService,
+ DaoFacade daoFacade,
+ PubKeyRing pubKeyRing,
+ BSFormatter formatter,
+ Preferences preferences) {
this.btcWalletService = btcWalletService;
this.bsqWalletService = bsqWalletService;
this.daoFacade = daoFacade;
+ this.pubKeyRing = pubKeyRing;
this.formatter = formatter;
this.preferences = preferences;
}
@@ -55,7 +64,13 @@ TransactionsListItem create(Transaction transaction, @Nullable TransactionAwareT
Optional maybeTradable = Optional.ofNullable(tradable)
.map(TransactionAwareTradable::asTradable);
- return new TransactionsListItem(transaction, btcWalletService, bsqWalletService, maybeTradable,
- daoFacade, formatter, preferences.getIgnoreDustThreshold());
+ return new TransactionsListItem(transaction,
+ btcWalletService,
+ bsqWalletService,
+ maybeTradable,
+ daoFacade,
+ pubKeyRing,
+ formatter,
+ preferences.getIgnoreDustThreshold());
}
}
diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java
index 8809542976f..4333db056db 100644
--- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java
+++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsListItem.java
@@ -30,10 +30,13 @@
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
+import bisq.core.trade.Contract;
import bisq.core.trade.Tradable;
import bisq.core.trade.Trade;
import bisq.core.util.BSFormatter;
+import bisq.common.crypto.PubKeyRing;
+
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
@@ -87,6 +90,7 @@ class TransactionsListItem {
BsqWalletService bsqWalletService,
Optional tradableOptional,
DaoFacade daoFacade,
+ PubKeyRing pubKeyRing,
BSFormatter formatter,
long ignoreDustThreshold) {
this.btcWalletService = btcWalletService;
@@ -216,27 +220,48 @@ public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
} else if (trade.getPayoutTx() != null &&
trade.getPayoutTx().getHashAsString().equals(txId)) {
details = Res.get("funds.tx.multiSigPayout", id);
- } else if (trade.getDisputeState() == Trade.DisputeState.DISPUTE_CLOSED) {
- if (valueSentToMe.isPositive()) {
- details = Res.get("funds.tx.disputePayout", id);
+ } else {
+ Trade.DisputeState disputeState = trade.getDisputeState();
+ if (disputeState == Trade.DisputeState.DISPUTE_CLOSED) {
+ if (valueSentToMe.isPositive()) {
+ details = Res.get("funds.tx.disputePayout", id);
+ } else {
+ details = Res.get("funds.tx.disputeLost", id);
+ txConfidenceIndicator.setVisible(false);
+ }
+ } else if (disputeState == Trade.DisputeState.REFUND_REQUEST_CLOSED ||
+ disputeState == Trade.DisputeState.REFUND_REQUESTED ||
+ disputeState == Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER) {
+ if (valueSentToMe.isPositive()) {
+ details = Res.get("funds.tx.refund", id);
+ } else {
+ Contract contract = trade.getContract();
+ Coin tradeAmount = trade.getTradeAmount();
+ if (contract != null && tradeAmount != null) {
+ boolean isBuyer = contract.isMyRoleBuyer(pubKeyRing);
+ amountAsCoin = isBuyer ? trade.getOffer().getBuyerSecurityDeposit().multiply(-1) :
+ (trade.getOffer().getSellerSecurityDeposit().add(tradeAmount)).multiply(-1);
+ details = Res.get("funds.tx.collateralForRefund", id);
+ txConfidenceIndicator.setVisible(false);
+ }
+ }
} else {
- details = Res.get("funds.tx.disputeLost", id);
- txConfidenceIndicator.setVisible(false);
+ details = Res.get("funds.tx.unknown", id);
}
- } else {
- details = Res.get("funds.tx.unknown", id);
}
}
}
} else {
- if (amountAsCoin.isZero())
+ if (amountAsCoin.isZero()) {
details = Res.get("funds.tx.noFundsFromDispute");
- else if (withdrawalFromBSQWallet)
+ txConfidenceIndicator.setVisible(false);
+ } else if (withdrawalFromBSQWallet) {
details = Res.get("funds.tx.withdrawnFromBSQWallet");
- else if (!txFeeForBsqPayment)
+ } else if (!txFeeForBsqPayment) {
details = received ? Res.get("funds.tx.receivedFunds") : Res.get("funds.tx.withdrawnFromWallet");
- else if (details.isEmpty())
+ } else if (details.isEmpty()) {
details = Res.get("funds.tx.txFeePaymentForBsqTx");
+ }
}
// Use tx.getIncludedInBestChainAt() when available, otherwise use tx.getUpdateTime()
date = transaction.getIncludedInBestChainAt() != null ? transaction.getIncludedInBestChainAt() : transaction.getUpdateTime();
diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java
index 39bbf2ccdf6..82931ef72cc 100644
--- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java
@@ -358,7 +358,8 @@ Offer createAndGetOffer() {
Map extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService,
referralIdService,
paymentAccount,
- currencyCode);
+ currencyCode,
+ preferences);
OfferUtil.validateOfferData(filterManager,
p2PService,
@@ -594,7 +595,7 @@ long getMaxTradeLimit() {
double calculateMarketPriceManual(double marketPrice, double volumeAsDouble, double amountAsDouble) {
double manualPriceAsDouble = volumeAsDouble / amountAsDouble;
double percentage = MathUtils.roundDouble(manualPriceAsDouble / marketPrice, 4);
-
+
setMarketPriceMargin(percentage);
return manualPriceAsDouble;
diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java
index 15372e9820f..24f04b304e3 100644
--- a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java
+++ b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java
@@ -27,8 +27,8 @@
import bisq.desktop.main.support.dispute.client.mediation.MediationClientView;
import bisq.core.locale.Res;
-import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
+import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.BuyerTrade;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.SellerTrade;
@@ -38,7 +38,6 @@
import bisq.core.user.Preferences;
import bisq.common.UserThread;
-import bisq.common.app.DevEnv;
import com.google.inject.Inject;
@@ -82,8 +81,8 @@ static void add(Notification notification) {
///////////////////////////////////////////////////////////////////////////////////////////
private final TradeManager tradeManager;
- private final ArbitrationManager arbitrationManager;
private final MediationManager mediationManager;
+ private final RefundManager refundManager;
private final Navigation navigation;
private final Map disputeStateSubscriptionsMap = new HashMap<>();
@@ -97,13 +96,13 @@ static void add(Notification notification) {
@Inject
public NotificationCenter(TradeManager tradeManager,
- ArbitrationManager arbitrationManager,
MediationManager mediationManager,
+ RefundManager refundManager,
Preferences preferences,
Navigation navigation) {
this.tradeManager = tradeManager;
- this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
+ this.refundManager = refundManager;
this.navigation = navigation;
EasyBind.subscribe(preferences.getUseAnimationsProperty(), useAnimations -> NotificationCenter.useAnimations = useAnimations);
@@ -230,26 +229,26 @@ else if (trade instanceof SellerTrade && phase.ordinal() == Trade.Phase.FIAT_SEN
private void onDisputeStateChanged(Trade trade, Trade.DisputeState disputeState) {
String message = null;
- if (arbitrationManager.findOwnDispute(trade.getId()).isPresent()) {
- String disputeOrTicket = arbitrationManager.findOwnDispute(trade.getId()).get().isSupportTicket() ?
+ if (refundManager.findOwnDispute(trade.getId()).isPresent()) {
+ String disputeOrTicket = refundManager.findOwnDispute(trade.getId()).get().isSupportTicket() ?
Res.get("shared.supportTicket") :
Res.get("shared.dispute");
switch (disputeState) {
case NO_DISPUTE:
break;
- case DISPUTE_REQUESTED:
+ case REFUND_REQUESTED:
break;
- case DISPUTE_STARTED_BY_PEER:
+ case REFUND_REQUEST_STARTED_BY_PEER:
message = Res.get("notification.trade.peerOpenedDispute", disputeOrTicket);
break;
- case DISPUTE_CLOSED:
+ case REFUND_REQUEST_CLOSED:
message = Res.get("notification.trade.disputeClosed", disputeOrTicket);
break;
default:
- if (DevEnv.isDevMode()) {
- log.error("arbitrationDisputeManager must not contain mediation disputes");
- throw new RuntimeException("arbitrationDisputeManager must not contain mediation disputes");
- }
+// if (DevEnv.isDevMode()) {
+// log.error("refundManager must not contain mediation or arbitration disputes. disputeState={}", disputeState);
+// throw new RuntimeException("arbitrationDisputeManager must not contain mediation disputes");
+// }
break;
}
if (message != null) {
@@ -271,10 +270,10 @@ private void onDisputeStateChanged(Trade trade, Trade.DisputeState disputeState)
message = Res.get("notification.trade.disputeClosed", disputeOrTicket);
break;
default:
- if (DevEnv.isDevMode()) {
- log.error("mediationDisputeManager must not contain arbitration disputes");
- throw new RuntimeException("mediationDisputeManager must not contain arbitration disputes");
- }
+// if (DevEnv.isDevMode()) {
+// log.error("mediationDisputeManager must not contain arbitration or refund disputes. disputeState={}", disputeState);
+// throw new RuntimeException("mediationDisputeManager must not contain arbitration disputes");
+// }
break;
}
if (message != null) {
diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java
index e64c57c996c..046c68dfc5a 100644
--- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java
+++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java
@@ -35,6 +35,7 @@
import bisq.core.support.dispute.DisputeManager;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
+import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Contract;
import bisq.core.util.BSFormatter;
@@ -72,6 +73,7 @@
public class ContractWindow extends Overlay {
private final ArbitrationManager arbitrationManager;
private final MediationManager mediationManager;
+ private final RefundManager refundManager;
private final AccountAgeWitnessService accountAgeWitnessService;
private final BSFormatter formatter;
private Dispute dispute;
@@ -84,10 +86,12 @@ public class ContractWindow extends Overlay {
@Inject
public ContractWindow(ArbitrationManager arbitrationManager,
MediationManager mediationManager,
+ RefundManager refundManager,
AccountAgeWitnessService accountAgeWitnessService,
BSFormatter formatter) {
this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
+ this.refundManager = refundManager;
this.accountAgeWitnessService = accountAgeWitnessService;
this.formatter = formatter;
type = Type.Confirmation;
@@ -169,8 +173,9 @@ private void addContent() {
getAccountAge(contract.getBuyerPaymentAccountPayload(), contract.getBuyerPubKeyRing(), offer.getCurrencyCode()) + " / " +
getAccountAge(contract.getSellerPaymentAccountPayload(), contract.getSellerPubKeyRing(), offer.getCurrencyCode()));
- String nrOfDisputesAsBuyer = getDisputeManager(dispute).getNrOfDisputes(true, contract);
- String nrOfDisputesAsSeller = getDisputeManager(dispute).getNrOfDisputes(false, contract);
+ DisputeManager extends DisputeList extends DisputeList>> disputeManager = getDisputeManager(dispute);
+ String nrOfDisputesAsBuyer = disputeManager != null ? disputeManager.getNrOfDisputes(true, contract) : "";
+ String nrOfDisputesAsSeller = disputeManager != null ? disputeManager.getNrOfDisputes(false, contract) : "";
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("contractWindow.numDisputes"),
nrOfDisputesAsBuyer + " / " + nrOfDisputesAsSeller);
@@ -179,9 +184,25 @@ private void addContent() {
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("shared.paymentDetails", Res.get("shared.seller")),
sellerPaymentAccountPayload.getPaymentDetails()).second.setMouseTransparent(false);
- // TODO update in next release to shared.selectedArbitrator and delete shared.arbitrator entry
- String title = dispute.isMediationDispute() ? Res.get("shared.selectedMediator") : Res.get("shared.arbitrator");
- String agentNodeAddress = getDisputeManager(dispute).getAgentNodeAddress(dispute).getFullAddress();
+ String title = "";
+ if (dispute.getSupportType() != null) {
+ switch (dispute.getSupportType()) {
+ case ARBITRATION:
+ // TODO update in next release to shared.selectedArbitrator and delete shared.arbitrator entry
+ title = Res.get("shared.arbitrator");
+ break;
+ case MEDIATION:
+ title = Res.get("shared.selectedMediator");
+ break;
+ case TRADE:
+ break;
+ case REFUND:
+ title = Res.get("shared.refundAgent");
+ break;
+ }
+ }
+
+ String agentNodeAddress = disputeManager != null ? disputeManager.getAgentNodeAddress(dispute).getFullAddress() : "";
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, title, agentNodeAddress);
if (showAcceptedCountryCodes) {
@@ -272,7 +293,19 @@ private void addContent() {
}
private DisputeManager extends DisputeList extends DisputeList>> getDisputeManager(Dispute dispute) {
- return dispute.isMediationDispute() ? mediationManager : arbitrationManager;
+ if (dispute.getSupportType() != null) {
+ switch (dispute.getSupportType()) {
+ case ARBITRATION:
+ return arbitrationManager;
+ case MEDIATION:
+ return mediationManager;
+ case TRADE:
+ break;
+ case REFUND:
+ return refundManager;
+ }
+ }
+ return null;
}
private String getAccountAge(PaymentAccountPayload paymentAccountPayload,
diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java
index 7af9ed4d5eb..7880d605dc8 100644
--- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java
+++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java
@@ -26,29 +26,38 @@
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.Layout;
+import bisq.core.btc.TxFeeEstimationService;
import bisq.core.btc.exceptions.TransactionVerificationException;
-import bisq.core.btc.model.AddressEntry;
+import bisq.core.btc.exceptions.TxBroadcastException;
+import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.TradeWalletService;
+import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
+import bisq.core.provider.fee.FeeService;
+import bisq.core.support.SupportType;
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.support.dispute.arbitration.ArbitrationManager;
import bisq.core.support.dispute.mediation.MediationManager;
+import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Contract;
import bisq.core.util.BSFormatter;
+import bisq.core.util.CoinUtil;
import bisq.core.util.ParsingUtils;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
+import bisq.common.handlers.ResultHandler;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
-import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.InsufficientMoneyException;
+import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
@@ -82,15 +91,18 @@
import static bisq.desktop.util.FormBuilder.addConfirmationLabelLabel;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
+import static com.google.common.base.Preconditions.checkNotNull;
public class DisputeSummaryWindow extends Overlay {
private static final Logger log = LoggerFactory.getLogger(DisputeSummaryWindow.class);
private final BSFormatter formatter;
- private final ArbitrationManager arbitrationManager;
private final MediationManager mediationManager;
- private final BtcWalletService walletService;
+ private final RefundManager refundManager;
private final TradeWalletService tradeWalletService;
+ private final BtcWalletService btcWalletService;
+ private final TxFeeEstimationService txFeeEstimationService;
+ private final FeeService feeService;
private Dispute dispute;
private Optional finalizeDisputeHandlerOptional = Optional.empty();
private ToggleGroup tradeAmountToggleGroup, reasonToggleGroup;
@@ -119,16 +131,20 @@ public class DisputeSummaryWindow extends Overlay {
@Inject
public DisputeSummaryWindow(BSFormatter formatter,
- ArbitrationManager arbitrationManager,
MediationManager mediationManager,
- BtcWalletService walletService,
- TradeWalletService tradeWalletService) {
+ RefundManager refundManager,
+ TradeWalletService tradeWalletService,
+ BtcWalletService btcWalletService,
+ TxFeeEstimationService txFeeEstimationService,
+ FeeService feeService) {
this.formatter = formatter;
- this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager;
- this.walletService = walletService;
+ this.refundManager = refundManager;
this.tradeWalletService = tradeWalletService;
+ this.btcWalletService = btcWalletService;
+ this.txFeeEstimationService = txFeeEstimationService;
+ this.feeService = feeService;
type = Type.Confirmation;
}
@@ -355,7 +371,12 @@ private boolean isPayoutAmountValid() {
.add(offer.getBuyerSecurityDeposit())
.add(offer.getSellerSecurityDeposit());
Coin totalAmount = buyerAmount.add(sellerAmount);
- return (totalAmount.compareTo(available) == 0);
+ if (getDisputeManager(dispute) instanceof RefundManager) {
+ // We allow to spend less in case of RefundAgent
+ return totalAmount.compareTo(available) <= 0;
+ } else {
+ return totalAmount.compareTo(available) == 0;
+ }
}
private void applyCustomAmounts(InputTextField inputTextField) {
@@ -365,10 +386,17 @@ private void applyCustomAmounts(InputTextField inputTextField) {
.add(offer.getBuyerSecurityDeposit())
.add(offer.getSellerSecurityDeposit());
Coin enteredAmount = ParsingUtils.parseToCoin(inputTextField.getText(), formatter);
+ if (enteredAmount.isNegative()) {
+ enteredAmount = Coin.ZERO;
+ inputTextField.setText(formatter.formatCoin(enteredAmount));
+ }
+ if (enteredAmount.isPositive() && !Restrictions.isAboveDust(enteredAmount)) {
+ enteredAmount = Restrictions.getMinNonDustOutput();
+ inputTextField.setText(formatter.formatCoin(enteredAmount));
+ }
if (enteredAmount.compareTo(available) > 0) {
enteredAmount = available;
- Coin finalEnteredAmount = enteredAmount;
- inputTextField.setText(formatter.formatCoin(finalEnteredAmount));
+ inputTextField.setText(formatter.formatCoin(enteredAmount));
}
Coin counterPartAsCoin = available.subtract(enteredAmount);
String formattedCounterPartAmount = formatter.formatCoin(counterPartAsCoin);
@@ -377,11 +405,27 @@ private void applyCustomAmounts(InputTextField inputTextField) {
if (inputTextField == buyerPayoutAmountInputTextField) {
buyerAmount = enteredAmount;
sellerAmount = counterPartAsCoin;
- sellerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
+ Coin sellerAmountFromField = ParsingUtils.parseToCoin(sellerPayoutAmountInputTextField.getText(), formatter);
+ Coin totalAmountFromFields = enteredAmount.add(sellerAmountFromField);
+ // RefundAgent can enter less then available
+ if (getDisputeManager(dispute) instanceof MediationManager ||
+ totalAmountFromFields.compareTo(available) > 0) {
+ sellerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
+ } else {
+ sellerAmount = sellerAmountFromField;
+ }
} else {
sellerAmount = enteredAmount;
buyerAmount = counterPartAsCoin;
- buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
+ Coin buyerAmountFromField = ParsingUtils.parseToCoin(buyerPayoutAmountInputTextField.getText(), formatter);
+ Coin totalAmountFromFields = enteredAmount.add(buyerAmountFromField);
+ // RefundAgent can enter less then available
+ if (getDisputeManager(dispute) instanceof MediationManager ||
+ totalAmountFromFields.compareTo(available) > 0) {
+ buyerPayoutAmountInputTextField.setText(formattedCounterPartAmount);
+ } else {
+ buyerAmount = buyerAmountFromField;
+ }
}
disputeResult.setBuyerPayoutAmount(buyerAmount);
@@ -506,7 +550,6 @@ private void addButtons(Contract contract) {
Tuple3