From ef97daac8998da2ae72ef6a6ac209c33a4c2223a Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Thu, 15 Oct 2020 12:52:18 -0300 Subject: [PATCH 01/18] Use bitcoinj 0.15.8 (commit fcec3da) --- build.gradle | 2 +- core/src/main/java/bisq/core/app/WalletAppSetup.java | 2 +- gradle/witness/gradle-witness.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index b8e2519e20b..fd827538fba 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ configure(subprojects) { ext { // in alphabetical order bcVersion = '1.63' - bitcoinjVersion = 'a733034' + bitcoinjVersion = 'fcec3da' btcdCli4jVersion = '27b94333' codecVersion = '1.13' easybindVersion = '1.0.3' diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java index c5d8b839f6f..bccd033ee0e 100644 --- a/core/src/main/java/bisq/core/app/WalletAppSetup.java +++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java @@ -107,7 +107,7 @@ void init(@Nullable Consumer chainFileLockedExceptionHandler, Runnable downloadCompleteHandler, Runnable walletInitializedHandler) { log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}", - VersionMessage.BITCOINJ_VERSION, "a733034"); + VersionMessage.BITCOINJ_VERSION, "fcec3da"); ObjectProperty walletServiceException = new SimpleObjectProperty<>(); btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(), diff --git a/gradle/witness/gradle-witness.gradle b/gradle/witness/gradle-witness.gradle index 8aff7277f1e..cecaf6c8b74 100644 --- a/gradle/witness/gradle-witness.gradle +++ b/gradle/witness/gradle-witness.gradle @@ -23,7 +23,7 @@ dependencyVerification { 'com.github.bisq-network.netlayer:tor.external:a3606a592d6b6caa6a2fb7db224eaf43c6874c6730da4815bd37ad686b283dcb', 'com.github.bisq-network.netlayer:tor.native:b15aba7fe987185037791c7ec7c529cb001b90d723d047d54aab87aceb3b3d45', 'com.github.bisq-network.netlayer:tor:a974190aa3a031067ccd1dda28a3ae58cad14060792299d86ea38a05fb21afc5', - 'com.github.bisq-network:bitcoinj:b8b6e4b8010f2b8d4aac7141c0809dea6d102c3ff3c06ceba78c2626d531b0af', + 'com.github.bisq-network.bitcoinj:bitcoinj-core:8af7faa2155feff5afd1fa0fcea6fe7f7fa0d7ee977bdc648d1e73f3dcf2c754', 'com.github.cd2357.tor-binary:tor-binary-geoip:ae27b6aca1a3a50a046eb11e38202b6d21c2fcd2b8643bbeb5ea85e065fbc1be', 'com.github.cd2357.tor-binary:tor-binary-linux32:7b5d6770aa442ef6d235e8a9bfbaa7c62560690f9fe69ff03c7a752eae84f7dc', 'com.github.cd2357.tor-binary:tor-binary-linux64:24111fa35027599a750b0176392dc1e9417d919414396d1b221ac2e707eaba76', From 161e220a4f5464f263639e93fdbb89f468f0106f Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Wed, 30 Sep 2020 14:58:15 -0300 Subject: [PATCH 02/18] BtcWalletService: Use segwit addresses --- .../main/java/bisq/core/btc/wallet/BtcWalletService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 27bb49a5b6c..375b23dd7a4 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -27,6 +27,7 @@ import bisq.core.provider.fee.FeeService; import bisq.core.user.Preferences; +import bisq.common.config.Config; import bisq.common.handlers.ErrorMessageHandler; import org.bitcoinj.core.Address; @@ -579,18 +580,17 @@ public AddressEntry getOrCreateAddressEntry(String offerId, AddressEntry.Context if (addressEntry.isPresent()) { return addressEntry.get(); } else { - // We still use non-segwit addresses for the trade protocol. // We try to use available and not yet used entries Optional emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream() .filter(e -> AddressEntry.Context.AVAILABLE == e.getContext()) .filter(e -> isAddressUnused(e.getAddress())) - .filter(e -> Script.ScriptType.P2PKH.equals(e.getAddress().getOutputScriptType())) + .filter(e -> Script.ScriptType.P2WPKH.equals(e.getAddress().getOutputScriptType())) .findAny(); if (emptyAvailableAddressEntry.isPresent()) { return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId); } else { - DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2PKH)); - AddressEntry entry = new AddressEntry(key, context, offerId, false); + DeterministicKey key = (DeterministicKey) wallet.findKeyFromAddress(wallet.freshReceiveAddress(Script.ScriptType.P2WPKH)); + AddressEntry entry = new AddressEntry(key, context, offerId, true); addressEntryList.addAddressEntry(entry); return entry; } From 06e5091f79024e5739e8b496f2aad79cf5795483 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 22 Sep 2020 18:28:54 -0300 Subject: [PATCH 03/18] TradeWalletService use P2WSH --- .../core/btc/wallet/TradeWalletService.java | 163 +++++++++++++----- .../buyer/BuyerSignsDelayedPayoutTx.java | 7 + .../seller/SellerSignsDelayedPayoutTx.java | 6 + .../windows/ManualPayoutTxWindow.java | 8 + 4 files changed, 144 insertions(+), 40 deletions(-) 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 eb7b7485c10..840048c9cbb 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -502,12 +502,12 @@ private PreparedDepositTxAndMakerInputs makerCreatesDepositTx(boolean makerIsBuy // Add MultiSig output - Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey); + Script hashedMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, false); // Tx fee for deposit tx will be paid by buyer. - TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount, - p2SHMultiSigOutputScript.getProgram()); - preparedDepositTx.addOutput(p2SHMultiSigOutput); + TransactionOutput hashedMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount, + hashedMultiSigOutputScript.getProgram()); + preparedDepositTx.addOutput(hashedMultiSigOutput); // We add the hash ot OP_RETURN with a 0 amount output TransactionOutput contractHashOutput = new TransactionOutput(params, preparedDepositTx, Coin.ZERO, @@ -587,9 +587,9 @@ public Transaction takerSignsDepositTx(boolean takerIsSeller, checkArgument(!sellerInputs.isEmpty()); // Check if maker's MultiSig script is identical to the takers - Script p2SHMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey); - if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript)) { - throw new TransactionVerificationException("Maker's p2SHMultiSigOutputScript does not match to takers p2SHMultiSigOutputScript"); + Script hashedMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, false); + if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(hashedMultiSigOutputScript)) { + throw new TransactionVerificationException("Maker's hashedMultiSigOutputScript does not match to takers hashedMultiSigOutputScript"); } // The outpoints are not available from the serialized makersDepositTx, so we cannot use that tx directly, but we use it to construct a new @@ -692,11 +692,11 @@ public Transaction createDelayedUnsignedPayoutTx(Transaction depositTx, Coin minerFee, long lockTime) throws AddressFormatException, TransactionVerificationException { - TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0); + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); Transaction delayedPayoutTx = new Transaction(params); - delayedPayoutTx.addInput(p2SHMultiSigOutput); + delayedPayoutTx.addInput(hashedMultiSigOutput); applyLockTime(lockTime, delayedPayoutTx); - Coin outputAmount = p2SHMultiSigOutput.getValue().subtract(minerFee); + Coin outputAmount = hashedMultiSigOutput.getValue().subtract(minerFee); delayedPayoutTx.addOutput(outputAmount, Address.fromString(params, donationAddressString)); WalletService.printTx("Unsigned delayedPayoutTx ToDonationAddress", delayedPayoutTx); WalletService.verifyTransaction(delayedPayoutTx); @@ -704,13 +704,17 @@ public Transaction createDelayedUnsignedPayoutTx(Transaction depositTx, } public byte[] signDelayedPayoutTx(Transaction delayedPayoutTx, + Transaction preparedDepositTx, DeterministicKey myMultiSigKeyPair, byte[] buyerPubKey, byte[] sellerPubKey) throws AddressFormatException, TransactionVerificationException { Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); - Sha256Hash sigHash = delayedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + Sha256Hash sigHash; + Coin delayedPayoutTxInputValue = preparedDepositTx.getOutput(0).getValue(); + sigHash = delayedPayoutTx.hashForWitnessSignature(0, redeemScript, + delayedPayoutTxInputValue, Transaction.SigHash.ALL, false); checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null"); if (myMultiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); @@ -733,9 +737,10 @@ public Transaction finalizeDelayedPayoutTx(Transaction delayedPayoutTx, 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); + input.setScriptSig(ScriptBuilder.createEmpty()); + TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); + input.setWitness(witness); WalletService.printTx("finalizeDelayedPayoutTx", delayedPayoutTx); WalletService.verifyTransaction(delayedPayoutTx); WalletService.checkWalletConsistency(wallet); @@ -779,7 +784,15 @@ public byte[] buyerSignsPayoutTx(Transaction depositTx, // MS redeemScript Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); // MS output from prev. tx is index 0 - Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + Sha256Hash sigHash; + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); + if (ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey())) { + sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + } else { + Coin inputValue = hashedMultiSigOutput.getValue(); + sigHash = preparedPayoutTx.hashForWitnessSignature(0, redeemScript, + inputValue, Transaction.SigHash.ALL, false); + } checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null"); if (multiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); @@ -822,7 +835,16 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx, // MS redeemScript Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); // MS output from prev. tx is index 0 - Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); + boolean hashedMultiSigOutputIsLegacy = ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey()); + Sha256Hash sigHash; + if (hashedMultiSigOutputIsLegacy) { + sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + } else { + Coin inputValue = hashedMultiSigOutput.getValue(); + sigHash = payoutTx.hashForWitnessSignature(0, redeemScript, + inputValue, Transaction.SigHash.ALL, false); + } checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null"); if (multiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); @@ -832,10 +854,16 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx, 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 (seller, buyer) - Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), - redeemScript); TransactionInput input = payoutTx.getInput(0); - input.setScriptSig(inputScript); + if (hashedMultiSigOutputIsLegacy) { + Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), + redeemScript); + input.setScriptSig(inputScript); + } else { + input.setScriptSig(ScriptBuilder.createEmpty()); + TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); + input.setWitness(witness); + } WalletService.printTx("payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); WalletService.checkWalletConsistency(wallet); @@ -863,7 +891,16 @@ public byte[] signMediatedPayoutTx(Transaction depositTx, // MS redeemScript Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); // MS output from prev. tx is index 0 - Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); + boolean hashedMultiSigOutputIsLegacy = ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey()); + Sha256Hash sigHash; + if (hashedMultiSigOutputIsLegacy) { + sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + } else { + Coin inputValue = hashedMultiSigOutput.getValue(); + sigHash = preparedPayoutTx.hashForWitnessSignature(0, redeemScript, + inputValue, Transaction.SigHash.ALL, false); + } checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null"); if (myMultiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); @@ -895,9 +932,18 @@ public Transaction finalizeMediatedPayoutTx(Transaction depositTx, 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 (seller, buyer) - Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript); + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); + boolean hashedMultiSigOutputIsLegacy = ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey()); TransactionInput input = payoutTx.getInput(0); - input.setScriptSig(inputScript); + if (hashedMultiSigOutputIsLegacy) { + Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), + redeemScript); + input.setScriptSig(inputScript); + } else { + input.setScriptSig(ScriptBuilder.createEmpty()); + TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); + input.setWitness(witness); + } WalletService.printTx("mediated payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); WalletService.checkWalletConsistency(wallet); @@ -945,9 +991,9 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali byte[] arbitratorPubKey) throws AddressFormatException, TransactionVerificationException, WalletException, SignatureDecodeException { Transaction depositTx = new Transaction(params, depositTxSerialized); - TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0); + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); Transaction payoutTx = new Transaction(params); - payoutTx.addInput(p2SHMultiSigOutput); + payoutTx.addInput(hashedMultiSigOutput); if (buyerPayoutAmount.isPositive()) { payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString)); } @@ -957,7 +1003,15 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali // take care of sorting! Script redeemScript = get2of3MultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey); - Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + Sha256Hash sigHash; + boolean hashedMultiSigOutputIsLegacy = !ScriptPattern.isP2SH(hashedMultiSigOutput.getScriptPubKey()); + if (hashedMultiSigOutputIsLegacy) { + sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + } else { + Coin inputValue = hashedMultiSigOutput.getValue(); + sigHash = payoutTx.hashForWitnessSignature(0, redeemScript, + inputValue, Transaction.SigHash.ALL, false); + } checkNotNull(tradersMultiSigKeyPair, "tradersMultiSigKeyPair must not be null"); if (tradersMultiSigKeyPair.isEncrypted()) { checkNotNull(aesKey); @@ -966,11 +1020,18 @@ public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSeriali 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); TransactionInput input = payoutTx.getInput(0); - input.setScriptSig(inputScript); + // Take care of order of signatures. See comment below at getMultiSigRedeemScript (sort order needed here: arbitrator, seller, buyer) + if (hashedMultiSigOutputIsLegacy) { + Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript( + ImmutableList.of(arbitratorTxSig, tradersTxSig), + redeemScript); + input.setScriptSig(inputScript); + } else { + input.setScriptSig(ScriptBuilder.createEmpty()); + TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, arbitratorTxSig, tradersTxSig); + input.setWitness(witness); + } WalletService.printTx("disputed payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); WalletService.checkWalletConsistency(wallet); @@ -995,21 +1056,23 @@ public void emergencySignAndPublishPayoutTxFrom2of2MultiSig(String depositTxHex, String sellerPrivateKeyAsHex, String buyerPubKeyAsHex, String sellerPubKeyAsHex, + boolean hashedMultiSigOutputIsLegacy, 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); + Script hashedMultiSigOutputScript = get2of2MultiSigOutputScript(buyerPubKey, sellerPubKey, + hashedMultiSigOutputIsLegacy); - Coin msOutput = buyerPayoutAmount.add(sellerPayoutAmount).add(txFee); - TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, null, msOutput, p2SHMultiSigOutputScript.getProgram()); + Coin msOutputValue = buyerPayoutAmount.add(sellerPayoutAmount).add(txFee); + TransactionOutput hashedMultiSigOutput = new TransactionOutput(params, null, msOutputValue, hashedMultiSigOutputScript.getProgram()); Transaction depositTx = new Transaction(params); - depositTx.addOutput(p2SHMultiSigOutput); + depositTx.addOutput(hashedMultiSigOutput); Transaction payoutTx = new Transaction(params); Sha256Hash spendTxHash = Sha256Hash.wrap(depositTxHex); - payoutTx.addInput(new TransactionInput(params, depositTx, p2SHMultiSigOutputScript.getProgram(), new TransactionOutPoint(params, 0, spendTxHash), msOutput)); + payoutTx.addInput(new TransactionInput(params, depositTx, null, new TransactionOutPoint(params, 0, spendTxHash), msOutputValue)); if (buyerPayoutAmount.isPositive()) { payoutTx.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString)); @@ -1020,7 +1083,14 @@ public void emergencySignAndPublishPayoutTxFrom2of2MultiSig(String depositTxHex, // take care of sorting! Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); - Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + Sha256Hash sigHash; + if (hashedMultiSigOutputIsLegacy) { + sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); + } else { + Coin inputValue = msOutputValue; + sigHash = payoutTx.hashForWitnessSignature(0, redeemScript, + inputValue, Transaction.SigHash.ALL, false); + } ECKey buyerPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(buyerPrivateKeyAsHex)); checkNotNull(buyerPrivateKey, "key must not be null"); @@ -1032,10 +1102,18 @@ public void emergencySignAndPublishPayoutTxFrom2of2MultiSig(String depositTxHex, 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); + if (hashedMultiSigOutputIsLegacy) { + Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), + redeemScript); + input.setScriptSig(inputScript); + } else { + input.setScriptSig(ScriptBuilder.createEmpty()); + TransactionWitness witness = TransactionWitness.redeemP2WSH(redeemScript, sellerTxSig, buyerTxSig); + input.setWitness(witness); + } + WalletService.printTx("payoutTx", payoutTx); WalletService.verifyTransaction(payoutTx); WalletService.checkWalletConsistency(wallet); @@ -1144,8 +1222,13 @@ private Script get2of2MultiSigRedeemScript(byte[] buyerPubKey, byte[] sellerPubK return ScriptBuilder.createMultiSigOutputScript(2, keys); } - private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey) { - return ScriptBuilder.createP2SHOutputScript(get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey)); + private Script get2of2MultiSigOutputScript(byte[] buyerPubKey, byte[] sellerPubKey, boolean legacy) { + Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey); + if (legacy) { + return ScriptBuilder.createP2SHOutputScript(redeemScript); + } else { + return ScriptBuilder.createP2WSHOutputScript(redeemScript); + } } private Transaction createPayoutTx(Transaction depositTx, @@ -1153,9 +1236,9 @@ private Transaction createPayoutTx(Transaction depositTx, Coin sellerPayoutAmount, String buyerAddressString, String sellerAddressString) throws AddressFormatException { - TransactionOutput p2SHMultiSigOutput = depositTx.getOutput(0); + TransactionOutput hashedMultiSigOutput = depositTx.getOutput(0); Transaction transaction = new Transaction(params); - transaction.addInput(p2SHMultiSigOutput); + transaction.addInput(hashedMultiSigOutput); if (buyerPayoutAmount.isPositive()) { transaction.addOutput(buyerPayoutAmount, Address.fromString(params, buyerAddressString)); } 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 2008b92b40a..d56cf2e8217 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 @@ -24,6 +24,8 @@ import bisq.common.taskrunner.TaskRunner; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.crypto.DeterministicKey; @@ -46,7 +48,11 @@ protected void run() { runInterceptHook(); Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx()); + BtcWalletService btcWalletService = processModel.getBtcWalletService(); + NetworkParameters params = btcWalletService.getParams(); + Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx()); + String id = processModel.getOffer().getId(); byte[] buyerMultiSigPubKey = processModel.getMyMultiSigPubKey(); @@ -58,6 +64,7 @@ protected void run() { byte[] sellerMultiSigPubKey = processModel.getTradingPeer().getMultiSigPubKey(); byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx( preparedDelayedPayoutTx, + preparedDepositTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey); 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 0ae1a159201..4d110fba390 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 @@ -24,6 +24,8 @@ import bisq.common.taskrunner.TaskRunner; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.crypto.DeterministicKey; @@ -47,6 +49,9 @@ protected void run() { Transaction preparedDelayedPayoutTx = checkNotNull(processModel.getPreparedDelayedPayoutTx()); BtcWalletService btcWalletService = processModel.getBtcWalletService(); + NetworkParameters params = btcWalletService.getParams(); + Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx()); + String id = processModel.getOffer().getId(); byte[] sellerMultiSigPubKey = processModel.getMyMultiSigPubKey(); @@ -59,6 +64,7 @@ protected void run() { byte[] delayedPayoutTxSignature = processModel.getTradeWalletService().signDelayedPayoutTx( preparedDelayedPayoutTx, + preparedDepositTx, myMultiSigKeyPair, buyerMultiSigPubKey, sellerMultiSigPubKey); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java index 6b89048980b..609c526fdec 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ManualPayoutTxWindow.java @@ -40,6 +40,7 @@ import javax.inject.Inject; import javafx.scene.Scene; +import javafx.scene.control.CheckBox; import javafx.scene.input.KeyCode; import org.slf4j.Logger; @@ -47,6 +48,7 @@ import javax.annotation.Nullable; +import static bisq.desktop.util.FormBuilder.addCheckBox; import static bisq.desktop.util.FormBuilder.addInputTextField; // We don't translate here as it is for dev only purpose @@ -116,6 +118,8 @@ private void addContent() { InputTextField buyerPubKeyAsHex = addInputTextField(gridPane, ++rowIndex, "buyerPubKeyAsHex"); InputTextField sellerPubKeyAsHex = addInputTextField(gridPane, ++rowIndex, "sellerPubKeyAsHex"); + CheckBox depositTxLegacy = addCheckBox(gridPane, ++rowIndex, "depositTxLegacy"); + // Notes: // Open with alt+g // Priv key is only visible if pw protection is removed (wallet details data (alt+j)) @@ -136,6 +140,9 @@ private void addContent() { sellerPubKeyAsHex.setText(""); sellerPrivateKeyAsHex.setText(""); + depositTxLegacy.setAllowIndeterminate(false); + depositTxLegacy.setSelected(false); + actionButtonText("Sign and publish transaction"); TxBroadcaster.Callback callback = new TxBroadcaster.Callback() { @@ -167,6 +174,7 @@ public void onFailure(TxBroadcastException exception) { sellerPrivateKeyAsHex.getText(), buyerPubKeyAsHex.getText(), sellerPubKeyAsHex.getText(), + depositTxLegacy.isSelected(), callback); } catch (AddressFormatException | WalletException | TransactionVerificationException e) { log.error(e.toString()); From 4a05b6d6d5ef1175f4a3918ed500b580c186ef9e Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 6 Oct 2020 15:22:22 -0300 Subject: [PATCH 04/18] Revert "Construct dummy outputs with LegacyAddress" This reverts commit b8f5c6e970fc29b705478aac8a655a73bed52a7e. --- .../main/java/bisq/core/btc/wallet/TradeWalletService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 840048c9cbb..7f6c28007b7 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -38,6 +38,7 @@ import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.SegwitAddress; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.SignatureDecodeException; import org.bitcoinj.core.Transaction; @@ -336,7 +337,7 @@ OUT[0] dummyOutputAmount (inputAmount - tx fee) Transaction dummyTX = new Transaction(params); // The output is just used to get the right inputs and change outputs, so we use an anonymous ECKey, as it will never be used for anything. // We don't care about fee calculation differences between the real tx and that dummy tx as we use a static tx fee. - TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, LegacyAddress.fromKey(params, new ECKey())); + TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, SegwitAddress.fromKey(params, new ECKey())); dummyTX.addOutput(dummyOutput); // Find the needed inputs to pay the output, optionally add 1 change output. @@ -455,7 +456,7 @@ private PreparedDepositTxAndMakerInputs makerCreatesDepositTx(boolean makerIsBuy // First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx. // Similar to the way we did in the createTakerDepositTxInputs method. Transaction dummyTx = new Transaction(params); - TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, LegacyAddress.fromKey(params, new ECKey())); + TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, SegwitAddress.fromKey(params, new ECKey())); dummyTx.addOutput(dummyOutput); addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress); // Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions. From 22ba9a898b5ad4db64f4cc1f3089347afbb5a6c0 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 6 Oct 2020 16:31:20 -0300 Subject: [PATCH 05/18] Explain why bitcoinSerialize(false) is used --- .../main/java/bisq/core/btc/wallet/TradeWalletService.java | 4 ++++ 1 file changed, 4 insertions(+) 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 7f6c28007b7..0c0f4284be8 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -1171,6 +1171,10 @@ private RawTransactionInput getRawInputFromTransactionInput(@NotNull Transaction "input.getConnectedOutput().getParentTransaction() must not be null"); checkNotNull(input.getValue(), "input.getValue() must not be null"); + // bitcoinSerialize(false) is used just in case the serialized tx is parsed by a bisq node still using + // bitcoinj 0.14. This is not supposed to happen ever since Version.TRADE_PROTOCOL_VERSION was set to 3, + // but it costs nothing to be on the safe side. + // The serialized tx is just used to obtain its hash, so the witness data is not relevant. return new RawTransactionInput(input.getOutpoint().getIndex(), input.getConnectedOutput().getParentTransaction().bitcoinSerialize(false), input.getValue().value); From 3585dc95fcda1ef27ade1bd832ee35b7ab0b222c Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 6 Oct 2020 20:26:09 -0300 Subject: [PATCH 06/18] Create the scriptCode the right way --- .../main/java/bisq/core/btc/wallet/TradeWalletService.java | 7 ++----- core/src/main/java/bisq/core/btc/wallet/WalletService.java | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) 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 0c0f4284be8..ef1f8eca856 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -1275,11 +1275,8 @@ private void signInput(Transaction transaction, TransactionInput input, int inpu input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); } } else if (ScriptPattern.isP2WPKH(scriptPubKey)) { - // TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master) - // Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey) - Script scriptCode = new ScriptBuilder().data( - ScriptBuilder.createOutputScript(LegacyAddress.fromKey(transaction.getParams(), sigKey)).getProgram()) - .build(); + // scriptCode is expected to have the format of a legacy P2PKH output script + Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey); Coin value = input.getValue(); TransactionSignature txSig = transaction.calculateWitnessSignature(inputIndex, sigKey, scriptCode, value, Transaction.SigHash.ALL, false); 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 251aac97172..e468a6a956b 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java @@ -325,11 +325,8 @@ public static void signTransactionInput(Wallet wallet, } } else if (ScriptPattern.isP2WPKH(scriptPubKey)) { try { - // TODO: Consider using this alternative way to build the scriptCode (taken from bitcoinj master) - // Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key); - Script scriptCode = new ScriptBuilder().data( - ScriptBuilder.createOutputScript(LegacyAddress.fromKey(tx.getParams(), key)).getProgram()) - .build(); + // scriptCode is expected to have the format of a legacy P2PKH output script + Script scriptCode = ScriptBuilder.createP2PKHOutputScript(key); Coin value = txIn.getValue(); TransactionSignature txSig = tx.calculateWitnessSignature(index, key, scriptCode, value, Transaction.SigHash.ALL, false); From 953a5f0bb547c8c390af49c6c8dd74a55aa62bd6 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 6 Oct 2020 21:15:07 -0300 Subject: [PATCH 07/18] Deal with P2WPKH has empty scriptSig --- .../bisq/core/btc/wallet/TradeWalletService.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) 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 ef1f8eca856..d0861b12b76 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -602,7 +602,10 @@ public Transaction takerSignsDepositTx(boolean takerIsSeller, // We grab the signature from the makersDepositTx and apply it to the new tx input for (int i = 0; i < buyerInputs.size(); i++) { TransactionInput makersInput = makersDepositTx.getInputs().get(i); - byte[] makersScriptSigProgram = getMakersScriptSigProgram(makersInput); + byte[] makersScriptSigProgram = makersInput.getScriptSig().getProgram(); + if (makersScriptSigProgram.length == 0 && TransactionWitness.EMPTY.equals(makersInput.getWitness())) { + throw new TransactionVerificationException("Inputs from maker not signed."); + } TransactionInput input = getTransactionInput(depositTx, makersScriptSigProgram, buyerInputs.get(i)); if (!TransactionWitness.EMPTY.equals(makersInput.getWitness())) { input.setWitness(makersInput.getWitness()); @@ -1180,15 +1183,6 @@ private RawTransactionInput getRawInputFromTransactionInput(@NotNull Transaction input.getValue().value); } - 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; - } - private TransactionInput getTransactionInput(Transaction depositTx, byte[] scriptProgram, RawTransactionInput rawTransactionInput) { From d1620c4fd76f7024d40a503c2b1eabea8a786134 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 6 Oct 2020 15:20:24 -0300 Subject: [PATCH 08/18] Revert "Validate AddressEntry.segwit" This reverts commit e49c56527825a443b794ab74cee24b12d5b9eb45. --- core/src/main/java/bisq/core/btc/model/AddressEntry.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/model/AddressEntry.java b/core/src/main/java/bisq/core/btc/model/AddressEntry.java index a3a0387be2a..5348950b391 100644 --- a/core/src/main/java/bisq/core/btc/model/AddressEntry.java +++ b/core/src/main/java/bisq/core/btc/model/AddressEntry.java @@ -97,10 +97,6 @@ public AddressEntry(@NotNull DeterministicKey keyPair, Context context, @Nullable String offerId, boolean segwit) { - if (segwit && (!Context.AVAILABLE.equals(context) || offerId != null)) { - throw new IllegalArgumentException("Segwit addresses are only allowed for " + - "AVAILABLE entries without an offerId"); - } this.keyPair = keyPair; this.context = context; this.offerId = offerId; From a3d7c71410f0ad0f6aa7476e6d2deba9c29dda5b Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Thu, 15 Oct 2020 19:19:17 -0300 Subject: [PATCH 09/18] Set TRADE_PROTOCOL_VERSION to 3 --- common/src/main/java/bisq/common/app/Version.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index 33efa828030..1b4ad6339b4 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -92,10 +92,13 @@ private static int getSubVersion(String version, int index) { // The version no. of the current protocol. The offer holds that version. // 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. + // For the switch to version 2, offers created with the old version will become invalid and have to be canceled. + // For the switch to version 3, offers created with the old version can be migrated to version 3 just by opening + // the Bisq app. // VERSION = 0.5.0 -> TRADE_PROTOCOL_VERSION = 1 // Version 1.2.2 -> TRADE_PROTOCOL_VERSION = 2 - public static final int TRADE_PROTOCOL_VERSION = 2; + // Version 1.5.0 -> TRADE_PROTOCOL_VERSION = 3 + public static final int TRADE_PROTOCOL_VERSION = 3; private static int p2pMessageVersion; public static final String BSQ_TX_VERSION = "1"; From dffa251e1dc160b7b1bfe334b759d2b08de2c3d9 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Fri, 9 Oct 2020 16:52:20 -0300 Subject: [PATCH 10/18] Remove unused imports --- core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java | 1 - core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java | 1 - core/src/main/java/bisq/core/btc/wallet/WalletService.java | 1 - .../trade/protocol/tasks/buyer/BuyerSignsDelayedPayoutTx.java | 1 - .../trade/protocol/tasks/seller/SellerSignsDelayedPayoutTx.java | 1 - 5 files changed, 5 deletions(-) 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 375b23dd7a4..2ab2e64e715 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -27,7 +27,6 @@ import bisq.core.provider.fee.FeeService; import bisq.core.user.Preferences; -import bisq.common.config.Config; import bisq.common.handlers.ErrorMessageHandler; import org.bitcoinj.core.Address; 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 d0861b12b76..46d9cea8bb4 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -36,7 +36,6 @@ import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.SegwitAddress; import org.bitcoinj.core.Sha256Hash; 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 e468a6a956b..fa285a3e557 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java @@ -37,7 +37,6 @@ import org.bitcoinj.core.Context; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; 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 d56cf2e8217..32748a319c3 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 @@ -24,7 +24,6 @@ import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.crypto.DeterministicKey; 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 4d110fba390..556e556b418 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 @@ -24,7 +24,6 @@ import bisq.common.taskrunner.TaskRunner; -import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.crypto.DeterministicKey; From 6bba6a526f0404701cce7c33a1a75961e64df2f1 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Mon, 26 Oct 2020 18:36:45 -0300 Subject: [PATCH 11/18] Use bitcoinj 0.15.8 (commit 60b4f2f) --- build.gradle | 2 +- core/src/main/java/bisq/core/app/WalletAppSetup.java | 2 +- gradle/witness/gradle-witness.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index fd827538fba..2b7c2853efd 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ configure(subprojects) { ext { // in alphabetical order bcVersion = '1.63' - bitcoinjVersion = 'fcec3da' + bitcoinjVersion = '60b4f2f' btcdCli4jVersion = '27b94333' codecVersion = '1.13' easybindVersion = '1.0.3' diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java index bccd033ee0e..9851996a7df 100644 --- a/core/src/main/java/bisq/core/app/WalletAppSetup.java +++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java @@ -107,7 +107,7 @@ void init(@Nullable Consumer chainFileLockedExceptionHandler, Runnable downloadCompleteHandler, Runnable walletInitializedHandler) { log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}", - VersionMessage.BITCOINJ_VERSION, "fcec3da"); + VersionMessage.BITCOINJ_VERSION, "60b4f2f"); ObjectProperty walletServiceException = new SimpleObjectProperty<>(); btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(), diff --git a/gradle/witness/gradle-witness.gradle b/gradle/witness/gradle-witness.gradle index cecaf6c8b74..339f6bb1064 100644 --- a/gradle/witness/gradle-witness.gradle +++ b/gradle/witness/gradle-witness.gradle @@ -23,7 +23,7 @@ dependencyVerification { 'com.github.bisq-network.netlayer:tor.external:a3606a592d6b6caa6a2fb7db224eaf43c6874c6730da4815bd37ad686b283dcb', 'com.github.bisq-network.netlayer:tor.native:b15aba7fe987185037791c7ec7c529cb001b90d723d047d54aab87aceb3b3d45', 'com.github.bisq-network.netlayer:tor:a974190aa3a031067ccd1dda28a3ae58cad14060792299d86ea38a05fb21afc5', - 'com.github.bisq-network.bitcoinj:bitcoinj-core:8af7faa2155feff5afd1fa0fcea6fe7f7fa0d7ee977bdc648d1e73f3dcf2c754', + 'com.github.bisq-network:bitcoinj:804f587a44b1ce9cd9b8dd1848fc911329634163b7905bafb86f502a3d08264c', 'com.github.cd2357.tor-binary:tor-binary-geoip:ae27b6aca1a3a50a046eb11e38202b6d21c2fcd2b8643bbeb5ea85e065fbc1be', 'com.github.cd2357.tor-binary:tor-binary-linux32:7b5d6770aa442ef6d235e8a9bfbaa7c62560690f9fe69ff03c7a752eae84f7dc', 'com.github.cd2357.tor-binary:tor-binary-linux64:24111fa35027599a750b0176392dc1e9417d919414396d1b221ac2e707eaba76', From b2023e236692e6ad77afd6f9379de4e275393928 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Fri, 16 Oct 2020 17:03:34 -0300 Subject: [PATCH 12/18] Use tx.getVsize() Replace tx.bitcoinSerialize().length --- .../core/btc/wallet/BtcWalletService.java | 20 +++++++++---------- .../blindvote/MyBlindVoteListService.java | 2 +- .../bond/lockup/LockupTxService.java | 2 +- .../bond/unlock/UnlockTxService.java | 2 +- .../dao/burnbsq/assetfee/AssetFeeView.java | 2 +- .../burnbsq/proofofburn/ProofOfBurnView.java | 2 +- .../dao/governance/make/MakeProposalView.java | 2 +- .../main/dao/wallet/send/BsqSendView.java | 4 ++-- .../main/funds/withdrawal/WithdrawalView.java | 2 +- .../steps/buyer/BuyerStep4View.java | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) 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 2ab2e64e715..fdbc5cbda73 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -263,7 +263,7 @@ private Transaction completePreparedProposalTx(Transaction feeTx, byte[] opRetur resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram())); numInputs = resultTx.getInputs().size(); - txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length; + txSizeWithUnsignedInputs = resultTx.getVsize(); long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value; // calculated fee must be inside of a tolerance range with tx fee isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000; @@ -373,7 +373,7 @@ private Transaction addInputsForMinerFee(Transaction preparedTx, resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram())); numInputs = resultTx.getInputs().size(); - txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length; + txSizeWithUnsignedInputs = resultTx.getVsize(); final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value; // calculated fee must be inside of a tolerance range with tx fee isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000; @@ -529,14 +529,14 @@ public Transaction completePreparedBsqTx(Transaction preparedBsqTx, resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram())); numInputs = resultTx.getInputs().size(); - txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length; + txSizeWithUnsignedInputs = resultTx.getVsize(); final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value; // calculated fee must be inside of a tolerance range with tx fee isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000; } while (opReturnIsOnlyOutput || isFeeOutsideTolerance || - resultTx.getFee().value < txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value); + resultTx.getFee().value < txFeePerByte.multiply(resultTx.getVsize()).value); // Sign all BTC inputs signAllBtcInputs(preparedBsqTxInputs.size(), resultTx); @@ -809,7 +809,7 @@ public void doubleSpendTransaction(String txId, Runnable resultHandler, ErrorMes ); log.info("newTransaction no. of inputs " + newTransaction.getInputs().size()); - log.info("newTransaction size in kB " + newTransaction.bitcoinSerialize().length / 1024); + log.info("newTransaction size in kB " + newTransaction.getVsize() / 1024); if (!newTransaction.getInputs().isEmpty()) { Coin amount = Coin.valueOf(newTransaction.getInputs().stream() @@ -839,7 +839,7 @@ public void doubleSpendTransaction(String txId, Runnable resultHandler, ErrorMes sendRequest.changeAddress = toAddress; wallet.completeTx(sendRequest); tx = sendRequest.tx; - txSize = tx.bitcoinSerialize().length; + txSize = tx.getVsize(); printTx("FeeEstimationTransaction", tx); sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString())); } @@ -947,7 +947,7 @@ public Transaction getFeeEstimationTransaction(String fromAddress, SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context); wallet.completeTx(sendRequest); tx = sendRequest.tx; - txSize = tx.bitcoinSerialize().length; + txSize = tx.getVsize(); printTx("FeeEstimationTransaction", tx); } while (feeEstimationNotSatisfied(counter, tx)); @@ -996,7 +996,7 @@ public Transaction getFeeEstimationTransactionForMultipleAddresses(Set f SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey); wallet.completeTx(sendRequest); tx = sendRequest.tx; - txSize = tx.bitcoinSerialize().length; + txSize = tx.getVsize(); printTx("FeeEstimationTransactionForMultipleAddresses", tx); } while (feeEstimationNotSatisfied(counter, tx)); @@ -1012,7 +1012,7 @@ public Transaction getFeeEstimationTransactionForMultipleAddresses(Set f } private boolean feeEstimationNotSatisfied(int counter, Transaction tx) { - long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.bitcoinSerialize().length).value; + long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.getVsize()).value; return counter < 10 && (tx.getFee().value < targetFee || tx.getFee().value - targetFee > 1000); @@ -1034,7 +1034,7 @@ public int getEstimatedFeeTxSize(List outputValues, Coin txFee) sendRequest.ensureMinRequiredFee = false; sendRequest.changeAddress = dummyAddress; wallet.completeTx(sendRequest); - return transaction.bitcoinSerialize().length; + return transaction.getVsize(); } diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java index cee5d1b9174..996ff207bc3 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java @@ -195,7 +195,7 @@ public Tuple2 getMiningFeeAndTxSize(Coin stake) Coin blindVoteFee = BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight()); Transaction dummyTx = getBlindVoteTx(stake, blindVoteFee, new byte[22]); Coin miningFee = dummyTx.getFee(); - int txSize = dummyTx.bitcoinSerialize().length; + int txSize = dummyTx.getVsize(); return new Tuple2<>(miningFee, txSize); } diff --git a/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java b/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java index 4ffa4772e9e..9a61c09be8d 100644 --- a/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java +++ b/core/src/main/java/bisq/core/dao/governance/bond/lockup/LockupTxService.java @@ -95,7 +95,7 @@ public Tuple2 getMiningFeeAndTxSize(Coin lockupAmount, int lockTi throws InsufficientMoneyException, WalletException, TransactionVerificationException, IOException { Transaction tx = getLockupTx(lockupAmount, lockTime, lockupReason, hash); Coin miningFee = tx.getFee(); - int txSize = tx.bitcoinSerialize().length; + int txSize = tx.getVsize(); return new Tuple2<>(miningFee, txSize); } diff --git a/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java b/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java index 0682186d2ec..fa4272d7b5c 100644 --- a/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java +++ b/core/src/main/java/bisq/core/dao/governance/bond/unlock/UnlockTxService.java @@ -93,7 +93,7 @@ public Tuple2 getMiningFeeAndTxSize(String lockupTxId) throws InsufficientMoneyException, WalletException, TransactionVerificationException { Transaction tx = getUnlockTx(lockupTxId); Coin miningFee = tx.getFee(); - int txSize = tx.bitcoinSerialize().length; + int txSize = tx.getVsize(); return new Tuple2<>(miningFee, txSize); } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java index d873ed60d79..f2f2e9b14e3 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java @@ -189,7 +189,7 @@ protected void activate() { try { Transaction transaction = assetService.payFee(selectedAsset, listingFee.value); Coin miningFee = transaction.getFee(); - int txSize = transaction.bitcoinSerialize().length; + int txSize = transaction.getVsize(); if (!DevEnv.isDevMode()) { GUIUtil.showBsqFeeInfoPopup(listingFee, miningFee, txSize, bsqFormatter, btcFormatter, diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java index d9b5d7c2813..b8ea8553b3b 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java @@ -172,7 +172,7 @@ protected void activate() { String preImageAsString = preImageTextField.getText(); Transaction transaction = proofOfBurnService.burn(preImageAsString, amount.value); Coin miningFee = transaction.getFee(); - int txSize = transaction.bitcoinSerialize().length; + int txSize = transaction.getVsize(); if (!DevEnv.isDevMode()) { GUIUtil.showBsqFeeInfoPopup(amount, miningFee, txSize, bsqFormatter, btcFormatter, diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java index 89372c93931..8ec6ed6d28b 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/make/MakeProposalView.java @@ -283,7 +283,7 @@ private void publishMyProposal(ProposalType type) { Proposal proposal = proposalWithTransaction.getProposal(); Transaction transaction = proposalWithTransaction.getTransaction(); Coin miningFee = transaction.getFee(); - int txSize = transaction.bitcoinSerialize().length; + int txSize = transaction.getVsize(); Coin fee = daoFacade.getProposalFee(daoFacade.getChainHeight()); if (type.equals(ProposalType.BONDED_ROLE)) { diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java index 0c8a6cd8418..06861f3002f 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java @@ -243,7 +243,7 @@ private void addSendBsqGroup() { Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true); Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); Coin miningFee = signedTx.getFee(); - int txSize = signedTx.bitcoinSerialize().length; + int txSize = signedTx.getVsize(); showPublishTxPopup(receiverAmount, txWithBtcFee, TxType.TRANSFER_BSQ, @@ -305,7 +305,7 @@ private void addSendBtcGroup() { if (miningFee.getValue() >= receiverAmount.getValue()) GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter); else { - int txSize = signedTx.bitcoinSerialize().length; + int txSize = signedTx.getVsize(); showPublishTxPopup(receiverAmount, txWithBtcFee, TxType.INVALID, diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index 8d3620c67e6..47a5ba31514 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -356,7 +356,7 @@ private void onWithdraw() { } if (areInputsValid()) { - int txSize = feeEstimationTransaction.bitcoinSerialize().length; + int txSize = feeEstimationTransaction.getVsize(); log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txSize, fee.toPlainString()); if (receiverAmount.isPositive()) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 55378fd5675..e01b1e0b911 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -205,7 +205,7 @@ private void reviewWithdrawal() { validateWithdrawAddress(); } else if (Restrictions.isAboveDust(receiverAmount)) { CoinFormatter formatter = model.btcFormatter; - int txSize = feeEstimationTransaction.bitcoinSerialize().length; + int txSize = feeEstimationTransaction.getVsize(); double feePerByte = CoinUtil.getFeePerByte(fee, txSize); double kb = txSize / 1000d; String recAmount = formatter.formatCoinWithCode(receiverAmount); From 7a58bfbafa19fd155ae95ba5daf12fb4fe58ce12 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 6 Oct 2020 15:22:53 -0300 Subject: [PATCH 13/18] Use SegwitAddress for fee estimation --- .../java/bisq/core/btc/wallet/BtcWalletService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 fdbc5cbda73..8573e1c6879 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -34,7 +34,7 @@ import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.LegacyAddress; +import org.bitcoinj.core.SegwitAddress; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionInput; @@ -992,7 +992,9 @@ public Transaction getFeeEstimationTransactionForMultipleAddresses(Set f counter++; fee = txFeeForWithdrawalPerByte.multiply(txSize); // We use a dummy address for the output - final String dummyReceiver = LegacyAddress.fromKey(params, new ECKey()).toBase58(); + // We don't know here whether the output is segwit or not but we don't care too much because the size of + // a segwit ouput is just 3 byte smaller than the size of a legacy ouput. + final String dummyReceiver = SegwitAddress.fromKey(params, new ECKey()).toString(); SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey); wallet.completeTx(sendRequest); tx = sendRequest.tx; @@ -1021,7 +1023,9 @@ private boolean feeEstimationNotSatisfied(int counter, Transaction tx) { public int getEstimatedFeeTxSize(List outputValues, Coin txFee) throws InsufficientMoneyException, AddressFormatException { Transaction transaction = new Transaction(params); - Address dummyAddress = LegacyAddress.fromKey(params, new ECKey()); + // In reality txs have a mix of segwit/legacy ouputs, but we don't care too much because the size of + // a segwit ouput is just 3 byte smaller than the size of a legacy ouput. + Address dummyAddress = SegwitAddress.fromKey(params, new ECKey()); outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress)); SendRequest sendRequest = SendRequest.forTx(transaction); From 29f23fe50c6727f8784f3af7c8de3d988ba517ac Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Fri, 16 Oct 2020 17:00:56 -0300 Subject: [PATCH 14/18] Use segwit tx sizes --- .../bisq/core/btc/TxFeeEstimationService.java | 41 +++++++++++++------ .../core/btc/TxFeeEstimationServiceTest.java | 18 ++++---- .../offer/takeoffer/TakeOfferDataModel.java | 32 +++++++-------- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java index c0fbfb5b682..71648011af6 100644 --- a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java +++ b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java @@ -41,9 +41,24 @@ */ @Slf4j public class TxFeeEstimationService { - public static int TYPICAL_TX_WITH_1_INPUT_SIZE = 260; - private static int DEPOSIT_TX_SIZE = 320; - private static int PAYOUT_TX_SIZE = 380; + +// Size/vsize of typical trade txs +// Real txs size/vsize may vary in 1 or 2 bytes from the estimated values. +// Values calculated with https://gist.github.com/oscarguindzberg/3d1349cb65d9fd9af9de0feaa3fd27ac +// legacy fee tx with 1 input, maker/taker fee paid in btc size/vsize = 258 +// legacy deposit tx without change size/vsize = 381 +// legacy deposit tx with change size/vsize = 414 +// legacy payout tx size/vsize = 337 +// legacy delayed payout tx size/vsize = 302 +// segwit fee tx with 1 input, maker/taker fee paid in btc vsize = 173 +// segwit deposit tx without change vsize = 232 +// segwit deposit tx with change vsize = 263 +// segwit payout tx vsize = 169 +// segwit delayed payout tx vsize = 139 + public static int TYPICAL_TX_WITH_1_INPUT_SIZE = 175; + private static int DEPOSIT_TX_SIZE = 233; + private static int PAYOUT_TX_SIZE = 169; + private static int BSQ_INPUT_INCREASE = 150; private static int MAX_ITERATIONS = 10; @@ -87,14 +102,14 @@ private Tuple2 getEstimatedFeeAndTxSize(boolean isTaker, BtcWalletService btcWalletService, Preferences preferences) { Coin txFeePerByte = feeService.getTxFeePerByte(); - // We start with min taker fee size of 260 + // We start with min taker fee size of 175 int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE; try { estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, amount), estimatedTxSize, txFeePerByte, btcWalletService); } catch (InsufficientMoneyException e) { if (isTaker) { - // if we cannot do the estimation we use the payout tx size - estimatedTxSize = PAYOUT_TX_SIZE; + // if we cannot do the estimation we use the deposit tx size + estimatedTxSize = DEPOSIT_TX_SIZE; } 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); @@ -109,13 +124,13 @@ private Tuple2 getEstimatedFeeAndTxSize(boolean isTaker, Coin txFee; int size; if (isTaker) { - int averageSize = (estimatedTxSize + DEPOSIT_TX_SIZE) / 2; // deposit tx has about 320 bytes - // We use at least the size of the payout tx to not underpay at payout. - size = Math.max(PAYOUT_TX_SIZE, averageSize); + int averageSize = (estimatedTxSize + DEPOSIT_TX_SIZE) / 2; // deposit tx has about 233 bytes + // We use at least the size of the deposit tx to not underpay it. + size = Math.max(DEPOSIT_TX_SIZE, averageSize); txFee = txFeePerByte.multiply(size); log.info("Fee estimation resulted in a tx size of {} bytes.\n" + - "We use an average between the taker fee tx and the deposit tx (320 bytes) which results in {} bytes.\n" + - "The payout tx has 380 bytes, we use that as our min value. Size for fee calculation is {} bytes.\n" + + "We use an average between the taker fee tx and the deposit tx (233 bytes) which results in {} bytes.\n" + + "The deposit tx has 233 bytes, we use that as our min value. Size for fee calculation is {} bytes.\n" + "The tx fee of {} Sat", estimatedTxSize, averageSize, size, txFee.value); } else { size = estimatedTxSize; @@ -130,7 +145,7 @@ public Tuple2 getEstimatedFeeAndTxSize(Coin amount, FeeService feeService, BtcWalletService btcWalletService) { Coin txFeePerByte = feeService.getTxFeePerByte(); - // We start with min taker fee size of 260 + // We start with min taker fee size of 175 int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE; try { estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, btcWalletService); @@ -145,7 +160,7 @@ public Tuple2 getEstimatedFeeAndTxSize(Coin amount, 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 + // We start with the initialEstimatedTxSize for a tx with 1 input (175) 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 // with the required inputs we compare if the size is not more then 20% different to our assumed tx size. If we are inside diff --git a/core/src/test/java/bisq/core/btc/TxFeeEstimationServiceTest.java b/core/src/test/java/bisq/core/btc/TxFeeEstimationServiceTest.java index a71fb8237ba..ca7076bf290 100644 --- a/core/src/test/java/bisq/core/btc/TxFeeEstimationServiceTest.java +++ b/core/src/test/java/bisq/core/btc/TxFeeEstimationServiceTest.java @@ -45,14 +45,14 @@ public void testGetEstimatedTxSize_withDefaultTxSize() throws InsufficientMoneyE int realTxSize; Coin txFee; - initialEstimatedTxSize = 260; + initialEstimatedTxSize = 175; txFeePerByte = Coin.valueOf(10); - realTxSize = 260; + realTxSize = 175; txFee = txFeePerByte.multiply(initialEstimatedTxSize); when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize); result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService); - assertEquals(260, result); + assertEquals(175, result); } // FIXME @Bernard could you have a look? @@ -67,16 +67,16 @@ public void testGetEstimatedTxSize_withLargeTx() throws InsufficientMoneyExcepti int realTxSize; Coin txFee; - initialEstimatedTxSize = 260; + initialEstimatedTxSize = 175; txFeePerByte = Coin.valueOf(10); - realTxSize = 2600; + realTxSize = 1750; txFee = txFeePerByte.multiply(initialEstimatedTxSize); when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize); // repeated calls to getEstimatedFeeTxSize do not work (returns 0 at second call in loop which cause test to fail) result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService); - assertEquals(2600, result); + assertEquals(1750, result); } // FIXME @Bernard could you have a look? @@ -91,14 +91,14 @@ public void testGetEstimatedTxSize_withSmallTx() throws InsufficientMoneyExcepti int realTxSize; Coin txFee; - initialEstimatedTxSize = 2600; + initialEstimatedTxSize = 1750; txFeePerByte = Coin.valueOf(10); - realTxSize = 260; + realTxSize = 175; txFee = txFeePerByte.multiply(initialEstimatedTxSize); when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize); result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService); - assertEquals(260, result); + assertEquals(175, result); } @Test diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 399f45bbef3..bbc060ea0e6 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -109,8 +109,8 @@ class TakeOfferDataModel extends OfferDataModel { private PaymentAccount paymentAccount; private boolean isTabSelected; Price tradePrice; - // 260 kb is size of typical trade fee tx with 1 input but trade tx (deposit and payout) are larger so we adjust to 320 - private int feeTxSize = 320; + // Use an average of a typical trade fee tx with 1 input, deposit tx and payout tx. + private int feeTxSize = 192; // (175+233+169)/3 private boolean freezeFee; private Coin txFeePerByteFromFeeService; @@ -213,13 +213,13 @@ void initWithData(Offer offer) { // multiple batch-signed payout tx with different fees might be an option but RBF is not supported yet in BitcoinJ // and batched txs would add more complexity to the trade protocol. - // A typical trade fee tx has about 260 bytes (if one input). The trade txs has about 336-414 bytes. - // We use 320 as a average value. + // A typical trade fee tx has about 175 bytes (if one input). The trade txs has about 169-263 bytes. + // We use 192 as average value. - // trade fee tx: 260 bytes (1 input) - // deposit tx: 336 bytes (1 MS output+ OP_RETURN) - 414 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount) - // payout tx: 371 bytes - // disputed payout tx: 408 bytes + // trade fee tx: 175 bytes (1 input) + // deposit tx: 233 bytes (1 MS output+ OP_RETURN) - 263 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount) + // payout tx: 169 bytes + // disputed payout tx: 139 bytes // Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values) // But the "take offer" happens usually after that so we should have already the value from the estimation service. @@ -351,22 +351,22 @@ public void estimateTxSize() { // there more deterministic. // The trade fee tx can be in the worst case very large if there are many inputs so if we take that tx alone // for the fee estimation we would overpay a lot. - // On the other side if we have the best case of a 1 input tx fee tx then it is only 260 bytes but the - // other 2 txs are larger (320 and 380 bytes) and would get a lower fee/byte as intended. + // On the other side if we have the best case of a 1 input tx fee tx then it is only 175 bytes but the + // other 2 txs are different (233 and 169 bytes) and may get a lower fee/byte as intended. // We apply following model to not overpay too much but be on the safe side as well. // We sum the taker fee tx and the deposit tx together as it can be assumed that both be in the same block and // as they are dependent txs the miner will pick both if the fee in total is good enough. - // We make sure that the fee is sufficient to meet our intended fee/byte for the larger payout tx with 380 bytes. + // We make sure that the fee is sufficient to meet our intended fee/byte for the larger deposit tx with 233 bytes. Tuple2 estimatedFeeAndTxSize = txFeeEstimationService.getEstimatedFeeAndTxSizeForTaker(fundsNeededForTrade, getTakerFee()); txFeeFromFeeService = estimatedFeeAndTxSize.first; feeTxSize = estimatedFeeAndTxSize.second; } else { - feeTxSize = 380; + feeTxSize = 233; txFeeFromFeeService = txFeePerByteFromFeeService.multiply(feeTxSize); log.info("We cannot do the fee estimation because there are no funds in the wallet.\nThis is expected " + "if the user has not funded their wallet yet.\n" + - "In that case we use an estimated tx size of 380 bytes.\n" + + "In that case we use an estimated tx size of 233 bytes.\n" + "txFee based on estimated size of {} bytes. feeTxSize = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", feeTxSize, feeTxSize, txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); } @@ -531,7 +531,7 @@ public void swapTradeToSavings() { // With that we avoid that we overpay in case that the trade fee has many inputs and we would apply that fee for the // other 2 txs as well. We still might overpay a bit for the payout tx. private int getAverageSize(int txSize) { - return (txSize + 320) / 2; + return (txSize + 233) / 2; } private Coin getTxFeeBySize(int sizeInBytes) { @@ -606,7 +606,7 @@ private Coin getTxFeeForDepositTx() { // Unfortunately we cannot change that to the correct fees as it would break backward compatibility // We still might find a way with offer version or app version checks so lets keep that commented out // code as that shows how it should be. - return txFeeFromFeeService; //feeService.getTxFee(320); + return txFeeFromFeeService; //feeService.getTxFee(233); } private Coin getTxFeeForPayoutTx() { @@ -614,7 +614,7 @@ private Coin getTxFeeForPayoutTx() { // Unfortunately we cannot change that to the correct fees as it would break backward compatibility // We still might find a way with offer version or app version checks so lets keep that commented out // code as that shows how it should be. - return txFeeFromFeeService; //feeService.getTxFee(380); + return txFeeFromFeeService; //feeService.getTxFee(169); } public AddressEntry getAddressEntry() { From 327a3584463d0cb4dbe4b529441f7f893c543687 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Wed, 21 Oct 2020 18:49:21 -0300 Subject: [PATCH 15/18] Split segwit from legacy inputs Goal: Have a more accurate fee calculation --- .../core/btc/wallet/BtcWalletService.java | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) 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 8573e1c6879..c324d5808f9 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -28,6 +28,7 @@ import bisq.core.user.Preferences; import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.util.Tuple2; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; @@ -44,6 +45,7 @@ import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.Wallet; @@ -228,7 +230,9 @@ private Transaction completePreparedProposalTx(Transaction feeTx, byte[] opRetur preferences.getIgnoreDustThreshold()); List preparedBsqTxInputs = preparedTx.getInputs(); List preparedBsqTxOutputs = preparedTx.getOutputs(); - int numInputs = preparedBsqTxInputs.size(); + Tuple2 numInputs = getNumInputs(preparedTx); + int numLegacyInputs = numInputs.first; + int numSegwitInputs = numInputs.second; Transaction resultTx = null; boolean isFeeOutsideTolerance; do { @@ -249,7 +253,10 @@ private Transaction completePreparedProposalTx(Transaction feeTx, byte[] opRetur // signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet) sendRequest.signInputs = false; - sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs); + sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + + sigSizePerInput * numLegacyInputs + + sigSizePerInput * numSegwitInputs / 4); + sendRequest.feePerKb = Coin.ZERO; sendRequest.ensureMinRequiredFee = false; @@ -262,9 +269,14 @@ private Transaction completePreparedProposalTx(Transaction feeTx, byte[] opRetur // add OP_RETURN output resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram())); - numInputs = resultTx.getInputs().size(); + numInputs = getNumInputs(resultTx); + numLegacyInputs = numInputs.first; + numSegwitInputs = numInputs.second; txSizeWithUnsignedInputs = resultTx.getVsize(); - long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value; + long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + + sigSizePerInput * numLegacyInputs + + sigSizePerInput * numSegwitInputs / 4).value; + // calculated fee must be inside of a tolerance range with tx fee isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000; } @@ -338,7 +350,9 @@ private Transaction addInputsForMinerFee(Transaction preparedTx, preferences.getIgnoreDustThreshold()); List preparedBsqTxInputs = preparedTx.getInputs(); List preparedBsqTxOutputs = preparedTx.getOutputs(); - int numInputs = preparedBsqTxInputs.size(); + Tuple2 numInputs = getNumInputs(preparedTx); + int numLegacyInputs = numInputs.first; + int numSegwitInputs = numInputs.second; Transaction resultTx = null; boolean isFeeOutsideTolerance; do { @@ -359,7 +373,9 @@ private Transaction addInputsForMinerFee(Transaction preparedTx, // signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet) sendRequest.signInputs = false; - sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs); + sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + + sigSizePerInput * numLegacyInputs + + sigSizePerInput * numSegwitInputs / 4); sendRequest.feePerKb = Coin.ZERO; sendRequest.ensureMinRequiredFee = false; @@ -372,9 +388,13 @@ private Transaction addInputsForMinerFee(Transaction preparedTx, // add OP_RETURN output resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram())); - numInputs = resultTx.getInputs().size(); + numInputs = getNumInputs(resultTx); + numLegacyInputs = numInputs.first; + numSegwitInputs = numInputs.second; txSizeWithUnsignedInputs = resultTx.getVsize(); - final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value; + final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + + sigSizePerInput * numLegacyInputs + + sigSizePerInput * numSegwitInputs / 4).value; // calculated fee must be inside of a tolerance range with tx fee isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000; } @@ -479,7 +499,10 @@ public Transaction completePreparedBsqTx(Transaction preparedBsqTx, preferences.getIgnoreDustThreshold()); List preparedBsqTxInputs = preparedBsqTx.getInputs(); List preparedBsqTxOutputs = preparedBsqTx.getOutputs(); - int numInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input + // We don't know at this point what type the btc input would be (segwit/legacy). + // We use legacy to be on the safe side. + int numLegacyInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input + int numSegwitInputs = 0; Transaction resultTx = null; boolean isFeeOutsideTolerance; boolean opReturnIsOnlyOutput; @@ -508,7 +531,9 @@ public Transaction completePreparedBsqTx(Transaction preparedBsqTx, // signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet) sendRequest.signInputs = false; - sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs); + sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + + sigSizePerInput * numLegacyInputs + + sigSizePerInput * numSegwitInputs / 4); sendRequest.feePerKb = Coin.ZERO; sendRequest.ensureMinRequiredFee = false; @@ -528,9 +553,13 @@ public Transaction completePreparedBsqTx(Transaction preparedBsqTx, if (opReturnData != null) resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram())); - numInputs = resultTx.getInputs().size(); + Tuple2 numInputs = getNumInputs(resultTx); + numLegacyInputs = numInputs.first; + numSegwitInputs = numInputs.second; txSizeWithUnsignedInputs = resultTx.getVsize(); - final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value; + final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + + sigSizePerInput * numLegacyInputs + + sigSizePerInput * numSegwitInputs / 4).value; // calculated fee must be inside of a tolerance range with tx fee isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000; } @@ -548,6 +577,23 @@ public Transaction completePreparedBsqTx(Transaction preparedBsqTx, return resultTx; } + private Tuple2 getNumInputs(Transaction tx) { + int numLegacyInputs = 0; + int numSegwitInputs = 0; + for (TransactionInput input : tx.getInputs()) { + TransactionOutput connectedOutput = input.getConnectedOutput(); + if (connectedOutput == null || ScriptPattern.isP2PKH(connectedOutput.getScriptPubKey()) || + ScriptPattern.isP2PK(connectedOutput.getScriptPubKey())) { + numLegacyInputs++; + } else if (ScriptPattern.isP2WPKH(connectedOutput.getScriptPubKey())) { + numSegwitInputs++; + } else { + throw new IllegalArgumentException("Inputs should spend a P2PKH, P2PK or P2WPKH ouput"); + } + } + return new Tuple2(numLegacyInputs, numSegwitInputs); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Commit tx From 5f0009e1a4ef1100ed35f0311e530b4c21416d40 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Wed, 28 Oct 2020 17:50:23 -0300 Subject: [PATCH 16/18] Explain why legacy is used by default --- core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java | 2 ++ 1 file changed, 2 insertions(+) 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 c324d5808f9..5a3b522d338 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -584,6 +584,8 @@ private Tuple2 getNumInputs(Transaction tx) { TransactionOutput connectedOutput = input.getConnectedOutput(); if (connectedOutput == null || ScriptPattern.isP2PKH(connectedOutput.getScriptPubKey()) || ScriptPattern.isP2PK(connectedOutput.getScriptPubKey())) { + // If connectedOutput is null, we don't know here the input type. To avoid underpaying fees, + // we treat it as a legacy input which will result in a higher fee estimation. numLegacyInputs++; } else if (ScriptPattern.isP2WPKH(connectedOutput.getScriptPubKey())) { numSegwitInputs++; From 0d59d5e8725cc3241e8370b3f755e7df05c4f1ec Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Wed, 28 Oct 2020 17:51:22 -0300 Subject: [PATCH 17/18] Remove unused PAYOUT_TX_SIZE --- core/src/main/java/bisq/core/btc/TxFeeEstimationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java index 71648011af6..9401fb0a9b0 100644 --- a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java +++ b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java @@ -57,7 +57,6 @@ public class TxFeeEstimationService { // segwit delayed payout tx vsize = 139 public static int TYPICAL_TX_WITH_1_INPUT_SIZE = 175; private static int DEPOSIT_TX_SIZE = 233; - private static int PAYOUT_TX_SIZE = 169; private static int BSQ_INPUT_INCREASE = 150; private static int MAX_ITERATIONS = 10; From 7740e3e56d397e94434460ad36eec6f482c1d3e8 Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Wed, 28 Oct 2020 17:53:20 -0300 Subject: [PATCH 18/18] Fix comment --- core/src/main/java/bisq/core/btc/TxFeeEstimationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java index 9401fb0a9b0..eeb5a545e2a 100644 --- a/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java +++ b/core/src/main/java/bisq/core/btc/TxFeeEstimationService.java @@ -107,7 +107,7 @@ private Tuple2 getEstimatedFeeAndTxSize(boolean isTaker, estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, amount), estimatedTxSize, txFeePerByte, btcWalletService); } catch (InsufficientMoneyException e) { if (isTaker) { - // if we cannot do the estimation we use the deposit tx size + // If we cannot do the estimation, we use the size o the largest of our txs which is the deposit tx. estimatedTxSize = DEPOSIT_TX_SIZE; } log.info("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +