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/account/sign/SignedWitnessService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
index ef87a6e35f1..f847a3d13e6 100644
--- a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
+++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java
@@ -18,14 +18,6 @@
package bisq.core.account.sign;
import bisq.core.account.witness.AccountAgeWitness;
-import bisq.core.account.witness.AccountAgeWitnessService;
-import bisq.core.payment.ChargeBackRisk;
-import bisq.core.payment.payload.PaymentAccountPayload;
-import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.support.dispute.Dispute;
-import bisq.core.support.dispute.DisputeResult;
-import bisq.core.support.dispute.arbitration.ArbitrationManager;
-import bisq.core.support.dispute.arbitration.BuyerDataItem;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.network.p2p.P2PService;
@@ -34,7 +26,6 @@
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing;
-import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Utilities;
@@ -57,26 +48,20 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.Nullable;
-
@Slf4j
public class SignedWitnessService {
- public static final long CHARGEBACK_SAFETY_DAYS = 30;
+ public static final long SIGNER_AGE_DAYS = 60;
+ public static final long SIGNER_AGE = SIGNER_AGE_DAYS * ChronoUnit.DAYS.getDuration().toMillis();
private final KeyRing keyRing;
private final P2PService p2PService;
- private final AccountAgeWitnessService accountAgeWitnessService;
private final ArbitratorManager arbitratorManager;
- private final ArbitrationManager arbitrationManager;
- private final ChargeBackRisk chargeBackRisk;
private final Map signedWitnessMap = new HashMap<>();
@@ -88,18 +73,12 @@ public class SignedWitnessService {
@Inject
public SignedWitnessService(KeyRing keyRing,
P2PService p2PService,
- AccountAgeWitnessService accountAgeWitnessService,
ArbitratorManager arbitratorManager,
SignedWitnessStorageService signedWitnessStorageService,
- AppendOnlyDataStoreService appendOnlyDataStoreService,
- ArbitrationManager arbitrationManager,
- ChargeBackRisk chargeBackRisk) {
+ AppendOnlyDataStoreService appendOnlyDataStoreService) {
this.keyRing = keyRing;
this.p2PService = p2PService;
- this.accountAgeWitnessService = accountAgeWitnessService;
this.arbitratorManager = arbitratorManager;
- this.arbitrationManager = arbitrationManager;
- this.chargeBackRisk = chargeBackRisk;
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
appendOnlyDataStoreService.addService(signedWitnessStorageService);
@@ -128,30 +107,46 @@ public void onAllServicesInitialized() {
// API
///////////////////////////////////////////////////////////////////////////////////////////
- public List getMyWitnessAgeList(PaymentAccountPayload myPaymentAccountPayload) {
- AccountAgeWitness accountAgeWitness = accountAgeWitnessService.getMyWitness(myPaymentAccountPayload);
- // We do not validate as it would not make sense to cheat one self...
+ /**
+ * List of dates as long when accountAgeWitness was signed
+ */
+ public List getVerifiedWitnessDateList(AccountAgeWitness accountAgeWitness) {
return getSignedWitnessSet(accountAgeWitness).stream()
+ .filter(this::verifySignature)
.map(SignedWitness::getDate)
.sorted()
.collect(Collectors.toList());
}
-
- public List getVerifiedWitnessAgeList(AccountAgeWitness accountAgeWitness) {
- return signedWitnessMap.values().stream()
- .filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
- .filter(this::verifySignature)
+ /**
+ * List of dates as long when accountAgeWitness was signed
+ * Not verifying that signatures are correct
+ */
+ public List getWitnessDateList(AccountAgeWitness accountAgeWitness) {
+ // We do not validate as it would not make sense to cheat one self...
+ return getSignedWitnessSet(accountAgeWitness).stream()
.map(SignedWitness::getDate)
.sorted()
.collect(Collectors.toList());
}
+ public boolean isSignedByArbitrator(AccountAgeWitness accountAgeWitness) {
+ return getSignedWitnessSet(accountAgeWitness).stream()
+ .map(SignedWitness::isSignedByArbitrator)
+ .findAny()
+ .orElse(false);
+ }
+
// Arbitrators sign with EC key
public SignedWitness signAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness,
ECKey key,
PublicKey peersPubKey) {
+ if (isValidAccountAgeWitness(accountAgeWitness)) {
+ log.warn("Arbitrator trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
+ return null;
+ }
+
String accountAgeWitnessHashAsHex = Utilities.encodeToHex(accountAgeWitness.getHash());
String signatureBase64 = key.signMessage(accountAgeWitnessHashAsHex);
SignedWitness signedWitness = new SignedWitness(true,
@@ -162,6 +157,7 @@ public SignedWitness signAccountAgeWitness(Coin tradeAmount,
new Date().getTime(),
tradeAmount.value);
publishSignedWitness(signedWitness);
+ log.info("Arbitrator signed witness {}", signedWitness.toString());
return signedWitness;
}
@@ -169,6 +165,11 @@ public SignedWitness signAccountAgeWitness(Coin tradeAmount,
public SignedWitness signAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness,
PublicKey peersPubKey) throws CryptoException {
+ if (isValidAccountAgeWitness(accountAgeWitness)) {
+ log.warn("Trader trying to sign already signed accountagewitness {}", accountAgeWitness.toString());
+ return null;
+ }
+
byte[] signature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), accountAgeWitness.getHash());
SignedWitness signedWitness = new SignedWitness(false,
accountAgeWitness.getHash(),
@@ -178,6 +179,7 @@ public SignedWitness signAccountAgeWitness(Coin tradeAmount,
new Date().getTime(),
tradeAmount.value);
publishSignedWitness(signedWitness);
+ log.info("Trader signed witness {}", signedWitness.toString());
return signedWitness;
}
@@ -220,7 +222,7 @@ private boolean verifySignatureWithDSAKey(SignedWitness signedWitness) {
}
}
- public Set getSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
+ private Set getSignedWitnessSet(AccountAgeWitness accountAgeWitness) {
return signedWitnessMap.values().stream()
.filter(e -> Arrays.equals(e.getWitnessHash(), accountAgeWitness.getHash()))
.collect(Collectors.toSet());
@@ -244,8 +246,8 @@ public Set getTrustedPeerSignedWitnessSet(AccountAgeWitness accou
// We go one level up by using the signer Key to lookup for SignedWitness objects which contain the signerKey as
// witnessOwnerPubKey
- public Set getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
- Stack excluded) {
+ private Set getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
+ Stack excluded) {
return signedWitnessMap.values().stream()
.filter(e -> Arrays.equals(e.getWitnessOwnerPubKey(), ownerPubKey))
.filter(e -> !excluded.contains(new P2PDataStorage.ByteArray(e.getSignerPubKey())))
@@ -254,7 +256,8 @@ public Set getSignedWitnessSetByOwnerPubKey(byte[] ownerPubKey,
/**
* Checks whether the accountAgeWitness has a valid signature from a peer/arbitrator.
- * @param accountAgeWitness
+ *
+ * @param accountAgeWitness accountAgeWitness
* @return true if accountAgeWitness is valid, false otherwise.
*/
public boolean isValidAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
@@ -272,9 +275,10 @@ public boolean isValidAccountAgeWitness(AccountAgeWitness accountAgeWitness) {
/**
* Helper to isValidAccountAgeWitness(accountAgeWitness)
- * @param signedWitness the signedWitness to validate
+ *
+ * @param signedWitness the signedWitness to validate
* @param childSignedWitnessDateMillis the date the child SignedWitness was signed or current time if it is a leave.
- * @param excludedPubKeys stack to prevent recursive loops
+ * @param excludedPubKeys stack to prevent recursive loops
* @return true if signedWitness is valid, false otherwise.
*/
private boolean isValidSignedWitnessInternal(SignedWitness signedWitness,
@@ -311,7 +315,8 @@ private boolean isValidSignedWitnessInternal(SignedWitness signedWitness,
}
private boolean verifyDate(SignedWitness signedWitness, long childSignedWitnessDateMillis) {
- long childSignedWitnessDateMinusChargebackPeriodMillis = Instant.ofEpochMilli(childSignedWitnessDateMillis).minus(CHARGEBACK_SAFETY_DAYS, ChronoUnit.DAYS).toEpochMilli();
+ long childSignedWitnessDateMinusChargebackPeriodMillis = Instant.ofEpochMilli(
+ childSignedWitnessDateMillis).minus(SIGNER_AGE, ChronoUnit.MILLIS).toEpochMilli();
long signedWitnessDateMillis = signedWitness.getDate();
return signedWitnessDateMillis <= childSignedWitnessDateMinusChargebackPeriodMillis;
}
@@ -322,49 +327,14 @@ private boolean verifyDate(SignedWitness signedWitness, long childSignedWitnessD
@VisibleForTesting
void addToMap(SignedWitness signedWitness) {
+ // TODO: Perhaps filter out all but one signedwitness per accountagewitness
signedWitnessMap.putIfAbsent(signedWitness.getHashAsByteArray(), signedWitness);
}
private void publishSignedWitness(SignedWitness signedWitness) {
if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) {
+ log.info("broadcast signed witness {}", signedWitness.toString());
p2PService.addPersistableNetworkPayload(signedWitness, false);
}
}
-
- // Arbitrator signing
- public List getBuyerPaymentAccounts(long safeDate, PaymentMethod paymentMethod) {
- return arbitrationManager.getDisputesAsObservableList().stream()
- .filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
- .filter(this::hasChargebackRisk)
- .filter(this::isBuyerWinner)
- .map(this::getBuyerData)
- .filter(Objects::nonNull)
- .filter(buyerDataItem -> buyerDataItem.getAccountAgeWitness().getDate() < safeDate)
- .distinct()
- .collect(Collectors.toList());
- }
-
- private boolean hasChargebackRisk(Dispute dispute) {
- return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
- dispute.getContract().getOfferPayload().getCurrencyCode());
- }
-
- private boolean isBuyerWinner(Dispute dispute) {
- return dispute.getDisputeResultProperty().get().getWinner() == DisputeResult.Winner.BUYER;
- }
-
- @Nullable
- private BuyerDataItem getBuyerData(Dispute dispute) {
- PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
- PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
- Optional optionalWitness = accountAgeWitnessService
- .findWitness(buyerPaymentAccountPaload, buyerPubKeyRing);
- return optionalWitness.map(witness -> new BuyerDataItem(
- buyerPaymentAccountPaload,
- witness,
- dispute.getContract().getTradeAmount(),
- dispute.getContract().getSellerPubKeyRing().getSignaturePubKey()))
- .orElse(null);
- }
-
}
diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeRestrictions.java b/core/src/main/java/bisq/core/account/witness/AccountAgeRestrictions.java
deleted file mode 100644
index 04c6ab2af43..00000000000
--- a/core/src/main/java/bisq/core/account/witness/AccountAgeRestrictions.java
+++ /dev/null
@@ -1,86 +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.account.witness;
-
-import bisq.core.offer.Offer;
-import bisq.core.offer.OfferPayload;
-import bisq.core.offer.OfferRestrictions;
-import bisq.core.payment.PaymentAccount;
-import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.trade.Trade;
-
-import bisq.common.util.Utilities;
-
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class AccountAgeRestrictions {
- private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 1).getTime();
-
- public static boolean isMakersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Offer offer) {
- long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMakersAccountAge(offer, new Date());
- return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
- }
-
- public static boolean isTradePeersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Trade trade) {
- long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getTradingPeersAccountAge(trade);
- return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
- }
-
- public static boolean isMyAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount myPaymentAccount) {
- long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMyAccountAge(myPaymentAccount.getPaymentAccountPayload());
- return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
- }
-
- public static long getMyTradeLimitAtCreateOffer(AccountAgeWitnessService accountAgeWitnessService,
- PaymentAccount paymentAccount,
- String currencyCode,
- OfferPayload.Direction direction) {
- if (direction == OfferPayload.Direction.BUY &&
- PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
- AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, paymentAccount)) {
- return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
- } else {
- return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
- }
- }
-
- public static long getMyTradeLimitAtTakeOffer(AccountAgeWitnessService accountAgeWitnessService,
- PaymentAccount paymentAccount,
- Offer offer,
- String currencyCode,
- OfferPayload.Direction direction) {
- if (direction == OfferPayload.Direction.BUY &&
- PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
- AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer)) {
- // Taker is seller
- return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
- } else if (direction == OfferPayload.Direction.SELL &&
- PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod(), currencyCode) &&
- AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, paymentAccount)) {
- // Taker is buyer
- return OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
- } else {
- // Offers with no chargeback risk or mature buyer accounts
- return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode);
- }
- }
-}
diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
index 74f7d276d3e..8e7ee37291a 100644
--- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
+++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java
@@ -17,12 +17,20 @@
package bisq.core.account.witness;
+import bisq.core.account.sign.SignedWitness;
+import bisq.core.account.sign.SignedWitnessService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.offer.Offer;
+import bisq.core.offer.OfferPayload;
+import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.AssetAccount;
+import bisq.core.payment.ChargeBackRisk;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod;
+import bisq.core.support.dispute.Dispute;
+import bisq.core.support.dispute.DisputeResult;
+import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer;
import bisq.core.user.User;
@@ -43,6 +51,7 @@
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.ECKey;
import javax.inject.Inject;
@@ -52,10 +61,13 @@
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
@@ -65,8 +77,10 @@
public class AccountAgeWitnessService {
private static final Date RELEASE = Utilities.getUTCDate(2017, GregorianCalendar.NOVEMBER, 11);
public static final Date FULL_ACTIVATION = Utilities.getUTCDate(2018, GregorianCalendar.FEBRUARY, 15);
+ private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.SEPTEMBER, 1).getTime();
public enum AccountAge {
+ UNVERIFIED,
LESS_ONE_MONTH,
ONE_TO_TWO_MONTHS,
TWO_MONTHS_OR_MORE
@@ -75,6 +89,8 @@ public enum AccountAge {
private final KeyRing keyRing;
private final P2PService p2PService;
private final User user;
+ private final SignedWitnessService signedWitnessService;
+ private final ChargeBackRisk chargeBackRisk;
private final Map accountAgeWitnessMap = new HashMap<>();
@@ -85,12 +101,18 @@ public enum AccountAge {
@Inject
- public AccountAgeWitnessService(KeyRing keyRing, P2PService p2PService, User user,
+ public AccountAgeWitnessService(KeyRing keyRing,
+ P2PService p2PService,
+ User user,
+ SignedWitnessService signedWitnessService,
+ ChargeBackRisk chargeBackRisk,
AccountAgeWitnessStorageService accountAgeWitnessStorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService) {
this.keyRing = keyRing;
this.p2PService = p2PService;
this.user = user;
+ this.signedWitnessService = signedWitnessService;
+ this.chargeBackRisk = chargeBackRisk;
// We need to add that early (before onAllServicesInitialized) as it will be used at startup.
appendOnlyDataStoreService.addService(accountAgeWitnessStorageService);
@@ -164,7 +186,8 @@ private AccountAgeWitness getNewWitness(PaymentAccountPayload paymentAccountPayl
return new AccountAgeWitness(hash, new Date().getTime());
}
- public Optional findWitness(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
+ private Optional findWitness(PaymentAccountPayload paymentAccountPayload,
+ PubKeyRing pubKeyRing) {
byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload);
byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt,
pubKeyRing.getSignaturePubKeyBytes()));
@@ -172,6 +195,19 @@ public Optional findWitness(PaymentAccountPayload paymentAcco
return getWitnessByHash(hash);
}
+ private Optional findWitness(Offer offer) {
+ final Optional accountAgeWitnessHash = offer.getAccountAgeWitnessHashAsHex();
+ return accountAgeWitnessHash.isPresent() ?
+ getWitnessByHashAsHex(accountAgeWitnessHash.get()) :
+ Optional.empty();
+ }
+
+ private Optional findTradePeerWitness(Trade trade) {
+ TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
+ return (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) ?
+ Optional.empty() : findWitness(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
+ }
+
private Optional getWitnessByHash(byte[] hash) {
P2PDataStorage.ByteArray hashAsByteArray = new P2PDataStorage.ByteArray(hash);
@@ -186,19 +222,64 @@ private Optional getWitnessByHashAsHex(String hashAsHex) {
return getWitnessByHash(Utilities.decodeFromHex(hashAsHex));
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Witness age
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
public long getAccountAge(AccountAgeWitness accountAgeWitness, Date now) {
log.debug("getAccountAge now={}, accountAgeWitness.getDate()={}", now.getTime(), accountAgeWitness.getDate());
return now.getTime() - accountAgeWitness.getDate();
}
+ // Return -1 if no witness found
public long getAccountAge(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) {
return findWitness(paymentAccountPayload, pubKeyRing)
.map(accountAgeWitness -> getAccountAge(accountAgeWitness, new Date()))
.orElse(-1L);
}
- public AccountAge getAccountAgeCategory(long accountAge) {
- if (accountAge < TimeUnit.DAYS.toMillis(30)) {
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Signed age
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ // Return -1 if not signed
+ public long getWitnessSignAge(AccountAgeWitness accountAgeWitness, Date now) {
+ List dates = signedWitnessService.getVerifiedWitnessDateList(accountAgeWitness);
+ if (dates.isEmpty()) {
+ return -1L;
+ } else {
+ return now.getTime() - dates.get(0);
+ }
+ }
+
+ // Return -1 if not signed
+ public long getWitnessSignAge(Offer offer, Date now) {
+ return findWitness(offer)
+ .map(witness -> getWitnessSignAge(witness, now))
+ .orElse(-1L);
+ }
+
+ // Return -1 if not signed
+ public long getWitnessSignAge(Trade trade, Date now) {
+ TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
+ if (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) {
+ // unexpected
+ return -1;
+ }
+
+ return findWitness(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing())
+ .map(witness -> getWitnessSignAge(witness, now))
+ .orElse(-1L);
+ }
+
+ public AccountAge getPeersAccountAgeCategory(long peersAccountAge) {
+ return getAccountAgeCategory(peersAccountAge);
+ }
+
+ private AccountAge getAccountAgeCategory(long accountAge) {
+ if (accountAge < 0) {
+ return AccountAge.UNVERIFIED;
+ } else if (accountAge < TimeUnit.DAYS.toMillis(30)) {
return AccountAge.LESS_ONE_MONTH;
} else if (accountAge < TimeUnit.DAYS.toMillis(60)) {
return AccountAge.ONE_TO_TWO_MONTHS;
@@ -207,41 +288,72 @@ public AccountAge getAccountAgeCategory(long accountAge) {
}
}
- private long getTradeLimit(Coin maxTradeLimit, String currencyCode, Optional accountAgeWitnessOptional, Date now) {
+ // Checks trade limit based on time since signing of AccountAgeWitness
+ private long getTradeLimit(Coin maxTradeLimit,
+ String currencyCode,
+ AccountAgeWitness accountAgeWitness,
+ AccountAge accountAgeCategory,
+ OfferPayload.Direction direction) {
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
double factor;
- final long accountAge = getAccountAge((accountAgeWitnessOptional.get()), now);
- AccountAge accountAgeCategory = accountAgeWitnessOptional
- .map(accountAgeWitness -> getAccountAgeCategory(accountAge))
- .orElse(AccountAge.LESS_ONE_MONTH);
-
- switch (accountAgeCategory) {
- case TWO_MONTHS_OR_MORE:
- factor = 1;
- break;
- case ONE_TO_TWO_MONTHS:
- factor = 0.5;
- break;
- case LESS_ONE_MONTH:
- default:
- factor = 0.25;
- break;
+ long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
+ if (direction == OfferPayload.Direction.BUY) {
+ switch (accountAgeCategory) {
+ case TWO_MONTHS_OR_MORE:
+ factor = 1;
+ break;
+ case ONE_TO_TWO_MONTHS:
+ factor = 0.25;
+ break;
+ case LESS_ONE_MONTH:
+ case UNVERIFIED:
+ default:
+ factor = 0;
+ }
+ } else {
+ switch (accountAgeCategory) {
+ case TWO_MONTHS_OR_MORE:
+ factor = 1;
+ break;
+ case ONE_TO_TWO_MONTHS:
+ factor = 0.5;
+ break;
+ case LESS_ONE_MONTH:
+ case UNVERIFIED:
+ factor = 0.25;
+ break;
+ default:
+ factor = 0;
+ }
+ }
+ if (factor > 0) {
+ limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
}
- final long limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor);
- log.debug("accountAgeCategory={}, accountAge={}, limit={}, factor={}, accountAgeWitnessHash={}",
+ log.debug("accountAgeCategory={}, limit={}, factor={}, accountAgeWitnessHash={}",
accountAgeCategory,
- accountAge / TimeUnit.DAYS.toMillis(1) + " days",
Coin.valueOf(limit).toFriendlyString(),
factor,
- accountAgeWitnessOptional.map(accountAgeWitness -> Utilities.bytesAsHexString(accountAgeWitness.getHash())).orElse("accountAgeWitnessOptional not present"));
+ Utilities.bytesAsHexString(accountAgeWitness.getHash()));
return limit;
} else {
return maxTradeLimit.value;
}
}
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Mature witness checks
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private boolean isImmature(AccountAgeWitness accountAgeWitness) {
+ return accountAgeWitness.getDate() > SAFE_ACCOUNT_AGE_DATE &&
+ getWitnessSignAge(accountAgeWitness, new Date()) < 0;
+ }
+
+ public boolean isMyAccountAgeImmature(PaymentAccount myPaymentAccount) {
+ return isImmature(getMyWitness(myPaymentAccount.getPaymentAccountPayload()));
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// My witness
@@ -264,44 +376,26 @@ public long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) {
return getAccountAge(getMyWitness(paymentAccountPayload), new Date());
}
- public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode) {
+ public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction
+ direction) {
if (paymentAccount == null)
return 0;
- Optional witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload()));
- return getTradeLimit(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode),
- currencyCode,
- witnessOptional,
- new Date());
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Peers witness
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- // Return -1 if witness data is not found (old versions)
- public long getMakersAccountAge(Offer offer, Date peersCurrentDate) {
- final Optional accountAgeWitnessHash = offer.getAccountAgeWitnessHashAsHex();
- final Optional witnessByHashAsHex = accountAgeWitnessHash.isPresent() ?
- getWitnessByHashAsHex(accountAgeWitnessHash.get()) :
- Optional.empty();
- return witnessByHashAsHex
- .map(accountAgeWitness -> getAccountAge(accountAgeWitness, peersCurrentDate))
- .orElse(-1L);
- }
-
- public long getTradingPeersAccountAge(Trade trade) {
- TradingPeer tradingPeer = trade.getProcessModel().getTradingPeer();
- if (tradingPeer.getPaymentAccountPayload() == null || tradingPeer.getPubKeyRing() == null) {
- // unexpected
- return -1;
+ AccountAgeWitness accountAgeWitness = getMyWitness(paymentAccount.getPaymentAccountPayload());
+ Coin maxTradeLimit = paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode);
+ if (!isImmature(accountAgeWitness)) {
+ return maxTradeLimit.value;
}
+ final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
+ AccountAge accountAgeCategory = getAccountAgeCategory(accountSignAge);
- return getAccountAge(tradingPeer.getPaymentAccountPayload(), tradingPeer.getPubKeyRing());
+ return getTradeLimit(maxTradeLimit,
+ currencyCode,
+ accountAgeWitness,
+ accountAgeCategory,
+ direction);
}
-
///////////////////////////////////////////////////////////////////////////////////////////
// Verification
///////////////////////////////////////////////////////////////////////////////////////////
@@ -343,7 +437,8 @@ public boolean verifyAccountAgeWitness(Trade trade,
return false;
// Check if the peers trade limit is not less than the trade amount
- if (!verifyPeersTradeLimit(trade, peersWitness, peersCurrentDate, errorMessageHandler)) {
+ if (!verifyPeersTradeLimit(trade.getOffer(), trade.getTradeAmount(), peersWitness, peersCurrentDate,
+ errorMessageHandler)) {
log.error("verifyPeersTradeLimit failed: peersPaymentAccountPayload {}", peersPaymentAccountPayload);
return false;
}
@@ -351,12 +446,22 @@ public boolean verifyAccountAgeWitness(Trade trade,
return verifySignature(peersPubKeyRing.getSignaturePubKey(), nonce, signature, errorMessageHandler);
}
+ public boolean verifyPeersTradeAmount(Offer offer,
+ Coin tradeAmount,
+ ErrorMessageHandler errorMessageHandler) {
+ checkNotNull(offer);
+ return findWitness(offer)
+ .map(witness -> verifyPeersTradeLimit(offer, tradeAmount, witness, new Date(), errorMessageHandler))
+ .orElse(false);
+ }
///////////////////////////////////////////////////////////////////////////////////////////
// Package scope verification subroutines
///////////////////////////////////////////////////////////////////////////////////////////
- boolean isDateAfterReleaseDate(long witnessDateAsLong, Date ageWitnessReleaseDate, ErrorMessageHandler errorMessageHandler) {
+ boolean isDateAfterReleaseDate(long witnessDateAsLong,
+ Date ageWitnessReleaseDate,
+ ErrorMessageHandler errorMessageHandler) {
// Release date minus 1 day as tolerance for not synced clocks
Date releaseDateWithTolerance = new Date(ageWitnessReleaseDate.getTime() - TimeUnit.DAYS.toMillis(1));
final Date witnessDate = new Date(witnessDateAsLong);
@@ -394,16 +499,23 @@ private boolean verifyWitnessHash(byte[] witnessHash,
return result;
}
- private boolean verifyPeersTradeLimit(Trade trade,
+ private boolean verifyPeersTradeLimit(Offer offer,
+ Coin tradeAmount,
AccountAgeWitness peersWitness,
Date peersCurrentDate,
ErrorMessageHandler errorMessageHandler) {
- Offer offer = trade.getOffer();
- Coin tradeAmount = checkNotNull(trade.getTradeAmount());
checkNotNull(offer);
final String currencyCode = offer.getCurrencyCode();
final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode);
- long peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, Optional.of(peersWitness), peersCurrentDate);
+ long peersCurrentTradeLimit = defaultMaxTradeLimit.value;
+ if (isImmature(peersWitness)) {
+ final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate);
+ AccountAge accountAgeCategory = getPeersAccountAgeCategory(accountSignAge);
+ OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
+ offer.getMirroredDirection() : offer.getDirection();
+ peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
+ accountAgeCategory, direction);
+ }
// Makers current trade limit cannot be smaller than that in the offer
boolean result = tradeAmount.value <= peersCurrentTradeLimit;
if (!result) {
@@ -436,4 +548,107 @@ boolean verifySignature(PublicKey peersPublicKey,
}
return result;
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Witness signing
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ public SignedWitness arbitratorSignAccountAgeWitness(Coin tradeAmount,
+ AccountAgeWitness accountAgeWitness,
+ ECKey key,
+ PublicKey peersPubKey) {
+ return signedWitnessService.signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey);
+ }
+
+ public void traderSignPeersAccountAgeWitness(Trade trade) {
+ AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
+ Coin tradeAmount = trade.getTradeAmount();
+ checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
+ PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey();
+ checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", trade.toString());
+ checkNotNull(tradeAmount, "Trade amount must not be null");
+ checkNotNull(peersPubKey, "Peers pub key must not be null");
+
+ try {
+ signedWitnessService.signAccountAgeWitness(tradeAmount, peersWitness, peersPubKey);
+ } catch (CryptoException e) {
+ log.warn("Trader failed to sign witness, exception {}", e.toString());
+ }
+ }
+
+ // Arbitrator signing
+ public List getTraderPaymentAccounts(long safeDate, PaymentMethod paymentMethod,
+ List disputes) {
+ return disputes.stream()
+ .filter(dispute -> dispute.getContract().getPaymentMethodId().equals(paymentMethod.getId()))
+ .filter(this::hasChargebackRisk)
+ .filter(this::isBuyerWinner)
+ .flatMap(this::getTraderData)
+ .filter(traderDataItem ->
+ !signedWitnessService.isValidAccountAgeWitness(traderDataItem.getAccountAgeWitness()))
+ .filter(traderDataItem -> traderDataItem.getAccountAgeWitness().getDate() < safeDate)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ private boolean hasChargebackRisk(Dispute dispute) {
+ return chargeBackRisk.hasChargebackRisk(dispute.getContract().getPaymentMethodId(),
+ dispute.getContract().getOfferPayload().getCurrencyCode());
+ }
+
+ private boolean isBuyerWinner(Dispute dispute) {
+ if (!dispute.isClosed() || dispute.getDisputeResultProperty() == null)
+ return false;
+ return dispute.getDisputeResultProperty().get().getWinner() == DisputeResult.Winner.BUYER;
+ }
+
+ private Stream getTraderData(Dispute dispute) {
+ Coin tradeAmount = dispute.getContract().getTradeAmount();
+
+ PubKeyRing buyerPubKeyRing = dispute.getContract().getBuyerPubKeyRing();
+ PubKeyRing sellerPubKeyRing = dispute.getContract().getSellerPubKeyRing();
+
+ PaymentAccountPayload buyerPaymentAccountPaload = dispute.getContract().getBuyerPaymentAccountPayload();
+ PaymentAccountPayload sellerPaymentAccountPaload = dispute.getContract().getSellerPaymentAccountPayload();
+
+ TraderDataItem buyerData = findWitness(buyerPaymentAccountPaload, buyerPubKeyRing)
+ .map(witness -> new TraderDataItem(
+ buyerPaymentAccountPaload,
+ witness,
+ tradeAmount,
+ sellerPubKeyRing.getSignaturePubKey()))
+ .orElse(null);
+ TraderDataItem sellerData = findWitness(sellerPaymentAccountPaload, sellerPubKeyRing)
+ .map(witness -> new TraderDataItem(
+ sellerPaymentAccountPaload,
+ witness,
+ tradeAmount,
+ buyerPubKeyRing.getSignaturePubKey()))
+ .orElse(null);
+ return Stream.of(buyerData, sellerData);
+ }
+
+ // Check if my account has a signed witness
+ public boolean myHasSignedWitness(PaymentAccountPayload paymentAccountPayload) {
+ return signedWitnessService.isValidAccountAgeWitness(getMyWitness(paymentAccountPayload));
+ }
+
+ public boolean hasSignedWitness(Offer offer) {
+ return findWitness(offer)
+ .map(signedWitnessService::isValidAccountAgeWitness)
+ .orElse(false);
+ }
+
+ public boolean peerHasSignedWitness(Trade trade) {
+ return findTradePeerWitness(trade)
+ .map(signedWitnessService::isValidAccountAgeWitness)
+ .orElse(false);
+ }
+
+ public boolean accountIsSigner(AccountAgeWitness accountAgeWitness) {
+ if (signedWitnessService.isSignedByArbitrator(accountAgeWitness)) {
+ return true;
+ }
+ return getWitnessSignAge(accountAgeWitness, new Date()) > SignedWitnessService.SIGNER_AGE;
+ }
}
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..798ae449c5f 100644
--- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
@@ -33,13 +33,11 @@
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
-import org.bitcoinj.core.Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
-import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput;
@@ -72,46 +70,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 +136,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 +169,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 +191,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 +247,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 +263,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 +284,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 +325,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 +342,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 +404,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 +428,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 +437,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 +473,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 +486,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 +523,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 +539,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 +569,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 +600,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 +617,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 +735,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 +746,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 +777,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 +790,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 +821,7 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Mediation
+ // Mediated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////
public byte[] signMediatedPayoutTx(Transaction depositTx,
@@ -808,38 +831,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 +857,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 +883,7 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx,
///////////////////////////////////////////////////////////////////////////////////////////
- // Arbitration
+ // Arbitrated payoutTx
///////////////////////////////////////////////////////////////////////////////////////////
/**
@@ -933,39 +913,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 +967,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 +1003,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 +1045,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 +1070,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;
}
@@ -1165,36 +1166,6 @@ public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback, int tim
// Misc
///////////////////////////////////////////////////////////////////////////////////////////
- /**
- * @param transaction The transaction to be added to the wallet
- * @return The transaction we added to the wallet, which is different as the one we passed as argument!
- * @throws VerificationException
- */
- 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);
-
- if (wallet != null)
- wallet.receivePending(result, null, true);
- return result;
- }
-
- /**
- * @param serializedTransaction The serialized transaction to be added to the wallet
- * @return The transaction we added to the wallet, which is different as the one we passed as argument!
- * @throws VerificationException
- */
- public Transaction addTxToWallet(byte[] serializedTransaction) throws VerificationException {
- // We need to recreate the tx otherwise we get a null pointer...
- Transaction transaction = new Transaction(params, serializedTransaction);
- transaction.getConfidence(Context.get()).setSource(TransactionConfidence.Source.NETWORK);
-
- if (wallet != null)
- wallet.receivePending(transaction, null, true);
- return transaction;
- }
-
/**
* @param txId The transaction ID of the transaction we want to lookup
* @return Returns local existing wallet transaction
@@ -1219,31 +1190,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 +1228,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 +1237,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 +1271,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 +1290,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 +1298,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 +1311,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/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
index 62822c4a0ad..d5749bae08b 100644
--- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
@@ -34,6 +34,7 @@
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
@@ -43,6 +44,7 @@
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
+import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter;
@@ -102,6 +104,7 @@ public abstract class WalletService {
protected final CopyOnWriteArraySet addressConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet txConfidenceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet balanceListeners = new CopyOnWriteArraySet<>();
+ @Getter
protected Wallet wallet;
@Getter
protected KeyParameter aesKey;
@@ -223,7 +226,9 @@ public static void checkAllScriptSignaturesForTx(Transaction transaction) throws
}
}
- public static void checkScriptSig(Transaction transaction, TransactionInput input, int inputIndex) throws TransactionVerificationException {
+ public static void checkScriptSig(Transaction transaction,
+ TransactionInput input,
+ int inputIndex) throws TransactionVerificationException {
try {
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
@@ -245,7 +250,11 @@ public static void removeSignatures(Transaction transaction) {
// Sign tx
///////////////////////////////////////////////////////////////////////////////////////////
- public static void signTransactionInput(Wallet wallet, KeyParameter aesKey, Transaction tx, TransactionInput txIn, int index) {
+ public static void signTransactionInput(Wallet wallet,
+ KeyParameter aesKey,
+ Transaction tx,
+ TransactionInput txIn,
+ int index) {
KeyBag maybeDecryptingKeyBag = new DecryptingKeyBag(wallet, aesKey);
if (txIn.getConnectedOutput() != null) {
try {
@@ -475,7 +484,10 @@ public boolean isAddressUnused(Address address) {
// Empty complete Wallet
///////////////////////////////////////////////////////////////////////////////////////////
- public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
+ public void emptyWallet(String toAddress,
+ KeyParameter aesKey,
+ ResultHandler resultHandler,
+ ErrorMessageHandler errorMessageHandler)
throws InsufficientMoneyException, AddressFormatException {
SendRequest sendRequest = SendRequest.emptyWallet(Address.fromBase58(params, toAddress));
sendRequest.fee = Coin.ZERO;
@@ -675,6 +687,46 @@ public static String getAddressStringFromOutput(TransactionOutput output) {
}
+ /**
+ * @param serializedTransaction The serialized transaction to be added to the wallet
+ * @return The transaction we added to the wallet, which is different as the one we passed as argument!
+ * @throws VerificationException
+ */
+ public static Transaction maybeAddTxToWallet(byte[] serializedTransaction,
+ Wallet wallet,
+ TransactionConfidence.Source source) throws VerificationException {
+ Transaction tx = new Transaction(wallet.getParams(), serializedTransaction);
+ Transaction walletTransaction = wallet.getTransaction(tx.getHash());
+ log.error("maybeAddTxToWallet id={}, is walletTransaction==null? {}", tx.getHashAsString(), walletTransaction == null);
+
+ if (walletTransaction == null) {
+ // We need to recreate the transaction otherwise we get a null pointer...
+ tx.getConfidence(Context.get()).setSource(source);
+ //wallet.maybeCommitTx(tx);
+ wallet.receivePending(tx, null, true);
+ return tx;
+ } else {
+ return walletTransaction;
+ }
+ }
+
+ public static Transaction maybeAddNetworkTxToWallet(byte[] serializedTransaction,
+ Wallet wallet) throws VerificationException {
+ return maybeAddTxToWallet(serializedTransaction, wallet, TransactionConfidence.Source.NETWORK);
+ }
+
+ public static Transaction maybeAddSelfTxToWallet(Transaction transaction,
+ Wallet wallet) throws VerificationException {
+ return maybeAddTxToWallet(transaction, wallet, TransactionConfidence.Source.SELF);
+ }
+
+ public static Transaction maybeAddTxToWallet(Transaction transaction,
+ Wallet wallet,
+ TransactionConfidence.Source source) throws VerificationException {
+ return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source);
+ }
+
+
///////////////////////////////////////////////////////////////////////////////////////////
// bisqWalletEventListener
///////////////////////////////////////////////////////////////////////////////////////////
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/OfferRestrictions.java b/core/src/main/java/bisq/core/offer/OfferRestrictions.java
index d7b64580bd5..856b7a6e206 100644
--- a/core/src/main/java/bisq/core/offer/OfferRestrictions.java
+++ b/core/src/main/java/bisq/core/offer/OfferRestrictions.java
@@ -17,9 +17,6 @@
package bisq.core.offer;
-import bisq.core.payment.payload.PaymentMethod;
-import bisq.core.trade.Trade;
-
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
import bisq.common.util.Utilities;
@@ -41,38 +38,6 @@ static boolean requiresUpdate() {
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");
- public static boolean isOfferRisky(Offer offer) {
- return offer != null &&
- offer.isBuyOffer() &&
- PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
- isMinTradeAmountRisky(offer);
- }
-
- public static boolean isSellOfferRisky(Offer offer) {
- return offer != null &&
- PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
- isMinTradeAmountRisky(offer);
- }
-
- public static boolean isTradeRisky(Trade trade) {
- if (trade == null)
- return false;
-
- Offer offer = trade.getOffer();
- return offer != null &&
- PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) &&
- trade.getTradeAmount() != null &&
- isAmountRisky(trade.getTradeAmount());
- }
-
- public static boolean isMinTradeAmountRisky(Offer offer) {
- return isAmountRisky(offer.getMinAmount());
- }
-
- private static boolean isAmountRisky(Coin amount) {
- return amount.isGreaterThan(TOLERATED_SMALL_TRADE_AMOUNT);
- }
-
static boolean hasOfferMandatoryCapability(Offer offer, Capability mandatoryCapability) {
Map extraDataMap = offer.getOfferPayload().getExtraDataMap();
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.CAPABILITIES)) {
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/payment/PaymentAccountUtil.java b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
index b4bba4f4e76..c4d5e545371 100644
--- a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
+++ b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
@@ -17,11 +17,9 @@
package bisq.core.payment;
-import bisq.core.account.witness.AccountAgeRestrictions;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.Country;
import bisq.core.offer.Offer;
-import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.payload.PaymentMethod;
import javafx.collections.FXCollections;
@@ -41,57 +39,8 @@
@Slf4j
public class PaymentAccountUtil {
- public static boolean isRiskyBuyOfferWithImmatureAccountAge(Offer offer, AccountAgeWitnessService accountAgeWitnessService) {
- return OfferRestrictions.isOfferRisky(offer) &&
- AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer);
- }
-
- public static boolean isSellOfferAndAllTakerPaymentAccountsForOfferImmature(Offer offer,
- Collection takerPaymentAccounts,
- AccountAgeWitnessService accountAgeWitnessService) {
- if (offer.isBuyOffer()) {
- return false;
- }
-
- if (!OfferRestrictions.isSellOfferRisky(offer)) {
- return false;
- }
-
- for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
- if (isTakerAccountForOfferMature(offer, takerPaymentAccount, accountAgeWitnessService))
- return false;
- }
- return true;
- }
-
- private static boolean isTakerAccountForOfferMature(Offer offer,
- PaymentAccount takerPaymentAccount,
- AccountAgeWitnessService accountAgeWitnessService) {
- return !PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode()) ||
- !OfferRestrictions.isMinTradeAmountRisky(offer) ||
- (isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount) &&
- !AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, takerPaymentAccount));
- }
-
- public static boolean hasMakerAnyMatureAccountForBuyOffer(Collection makerPaymentAccounts,
- AccountAgeWitnessService accountAgeWitnessService) {
- for (PaymentAccount makerPaymentAccount : makerPaymentAccounts) {
- if (hasMyMatureAccountForBuyOffer(makerPaymentAccount, accountAgeWitnessService))
- return true;
- }
- return false;
- }
-
- private static boolean hasMyMatureAccountForBuyOffer(PaymentAccount myPaymentAccount,
- AccountAgeWitnessService accountAgeWitnessService) {
- if (myPaymentAccount.selectedTradeCurrency == null)
- return false;
- return !PaymentMethod.hasChargebackRisk(myPaymentAccount.getPaymentMethod(),
- myPaymentAccount.selectedTradeCurrency.getCode()) ||
- !AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, myPaymentAccount);
- }
-
- public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer, Collection takerPaymentAccounts) {
+ public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer,
+ Collection takerPaymentAccounts) {
for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
if (isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount))
return true;
@@ -105,11 +54,21 @@ public static ObservableList getPossiblePaymentAccounts(Offer of
ObservableList result = FXCollections.observableArrayList();
result.addAll(paymentAccounts.stream()
.filter(paymentAccount -> isTakerPaymentAccountValidForOffer(offer, paymentAccount))
- .filter(paymentAccount -> offer.isBuyOffer() || isTakerAccountForOfferMature(offer, paymentAccount, accountAgeWitnessService))
+ .filter(paymentAccount -> isAmountValidForOffer(offer, paymentAccount, accountAgeWitnessService))
.collect(Collectors.toList()));
return result;
}
+ // Return true if paymentAccount can take this offer
+ public static boolean isAmountValidForOffer(Offer offer,
+ PaymentAccount paymentAccount,
+ AccountAgeWitnessService accountAgeWitnessService) {
+ boolean hasChargebackRisk = PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
+ boolean hasValidAccountAgeWitness = accountAgeWitnessService.getMyTradeLimit(paymentAccount,
+ offer.getCurrencyCode(), offer.getMirroredDirection()) > offer.getAmount().value;
+ return !hasChargebackRisk || hasValidAccountAgeWitness;
+ }
+
// TODO might be used to show more details if we get payment methods updates with diff. limits
public static String getInfoForMismatchingPaymentMethodLimits(Offer offer, PaymentAccount paymentAccount) {
// dont translate atm as it is not used so far in the UI just for logs
diff --git a/core/src/main/java/bisq/core/payment/PaymentAccounts.java b/core/src/main/java/bisq/core/payment/PaymentAccounts.java
index e6fe3a32bb4..3a04d637d42 100644
--- a/core/src/main/java/bisq/core/payment/PaymentAccounts.java
+++ b/core/src/main/java/bisq/core/payment/PaymentAccounts.java
@@ -37,17 +37,17 @@ class PaymentAccounts {
private static final Logger log = LoggerFactory.getLogger(PaymentAccounts.class);
private final Set accounts;
- private final AccountAgeWitnessService service;
+ private final AccountAgeWitnessService accountAgeWitnessService;
private final BiFunction validator;
- PaymentAccounts(Set accounts, AccountAgeWitnessService service) {
- this(accounts, service, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
+ PaymentAccounts(Set accounts, AccountAgeWitnessService accountAgeWitnessService) {
+ this(accounts, accountAgeWitnessService, PaymentAccountUtil::isTakerPaymentAccountValidForOffer);
}
- PaymentAccounts(Set accounts, AccountAgeWitnessService service,
+ PaymentAccounts(Set accounts, AccountAgeWitnessService accountAgeWitnessService,
BiFunction validator) {
this.accounts = accounts;
- this.service = service;
+ this.accountAgeWitnessService = accountAgeWitnessService;
this.validator = validator;
}
@@ -61,7 +61,7 @@ PaymentAccount getOldestPaymentAccountForOffer(Offer offer) {
}
private List sortValidAccounts(Offer offer) {
- Comparator comparator = this::compareByAge;
+ Comparator comparator = this::compareBySignAgeOrMatureAccount;
return accounts.stream()
.filter(account -> validator.apply(offer, account))
.sorted(comparator.reversed())
@@ -78,7 +78,7 @@ private void logAccounts(List accounts) {
StringBuilder message = new StringBuilder("Valid accounts: \n");
for (PaymentAccount account : accounts) {
String accountName = account.getAccountName();
- String witnessHex = service.getMyWitnessHashAsHex(account.getPaymentAccountPayload());
+ String witnessHex = accountAgeWitnessService.getMyWitnessHashAsHex(account.getPaymentAccountPayload());
message.append("name = ")
.append(accountName)
@@ -91,15 +91,24 @@ private void logAccounts(List accounts) {
}
}
- private int compareByAge(PaymentAccount left, PaymentAccount right) {
- AccountAgeWitness leftWitness = service.getMyWitness(left.getPaymentAccountPayload());
- AccountAgeWitness rightWitness = service.getMyWitness(right.getPaymentAccountPayload());
+ // Accounts created before
+ private int compareBySignAgeOrMatureAccount(PaymentAccount left, PaymentAccount right) {
+ // Mature accounts count as infinite sign age
+ if (!accountAgeWitnessService.isMyAccountAgeImmature(left)) {
+ return accountAgeWitnessService.isMyAccountAgeImmature(right) ? 1 : 0;
+ }
+ if (!accountAgeWitnessService.isMyAccountAgeImmature(right)) {
+ return -1;
+ }
+
+ AccountAgeWitness leftWitness = accountAgeWitnessService.getMyWitness(left.getPaymentAccountPayload());
+ AccountAgeWitness rightWitness = accountAgeWitnessService.getMyWitness(right.getPaymentAccountPayload());
Date now = new Date();
- long leftAge = service.getAccountAge(leftWitness, now);
- long rightAge = service.getAccountAge(rightWitness, now);
+ long leftSignAge = accountAgeWitnessService.getWitnessSignAge(leftWitness, now);
+ long rightSignAge = accountAgeWitnessService.getWitnessSignAge(rightWitness, now);
- return Long.compare(leftAge, rightAge);
+ return Long.compare(leftSignAge, rightSignAge);
}
}
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/CoreNetworkCapabilities.java b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
index 757f1f09df7..09181eebf73 100644
--- a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
+++ b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
@@ -37,7 +37,8 @@ static void setSupportedCapabilities(BisqEnvironment bisqEnvironment) {
Capability.BLIND_VOTE,
Capability.DAO_STATE,
Capability.BUNDLE_OF_ENVELOPES,
- Capability.MEDIATION
+ Capability.MEDIATION,
+ Capability.SIGNED_ACCOUNT_AGE_WITNESS
);
if (BisqEnvironment.isDaoActivated(bisqEnvironment)) {
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..53adb7cd998 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,8 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
+import bisq.core.btc.wallet.WalletService;
+import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.support.SupportType;
@@ -140,6 +142,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 +161,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 +239,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(),
@@ -243,7 +252,7 @@ else if (publisher == DisputeResult.Winner.SELLER)
contract.getSellerMultiSigPubKey(),
disputeResult.getArbitratorPubKey()
);
- Transaction committedDisputedPayoutTx = tradeWalletService.addTxToWallet(signedDisputedPayoutTx);
+ Transaction committedDisputedPayoutTx = WalletService.maybeAddSelfTxToWallet(signedDisputedPayoutTx, btcWalletService.getWallet());
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
@@ -328,9 +337,11 @@ private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerP
PubKeyRing peersPubKeyRing = isBuyer ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
cleanupRetryMap(uid);
- Transaction walletTx = tradeWalletService.addTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction());
- dispute.setDisputePayoutTxId(walletTx.getHashAsString());
- BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx);
+
+ Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
+
+ dispute.setDisputePayoutTxId(committedDisputePayoutTx.getHashAsString());
+ BtcWalletService.printTx("Disputed payoutTx received from peer", committedDisputePayoutTx);
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/BuyerDataItem.java b/core/src/main/java/bisq/core/support/dispute/arbitration/TraderDataItem.java
similarity index 80%
rename from core/src/main/java/bisq/core/support/dispute/arbitration/BuyerDataItem.java
rename to core/src/main/java/bisq/core/support/dispute/arbitration/TraderDataItem.java
index 7962a4e074f..1ae4cc16462 100644
--- a/core/src/main/java/bisq/core/support/dispute/arbitration/BuyerDataItem.java
+++ b/core/src/main/java/bisq/core/support/dispute/arbitration/TraderDataItem.java
@@ -30,20 +30,20 @@
// TODO consider to move to signed witness domain
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
-public class BuyerDataItem {
+public class TraderDataItem {
private final PaymentAccountPayload paymentAccountPayload;
@EqualsAndHashCode.Include
private final AccountAgeWitness accountAgeWitness;
private final Coin tradeAmount;
- private final PublicKey sellerPubKey;
+ private final PublicKey peersPubKey;
- public BuyerDataItem(PaymentAccountPayload paymentAccountPayload,
- AccountAgeWitness accountAgeWitness,
- Coin tradeAmount,
- PublicKey sellerPubKey) {
+ public TraderDataItem(PaymentAccountPayload paymentAccountPayload,
+ AccountAgeWitness accountAgeWitness,
+ Coin tradeAmount,
+ PublicKey peersPubKey) {
this.paymentAccountPayload = paymentAccountPayload;
this.accountAgeWitness = accountAgeWitness;
this.tradeAmount = tradeAmount;
- this.sellerPubKey = sellerPubKey;
+ this.peersPubKey = peersPubKey;
}
}
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..d3612d95165 100644
--- a/core/src/main/java/bisq/core/trade/TradeManager.java
+++ b/core/src/main/java/bisq/core/trade/TradeManager.java
@@ -19,10 +19,14 @@
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.btc.wallet.WalletService;
+import bisq.core.dao.DaoFacade;
import bisq.core.filter.FilterManager;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
@@ -32,10 +36,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 +53,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 +89,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 +124,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 +160,8 @@ public TradeManager(User user,
AccountAgeWitnessService accountAgeWitnessService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
+ DaoFacade daoFacade,
ClockWatcher clockWatcher,
Storage> storage) {
this.user = user;
@@ -168,6 +180,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 +190,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 +321,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 +340,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 +378,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo
btcWalletService,
bsqWalletService,
tradeWalletService,
+ daoFacade,
this,
openOfferManager,
referralIdService,
@@ -371,6 +388,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo
tradeStatisticsManager,
arbitratorManager,
mediatorManager,
+ refundAgentManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
@@ -453,6 +471,7 @@ private void createTrade(Coin amount,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
+ model.getSelectedRefundAgent(),
tradableListStorage,
btcWalletService);
else
@@ -465,6 +484,7 @@ private void createTrade(Coin amount,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
model.getSelectedMediator(),
+ model.getSelectedRefundAgent(),
tradableListStorage,
btcWalletService);
@@ -567,6 +587,70 @@ 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
+
+ Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet());
+
+ tradeWalletService.broadcastTx(committedDelayedPayoutTx, 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..9e25fa1a0f8 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,29 @@ public class ProcessModel implements Model, PersistablePayload {
@Setter
transient private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
-
- // Persistable Immutable (only set by PB)
+ // Added in v1.2.0
@Setter
- private TradingPeer tradingPeer = new TradingPeer();
+ @Nullable
+ transient private byte[] delayedPayoutTxSignature;
@Setter
+ @Nullable
+ transient private Transaction preparedDelayedPayoutTx;
+
+ // Persistable Immutable (private setter only used by PB method)
+ private TradingPeer tradingPeer = new TradingPeer();
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 +187,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 +212,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 +239,7 @@ public void onAllServicesInitialized(Offer offer,
BtcWalletService walletService,
BsqWalletService bsqWalletService,
TradeWalletService tradeWalletService,
+ DaoFacade daoFacade,
ReferralIdService referralIdService,
User user,
FilterManager filterManager,
@@ -250,6 +247,7 @@ public void onAllServicesInitialized(Offer offer,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
+ RefundAgentManager refundAgentManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@@ -259,6 +257,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 +265,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 +339,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..f5090965295 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,42 @@
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.SellerVerifiesPeersAccountAge;
-import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerCreatesAndSignsDepositTx;
+import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
+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 +77,6 @@ public SellerAsMakerProtocol(SellerAsMakerTrade trade) {
() -> handleTaskRunnerSuccess("MakerSetupDepositTxListener"),
this::handleTaskRunnerFault);
- taskRunner.addTasks(MakerSetupDepositTxListener.class);
taskRunner.run();
}
}
@@ -84,23 +87,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 +101,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 +116,17 @@ 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 +135,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 +146,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 +264,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..fa4c4d3d0c5 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,46 @@
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.SellerVerifiesPeersAccountAge;
+import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
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 +72,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 +82,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 +106,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 +118,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,16 +130,37 @@ private void handle(PublishDepositTxRequest tradeMessage, NodeAddress sender) {
errorMessage -> handleTaskRunnerFault(tradeMessage, errorMessage));
taskRunner.addTasks(
- TakerProcessPublishDepositTxRequest.class,
+ TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
TakerVerifyMakerAccount.class,
VerifyPeersAccountAgeWitness.class,
- SellerVerifiesPeersAccountAge.class,
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 +254,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..d0dc9300530
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java
@@ -0,0 +1,64 @@
+/*
+ * 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.btc.wallet.WalletService;
+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());
+ WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
+
+ // 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..c9fe2e8b7ad
--- /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 preparedDelayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes);
+ processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
+
+ // 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 61%
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..7c0e3c7b3b2 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,13 @@
* 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.btc.wallet.WalletService;
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 +35,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 +45,24 @@ 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 committedDepositTx = WalletService.maybeAddSelfTxToWallet(depositTx, processModel.getBtcWalletService().getWallet());
+ trade.applyDepositTx(committedDepositTx);
+ BtcWalletService.printTx("depositTx received from peer", committedDepositTx);
+
+ // 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);
+
+ WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet());
// update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
@@ -63,8 +70,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/BuyerProcessPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java
index 6fc5f2d18c7..245dd40aacb 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessPayoutTxPublishedMessage.java
@@ -19,6 +19,7 @@
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.WalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
@@ -54,9 +55,9 @@ protected void run() {
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
if (trade.getPayoutTx() == null) {
- Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(message.getPayoutTx());
- trade.setPayoutTx(walletTx);
- BtcWalletService.printTx("payoutTx received from peer", walletTx);
+ Transaction committedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet());
+ trade.setPayoutTx(committedPayoutTx);
+ BtcWalletService.printTx("payoutTx received from peer", committedPayoutTx);
trade.setState(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
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..0d0bb585ae4
--- /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 preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
+ 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(preparedDelayedPayoutTx, 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/SellerVerifiesPeersAccountAge.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
similarity index 50%
rename from core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerVerifiesPeersAccountAge.java
rename to core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
index 3f2637328a4..3ec30a67786 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerVerifiesPeersAccountAge.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerVerifiesDelayedPayoutTx.java
@@ -15,22 +15,23 @@
* along with Bisq. If not, see .
*/
-package bisq.core.trade.protocol.tasks.seller;
+package bisq.core.trade.protocol.tasks.buyer;
-import bisq.core.account.witness.AccountAgeRestrictions;
-import bisq.core.offer.OfferRestrictions;
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 SellerVerifiesPeersAccountAge extends TradeTask {
+import static com.google.common.base.Preconditions.checkNotNull;
+@Slf4j
+public class BuyerVerifiesDelayedPayoutTx extends TradeTask {
@SuppressWarnings({"unused"})
- public SellerVerifiesPeersAccountAge(TaskRunner taskHandler, Trade trade) {
+ public BuyerVerifiesDelayedPayoutTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@@ -39,19 +40,12 @@ protected void run() {
try {
runInterceptHook();
- boolean isTradeRisky = OfferRestrictions.isTradeRisky(trade);
- boolean isTradePeersAccountAgeImmature = AccountAgeRestrictions.isTradePeersAccountAgeImmature(
- processModel.getAccountAgeWitnessService(), trade);
- log.debug("SellerVerifiesPeersAccountAge isOfferRisky={} isTradePeersAccountAgeImmature={}",
- isTradeRisky, isTradePeersAccountAgeImmature);
- if (isTradeRisky &&
- isTradePeersAccountAgeImmature) {
- failed("Violation of security restrictions:\n" +
- " - The peer's account was created after March 1st 2019\n" +
- " - The trade amount is above 0.01 BTC\n" +
- " - The payment method for that offer is considered risky for bank chargebacks\n");
- } else {
+ 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/ProcessMediatedPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java
index cb94c85d5a2..e6c60eb4a10 100644
--- a/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/mediation/ProcessMediatedPayoutTxPublishedMessage.java
@@ -19,6 +19,7 @@
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.WalletService;
import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
@@ -55,9 +56,9 @@ protected void run() {
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
if (trade.getPayoutTx() == null) {
- Transaction walletTx = processModel.getTradeWalletService().addTxToWallet(message.getPayoutTx());
- trade.setPayoutTx(walletTx);
- BtcWalletService.printTx("payoutTx received from peer", walletTx);
+ Transaction committedMediatedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet());
+ trade.setPayoutTx(committedMediatedPayoutTx);
+ BtcWalletService.printTx("MediatedPayoutTx received from peer", committedMediatedPayoutTx);
trade.setMediationResultState(MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
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..6bddc5d9d64
--- /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 preparedDelayedPayoutTx = tradeWalletService.createDelayedUnsignedPayoutTx(depositTx,
+ donationAddressString,
+ minerFee,
+ lockTime);
+
+ processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx);
+
+ 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..f615bad0c44
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerFinalizesDelayedPayoutTx.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.model.AddressEntry;
+import bisq.core.btc.wallet.BtcWalletService;
+import bisq.core.btc.wallet.WalletService;
+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 preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
+ 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(preparedDelayedPayoutTx,
+ buyerMultiSigPubKey,
+ sellerMultiSigPubKey,
+ buyerSignature,
+ sellerSignature);
+
+ trade.applyDelayedPayoutTx(signedDelayedPayoutTx);
+ WalletService.maybeAddSelfTxToWallet(signedDelayedPayoutTx, processModel.getBtcWalletService().getWallet());
+
+ 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..187c274304b
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendDelayedPayoutTxSignatureRequest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx(),
+ "processModel.getPreparedDelayedPayoutTx() must not be null");
+ DelayedPayoutTxSignatureRequest message = new DelayedPayoutTxSignatureRequest(UUID.randomUUID().toString(),
+ processModel.getOfferId(),
+ processModel.getMyNodeAddress(),
+ preparedDelayedPayoutTx.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..b8b8e155c2d
--- /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 preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx());
+ 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(preparedDelayedPayoutTx,
+ 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..75891395d22 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();
@@ -385,6 +387,12 @@ public ObservableSet getPaymentAccountsAsObservable() {
return paymentAccountsAsObservable;
}
+
+ /**
+ * If this user is an arbitrator it returns the registered arbitrator.
+ *
+ * @return The arbitrator registered for this user
+ */
@Nullable
public Arbitrator getRegisteredArbitrator() {
return userPayload.getRegisteredArbitrator();
@@ -395,20 +403,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 +448,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 +475,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..bb724a2e47f 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
@@ -329,6 +330,11 @@ offerbook.offerersAcceptedBankSeats=Accepted seat of bank countries (taker):\n {
offerbook.availableOffers=Available offers
offerbook.filterByCurrency=Filter by currency
offerbook.filterByPaymentMethod=Filter by payment method
+offerbook.timeSinceSigning=Time since signing
+offerbook.timeSinceSigning.help=By trading with a payment account that was verified by an arbitrator or a peer, your account gets signed as well.\n\
+ 30 days later the initial limit of 0.01 BTC gets lifted and after 90 days your account can sign other peers as well.
+offerbook.timeSinceSigning.notSigned=Not signed yet
+shared.notSigned=This account hasn't been signed yet.
offerbook.nrOffers=No. of offers: {0}
offerbook.volume={0} (min - max)
@@ -364,6 +370,8 @@ offerbook.warning.sellOfferAndAnyTakerPaymentAccountForOfferMature=This offer ca
- The minimum trade amount is above 0.01 BTC\n\
- The payment method for this offer is considered risky for bank chargebacks\n\n{0}
+offerbook.warning.counterpartyTradeRestrictions=This offer cannot be taken due to counterparty trade restrictions
+
offerbook.warning.newVersionAnnouncement=We needed to deploy this restriction as a short-term measure for enhanced security.\n\n\
The next software release will provide more robust protection tools so that offers with this risk profile can be traded again.
@@ -705,9 +713,11 @@ portfolio.pending.step3_seller.onPaymentReceived.part1=Have you received the {0}
portfolio.pending.step3_seller.onPaymentReceived.fiat=The trade ID (\"reason for payment\" text) of the transaction is: \"{0}\"\n\n
# suppress inspection "TrailingSpacesInProperty"
portfolio.pending.step3_seller.onPaymentReceived.name=Please also verify that the sender's name in your bank statement matches that one from the trade contract:\nSender's name: {0}\n\nIf the name is not the same as the one displayed here, please don't confirm but open a dispute by entering \"alt + o\" or \"option + o\".\n\n
-portfolio.pending.step3_seller.onPaymentReceived.note=Please note, that as soon you have confirmed the receipt, the locked trade amount will be released to the BTC buyer and the security deposit will be refunded.
+portfolio.pending.step3_seller.onPaymentReceived.note=Please note, that as soon you have confirmed the receipt, the locked trade amount will be released to the BTC buyer and the security deposit will be refunded.\n\n
portfolio.pending.step3_seller.onPaymentReceived.confirm.headline=Confirm that you have received the payment
portfolio.pending.step3_seller.onPaymentReceived.confirm.yes=Yes, I have received the payment
+portfolio.pending.step3_seller.onPaymentReceived.signer=By confirming receipt of payment you verify that the \
+ counterparty has acted according to the trade protocol.
portfolio.pending.step5_buyer.groupTitle=Summary of completed trade
portfolio.pending.step5_buyer.tradeFee=Trade fee
@@ -716,6 +726,8 @@ portfolio.pending.step5_buyer.takersMiningFee=Total mining fees
portfolio.pending.step5_buyer.refunded=Refunded security deposit
portfolio.pending.step5_buyer.withdrawBTC=Withdraw your bitcoin
portfolio.pending.step5_buyer.amount=Amount to withdraw
+portfolio.pending.step5_buyer.signer=By withdrawing your bitcoins you verify that the \
+ counterparty has acted according to the trade protocol.
portfolio.pending.step5_buyer.withdrawToAddress=Withdraw to address
portfolio.pending.step5_buyer.moveToBisqWallet=Move funds to Bisq wallet
portfolio.pending.step5_buyer.withdrawExternal=Withdraw to external wallet
@@ -754,6 +766,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 +781,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 +817,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 +893,9 @@ 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.timeLockedPayoutTx=Time locked payout tx: {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 +923,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 +992,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 +1125,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 +2228,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 +2262,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)
@@ -2483,6 +2514,22 @@ popup.dao.launch.cheaperFees.title=Cheaper fees
# suppress inspection "TrailingSpacesInProperty"
popup.dao.launch.cheaperFees=Get a 90% discount on trading fees when you use BSQ. Save money and support the project at the same time!\n\n
+popup.accountSigning.selectAccounts.headline=Select payment accounts
+popup.accountSigning.selectAccounts.description=Based on the payment method and point of time all payment accounts that are connected to a dispute where a payout to the buyer occurred will be selected for you to sign.
+popup.accountSigning.selectAccounts.datePicker=Select point of time until which accounts will be signed
+
+popup.accountSigning.confirmSelectedAccounts.headline=Confirm selected payment accounts
+popup.accountSigning.confirmSelectedAccounts.description=Based on your input, {0} payment accounts will be selected.
+popup.accountSigning.confirmSelectedAccounts.button=Confirm payment accounts
+popup.accountSigning.signAccounts.headline=Confirm signing of payment accounts
+popup.accountSigning.signAccounts.description=Based on your selection, {0} payment accounts will be signed.
+popup.accountSigning.signAccounts.button=Sign payment accounts
+popup.accountSigning.signAccounts.ECKey=Enter private arbitrator key
+popup.accountSigning.signAccounts.ECKey.error=Bad arbitrator ECKey
+
+popup.accountSigning.success.headline=Congratulations
+popup.accountSigning.success.description=All {0} payment accounts were successfully signed!
+
####################################################################
# Notifications
####################################################################
@@ -2749,6 +2796,7 @@ payment.accepted.banks=Accepted banks (ID)
payment.mobile=Mobile no.
payment.postal.address=Postal address
payment.national.account.id.AR=CBU number
+shared.accountSigningState=Account signing status
#new
payment.altcoin.address.dyn={0} address
@@ -2757,7 +2805,7 @@ payment.accountNr=Account number
payment.emailOrMobile=Email or mobile nr
payment.useCustomAccountName=Use custom account name
payment.maxPeriod=Max. allowed trade period
-payment.maxPeriodAndLimit=Max. trade duration: {0} / Max. trade limit: {1} / Account age: {2}
+payment.maxPeriodAndLimit=Max. trade duration: {0} / Max. buy: {1} / Max. sell: {2} / Account age: {3}
payment.maxPeriodAndLimitCrypto=Max. trade duration: {0} / Max. trade limit: {1}
payment.currencyWithSymbol=Currency: {0}
payment.nameOfAcceptedBank=Name of accepted bank
diff --git a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java
index 941a8996c3c..67bafddde1e 100644
--- a/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java
+++ b/core/src/test/java/bisq/core/account/sign/SignedWitnessServiceTest.java
@@ -70,6 +70,10 @@ public class SignedWitnessServiceTest {
private long tradeAmount1;
private long tradeAmount2;
private long tradeAmount3;
+ private long SIGN_AGE_1 = SignedWitnessService.SIGNER_AGE_DAYS * 3 + 5;
+ private long SIGN_AGE_2 = SignedWitnessService.SIGNER_AGE_DAYS * 2 + 4;
+ private long SIGN_AGE_3 = SignedWitnessService.SIGNER_AGE_DAYS + 3;
+
@Before
public void setup() throws Exception {
@@ -77,13 +81,13 @@ public void setup() throws Exception {
ArbitratorManager arbitratorManager = mock(ArbitratorManager.class);
ArbitrationManager arbitrationManager = mock(ArbitrationManager.class);
when(arbitratorManager.isPublicKeyInList(any())).thenReturn(true);
- signedWitnessService = new SignedWitnessService(null, null, null, arbitratorManager, null, appendOnlyDataStoreService, arbitrationManager, null);
+ signedWitnessService = new SignedWitnessService(null, null, arbitratorManager, null, appendOnlyDataStoreService);
account1DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{1});
account2DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{2});
account3DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{3});
- long account1CreationTime = getTodayMinusNDays(96);
- long account2CreationTime = getTodayMinusNDays(66);
- long account3CreationTime = getTodayMinusNDays(36);
+ long account1CreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1);
+ long account2CreationTime = getTodayMinusNDays(SIGN_AGE_2 + 1);
+ long account3CreationTime = getTodayMinusNDays(SIGN_AGE_3 + 1);
aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime);
aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime);
aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime);
@@ -94,9 +98,9 @@ public void setup() throws Exception {
signature1 = arbitrator1Key.signMessage(Utilities.encodeToHex(account1DataHash)).getBytes(Charsets.UTF_8);
signature2 = Sig.sign(peer1KeyPair.getPrivate(), Utilities.encodeToHex(account2DataHash).getBytes(Charsets.UTF_8));
signature3 = Sig.sign(peer2KeyPair.getPrivate(), Utilities.encodeToHex(account3DataHash).getBytes(Charsets.UTF_8));
- date1 = getTodayMinusNDays(95);
- date2 = getTodayMinusNDays(64);
- date3 = getTodayMinusNDays(33);
+ date1 = getTodayMinusNDays(SIGN_AGE_1);
+ date2 = getTodayMinusNDays(SIGN_AGE_2);
+ date3 = getTodayMinusNDays(SIGN_AGE_3);
signer1PubKey = arbitrator1Key.getPubKey();
signer2PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
signer3PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
@@ -159,7 +163,7 @@ public void testIsValidAccountAgeWitnessPeerSignatureProblem() {
@Test
public void testIsValidAccountAgeWitnessDateTooSoonProblem() {
- date3 = getTodayMinusNDays(63);
+ date3 = getTodayMinusNDays(SIGN_AGE_2 - 1);
SignedWitness sw1 = new SignedWitness(true, account1DataHash, signature1, signer1PubKey, witnessOwner1PubKey, date1, tradeAmount1);
SignedWitness sw2 = new SignedWitness(false, account2DataHash, signature2, signer2PubKey, witnessOwner2PubKey, date2, tradeAmount2);
@@ -197,9 +201,9 @@ public void testIsValidAccountAgeWitnessEndlessLoop() throws Exception {
byte[] account1DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{1});
byte[] account2DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{2});
byte[] account3DataHash = org.bitcoinj.core.Utils.sha256hash160(new byte[]{3});
- long account1CreationTime = getTodayMinusNDays(96);
- long account2CreationTime = getTodayMinusNDays(66);
- long account3CreationTime = getTodayMinusNDays(36);
+ long account1CreationTime = getTodayMinusNDays(SIGN_AGE_1 + 1);
+ long account2CreationTime = getTodayMinusNDays(SIGN_AGE_2 + 1);
+ long account3CreationTime = getTodayMinusNDays(SIGN_AGE_3 + 1);
AccountAgeWitness aew1 = new AccountAgeWitness(account1DataHash, account1CreationTime);
AccountAgeWitness aew2 = new AccountAgeWitness(account2DataHash, account2CreationTime);
AccountAgeWitness aew3 = new AccountAgeWitness(account3DataHash, account3CreationTime);
@@ -223,9 +227,9 @@ public void testIsValidAccountAgeWitnessEndlessLoop() throws Exception {
byte[] witnessOwner1PubKey = Sig.getPublicKeyBytes(peer1KeyPair.getPublic());
byte[] witnessOwner2PubKey = Sig.getPublicKeyBytes(peer2KeyPair.getPublic());
byte[] witnessOwner3PubKey = Sig.getPublicKeyBytes(peer3KeyPair.getPublic());
- long date1 = getTodayMinusNDays(95);
- long date2 = getTodayMinusNDays(64);
- long date3 = getTodayMinusNDays(33);
+ long date1 = getTodayMinusNDays(SIGN_AGE_1);
+ long date2 = getTodayMinusNDays(SIGN_AGE_2);
+ long date3 = getTodayMinusNDays(SIGN_AGE_3);
long tradeAmount1 = 1000;
long tradeAmount2 = 1001;
@@ -251,7 +255,7 @@ public void testIsValidAccountAgeWitnessLongLoop() throws Exception {
int iterations = 1002;
for (int i = 0; i < iterations; i++) {
byte[] accountDataHash = org.bitcoinj.core.Utils.sha256hash160(String.valueOf(i).getBytes(Charsets.UTF_8));
- long accountCreationTime = getTodayMinusNDays((iterations - i) * (SignedWitnessService.CHARGEBACK_SAFETY_DAYS + 1));
+ long accountCreationTime = getTodayMinusNDays((iterations - i) * (SignedWitnessService.SIGNER_AGE_DAYS + 1));
aew = new AccountAgeWitness(accountDataHash, accountCreationTime);
String accountDataHashAsHexString = Utilities.encodeToHex(accountDataHash);
byte[] signature;
@@ -270,7 +274,7 @@ public void testIsValidAccountAgeWitnessLongLoop() throws Exception {
signerPubKey = Sig.getPublicKeyBytes(signerKeyPair.getPublic());
}
byte[] witnessOwnerPubKey = Sig.getPublicKeyBytes(signedKeyPair.getPublic());
- long date = getTodayMinusNDays((iterations - i) * (SignedWitnessService.CHARGEBACK_SAFETY_DAYS + 1));
+ long date = getTodayMinusNDays((iterations - i) * (SignedWitnessService.SIGNER_AGE_DAYS + 1));
SignedWitness sw = new SignedWitness(i == 0, accountDataHash, signature, signerPubKey, witnessOwnerPubKey, date, tradeAmount1);
signedWitnessService.addToMap(sw);
}
diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java
index c5f28113f55..22e7cd16dd6 100644
--- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java
+++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java
@@ -17,6 +17,9 @@
package bisq.core.account.witness;
+import bisq.core.account.sign.SignedWitnessService;
+import bisq.core.payment.ChargeBackRisk;
+
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.Sig;
@@ -38,6 +41,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
// Restricted default Java security policy on Travis does not allow long keys, so test fails.
// Using Utilities.removeCryptographyRestrictions(); did not work.
@@ -49,7 +53,9 @@ public class AccountAgeWitnessServiceTest {
@Before
public void setup() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
- service = new AccountAgeWitnessService(null, null, null, null, null);
+ SignedWitnessService signedWitnessService = mock(SignedWitnessService.class);
+ ChargeBackRisk chargeBackRisk = mock(ChargeBackRisk.class);
+ service = new AccountAgeWitnessService(null, null, null, signedWitnessService, chargeBackRisk, null, null);
keypair = Sig.generateKeyPair();
publicKey = keypair.getPublic();
}
diff --git a/core/src/test/java/bisq/core/arbitration/BuyerDataItemTest.java b/core/src/test/java/bisq/core/arbitration/TraderDataItemTest.java
similarity index 64%
rename from core/src/test/java/bisq/core/arbitration/BuyerDataItemTest.java
rename to core/src/test/java/bisq/core/arbitration/TraderDataItemTest.java
index 61dc33b8348..7909a6fdf98 100644
--- a/core/src/test/java/bisq/core/arbitration/BuyerDataItemTest.java
+++ b/core/src/test/java/bisq/core/arbitration/TraderDataItemTest.java
@@ -1,7 +1,7 @@
package bisq.core.arbitration;
import bisq.core.account.witness.AccountAgeWitness;
-import bisq.core.support.dispute.arbitration.BuyerDataItem;
+import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.payment.payload.PaymentAccountPayload;
import org.bitcoinj.core.Coin;
@@ -31,10 +31,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see .
*/
-public class BuyerDataItemTest {
- private BuyerDataItem buyerDataItem1;
- private BuyerDataItem buyerDataItem2;
- private BuyerDataItem buyerDataItem3;
+public class TraderDataItemTest {
+ private TraderDataItem traderDataItem1;
+ private TraderDataItem traderDataItem2;
+ private TraderDataItem traderDataItem3;
private AccountAgeWitness accountAgeWitness1;
private AccountAgeWitness accountAgeWitness2;
private byte[] hash1 = "1".getBytes();
@@ -44,24 +44,24 @@ public class BuyerDataItemTest {
public void setup() {
accountAgeWitness1 = new AccountAgeWitness(hash1, 123);
accountAgeWitness2 = new AccountAgeWitness(hash2, 124);
- buyerDataItem1 = new BuyerDataItem(mock(PaymentAccountPayload.class), accountAgeWitness1, Coin.valueOf(546),
+ traderDataItem1 = new TraderDataItem(mock(PaymentAccountPayload.class), accountAgeWitness1, Coin.valueOf(546),
mock(PublicKey.class));
- buyerDataItem2 = new BuyerDataItem(mock(PaymentAccountPayload.class), accountAgeWitness1, Coin.valueOf(547),
+ traderDataItem2 = new TraderDataItem(mock(PaymentAccountPayload.class), accountAgeWitness1, Coin.valueOf(547),
mock(PublicKey.class));
- buyerDataItem3 = new BuyerDataItem(mock(PaymentAccountPayload.class), accountAgeWitness2, Coin.valueOf(548),
+ traderDataItem3 = new TraderDataItem(mock(PaymentAccountPayload.class), accountAgeWitness2, Coin.valueOf(548),
mock(PublicKey.class));
}
@Test
public void testEquals() {
- assertEquals(buyerDataItem1, buyerDataItem2);
- assertNotEquals(buyerDataItem1, buyerDataItem3);
- assertNotEquals(buyerDataItem2, buyerDataItem3);
+ assertEquals(traderDataItem1, traderDataItem2);
+ assertNotEquals(traderDataItem1, traderDataItem3);
+ assertNotEquals(traderDataItem2, traderDataItem3);
}
@Test
public void testHashCode() {
- assertEquals(buyerDataItem1.hashCode(), buyerDataItem2.hashCode());
- assertNotEquals(buyerDataItem1.hashCode(), buyerDataItem3.hashCode());
+ assertEquals(traderDataItem1.hashCode(), traderDataItem2.hashCode());
+ assertNotEquals(traderDataItem1.hashCode(), traderDataItem3.hashCode());
}
}
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/payment/PaymentAccountsTest.java b/core/src/test/java/bisq/core/payment/PaymentAccountsTest.java
index 8402ebab474..b869045683b 100644
--- a/core/src/test/java/bisq/core/payment/PaymentAccountsTest.java
+++ b/core/src/test/java/bisq/core/payment/PaymentAccountsTest.java
@@ -22,15 +22,10 @@
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload;
-import com.google.common.collect.Sets;
-
import java.util.Collections;
-import java.util.Set;
-import java.util.function.BiFunction;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -46,22 +41,22 @@ public void testGetOldestPaymentAccountForOfferWhenNoValidAccounts() {
assertNull(actual);
}
- @Test
- public void testGetOldestPaymentAccountForOffer() {
- AccountAgeWitnessService service = mock(AccountAgeWitnessService.class);
-
- PaymentAccount oldest = createAccountWithAge(service, 3);
- Set accounts = Sets.newHashSet(
- oldest,
- createAccountWithAge(service, 2),
- createAccountWithAge(service, 1));
-
- BiFunction dummyValidator = (offer, account) -> true;
- PaymentAccounts testedEntity = new PaymentAccounts(accounts, service, dummyValidator);
-
- PaymentAccount actual = testedEntity.getOldestPaymentAccountForOffer(mock(Offer.class));
- assertEquals(oldest, actual);
- }
+// @Test
+// public void testGetOldestPaymentAccountForOffer() {
+// AccountAgeWitnessService service = mock(AccountAgeWitnessService.class);
+//
+// PaymentAccount oldest = createAccountWithAge(service, 3);
+// Set accounts = Sets.newHashSet(
+// oldest,
+// createAccountWithAge(service, 2),
+// createAccountWithAge(service, 1));
+//
+// BiFunction dummyValidator = (offer, account) -> true;
+// PaymentAccounts testedEntity = new PaymentAccounts(accounts, service, dummyValidator);
+//
+// PaymentAccount actual = testedEntity.getOldestPaymentAccountForOffer(mock(Offer.class));
+// assertEquals(oldest, actual);
+// }
private static PaymentAccount createAccountWithAge(AccountAgeWitnessService service, long age) {
PaymentAccountPayload payload = mock(PaymentAccountPayload.class);
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/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css
index 53caec8ed05..a0b23994556 100644
--- a/desktop/src/main/java/bisq/desktop/bisq.css
+++ b/desktop/src/main/java/bisq/desktop/bisq.css
@@ -2040,3 +2040,12 @@ textfield */
.status-icon {
-fx-text-fill: -fx-faint-focus-color;
}
+
+/********************************************************************************************************************
+ * *
+ * Popover *
+ * *
+ ********************************************************************************************************************/
+.popover > .content {
+ -fx-padding: 10;
+}
diff --git a/desktop/src/main/java/bisq/desktop/components/AutoTooltipTableColumn.java b/desktop/src/main/java/bisq/desktop/components/AutoTooltipTableColumn.java
index 0a1c12781ec..906a751e2cb 100644
--- a/desktop/src/main/java/bisq/desktop/components/AutoTooltipTableColumn.java
+++ b/desktop/src/main/java/bisq/desktop/components/AutoTooltipTableColumn.java
@@ -28,8 +28,6 @@
import javafx.scene.control.TableColumn;
import javafx.scene.layout.HBox;
-import javafx.geometry.Insets;
-
import java.util.concurrent.TimeUnit;
public class AutoTooltipTableColumn extends TableColumn {
@@ -66,7 +64,6 @@ public void setTitleWithHelpText(String title, String help) {
final Label helpLabel = new Label(help);
helpLabel.setMaxWidth(300);
helpLabel.setWrapText(true);
- helpLabel.setPadding(new Insets(10));
showInfoPopOver(helpLabel);
});
helpIcon.setOnMouseExited(e -> {
diff --git a/desktop/src/main/java/bisq/desktop/components/InfoAutoTooltipLabel.java b/desktop/src/main/java/bisq/desktop/components/InfoAutoTooltipLabel.java
index fe418e76d1f..2b033827f54 100644
--- a/desktop/src/main/java/bisq/desktop/components/InfoAutoTooltipLabel.java
+++ b/desktop/src/main/java/bisq/desktop/components/InfoAutoTooltipLabel.java
@@ -35,25 +35,41 @@
public class InfoAutoTooltipLabel extends AutoTooltipLabel {
+ public static final int DEFAULT_WIDTH = 300;
private Node textIcon;
private Boolean hidePopover;
private PopOver infoPopover;
+ private ContentDisplay contentDisplay;
public InfoAutoTooltipLabel(String text, GlyphIcons icon, ContentDisplay contentDisplay, String info) {
super(text);
+ this.contentDisplay = contentDisplay;
- textIcon = getIcon(icon);
- addIcon(contentDisplay, info, 300);
+ setIcon(icon);
+ positionAndActivateIcon(contentDisplay, info, DEFAULT_WIDTH);
}
public InfoAutoTooltipLabel(String text, AwesomeIcon icon, ContentDisplay contentDisplay, String info, double width) {
super(text);
+ setIcon(icon);
+ positionAndActivateIcon(contentDisplay, info, width);
+ }
+
+ public void setIcon(GlyphIcons icon) {
+ textIcon = getIcon(icon);
+ }
+
+ public void setIcon(GlyphIcons icon, String info) {
+ setIcon(icon);
+ positionAndActivateIcon(contentDisplay, info, DEFAULT_WIDTH);
+ }
+
+ public void setIcon(AwesomeIcon icon) {
textIcon = getIcon(icon);
- addIcon(contentDisplay, info, width);
}
- private void addIcon(ContentDisplay contentDisplay, String info, double width) {
+ private void positionAndActivateIcon(ContentDisplay contentDisplay, String info, double width) {
textIcon.setOpacity(0.4);
textIcon.setOnMouseEntered(e -> {
diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java
index 54d3725e618..172c0296dd8 100644
--- a/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java
+++ b/desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java
@@ -152,7 +152,7 @@ private PeerInfoIcon(NodeAddress nodeAddress,
// outer circle
Color ringColor;
if (isFiatCurrency) {
- switch (accountAgeWitnessService.getAccountAgeCategory(peersAccountAge)) {
+ switch (accountAgeWitnessService.getPeersAccountAgeCategory(peersAccountAge)) {
case TWO_MONTHS_OR_MORE:
ringColor = Color.rgb(0, 225, 0); // > 2 months green
break;
@@ -160,9 +160,12 @@ private PeerInfoIcon(NodeAddress nodeAddress,
ringColor = Color.rgb(0, 139, 205); // 1-2 months blue
break;
case LESS_ONE_MONTH:
- default:
ringColor = Color.rgb(255, 140, 0); //< 1 month orange
break;
+ case UNVERIFIED:
+ default:
+ ringColor = Color.rgb(255, 0, 0); // not signed, red
+ break;
}
@@ -240,7 +243,18 @@ private PeerInfoIcon(NodeAddress nodeAddress,
getChildren().addAll(outerBackground, innerBackground, avatarImageView, tagPane, numTradesPane);
- addMouseListener(numTrades, privateNotificationManager, offer, preferences, formatter, useDevPrivilegeKeys, isFiatCurrency, peersAccountAge);
+ //TODO sqrrm: We need these states in here:
+ // - signed by arbitrator
+ // - signed by peer
+ // - signed by peer and limit lifted
+ // - signed by peer and able to sign
+ // - not signing necessary for this payment account
+ // - signing required and not signed
+ // Additionally we need to have some enum or so how the account signing took place.
+ // e.g. if in the future we'll also offer the "pay with two different accounts"-signing
+ String accountSigningState = Res.get("shared.notSigned");
+
+ addMouseListener(numTrades, privateNotificationManager, offer, preferences, formatter, useDevPrivilegeKeys, isFiatCurrency, peersAccountAge, accountSigningState);
}
private long getPeersAccountAge(@Nullable Trade trade, @Nullable Offer offer) {
@@ -251,11 +265,11 @@ private long getPeersAccountAge(@Nullable Trade trade, @Nullable Offer offer) {
return -1;
}
- return accountAgeWitnessService.getTradingPeersAccountAge(trade);
+ return accountAgeWitnessService.getWitnessSignAge(trade, new Date());
} else {
checkNotNull(offer, "Offer must not be null if trade is null.");
- return accountAgeWitnessService.getMakersAccountAge(offer, new Date());
+ return accountAgeWitnessService.getWitnessSignAge(offer, new Date());
}
}
@@ -265,17 +279,17 @@ protected void addMouseListener(int numTrades,
Preferences preferences,
BSFormatter formatter,
boolean useDevPrivilegeKeys,
- boolean isFiatCurrency,
- long makersAccountAge) {
+ boolean isFiatCurrency, long makersAccountAge, String accountSigningState) {
final String accountAgeTagEditor = isFiatCurrency ?
makersAccountAge > -1 ?
DisplayUtils.formatAccountAge(makersAccountAge) :
Res.get("peerInfo.unknownAge") :
null;
+
setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, offer, preferences, useDevPrivilegeKeys)
.fullAddress(fullAddress)
.numTrades(numTrades)
- .accountAge(accountAgeTagEditor)
+ .accountAge(accountAgeTagEditor).accountSigningState(accountSigningState)
.position(localToScene(new Point2D(0, 0)))
.onSave(newTag -> {
preferences.setTagForPeer(fullAddress, newTag);
diff --git a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java
index c07bc5ff328..d72b2568e45 100644
--- a/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java
+++ b/desktop/src/main/java/bisq/desktop/components/PeerInfoIconSmall.java
@@ -38,8 +38,7 @@ protected void addMouseListener(int numTrades,
Offer offer, Preferences preferences,
BSFormatter formatter,
boolean useDevPrivilegeKeys,
- boolean isFiatCurrency,
- long makersAccountAge) {
+ boolean isFiatCurrency, long makersAccountAge, String accountSigningState) {
}
@Override
diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java
index 1ac3618a0f2..dd3c0ef8230 100644
--- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java
+++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/PaymentMethodForm.java
@@ -32,6 +32,7 @@
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
import bisq.core.offer.Offer;
+import bisq.core.offer.OfferPayload;
import bisq.core.payment.AssetAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.util.BSFormatter;
@@ -178,15 +179,40 @@ else if (!paymentAccount.getTradeCurrencies().isEmpty())
final String limitationsText = paymentAccount instanceof AssetAccount ?
Res.get("payment.maxPeriodAndLimitCrypto",
getTimeText(hours),
- formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrency.getCode()))))
+ formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit(
+ paymentAccount, tradeCurrency.getCode(), OfferPayload.Direction.BUY))))
:
Res.get("payment.maxPeriodAndLimit",
getTimeText(hours),
- formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrency.getCode()))),
+ formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit(
+ paymentAccount, tradeCurrency.getCode(), OfferPayload.Direction.BUY))),
+ formatter.formatCoinWithCode(Coin.valueOf(accountAgeWitnessService.getMyTradeLimit(
+ paymentAccount, tradeCurrency.getCode(), OfferPayload.Direction.SELL))),
DisplayUtils.formatAccountAge(accountAge));
- if (isDisplayForm)
+ if (isDisplayForm) {
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.limitations"), limitationsText);
+
+ String accountSigningStateText;
+
+ if (accountAgeWitnessService.myHasSignedWitness(paymentAccount.getPaymentAccountPayload())) {
+ //TODO sqrrm: We need four states in here:
+ // - signed by arbitrator
+ // - signed by peer
+ // - signed by peer and limit lifted
+ // - signed by peer and able to sign
+ // Additionally we need to have some enum or so how the account signing took place.
+ // e.g. if in the future we'll also offer the "pay with two different accounts"-signing
+ accountSigningStateText = "This account was verified and signed by an arbitrator or peer / Time since signing: 3 days";
+ } else {
+ //TODO sqrrm: Here we need two states:
+ // - not signing necessary for this payment account
+ // - signing required and not signed
+ accountSigningStateText = Res.get("shared.notSigned");
+ }
+
+ addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.accountSigningState"), accountSigningStateText);
+ }
else
addTopLabelTextField(gridPane, ++gridRow, Res.get("payment.limitations"), limitationsText);
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/content/PaymentAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java
index d4a051dd9fc..834229fb560 100644
--- a/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java
+++ b/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java
@@ -4,16 +4,22 @@
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
+import bisq.desktop.components.InfoAutoTooltipLabel;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.ImageUtil;
+import bisq.core.account.sign.SignedWitnessService;
+import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.Res;
import bisq.core.payment.PaymentAccount;
import bisq.common.UserThread;
+import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
+
import javafx.scene.Node;
import javafx.scene.control.Button;
+import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
@@ -31,11 +37,14 @@
public abstract class PaymentAccountsView extends ActivatableViewAndModel {
protected ListView paymentAccountsListView;
- protected ChangeListener paymentAccountChangeListener;
+ private ChangeListener paymentAccountChangeListener;
protected Button addAccountButton, exportButton, importButton;
+ SignedWitnessService signedWitnessService;
+ protected AccountAgeWitnessService accountAgeWitnessService;
- public PaymentAccountsView(M model) {
+ public PaymentAccountsView(M model, AccountAgeWitnessService accountAgeWitnessService) {
super(model);
+ this.accountAgeWitnessService = accountAgeWitnessService;
}
@Override
@@ -84,11 +93,11 @@ protected void onDeleteAccount(PaymentAccount paymentAccount) {
}
protected void setPaymentAccountsCellFactory() {
- paymentAccountsListView.setCellFactory(new Callback, ListCell>() {
+ paymentAccountsListView.setCellFactory(new Callback<>() {
@Override
public ListCell call(ListView list) {
- return new ListCell() {
- final Label label = new AutoTooltipLabel();
+ return new ListCell<>() {
+ final InfoAutoTooltipLabel label = new InfoAutoTooltipLabel("", MaterialDesignIcon.ALERT_CIRCLE_OUTLINE, ContentDisplay.RIGHT, "");
final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON);
final Button removeButton = new AutoTooltipButton("", icon);
final AnchorPane pane = new AnchorPane(label, removeButton);
@@ -104,6 +113,22 @@ public void updateItem(final PaymentAccount item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
label.setText(item.getAccountName());
+
+ if (accountAgeWitnessService.myHasSignedWitness(item.paymentAccountPayload)) {
+ //TODO sqrrm: We need four states in here:
+ // - signed by arbitrator
+ // - signed by peer
+ // - signed by peer and limit lifted
+ // - signed by peer and able to sign
+ // Additionally we need to have some enum or so how the account signing took place.
+ // e.g. if in the future we'll also offer the "pay with two different accounts"-signing
+ label.setIcon(MaterialDesignIcon.APPROVAL, "This account was verified and signed by an arbitrator or peer.");
+ } else {
+ //TODO sqrrm: Here we need two states:
+ // - not signing necessary for this payment account
+ // - signing required and not signed
+ label.setIcon(MaterialDesignIcon.ALERT_CIRCLE_OUTLINE, Res.get("shared.notSigned"));
+ }
removeButton.setOnAction(e -> onDeleteAccount(item));
setGraphic(pane);
} else {
diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java
index ba0048bccdf..ae03f56d923 100644
--- a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java
+++ b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsView.java
@@ -72,7 +72,6 @@ public class AltCoinAccountsView extends PaymentAccountsView paymentMethodComboBox;
private PaymentMethodForm paymentMethodForm;
@@ -173,7 +172,7 @@ public FiatAccountsView(FiatAccountsViewModel model,
AdvancedCashValidator advancedCashValidator,
AccountAgeWitnessService accountAgeWitnessService,
BSFormatter formatter) {
- super(model);
+ super(model, accountAgeWitnessService);
this.ibanValidator = ibanValidator;
this.bicValidator = bicValidator;
@@ -195,7 +194,6 @@ public FiatAccountsView(FiatAccountsViewModel model,
this.f2FValidator = f2FValidator;
this.promptPayValidator = promptPayValidator;
this.advancedCashValidator = advancedCashValidator;
- this.accountAgeWitnessService = accountAgeWitnessService;
this.formatter = formatter;
}
@@ -343,8 +341,7 @@ protected void addNewAccount() {
removeAccountRows();
addAccountButton.setDisable(true);
accountTitledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("shared.createNewAccount"), Layout.GROUP_DISTANCE);
- paymentMethodComboBox = FormBuilder.addComboBox(root, gridRow, Res.get("shared.paymentMethod"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
- paymentMethodComboBox.setPromptText(Res.get("shared.selectPaymentMethod"));
+ paymentMethodComboBox = FormBuilder.addComboBox(root, gridRow, Res.get("shared.selectPaymentMethod"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
paymentMethodComboBox.setVisibleRowCount(11);
paymentMethodComboBox.setPrefWidth(250);
List list = PaymentMethod.getPaymentMethods().stream()
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..f37a4057490 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,90 @@
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 org.bitcoinj.core.TransactionOutput;
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 +115,67 @@ private boolean isDisputedPayoutTx(String txId) {
});
}
+ boolean isDelayedPayoutTx(String txId) {
+ Transaction transaction = btcWalletService.getTransaction(txId);
+ if (transaction == null)
+ return false;
+
+ if (transaction.getLockTime() == 0)
+ return false;
+
+ if (transaction.getInputs() == null)
+ return false;
+
+ return transaction.getInputs().stream()
+ .anyMatch(input -> {
+ TransactionOutput connectedOutput = input.getConnectedOutput();
+ if (connectedOutput == null) {
+ return false;
+ }
+ Transaction parentTransaction = connectedOutput.getParentTransaction();
+ if (parentTransaction == null) {
+ return false;
+ }
+ return isDepositTx(parentTransaction.getHashAsString());
+ });
+ }
+
+ 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..63ad288f1c1 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
@@ -20,42 +20,51 @@
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoFacade;
-import bisq.core.trade.Tradable;
import bisq.core.user.Preferences;
import bisq.core.util.BSFormatter;
+import bisq.common.crypto.PubKeyRing;
+
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Singleton;
-import java.util.Optional;
-
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;
}
TransactionsListItem create(Transaction transaction, @Nullable TransactionAwareTradable tradable) {
- 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,
+ tradable,
+ 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..09e6b349be3 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;
@@ -85,8 +88,9 @@ class TransactionsListItem {
TransactionsListItem(Transaction transaction,
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
- Optional tradableOptional,
+ TransactionAwareTradable transactionAwareTradable,
DaoFacade daoFacade,
+ PubKeyRing pubKeyRing,
BSFormatter formatter,
long ignoreDustThreshold) {
this.btcWalletService = btcWalletService;
@@ -94,6 +98,9 @@ class TransactionsListItem {
txId = transaction.getHashAsString();
+ Optional optionalTradable = Optional.ofNullable(transactionAwareTradable)
+ .map(TransactionAwareTradable::asTradable);
+
Coin valueSentToMe = btcWalletService.getValueSentToMeForTransaction(transaction);
Coin valueSentFromMe = btcWalletService.getValueSentFromMeForTransaction(transaction);
@@ -195,48 +202,75 @@ public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
confirmations = confidence.getDepthInBlocks();
- if (tradableOptional.isPresent()) {
- tradable = tradableOptional.get();
+ if (optionalTradable.isPresent()) {
+ tradable = optionalTradable.get();
detailsAvailable = true;
- String id = tradable.getShortId();
+ String tradeId = tradable.getShortId();
if (tradable instanceof OpenOffer) {
- details = Res.get("funds.tx.createOfferFee", id);
+ details = Res.get("funds.tx.createOfferFee", tradeId);
} else if (tradable instanceof Trade) {
Trade trade = (Trade) tradable;
+ TransactionAwareTrade transactionAwareTrade = (TransactionAwareTrade) transactionAwareTradable;
if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().equals(txId)) {
- details = Res.get("funds.tx.takeOfferFee", id);
+ details = Res.get("funds.tx.takeOfferFee", tradeId);
} else {
Offer offer = trade.getOffer();
String offerFeePaymentTxID = offer.getOfferFeePaymentTxId();
if (offerFeePaymentTxID != null && offerFeePaymentTxID.equals(txId)) {
- details = Res.get("funds.tx.createOfferFee", id);
+ details = Res.get("funds.tx.createOfferFee", tradeId);
} else if (trade.getDepositTx() != null &&
trade.getDepositTx().getHashAsString().equals(txId)) {
- details = Res.get("funds.tx.multiSigDeposit", id);
+ details = Res.get("funds.tx.multiSigDeposit", tradeId);
} 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);
+ details = Res.get("funds.tx.multiSigPayout", tradeId);
+ } else {
+ Trade.DisputeState disputeState = trade.getDisputeState();
+ if (disputeState == Trade.DisputeState.DISPUTE_CLOSED) {
+ if (valueSentToMe.isPositive()) {
+ details = Res.get("funds.tx.disputePayout", tradeId);
+ } else {
+ details = Res.get("funds.tx.disputeLost", tradeId);
+ 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", tradeId);
+ } 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", tradeId);
+ txConfidenceIndicator.setVisible(false);
+ }
+ }
} else {
- details = Res.get("funds.tx.disputeLost", id);
- txConfidenceIndicator.setVisible(false);
+ if (transactionAwareTrade.isDelayedPayoutTx(txId)) {
+ details = Res.get("funds.tx.timeLockedPayoutTx", tradeId);
+ txConfidenceIndicator.setVisible(false);
+ } else {
+ details = Res.get("funds.tx.unknown", tradeId);
+ }
}
- } 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..97ff63c8f1c 100644
--- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java
@@ -21,7 +21,6 @@
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
-import bisq.core.account.witness.AccountAgeRestrictions;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.TxFeeEstimationService;
import bisq.core.btc.listeners.BalanceListener;
@@ -358,7 +357,8 @@ Offer createAndGetOffer() {
Map extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService,
referralIdService,
paymentAccount,
- currencyCode);
+ currencyCode,
+ preferences);
OfferUtil.validateOfferData(filterManager,
p2PService,
@@ -581,10 +581,11 @@ boolean isMakerFeeValid() {
}
long getMaxTradeLimit() {
- if (paymentAccount != null)
- return AccountAgeRestrictions.getMyTradeLimitAtCreateOffer(accountAgeWitnessService, paymentAccount, tradeCurrencyCode.get(), direction);
- else
+ if (paymentAccount != null) {
+ return accountAgeWitnessService.getMyTradeLimit(paymentAccount, tradeCurrencyCode.get(), direction);
+ } else {
return 0;
+ }
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -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/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java
index 26eb7e603b7..7cff53410e7 100644
--- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java
+++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java
@@ -42,6 +42,7 @@
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
+import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.alert.PrivateNotificationManager;
import bisq.core.app.AppOptionKeys;
import bisq.core.locale.CurrencyUtil;
@@ -67,6 +68,7 @@
import javax.inject.Inject;
+import de.jensd.fx.glyphs.GlyphIcons;
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
import javafx.scene.Scene;
@@ -119,12 +121,12 @@ public class OfferBookView extends ActivatableViewAndModel currencyComboBox;
private AutocompleteComboBox paymentMethodComboBox;
private AutoTooltipButton createOfferButton;
- private AutoTooltipTableColumn amountColumn, volumeColumn, marketColumn,
- priceColumn, avatarColumn;
+ private AutoTooltipTableColumn amountColumn, volumeColumn, marketColumn, priceColumn, signingStateColumn, avatarColumn;
private TableView tableView;
private OfferView.OfferActionHandler offerActionHandler;
@@ -145,7 +147,8 @@ public class OfferBookView extends ActivatableViewAndModel paymentMethodColumn = getPaymentMethodColumn();
tableView.getColumns().add(paymentMethodColumn);
+ signingStateColumn = getSigningStateColumn();
+ tableView.getColumns().add(signingStateColumn);
avatarColumn = getAvatarColumn();
tableView.getColumns().add(getActionColumn());
tableView.getColumns().add(avatarColumn);
@@ -530,8 +536,7 @@ private void onCreateOffer() {
private void onShowInfo(Offer offer,
boolean isPaymentAccountValidForOffer,
- boolean isRiskyBuyOfferWithImmatureAccountAge,
- boolean isSellOfferAndAllTakerPaymentAccountsForOfferImmature,
+ boolean isInsufficientCounterpartyTradeLimit,
boolean hasSameProtocolVersion,
boolean isIgnored,
boolean isOfferBanned,
@@ -545,12 +550,8 @@ private void onShowInfo(Offer offer,
Res.get("offerbook.warning.noMatchingAccount.msg"),
FiatAccountsView.class,
"navigation.account");
- } else if (isRiskyBuyOfferWithImmatureAccountAge) {
- new Popup<>().warning(Res.get("offerbook.warning.riskyBuyOfferWithImmatureAccountAge",
- Res.get("offerbook.warning.newVersionAnnouncement"))).show();
- } else if (isSellOfferAndAllTakerPaymentAccountsForOfferImmature) {
- new Popup<>().warning(Res.get("offerbook.warning.sellOfferAndAnyTakerPaymentAccountForOfferMature",
- Res.get("offerbook.warning.newVersionAnnouncement"))).show();
+ } else if (isInsufficientCounterpartyTradeLimit) {
+ new Popup<>().warning(Res.get("offerbook.warning.counterpartyTradeRestrictions")).show();
} else if (!hasSameProtocolVersion) {
new Popup<>().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show();
} else if (isIgnored) {
@@ -568,7 +569,8 @@ private void onShowInfo(Offer offer,
} else if (isInsufficientTradeLimit) {
final Optional account = model.getMostMaturePaymentAccountForOffer(offer);
if (account.isPresent()) {
- final long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(), offer.getCurrencyCode());
+ final long tradeLimit = model.accountAgeWitnessService.getMyTradeLimit(account.get(),
+ offer.getCurrencyCode(), offer.getMirroredDirection());
new Popup<>()
.warning(Res.get("offerbook.warning.tradeLimitNotMatching",
DisplayUtils.formatAccountAge(model.accountAgeWitnessService.getMyAccountAge(account.get().getPaymentAccountPayload())),
@@ -885,6 +887,7 @@ public void updateItem(final OfferBookListItem item, boolean empty) {
field = new HyperlinkWithIcon(model.getPaymentMethod(item));
field.setOnAction(event -> offerDetailsWindow.show(item.getOffer()));
field.setTooltip(new Tooltip(model.getPaymentMethodToolTip(item)));
+
setGraphic(field);
} else {
setGraphic(null);
@@ -914,10 +917,10 @@ public TableCell call(TableColumn() {
final ImageView iconView = new ImageView();
final AutoTooltipButton button = new AutoTooltipButton();
- boolean isTradable, isPaymentAccountValidForOffer, isRiskyBuyOfferWithImmatureAccountAge,
- isSellOfferAndAllTakerPaymentAccountsForOfferImmature,
+ boolean isTradable, isPaymentAccountValidForOffer,
+ isInsufficientCounterpartyTradeLimit,
hasSameProtocolVersion, isIgnored, isOfferBanned, isCurrencyBanned,
- isPaymentMethodBanned, isNodeAddressBanned, isInsufficientTradeLimit,
+ isPaymentMethodBanned, isNodeAddressBanned, isMyInsufficientTradeLimit,
requireUpdateToNewVersion;
{
@@ -937,8 +940,7 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
boolean myOffer = model.isMyOffer(offer);
if (tableRow != null) {
isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer);
- isRiskyBuyOfferWithImmatureAccountAge = model.isRiskyBuyOfferWithImmatureAccountAge(offer);
- isSellOfferAndAllTakerPaymentAccountsForOfferImmature = model.isSellOfferAndAllTakerPaymentAccountsForOfferImmature(offer);
+ isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer);
hasSameProtocolVersion = model.hasSameProtocolVersion(offer);
isIgnored = model.isIgnored(offer);
isOfferBanned = model.isOfferBanned(offer);
@@ -946,10 +948,9 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
isPaymentMethodBanned = model.isPaymentMethodBanned(offer);
isNodeAddressBanned = model.isNodeAddressBanned(offer);
requireUpdateToNewVersion = model.requireUpdateToNewVersion();
- isInsufficientTradeLimit = model.isInsufficientTradeLimit(offer);
+ isMyInsufficientTradeLimit = model.isMyInsufficientTradeLimit(offer);
isTradable = isPaymentAccountValidForOffer &&
- !isRiskyBuyOfferWithImmatureAccountAge &&
- !isSellOfferAndAllTakerPaymentAccountsForOfferImmature &&
+ !isInsufficientCounterpartyTradeLimit &&
hasSameProtocolVersion &&
!isIgnored &&
!isOfferBanned &&
@@ -957,7 +958,7 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
!isPaymentMethodBanned &&
!isNodeAddressBanned &&
!requireUpdateToNewVersion &&
- !isInsufficientTradeLimit;
+ !isMyInsufficientTradeLimit;
tableRow.setOpacity(isTradable || myOffer ? 1 : 0.4);
@@ -972,8 +973,7 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas))
onShowInfo(offer,
isPaymentAccountValidForOffer,
- isRiskyBuyOfferWithImmatureAccountAge,
- isSellOfferAndAllTakerPaymentAccountsForOfferImmature,
+ isInsufficientCounterpartyTradeLimit,
hasSameProtocolVersion,
isIgnored,
isOfferBanned,
@@ -981,7 +981,7 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
isPaymentMethodBanned,
isNodeAddressBanned,
requireUpdateToNewVersion,
- isInsufficientTradeLimit);
+ isMyInsufficientTradeLimit);
});
}
}
@@ -1014,8 +1014,7 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
if (!myOffer && !isTradable)
button.setOnAction(e -> onShowInfo(offer,
isPaymentAccountValidForOffer,
- isRiskyBuyOfferWithImmatureAccountAge,
- isSellOfferAndAllTakerPaymentAccountsForOfferImmature,
+ isInsufficientCounterpartyTradeLimit,
hasSameProtocolVersion,
isIgnored,
isOfferBanned,
@@ -1023,7 +1022,7 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
isPaymentMethodBanned,
isNodeAddressBanned,
requireUpdateToNewVersion,
- isInsufficientTradeLimit));
+ isMyInsufficientTradeLimit));
button.updateText(title);
setPadding(new Insets(0, 15, 0, 0));
@@ -1043,6 +1042,66 @@ public void updateItem(final OfferBookListItem newItem, boolean empty) {
return column;
}
+ private AutoTooltipTableColumn getSigningStateColumn() {
+ AutoTooltipTableColumn column = new AutoTooltipTableColumn<>(Res.get("offerbook.timeSinceSigning"), Res.get("offerbook.timeSinceSigning.help")) {
+ {
+ setMinWidth(60);
+ setSortable(true);
+ }
+ };
+
+ column.getStyleClass().add("number-column");
+ column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
+ column.setCellFactory(new Callback<>() {
+ @Override
+ public TableCell