From 98402a73e41b6ef38ca3c670e7b82204a14055af Mon Sep 17 00:00:00 2001 From: chimp1984 <54558767+chimp1984@users.noreply.github.com> Date: Mon, 30 Sep 2019 10:02:03 +0200 Subject: [PATCH] Complete new trade protocol (#3340) * Improve handling of adding tx to wallet * Add delayedPayoutTx to dispute * Fix test * Use RECIPIENT_BTC_ADDRESS from DAO for trade fee * Set lockTime to 10 days for altcoins, 20 days others. - Devmode uses 1 block * Fix params * Update text * Update docs * Update logging if (log.isDebugEnabled()) only matches if logLevel is debug not if it is INFO * Remove log * Remove arbitrator checks * Remove arbitrator address - It works not if not legacy arbitrator is registered. We cannot remove too much from arbitration as we would risk to break account signing and display of old arbitration cases. Though if testing time permits we should try to clean out more of arbitration domain what is not needed anymore. --- common/src/main/proto/pb.proto | 3 +- .../main/java/bisq/core/app/BisqSetup.java | 7 +- .../core/btc/wallet/TradeWalletService.java | 34 --------- .../bisq/core/btc/wallet/WalletService.java | 58 ++++++++++++++- .../bisq/core/offer/OpenOfferManager.java | 8 +- .../messages/OfferAvailabilityResponse.java | 6 +- .../offer/placeoffer/PlaceOfferModel.java | 4 + .../placeoffer/tasks/CreateMakerFeeTx.java | 14 ++-- .../bisq/core/support/dispute/Dispute.java | 73 ++++++++++++------- .../core/support/dispute/DisputeManager.java | 11 ++- .../arbitration/ArbitrationManager.java | 16 ++-- .../dispute/mediation/MediationManager.java | 3 + .../support/dispute/refund/RefundManager.java | 3 + .../main/java/bisq/core/trade/Contract.java | 6 -- core/src/main/java/bisq/core/trade/Trade.java | 1 - .../java/bisq/core/trade/TradeManager.java | 6 +- .../messages/InputsForDepositTxRequest.java | 3 +- .../core/trade/protocol/ProcessModel.java | 4 +- ...ssPeerPublishedDelayedPayoutTxMessage.java | 3 +- ...rocessDelayedPayoutTxSignatureRequest.java | 4 +- ...essDepositTxAndDelayedPayoutTxMessage.java | 9 ++- .../BuyerProcessPayoutTxPublishedMessage.java | 7 +- .../buyer/BuyerSignsDelayedPayoutTx.java | 4 +- .../maker/MakerCreateAndSignContract.java | 1 - ...kerProcessesInputsForDepositTxRequest.java | 22 +++--- .../tasks/maker/MakerSetsLockTime.java | 13 ++-- ...ocessMediatedPayoutTxPublishedMessage.java | 7 +- .../seller/SellerCreatesDelayedPayoutTx.java | 4 +- .../SellerFinalizesDelayedPayoutTx.java | 6 +- ...erSendDelayedPayoutTxSignatureRequest.java | 5 +- .../seller/SellerSignsDelayedPayoutTx.java | 4 +- .../tasks/taker/CreateTakerFeeTx.java | 8 +- .../taker/TakerVerifyAndSignContract.java | 1 - .../resources/i18n/displayStrings.properties | 22 +++--- .../bisq/core/offer/OpenOfferManagerTest.java | 6 +- .../transactions/TransactionAwareTrade.java | 32 +++++--- .../TransactionListItemFactory.java | 8 +- .../transactions/TransactionsListItem.java | 37 ++++++---- .../main/overlays/windows/ContractWindow.java | 17 ++++- .../main/overlays/windows/TacWindow.java | 19 ++--- .../pendingtrades/PendingTradesDataModel.java | 2 + .../pendingtrades/steps/TradeStepView.java | 2 - .../desktop/main/support/SupportView.java | 4 +- .../main/java/bisq/desktop/util/GUIUtil.java | 2 +- .../TransactionAwareTradeTest.java | 5 +- docs/dev-setup.md | 4 +- .../bisq/network/p2p/network/Connection.java | 3 - .../p2p/peers/getdata/RequestDataHandler.java | 14 ++-- 48 files changed, 309 insertions(+), 226 deletions(-) diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index e59d12e4170..43911ea5f75 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -762,6 +762,7 @@ message Dispute { string dispute_payout_tx_id = 23; SupportType support_type = 24; string mediators_dispute_result = 25; + string delayed_payout_tx_id = 26; } message Attachment { @@ -813,7 +814,7 @@ message Contract { int64 trade_amount = 2; int64 trade_price = 3; string taker_fee_tx_id = 4; - NodeAddress arbitrator_node_address = 5; + reserved 5; // WAS: arbitrator_node_address bool is_buyer_maker_and_seller_taker = 6; string maker_account_id = 7; string taker_account_id = 8; diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 897899cc223..4ae7d658f6b 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -309,11 +309,8 @@ public void addBisqSetupCompleteListener(BisqSetupCompleteListener listener) { } public void start() { - if (log.isDebugEnabled()) { - UserThread.runPeriodically(() -> { - log.debug("1 second heartbeat"); - }, 1); - } + UserThread.runPeriodically(() -> { + }, 1); maybeReSyncSPVChain(); maybeShowTac(); } 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 d0fba89d717..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; @@ -1168,38 +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 tx = new Transaction(params, transaction.bitcoinSerialize()); - tx.getConfidence(Context.get()).setSource(TransactionConfidence.Source.SELF); - - if (wallet != null) { - wallet.receivePending(tx, null, true); - } - return tx; - } - - /** - * @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 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/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index f0226ee6a52..f0f41978e8b 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -20,6 +20,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.exceptions.TradePriceOutOfToleranceException; import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.messages.OfferAvailabilityRequest; @@ -107,6 +108,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; + private final DaoFacade daoFacade; private final Storage> openOfferTradableListStorage; private final Map offersToBeEdited = new HashMap<>(); private boolean stopped; @@ -133,6 +135,7 @@ public OpenOfferManager(KeyRing keyRing, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, + DaoFacade daoFacade, Storage> storage) { this.keyRing = keyRing; this.user = user; @@ -148,6 +151,7 @@ public OpenOfferManager(KeyRing keyRing, this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; + this.daoFacade = daoFacade; openOfferTradableListStorage = storage; @@ -344,6 +348,7 @@ public void placeOffer(Offer offer, offerBookService, arbitratorManager, tradeStatisticsManager, + daoFacade, user); PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( model, @@ -590,9 +595,6 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { availabilityResult = AvailabilityResult.AVAILABLE; - arbitratorNodeAddress = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager).getNodeAddress(); - openOffer.setArbitratorNodeAddress(arbitratorNodeAddress); - mediatorNodeAddress = DisputeAgentSelection.getLeastUsedMediator(tradeStatisticsManager, mediatorManager).getNodeAddress(); openOffer.setMediatorNodeAddress(mediatorNodeAddress); 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 1f5161f6f2e..41b5aa0fcc7 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java @@ -93,13 +93,13 @@ private OfferAvailabilityResponse(String offerId, public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder() .setOfferId(offerId) - .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())) - .setArbitrator(arbitrator.toProtoMessage()); + .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); Optional.ofNullable(mediator).ifPresent(e -> builder.setMediator(mediator.toProtoMessage())); Optional.ofNullable(refundAgent).ifPresent(e -> builder.setRefundAgent(refundAgent.toProtoMessage())); + Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage())); return getNetworkEnvelopeBuilder() .setOfferAvailabilityResponse(builder) @@ -112,7 +112,7 @@ public static OfferAvailabilityResponse fromProto(protobuf.OfferAvailabilityResp Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, proto.getUid().isEmpty() ? null : proto.getUid(), - NodeAddress.fromProto(proto.getArbitrator()), + proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null, proto.hasMediator() ? NodeAddress.fromProto(proto.getMediator()) : null, proto.hasRefundAgent() ? NodeAddress.fromProto(proto.getRefundAgent()) : null); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java index e63e31ecdf2..294f7cd19ff 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java @@ -20,6 +20,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.offer.Offer; import bisq.core.offer.OfferBookService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; @@ -48,6 +49,7 @@ public class PlaceOfferModel implements Model { private final OfferBookService offerBookService; private final ArbitratorManager arbitratorManager; private final TradeStatisticsManager tradeStatisticsManager; + private final DaoFacade daoFacade; private final User user; // Mutable @@ -65,6 +67,7 @@ public PlaceOfferModel(Offer offer, OfferBookService offerBookService, ArbitratorManager arbitratorManager, TradeStatisticsManager tradeStatisticsManager, + DaoFacade daoFacade, User user) { this.offer = offer; this.reservedFundsForOffer = reservedFundsForOffer; @@ -75,6 +78,7 @@ public PlaceOfferModel(Offer offer, this.offerBookService = offerBookService; this.arbitratorManager = arbitratorManager; this.tradeStatisticsManager = tradeStatisticsManager; + this.daoFacade = daoFacade; this.user = user; } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java index f7e35d82a08..70bf6d5cf25 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java @@ -25,11 +25,10 @@ import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.WalletService; import bisq.core.dao.exceptions.DaoDisabledException; +import bisq.core.dao.governance.param.Param; import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.offer.Offer; -import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.placeoffer.PlaceOfferModel; -import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.common.UserThread; import bisq.common.taskrunner.Task; @@ -45,7 +44,6 @@ public class CreateMakerFeeTx extends Task { private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class); - private Transaction tradeFeeTx = null; @SuppressWarnings({"unused"}) public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) { @@ -62,17 +60,15 @@ protected void run() { String id = offer.getId(); BtcWalletService walletService = model.getWalletService(); - Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), - model.getArbitratorManager()); - Address fundingAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(); Address reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); Address changeAddress = walletService.getFreshAddressEntry().getAddress(); - final TradeWalletService tradeWalletService = model.getTradeWalletService(); + TradeWalletService tradeWalletService = model.getTradeWalletService(); + String feeReceiver = model.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS); if (offer.isCurrencyForMakerFeeBtc()) { - tradeFeeTx = tradeWalletService.createBtcTradingFeeTx( + tradeWalletService.createBtcTradingFeeTx( fundingAddress, reservedForTradeAddress, changeAddress, @@ -80,7 +76,7 @@ protected void run() { model.isUseSavingsWallet(), offer.getMakerFee(), offer.getTxFee(), - arbitrator.getBtcAddress(), + feeReceiver, true, new TxBroadcaster.Callback() { @Override 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 abe76911ada..e058c4d462d 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -99,6 +99,9 @@ public final class Dispute implements NetworkPayload { @Setter @Nullable private String mediatorsDisputeResult; + @Setter + @Nullable + private String delayedPayoutTxId; /////////////////////////////////////////////////////////////////////////////////////////// @@ -224,11 +227,12 @@ public protobuf.Dispute toProtoMessage() { Optional.ofNullable(disputeResultProperty.get()).ifPresent(result -> builder.setDisputeResult(disputeResultProperty.get().toProtoMessage())); Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType))); Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult)); + Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId)); return builder.build(); } public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver coreProtoResolver) { - final Dispute dispute = new Dispute(proto.getTradeId(), + Dispute dispute = new Dispute(proto.getTradeId(), proto.getTraderId(), proto.getDisputeOpenerIsBuyer(), proto.getDisputeOpenerIsMaker(), @@ -256,7 +260,17 @@ 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()); + + String mediatorsDisputeResult = proto.getMediatorsDisputeResult(); + if (!mediatorsDisputeResult.isEmpty()) { + dispute.setMediatorsDisputeResult(mediatorsDisputeResult); + } + + String delayedPayoutTxId = proto.getDelayedPayoutTxId(); + if (!delayedPayoutTxId.isEmpty()) { + dispute.setDelayedPayoutTxId(delayedPayoutTxId); + } + return dispute; } @@ -334,34 +348,37 @@ public boolean isClosed() { return isClosedProperty.get(); } + @Override public String toString() { return "Dispute{" + - "tradeId='" + tradeId + '\'' + - ", id='" + id + '\'' + - ", traderId=" + traderId + - ", disputeOpenerIsBuyer=" + disputeOpenerIsBuyer + - ", disputeOpenerIsMaker=" + disputeOpenerIsMaker + - ", openingDate=" + openingDate + - ", traderPubKeyRing=" + traderPubKeyRing + - ", tradeDate=" + tradeDate + - ", contract=" + contract + - ", contractHash=" + Utilities.bytesAsHexString(contractHash) + - ", depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) + - ", payoutTxSerialized not displayed for privacy reasons..." + - ", depositTxId='" + depositTxId + '\'' + - ", payoutTxId='" + payoutTxId + '\'' + - ", contractAsJson='" + contractAsJson + '\'' + - ", makerContractSignature='" + makerContractSignature + '\'' + - ", takerContractSignature='" + takerContractSignature + '\'' + - ", agentPubKeyRing=" + agentPubKeyRing + - ", isSupportTicket=" + isSupportTicket + - ", chatMessages=" + chatMessages + - ", isClosed=" + isClosedProperty.get() + - ", disputeResult=" + disputeResultProperty.get() + - ", disputePayoutTxId='" + disputePayoutTxId + '\'' + - ", isClosedProperty=" + isClosedProperty + - ", disputeResultProperty=" + disputeResultProperty + - '}'; + "\n tradeId='" + tradeId + '\'' + + ",\n id='" + id + '\'' + + ",\n traderId=" + traderId + + ",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer + + ",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker + + ",\n traderPubKeyRing=" + traderPubKeyRing + + ",\n tradeDate=" + tradeDate + + ",\n contract=" + contract + + ",\n contractHash=" + Utilities.bytesAsHexString(contractHash) + + ",\n depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) + + ",\n payoutTxSerialized=" + Utilities.bytesAsHexString(payoutTxSerialized) + + ",\n depositTxId='" + depositTxId + '\'' + + ",\n payoutTxId='" + payoutTxId + '\'' + + ",\n contractAsJson='" + contractAsJson + '\'' + + ",\n makerContractSignature='" + makerContractSignature + '\'' + + ",\n takerContractSignature='" + takerContractSignature + '\'' + + ",\n agentPubKeyRing=" + agentPubKeyRing + + ",\n isSupportTicket=" + isSupportTicket + + ",\n chatMessages=" + chatMessages + + ",\n isClosedProperty=" + isClosedProperty + + ",\n disputeResultProperty=" + disputeResultProperty + + ",\n disputePayoutTxId='" + disputePayoutTxId + '\'' + + ",\n openingDate=" + openingDate + + ",\n storage=" + storage + + ",\n supportType=" + supportType + + ",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' + + ",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' + + "\n}"; } } 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 e7ae907394d..8c4964b3d45 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -55,6 +55,8 @@ import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -157,6 +159,7 @@ public void addAndPersistChatMessage(ChatMessage message) { // We get that message at both peers. The dispute object is in context of the trader public abstract void onDisputeResultMessage(DisputeResultMessage disputeResultMessage); + @Nullable public abstract NodeAddress getAgentNodeAddress(Dispute dispute); protected abstract Trade.DisputeState getDisputeState_StartedByPeer(); @@ -248,13 +251,13 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa String errorMessage = null; Dispute dispute = openNewDisputeMessage.getDispute(); + dispute.setStorage(disputeListService.getStorage()); Contract contractFromOpener = dispute.getContract(); PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contractFromOpener.getSellerPubKeyRing() : contractFromOpener.getBuyerPubKeyRing(); if (isAgent(dispute)) { if (!disputeList.contains(dispute)) { Optional storedDisputeOptional = findDispute(dispute); if (!storedDisputeOptional.isPresent()) { - dispute.setStorage(disputeListService.getStorage()); disputeList.add(dispute); errorMessage = sendPeerOpenedDisputeMessage(dispute, contractFromOpener, peersPubKeyRing); } else { @@ -381,6 +384,10 @@ public void sendOpenNewDisputeMessage(Dispute dispute, } NodeAddress agentNodeAddress = getAgentNodeAddress(dispute); + if (agentNodeAddress == null) { + return; + } + OpenNewDisputeMessage openNewDisputeMessage = new OpenNewDisputeMessage(dispute, p2PService.getAddress(), UUID.randomUUID().toString(), @@ -481,6 +488,8 @@ private String sendPeerOpenedDisputeMessage(Dispute disputeFromOpener, disputeFromOpener.getAgentPubKeyRing(), disputeFromOpener.isSupportTicket(), disputeFromOpener.getSupportType()); + dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId()); + Optional storedDisputeOptional = findDispute(dispute); if (!storedDisputeOptional.isPresent()) { String disputeInfo = getDisputeInfo(dispute); 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 3dd3b779fd8..b1393bb1585 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -24,6 +24,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TxBroadcaster; +import bisq.core.btc.wallet.WalletService; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; @@ -65,6 +66,8 @@ import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -121,9 +124,10 @@ public void dispatchMessage(SupportMessage message) { } } + @Nullable @Override public NodeAddress getAgentNodeAddress(Dispute dispute) { - return dispute.getContract().getArbitratorNodeAddress(); + return null; } @Override @@ -251,7 +255,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) { @@ -336,9 +340,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/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 7ad44e76509..506c881a9e6 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 @@ -57,6 +57,8 @@ import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -202,6 +204,7 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { // API /////////////////////////////////////////////////////////////////////////////////////////// + @Nullable @Override public NodeAddress getAgentNodeAddress(Dispute dispute) { return dispute.getContract().getMediatorNodeAddress(); 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 index 34dd6711383..a406cd64126 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -51,6 +51,8 @@ import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -199,6 +201,7 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { // API /////////////////////////////////////////////////////////////////////////////////////////// + @Nullable @Override public NodeAddress getAgentNodeAddress(Dispute dispute) { return dispute.getContract().getRefundAgentNodeAddress(); diff --git a/core/src/main/java/bisq/core/trade/Contract.java b/core/src/main/java/bisq/core/trade/Contract.java index 43bd345813b..65a8b00d9f7 100644 --- a/core/src/main/java/bisq/core/trade/Contract.java +++ b/core/src/main/java/bisq/core/trade/Contract.java @@ -55,7 +55,6 @@ public final class Contract implements NetworkPayload { private final String takerFeeTxID; private final NodeAddress buyerNodeAddress; private final NodeAddress sellerNodeAddress; - private final NodeAddress arbitratorNodeAddress; private final NodeAddress mediatorNodeAddress; private final boolean isBuyerMakerAndSellerTaker; private final String makerAccountId; @@ -83,7 +82,6 @@ public Contract(OfferPayload offerPayload, String takerFeeTxID, NodeAddress buyerNodeAddress, NodeAddress sellerNodeAddress, - NodeAddress arbitratorNodeAddress, NodeAddress mediatorNodeAddress, boolean isBuyerMakerAndSellerTaker, String makerAccountId, @@ -104,7 +102,6 @@ public Contract(OfferPayload offerPayload, this.takerFeeTxID = takerFeeTxID; this.buyerNodeAddress = buyerNodeAddress; this.sellerNodeAddress = sellerNodeAddress; - this.arbitratorNodeAddress = arbitratorNodeAddress; this.mediatorNodeAddress = mediatorNodeAddress; this.isBuyerMakerAndSellerTaker = isBuyerMakerAndSellerTaker; this.makerAccountId = makerAccountId; @@ -143,7 +140,6 @@ public static Contract fromProto(protobuf.Contract proto, CoreProtoResolver core proto.getTakerFeeTxId(), NodeAddress.fromProto(proto.getBuyerNodeAddress()), NodeAddress.fromProto(proto.getSellerNodeAddress()), - NodeAddress.fromProto(proto.getArbitratorNodeAddress()), NodeAddress.fromProto(proto.getMediatorNodeAddress()), proto.getIsBuyerMakerAndSellerTaker(), proto.getMakerAccountId(), @@ -169,7 +165,6 @@ public protobuf.Contract toProtoMessage() { .setTakerFeeTxId(takerFeeTxID) .setBuyerNodeAddress(buyerNodeAddress.toProtoMessage()) .setSellerNodeAddress(sellerNodeAddress.toProtoMessage()) - .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()) .setMediatorNodeAddress(mediatorNodeAddress.toProtoMessage()) .setIsBuyerMakerAndSellerTaker(isBuyerMakerAndSellerTaker) .setMakerAccountId(makerAccountId) @@ -300,7 +295,6 @@ public String toString() { ",\n takerFeeTxID='" + takerFeeTxID + '\'' + ",\n buyerNodeAddress=" + buyerNodeAddress + ",\n sellerNodeAddress=" + sellerNodeAddress + - ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + ",\n mediatorNodeAddress=" + mediatorNodeAddress + ",\n refundAgentNodeAddress=" + refundAgentNodeAddress + ",\n isBuyerMakerAndSellerTaker=" + isBuyerMakerAndSellerTaker + diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 938b225e437..fa06ff8219a 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -652,7 +652,6 @@ void updateDepositTxFromWallet() { } public void applyDepositTx(Transaction tx) { - log.debug("setDepositTx " + tx); this.depositTx = tx; depositTxId = depositTx.getHashAsString(); setupConfidenceListener(); diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 86cc5a7ed32..d3612d95165 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -25,6 +25,7 @@ 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; @@ -601,8 +602,9 @@ public void publishDelayedPayoutTx(String tradeId, btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); // We might receive funds on AddressEntry.Context.TRADE_PAYOUT so we don't swap that - tradeWalletService.addTxToWallet(delayedPayoutTx); - tradeWalletService.broadcastTx(delayedPayoutTx, new TxBroadcaster.Callback() { + Transaction committedDelayedPayoutTx = WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, btcWalletService.getWallet()); + + tradeWalletService.broadcastTx(committedDelayedPayoutTx, new TxBroadcaster.Callback() { @Override public void onSuccess(Transaction transaction) { log.info("publishDelayedPayoutTx onSuccess " + transaction); diff --git a/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java index defe3165080..57df03a1b24 100644 --- a/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/messages/InputsForDepositTxRequest.java @@ -61,6 +61,7 @@ public final class InputsForDepositTxRequest extends TradeMessage implements Dir private final List acceptedArbitratorNodeAddresses; private final List acceptedMediatorNodeAddresses; private final List acceptedRefundAgentNodeAddresses; + @Nullable private final NodeAddress arbitratorNodeAddress; private final NodeAddress mediatorNodeAddress; private final NodeAddress refundAgentNodeAddress; @@ -152,13 +153,13 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { .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))); + Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())); builder.setCurrentDate(currentDate); return getNetworkEnvelopeBuilder().setInputsForDepositTxRequest(builder).build(); 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 f5678586379..9e25fa1a0f8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -108,7 +108,9 @@ public class ProcessModel implements Model, PersistablePayload { @Setter @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(); 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 index d1efcb140bd..d0dc9300530 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPeerPublishedDelayedPayoutTxMessage.java @@ -17,6 +17,7 @@ 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; @@ -51,7 +52,7 @@ protected void run() { // We add the tx to our wallet. Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); - processModel.getTradeWalletService().addTxToWallet(delayedPayoutTx); + WalletService.maybeAddSelfTxToWallet(delayedPayoutTx, processModel.getBtcWalletService().getWallet()); // todo trade.setState 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 index 1d2f0f35292..c9fe2e8b7ad 100644 --- 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 @@ -45,8 +45,8 @@ protected void run() { checkNotNull(message); Validator.checkTradeId(processModel.getOfferId(), message); byte[] delayedPayoutTxAsBytes = checkNotNull(message.getDelayedPayoutTx()); - Transaction delayedPayoutTx = processModel.getBtcWalletService().getTxFromSerializedTx(delayedPayoutTxAsBytes); - trade.applyDelayedPayoutTx(delayedPayoutTx); + 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()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java index 28e3b0a5049..7c0e3c7b3b2 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerProcessDepositTxAndDelayedPayoutTxMessage.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.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.TradeTask; @@ -52,15 +53,17 @@ protected void run() { // To access tx confidence we need to add that tx into our wallet. Transaction depositTx = processModel.getBtcWalletService().getTxFromSerializedTx(message.getDepositTx()); // update with full tx - Transaction depositTxWalletTx = processModel.getTradeWalletService().addTxToWallet(depositTx); - trade.applyDepositTx(depositTxWalletTx); - BtcWalletService.printTx("depositTx received from peer", depositTxWalletTx); + 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()); 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/BuyerSignsDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java index 8fa17901d6d..0d0bb585ae4 100644 --- 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 @@ -46,7 +46,7 @@ protected void run() { try { runInterceptHook(); - Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); + Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx()); BtcWalletService btcWalletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); @@ -57,7 +57,7 @@ protected void run() { btcWalletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey()), "buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); - byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(delayedPayoutTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey); + byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(preparedDelayedPayoutTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey); processModel.setDelayedPayoutTxSignature(delayedPayoutTxSignature); complete(); 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 9f963e0afac..fcd6ec6a797 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 @@ -79,7 +79,6 @@ protected void run() { trade.getTakerFeeTxId(), buyerNodeAddress, sellerNodeAddress, - trade.getArbitratorNodeAddress(), trade.getMediatorNodeAddress(), isBuyerMakerAndSellerTaker, processModel.getAccountId(), diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java index d449accd164..6c5c1ea05e7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java @@ -72,8 +72,6 @@ protected void run() { 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)); @@ -82,15 +80,17 @@ protected void run() { User user = checkNotNull(processModel.getUser(), "User must not be null"); - NodeAddress arbitratorNodeAddress = checkNotNull(inputsForDepositTxRequest.getArbitratorNodeAddress(), - "payDepositRequest.getArbitratorNodeAddress() must not be null"); - trade.setArbitratorNodeAddress(arbitratorNodeAddress); - Arbitrator arbitrator = checkNotNull(user.getAcceptedArbitratorByAddress(arbitratorNodeAddress), - "user.getAcceptedArbitratorByAddress(arbitratorNodeAddress) must not be null"); - trade.setArbitratorBtcPubKey(checkNotNull(arbitrator.getBtcPubKey(), - "arbitrator.getBtcPubKey() must not be null")); - trade.setArbitratorPubKeyRing(checkNotNull(arbitrator.getPubKeyRing(), - "arbitrator.getPubKeyRing() must not be null")); + NodeAddress arbitratorNodeAddress = inputsForDepositTxRequest.getArbitratorNodeAddress(); + if (arbitratorNodeAddress != null) { + trade.setArbitratorNodeAddress(arbitratorNodeAddress); + Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(arbitratorNodeAddress); + if (arbitrator != null) { + trade.setArbitratorBtcPubKey(checkNotNull(arbitrator.getBtcPubKey(), + "arbitrator.getBtcPubKey() must not be null")); + trade.setArbitratorPubKeyRing(checkNotNull(arbitrator.getPubKeyRing(), + "arbitrator.getPubKeyRing() must not be null")); + } + } NodeAddress mediatorNodeAddress = checkNotNull(inputsForDepositTxRequest.getMediatorNodeAddress(), "payDepositRequest.getMediatorNodeAddress() must not be null"); 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 index bea1d452e3c..94158523d7c 100644 --- 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 @@ -20,10 +20,9 @@ import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.common.app.DevEnv; import bisq.common.taskrunner.TaskRunner; -import java.util.Random; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -38,13 +37,15 @@ protected void run() { try { runInterceptHook(); - // 20-30 days - int delay = 144 * 20 + new Random().nextInt(144 * 10); + // 10 days for altcoins, 20 days for other payment methods + int delay = processModel.getOffer().getPaymentMethod().isAsset() ? 144 * 10 : 144 * 20; + if (DevEnv.isDevMode()) { + delay = 1; + } + 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) { 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/seller/SellerCreatesDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerCreatesDelayedPayoutTx.java index 437dbaed0fa..6bddc5d9d64 100644 --- 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 @@ -50,12 +50,12 @@ protected void run() { Transaction depositTx = checkNotNull(trade.getDepositTx()); long lockTime = trade.getLockTime(); - Transaction unsignedDelayedPayoutTx = tradeWalletService.createDelayedUnsignedPayoutTx(depositTx, + Transaction preparedDelayedPayoutTx = tradeWalletService.createDelayedUnsignedPayoutTx(depositTx, donationAddressString, minerFee, lockTime); - trade.applyDelayedPayoutTx(unsignedDelayedPayoutTx); + processModel.setPreparedDelayedPayoutTx(preparedDelayedPayoutTx); complete(); } catch (Throwable 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 index 67583a95981..f615bad0c44 100644 --- 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 @@ -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.protocol.tasks.TradeTask; @@ -45,7 +46,7 @@ protected void run() { try { runInterceptHook(); - Transaction unsignedDelayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); + Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx()); BtcWalletService btcWalletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); @@ -58,13 +59,14 @@ protected void run() { byte[] buyerSignature = processModel.getTradingPeer().getDelayedPayoutTxSignature(); byte[] sellerSignature = processModel.getDelayedPayoutTxSignature(); - Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeDelayedPayoutTx(unsignedDelayedPayoutTx, + Transaction signedDelayedPayoutTx = processModel.getTradeWalletService().finalizeDelayedPayoutTx(preparedDelayedPayoutTx, buyerMultiSigPubKey, sellerMultiSigPubKey, buyerSignature, sellerSignature); trade.applyDelayedPayoutTx(signedDelayedPayoutTx); + WalletService.maybeAddSelfTxToWallet(signedDelayedPayoutTx, processModel.getBtcWalletService().getWallet()); complete(); } catch (Throwable 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 index 8b6fc402ee6..187c274304b 100644 --- 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 @@ -46,11 +46,12 @@ protected void run() { try { runInterceptHook(); - Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx(), "trade.getDelayedPayoutTx() must not be null"); + Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx(), + "processModel.getPreparedDelayedPayoutTx() must not be null"); DelayedPayoutTxSignatureRequest message = new DelayedPayoutTxSignatureRequest(UUID.randomUUID().toString(), processModel.getOfferId(), processModel.getMyNodeAddress(), - delayedPayoutTx.bitcoinSerialize()); + preparedDelayedPayoutTx.bitcoinSerialize()); // todo trade.setState 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 index 6947ba908c2..b8b8e155c2d 100644 --- 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 @@ -46,7 +46,7 @@ protected void run() { try { runInterceptHook(); - Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); + Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx()); BtcWalletService btcWalletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); @@ -58,7 +58,7 @@ protected void run() { "sellerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id); byte[] buyerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); - byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(delayedPayoutTx, + byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx(preparedDelayedPayoutTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java index 1b9241f8408..15ede07092e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/CreateTakerFeeTx.java @@ -22,8 +22,7 @@ import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.WalletService; import bisq.core.dao.exceptions.DaoDisabledException; -import bisq.core.offer.availability.DisputeAgentSelection; -import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; +import bisq.core.dao.governance.param.Param; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; @@ -47,8 +46,6 @@ protected void run() { try { runInterceptHook(); - Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(processModel.getTradeStatisticsManager(), - processModel.getArbitratorManager()); BtcWalletService walletService = processModel.getBtcWalletService(); String id = processModel.getOffer().getId(); @@ -68,6 +65,7 @@ protected void run() { Address changeAddress = changeAddressEntry.getAddress(); TradeWalletService tradeWalletService = processModel.getTradeWalletService(); Transaction transaction; + String feeReceiver = processModel.getDaoFacade().getParamValue(Param.RECIPIENT_BTC_ADDRESS); if (trade.isCurrencyForTakerFeeBtc()) { transaction = tradeWalletService.createBtcTradingFeeTx( fundingAddress, @@ -77,7 +75,7 @@ protected void run() { processModel.isUseSavingsWallet(), trade.getTakerFee(), trade.getTxFee(), - arbitrator.getBtcAddress(), + feeReceiver, false, null); } else { 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 fb4ddc662ef..c8134b59130 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 @@ -85,7 +85,6 @@ protected void run() { trade.getTakerFeeTxId(), buyerNodeAddress, sellerNodeAddress, - trade.getArbitratorNodeAddress(), trade.getMediatorNodeAddress(), isBuyerMakerAndSellerTaker, maker.getAccountId(), diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 90d033a1d67..00184e5f02b 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -211,6 +211,8 @@ shared.selectedMediator=Selected mediator shared.mediator=Mediator shared.arbitrator2=Arbitrator shared.refundAgent=Refund agent +shared.delayedPayoutTxId=Refund collateral transaction ID + #################################################################### # UI views @@ -823,15 +825,15 @@ portfolio.pending.mediationResult.popup.info=The mediator has suggested the foll You receive: {0}\n\ Your trade peer receives: {1}\n\n\ 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, they have to wait until {2} (block {3}) and can afterwards open a \ + By accepting, you sign the proposed payout transaction. \ + If your trade peer also accepts and signs, the payout will be completed, and the trade will be closed.\n\n\ + If one or both of you reject the suggestion, you will have to wait until {2} (block {3}) to 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\ + The arbitrator may charge a small fee (max. the trader's security deposit) as compensation for their work. \ + Both traders agreeing to the mediator's suggestion is the happy path — requesting arbitration is meant for \ + exceptional circumstances, such as if a trader is sure the mediator did not make a fair payout suggestion \ + (or if the other peer is unresponsive).\n\n\ + More details about the new arbitration model:\n\ https://docs.bisq.network/trading-rules.html#arbitration portfolio.pending.mediationResult.popup.openArbitration=Reject and request arbitration @@ -902,6 +904,7 @@ 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 @@ -930,7 +933,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.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets support.filter=Filter list support.filter.prompt=Enter trade ID, date, onion address or account data @@ -968,7 +971,6 @@ support.sellerOfferer=BTC seller/Maker support.buyerTaker=BTC buyer/Taker support.sellerTaker=BTC seller/Taker -# TODO @m52go could you provide a good text here? support.backgroundInfo=Bisq is not a company, so it handles disputes differently.\n\n\ Traders can communicate within the application via a secure chat on the pending trades screen to attempt solving a dispute on their own. \ If that is not sufficient, a mediator can step in to help. The mediator will evaluate the situation and give a recommendation for the \ diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index 5fead0386be..c80ea13e0e1 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, 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, 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, null, null, new Storage>(null, null, corruptedDatabaseFilesHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); 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 8f66ee1f140..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 @@ -30,6 +30,7 @@ import org.bitcoinj.core.Address; import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; import javafx.collections.ObservableList; @@ -114,21 +115,28 @@ private boolean isDisputedPayoutTx(String txId) { }); } - private boolean isDelayedPayoutTx(String txId) { - String delegateId = trade.getId(); + boolean isDelayedPayoutTx(String txId) { + Transaction transaction = btcWalletService.getTransaction(txId); + if (transaction == null) + return false; - ObservableList disputes = refundManager.getDisputesAsObservableList(); - return disputes.stream() - .anyMatch(dispute -> { - Transaction delayedPayoutTx = trade.getDelayedPayoutTx(); - if (delayedPayoutTx != null) { - boolean isDelayedPayoutTx = txId.equals(delayedPayoutTx.getHashAsString()); - String disputeTradeId = dispute.getTradeId(); - boolean isDisputeRelatedToThis = delegateId.equals(disputeTradeId); - return isDelayedPayoutTx && isDisputeRelatedToThis; - } else { + 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()); }); } 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 92fa004c420..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,7 +20,6 @@ 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; @@ -31,8 +30,6 @@ import javax.inject.Inject; import javax.inject.Singleton; -import java.util.Optional; - import javax.annotation.Nullable; @@ -61,13 +58,10 @@ public class TransactionListItemFactory { } TransactionsListItem create(Transaction transaction, @Nullable TransactionAwareTradable tradable) { - Optional maybeTradable = Optional.ofNullable(tradable) - .map(TransactionAwareTradable::asTradable); - return new TransactionsListItem(transaction, btcWalletService, bsqWalletService, - maybeTradable, + tradable, daoFacade, pubKeyRing, formatter, 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 4333db056db..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 @@ -88,7 +88,7 @@ class TransactionsListItem { TransactionsListItem(Transaction transaction, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, - Optional tradableOptional, + TransactionAwareTradable transactionAwareTradable, DaoFacade daoFacade, PubKeyRing pubKeyRing, BSFormatter formatter, @@ -98,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); @@ -199,41 +202,42 @@ 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); + 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", id); + details = Res.get("funds.tx.disputePayout", tradeId); } else { - details = Res.get("funds.tx.disputeLost", id); + 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", id); + details = Res.get("funds.tx.refund", tradeId); } else { Contract contract = trade.getContract(); Coin tradeAmount = trade.getTradeAmount(); @@ -241,12 +245,17 @@ public void onTransactionConfidenceChanged(TransactionConfidence confidence) { boolean isBuyer = contract.isMyRoleBuyer(pubKeyRing); amountAsCoin = isBuyer ? trade.getOffer().getBuyerSecurityDeposit().multiply(-1) : (trade.getOffer().getSellerSecurityDeposit().add(tradeAmount)).multiply(-1); - details = Res.get("funds.tx.collateralForRefund", id); + details = Res.get("funds.tx.collateralForRefund", tradeId); txConfidenceIndicator.setVisible(false); } } } else { - details = Res.get("funds.tx.unknown", id); + if (transactionAwareTrade.isDelayedPayoutTx(txId)) { + details = Res.get("funds.tx.timeLockedPayoutTx", tradeId); + txConfidenceIndicator.setVisible(false); + } else { + details = Res.get("funds.tx.unknown", tradeId); + } } } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index 046c68dfc5a..181eab156eb 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -39,6 +39,8 @@ import bisq.core.trade.Contract; import bisq.core.util.BSFormatter; +import bisq.network.p2p.NodeAddress; + import bisq.common.UserThread; import bisq.common.crypto.PubKeyRing; @@ -135,6 +137,8 @@ private void addContent() { rows++; if (dispute.getPayoutTxSerialized() != null) rows++; + if (dispute.getDelayedPayoutTxId() != null) + rows++; if (showAcceptedCountryCodes) rows++; if (showAcceptedBanks) @@ -202,8 +206,12 @@ private void addContent() { } } - String agentNodeAddress = disputeManager != null ? disputeManager.getAgentNodeAddress(dispute).getFullAddress() : ""; - addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, title, agentNodeAddress); + if (disputeManager != null) { + NodeAddress agentNodeAddress = disputeManager.getAgentNodeAddress(dispute); + if (agentNodeAddress != null) { + addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, title, agentNodeAddress.getFullAddress()); + } + } if (showAcceptedCountryCodes) { String countries; @@ -232,8 +240,13 @@ private void addContent() { addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.makerFeeTxId"), offer.getOfferFeePaymentTxId()); addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.takerFeeTxId"), contract.getTakerFeeTxID()); + if (dispute.getDepositTxSerialized() != null) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.depositTransactionId"), dispute.getDepositTxId()); + + if (dispute.getDelayedPayoutTxId() != null) + addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.delayedPayoutTxId"), dispute.getDelayedPayoutTxId()); + if (dispute.getPayoutTxSerialized() != null) addLabelTxIdTextField(gridPane, ++rowIndex, Res.get("shared.payoutTxId"), dispute.getPayoutTxId()); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TacWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TacWindow.java index bac108ae2c2..790afe3ac47 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TacWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TacWindow.java @@ -62,7 +62,6 @@ public void show() { // We do not translate the tacs because of the legal nature. We would need translations checked by lawyers // in each language which is too expensive atm. - String text = "1. In no event, unless for damages caused by acts of intent and gross negligence, damages resulting from personal injury, " + "or damages ensuing from other instances where liability is required by applicable law or agreed to in writing, will any " + "developer, copyright holder and/or any other party who modifies and/or conveys the software as permitted above or " + @@ -71,7 +70,7 @@ public void show() { "rendered inaccurate or losses sustained by you or third parties or a failure of the software to operate with any " + "other software), even if such developer, copyright holder and/or other party has been advised of the possibility of such damages.\n\n" + - "2. The user is responsible to use the software in compliance with local laws. Don't use the software if the usage is not legal in your jurisdiction.\n\n" + + "2. The user is responsible for using the software in compliance with local laws. Don't use the software if using it is not legal in your jurisdiction.\n\n" + "3. The " + Res.getBaseCurrencyName() + " market price is delivered by 3rd parties (BitcoinAverage, Poloniex, Coinmarketcap). " + "It is your responsibility to verify the price with other sources for correctness.\n\n" + @@ -85,16 +84,14 @@ public void show() { "6. The user confirms that they have read and agreed to the rules regarding the dispute process:\n" + " - You must complete trades within the maximum duration specified for each payment method.\n" + - " - You must enter the trade ID in the \"reason for payment\" text field when doing the fiat payment transfer.\n" + + " - You must enter the trade ID in the \"reason for payment\" text field (if possible) when doing the fiat payment transfer.\n" + " - If the bank of the fiat sender charges fees, the sender (" + Res.getBaseCurrencyCode() + " buyer) has to cover the fees.\n" + - " - You must cooperate with the mediator during the mediation process.\n" + - " - You must reply within 48 hours to each mediator inquiry.\n" + - " - If mediation does not lead to a payout by consensus of both traders the traders can open arbitration after 2 weeks.\n" + - " - Opening a refund request from arbitrators will trigger publishing the delayed payout transaction where the funds from the deposit transaction are sent to the Bisq DAO receiver address as collateral. The arbitrator will refund the traders according to his judgement.\n" + - " - Opening a refund request from arbitrators should be used only if the trade peer is not reacting or the trader considers the mediators suggested payout as unfair. " + - "If the arbitrator comes to the same conclusion as the mediator he will take a part of the payout from the trader who opened the dispute for covering his efforts.\n" + - " - The arbitrator will make a reimbursement request to the Bisq DAO to get refunded for the funds he paid out to traders in refund requests.\n" + - " - Failure to follow the above requirements may result in loss of your security deposit.\n\n" + + " - You must cooperate with the mediator during the mediation process, and respond to each mediator message within 48 hours.\n" + + " - If either (or both) traders do not accept the mediator's suggested payout, traders can open a refund request from an arbitrator after 10 days in case of altcoin trades and 20 days for fiat trades.\n" + + " - You should only open a refund request from an arbitrator if you think think the mediator's suggested payout is unfair, or if your trading peer is unresponsive." + + " - Opening a refund request from an arbitrator triggers the delayed payout transaction, sending all funds from the deposit transaction to the Bisq DAO receiver address ('collateral for refund to avoid scamming the refund process'). At this point, the arbitrator will re-investigate the case and personally refund (at their discretion) the trader who requested arbitration.\n" + + "The arbitrator may charge a small fee (max. the traders security deposit) as compensation for their work.\n" + + " - The arbitrator will then make a reimbursement request to the Bisq DAO to get reimbursed for the refund they paid to the trader.\n" + "For more details and a general overview please read the full documentation about the " + "arbitration system and the dispute process."; message(text); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index 1aaf7319963..3415e178405 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -616,6 +616,8 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { } }); + dispute.setDelayedPayoutTxId(trade.getDelayedPayoutTx().getHashAsString()); + trade.setDisputeState(Trade.DisputeState.REFUND_REQUESTED); //todo add UI spinner as it can take a bit if peer is offline diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 0d3f9c80ebd..fb09be39ebd 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -414,8 +414,6 @@ private void updateDisputeState(Trade.DisputeState disputeState) { }); break; case MEDIATION_CLOSED: - deactivatePaymentButtons(true); - if (tradeStepInfo != null) { tradeStepInfo.setOnAction(e -> { updateMediationResultState(); diff --git a/desktop/src/main/java/bisq/desktop/main/support/SupportView.java b/desktop/src/main/java/bisq/desktop/main/support/SupportView.java index ac314e66c0c..cdf220f83ff 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/SupportView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/SupportView.java @@ -113,8 +113,8 @@ public void initialize() { updateAgentTabs(); tradersMediationDisputesTab.setText(Res.get("support.tab.mediation.support").toUpperCase()); - tradersRefundDisputesTab.setText(Res.get("support.tab.refund.support").toUpperCase()); - tradersArbitrationDisputesTab.setText(Res.get("support.tab.arbitration.support").toUpperCase()); + tradersRefundDisputesTab.setText(Res.get("support.tab.arbitration.support").toUpperCase()); + tradersArbitrationDisputesTab.setText(Res.get("support.tab.legacyArbitration.support").toUpperCase()); navigationListener = viewPath -> { if (viewPath.size() == 3 && viewPath.indexOf(SupportView.class) == 1) loadView(viewPath.tip()); diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index b14677083f0..d52a227007e 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -786,7 +786,7 @@ public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, Wa } public static boolean canCreateOrTakeOfferOrShowPopup(User user, Navigation navigation) { - if (!user.hasAcceptedArbitrators()) { + if (!user.hasAcceptedRefundAgents()) { new Popup<>().warning(Res.get("popup.warning.noArbitratorsAvailable")).show(); return false; } diff --git a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java index fb520f46d7f..d8b12296633 100644 --- a/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java +++ b/desktop/src/test/java/bisq/desktop/main/funds/transactions/TransactionAwareTradeTest.java @@ -17,6 +17,7 @@ package bisq.desktop.main.funds.transactions; +import bisq.core.btc.wallet.BtcWalletService; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.refund.RefundManager; @@ -44,6 +45,7 @@ public class TransactionAwareTradeTest { private Trade delegate; private TransactionAwareTradable trade; private RefundManager refundManager; + private BtcWalletService btcWalletService; @Before public void setUp() { @@ -53,7 +55,8 @@ public void setUp() { delegate = mock(Trade.class, RETURNS_DEEP_STUBS); arbitrationManager = mock(ArbitrationManager.class, RETURNS_DEEP_STUBS); refundManager = mock(RefundManager.class, RETURNS_DEEP_STUBS); - trade = new TransactionAwareTrade(delegate, arbitrationManager, refundManager, null, null); + btcWalletService = mock(BtcWalletService.class, RETURNS_DEEP_STUBS); + trade = new TransactionAwareTrade(delegate, arbitrationManager, refundManager, btcWalletService, null); } @Test diff --git a/docs/dev-setup.md b/docs/dev-setup.md index 2b4f4c5b594..0c7b9a31df1 100644 --- a/docs/dev-setup.md +++ b/docs/dev-setup.md @@ -72,8 +72,8 @@ For localhost/regtest mode run the `BisqAppMain` class or `./bisq-desktop` scrip --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=4444 --appName=bisq-BTC_REGTEST_arbitrator -Once it has started up go to `Account` and click `CMD +r`. This will open a new tab for `Arbitration registration`. Select the tab and you will see a popup with a pre-filled private key. That is the developer private key (which is only valid if `--useDevPrivilegeKeys` is set) which allows you to register a new arbitrator. Follow the next screen and complete registration. -Next you have to register a mediator as well. Click `CMD + d`. This will open a new tab for `Mediator registration`. Follow the same steps as for the arbitrator registration before. +Once it has started up go to `Account` and click `CMD +n`. This will open a new tab for `Arbitration registration`. Select the tab and you will see a popup with a pre-filled private key. That is the developer private key (which is only valid if `--useDevPrivilegeKeys` is set) which allows you to register a new arbitrator. Follow the next screen and complete registration. +Next you have to register a mediator as well. Click `CMD + d`. This will open a new tab for `Mediator registration`. Follow the same steps as for the arbitrator registration before. Registration of legacy arbitrators was done with `CMD +n`. It is not needed anymore so we refer with the term arbitrator to the new arbitrator (or refund agent). _Note: You need only register once but if you have shut down all nodes (including seed node) you need to start up the arbitrator again after you start the seed node so the arbitrator re-publishes his data to the P2P network. After it has started up you can close it again. You cannot trade without having an arbitrator available._ diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java index 989dc9729bf..ef552b9cec3 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java +++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java @@ -759,9 +759,6 @@ public void run() { boolean exceeds; if (networkEnvelope instanceof ExtendedDataSizePermission) { exceeds = size > MAX_PERMITTED_MESSAGE_SIZE; - if (log.isDebugEnabled()) { - log.debug("size={}; object={}", size, Utilities.toTruncatedString(proto, 100)); - } } else { exceeds = size > PERMITTED_MESSAGE_SIZE; } diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java index 2cfb7e376b7..db4d94b3637 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java @@ -161,7 +161,7 @@ void requestData(NodeAddress nodeAddress, boolean isPreliminaryDataRequest) { networkNode.addMessageListener(this); SettableFuture future = networkNode.sendMessage(nodeAddress, getDataRequest); //noinspection UnstableApiUsage - Futures.addCallback(future, new FutureCallback() { + Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(Connection connection) { if (!stopped) { @@ -205,7 +205,7 @@ public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { final Set dataSet = getDataResponse.getDataSet(); Set persistableNetworkPayloadSet = getDataResponse.getPersistableNetworkPayloadSet(); - if (log.isDebugEnabled()) logContents(networkEnvelope, dataSet, persistableNetworkPayloadSet); + logContents(networkEnvelope, dataSet, persistableNetworkPayloadSet); if (getDataResponse.getRequestNonce() == nonce) { stopTimeoutTimer(); @@ -282,8 +282,8 @@ private void logContents(NetworkEnvelope networkEnvelope, Set dataSet, Set persistableNetworkPayloadSet) { Map> payloadByClassName = new HashMap<>(); - dataSet.stream().forEach(e -> { - final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload(); + dataSet.forEach(e -> { + ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload(); if (protectedStoragePayload == null) { log.warn("StoragePayload was null: {}", networkEnvelope.toString()); return; @@ -299,7 +299,7 @@ private void logContents(NetworkEnvelope networkEnvelope, if (persistableNetworkPayloadSet != null) { - persistableNetworkPayloadSet.stream().forEach(persistableNetworkPayload -> { + persistableNetworkPayloadSet.forEach(persistableNetworkPayload -> { // For logging different data types String className = persistableNetworkPayload.getClass().getSimpleName(); if (!payloadByClassName.containsKey(className)) @@ -316,9 +316,9 @@ private void logContents(NetworkEnvelope networkEnvelope, final int items = dataSet.size() + (persistableNetworkPayloadSet != null ? persistableNetworkPayloadSet.size() : 0); sb.append("Received ").append(items).append(" instances\n"); - payloadByClassName.entrySet().stream().forEach(e -> sb.append(e.getKey()) + payloadByClassName.forEach((key, value) -> sb.append(key) .append(": ") - .append(e.getValue().size()) + .append(value.size()) .append("\n")); sb.append("#################################################################"); log.info(sb.toString());