Skip to content

Commit a8b0892

Browse files
committed
Merge #20536: wallet: Error with "Transaction too large" if the funded tx will end up being too large after signing
48a0319 Add a test that selects too large if BnB is used (Andrew Chow) 3e69939 Fail if maximum weight is too large (Andrew Chow) 51e2cd3 Have CalculateMaximumSignedTxSize also compute tx weight (Andrew Chow) Pull request description: Currently the `Transaction too large` is calculated on the transaction that is returned from `CreateTransaction`. This does not make sense for when `CreateTransaction` is being used for `fundrawtransaction` as no signing occurs so the final returned transaction is missing signatures. Thus users may successfully fund a transaction but fail to broadcast it after it has been fully signed. So instead we should figure out whether the transaction we are funding will be too large after it is signed. We can do this by having `CalculateMaximumSignedTxSize` also return the transaction weight and then comparing that weight against the maximum weight. ACKs for top commit: instagibbs: ACK bitcoin/bitcoin@48a0319 meshcollider: utACK 48a0319 Xekyo: utACK with nits 48a0319 Tree-SHA512: 1700c60b07f67e2d5c591c5ccd131ac9f1861fab3def961c3c9c4b3281ec1063fe8e4f0f7f1038cac72692340856406bcee8fb45c8104d2ad34357a0ec878ac7
2 parents 2067f9e + 48a0319 commit a8b0892

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

src/wallet/feebumper.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
190190
if (coin_control.m_feerate) {
191191
// The user provided a feeRate argument.
192192
// We calculate this here to avoid compiler warning on the cs_wallet lock
193-
const int64_t maxTxSize = CalculateMaximumSignedTxSize(*wtx.tx, &wallet);
193+
const int64_t maxTxSize = CalculateMaximumSignedTxSize(*wtx.tx, &wallet).first;
194194
Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, errors);
195195
if (res != Result::OK) {
196196
return res;

src/wallet/wallet.cpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,14 +1611,15 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
16111611
return true;
16121612
}
16131613

1614-
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
1614+
// Returns pair of vsize and weight
1615+
std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
16151616
{
16161617
std::vector<CTxOut> txouts;
16171618
for (const CTxIn& input : tx.vin) {
16181619
const auto mi = wallet->mapWallet.find(input.prevout.hash);
16191620
// Can not estimate size without knowing the input details
16201621
if (mi == wallet->mapWallet.end()) {
1621-
return -1;
1622+
return std::make_pair(-1, -1);
16221623
}
16231624
assert(input.prevout.n < mi->second.tx->vout.size());
16241625
txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
@@ -1627,13 +1628,16 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall
16271628
}
16281629

16291630
// txouts needs to be in the order of tx.vin
1630-
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
1631+
std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
16311632
{
16321633
CMutableTransaction txNew(tx);
16331634
if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
1634-
return -1;
1635+
return std::make_pair(-1, -1);
16351636
}
1636-
return GetVirtualTransactionSize(CTransaction(txNew));
1637+
CTransaction ctx(txNew);
1638+
int64_t vsize = GetVirtualTransactionSize(ctx);
1639+
int64_t weight = GetTransactionWeight(ctx);
1640+
return std::make_pair(vsize, weight);
16371641
}
16381642

16391643
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
@@ -2779,6 +2783,7 @@ bool CWallet::CreateTransactionInternal(
27792783
CMutableTransaction txNew;
27802784
FeeCalculation feeCalc;
27812785
CAmount nFeeNeeded;
2786+
std::pair<int64_t, int64_t> tx_sizes;
27822787
int nBytes;
27832788
{
27842789
std::set<CInputCoin> setCoins;
@@ -2962,7 +2967,8 @@ bool CWallet::CreateTransactionInternal(
29622967
txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
29632968
}
29642969

2965-
nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
2970+
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
2971+
nBytes = tx_sizes.first;
29662972
if (nBytes < 0) {
29672973
error = _("Signing transaction failed");
29682974
return false;
@@ -3072,7 +3078,8 @@ bool CWallet::CreateTransactionInternal(
30723078
tx = MakeTransactionRef(std::move(txNew));
30733079

30743080
// Limit size
3075-
if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT)
3081+
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
3082+
(!sign && tx_sizes.second > MAX_STANDARD_TX_WEIGHT))
30763083
{
30773084
error = _("Transaction too large");
30783085
return false;

src/wallet/wallet.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,8 +1334,8 @@ class WalletRescanReserver
13341334
// Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
13351335
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
13361336
// be IsAllFromMe).
1337-
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
1338-
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
1337+
std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
1338+
std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
13391339

13401340
//! Add wallet name to persistent configuration so it will be loaded on startup.
13411341
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);

test/functional/rpc_fundrawtransaction.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def run_test(self):
9494
self.test_address_reuse()
9595
self.test_option_subtract_fee_from_outputs()
9696
self.test_subtract_fee_with_presets()
97+
self.test_transaction_too_large()
9798

9899
def test_change_position(self):
99100
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -907,5 +908,24 @@ def test_subtract_fee_with_presets(self):
907908
signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx['hex'])
908909
self.nodes[0].sendrawtransaction(signedtx['hex'])
909910

911+
def test_transaction_too_large(self):
912+
self.log.info("Test fundrawtx where BnB solution would result in a too large transaction, but Knapsack would not")
913+
914+
self.nodes[0].createwallet("large")
915+
wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
916+
recipient = self.nodes[0].get_wallet_rpc("large")
917+
outputs = {}
918+
rawtx = recipient.createrawtransaction([], {wallet.getnewaddress(): 147.99899260})
919+
920+
# Make 1500 0.1 BTC outputs
921+
# The amount that we target for funding is in the BnB range when these outputs are used.
922+
# However if these outputs are selected, the transaction will end up being too large, so it shouldn't use BnB and instead fallback to Knapsack
923+
# but that behavior is not implemented yet. For now we just check that we get an error.
924+
for i in range(0, 1500):
925+
outputs[recipient.getnewaddress()] = 0.1
926+
wallet.sendmany("", outputs)
927+
self.nodes[0].generate(10)
928+
assert_raises_rpc_error(-4, "Transaction too large", recipient.fundrawtransaction, rawtx)
929+
910930
if __name__ == '__main__':
911931
RawTransactionsTest().main()

0 commit comments

Comments
 (0)