Skip to content

Commit 808068e

Browse files
committed
wallet: Allow user specified input size to override
If the user specifies an input size, allow it to override any input size calculations during coin selection.
1 parent 4060c50 commit 808068e

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

src/wallet/spend.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -455,15 +455,17 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
455455
}
456456
input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false);
457457
txout = wtx.tx->vout.at(outpoint.n);
458-
}
459-
if (input_bytes == -1) {
460-
// The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data
458+
} else {
459+
// The input is external. We did not find the tx in mapWallet.
461460
if (!coin_control.GetExternalOutput(outpoint, txout)) {
462-
// Not ours, and we don't have solving data.
463461
return std::nullopt;
464462
}
465463
input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
466464
}
465+
// If available, override calculated size with coin control specified size
466+
if (coin_control.HasInputWeight(outpoint)) {
467+
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
468+
}
467469

468470
CInputCoin coin(outpoint, txout, input_bytes);
469471
if (coin.m_input_bytes == -1) {

src/wallet/test/spend_tests.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,56 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
6363
BOOST_CHECK_EQUAL(fee, check_tx(fee + 123));
6464
}
6565

66+
static void TestFillInputToWeight(int64_t additional_weight, std::vector<int64_t> expected_stack_sizes)
67+
{
68+
static const int64_t EMPTY_INPUT_WEIGHT = GetTransactionInputWeight(CTxIn());
69+
70+
CTxIn input;
71+
int64_t target_weight = EMPTY_INPUT_WEIGHT + additional_weight;
72+
BOOST_CHECK(FillInputToWeight(input, target_weight));
73+
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), target_weight);
74+
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), expected_stack_sizes.size());
75+
for (unsigned int i = 0; i < expected_stack_sizes.size(); ++i) {
76+
BOOST_CHECK_EQUAL(input.scriptWitness.stack[i].size(), expected_stack_sizes[i]);
77+
}
78+
}
79+
80+
BOOST_FIXTURE_TEST_CASE(FillInputToWeightTest, BasicTestingSetup)
81+
{
82+
{
83+
// Less than or equal minimum of 165 should not add any witness data
84+
CTxIn input;
85+
BOOST_CHECK(!FillInputToWeight(input, -1));
86+
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
87+
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
88+
BOOST_CHECK(!FillInputToWeight(input, 0));
89+
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
90+
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
91+
BOOST_CHECK(!FillInputToWeight(input, 164));
92+
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
93+
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
94+
BOOST_CHECK(FillInputToWeight(input, 165));
95+
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
96+
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
97+
}
98+
99+
// Make sure we can add at least one weight
100+
TestFillInputToWeight(1, {0});
101+
102+
// 1 byte compact size uint boundary
103+
TestFillInputToWeight(252, {251});
104+
TestFillInputToWeight(253, {83, 168});
105+
TestFillInputToWeight(262, {86, 174});
106+
TestFillInputToWeight(263, {260});
107+
108+
// 3 byte compact size uint boundary
109+
TestFillInputToWeight(65535, {65532});
110+
TestFillInputToWeight(65536, {21842, 43688});
111+
TestFillInputToWeight(65545, {21845, 43694});
112+
TestFillInputToWeight(65546, {65541});
113+
114+
// Note: We don't test the next boundary because of memory allocation constraints.
115+
}
116+
66117
BOOST_AUTO_TEST_SUITE_END()
67118
} // namespace wallet

src/wallet/wallet.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,49 @@ bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut
15071507
return true;
15081508
}
15091509

1510+
bool FillInputToWeight(CTxIn& txin, int64_t target_weight)
1511+
{
1512+
assert(txin.scriptSig.empty());
1513+
assert(txin.scriptWitness.IsNull());
1514+
1515+
int64_t txin_weight = GetTransactionInputWeight(txin);
1516+
1517+
// Do nothing if the weight that should be added is less than the weight that already exists
1518+
if (target_weight < txin_weight) {
1519+
return false;
1520+
}
1521+
if (target_weight == txin_weight) {
1522+
return true;
1523+
}
1524+
1525+
// Subtract current txin weight, which should include empty witness stack
1526+
int64_t add_weight = target_weight - txin_weight;
1527+
assert(add_weight > 0);
1528+
1529+
// We will want to subtract the size of the Compact Size UInt that will also be serialized.
1530+
// However doing so when the size is near a boundary can result in a problem where it is not
1531+
// possible to have a stack element size and combination to exactly equal a target.
1532+
// To avoid this possibility, if the weight to add is less than 10 bytes greater than
1533+
// a boundary, the size will be split so that 2/3rds will be in one stack element, and
1534+
// the remaining 1/3rd in another. Using 3rds allows us to avoid additional boundaries.
1535+
// 10 bytes is used because that accounts for the maximum size. This does not need to be super precise.
1536+
if ((add_weight >= 253 && add_weight < 263)
1537+
|| (add_weight > std::numeric_limits<uint16_t>::max() && add_weight <= std::numeric_limits<uint16_t>::max() + 10)
1538+
|| (add_weight > std::numeric_limits<uint32_t>::max() && add_weight <= std::numeric_limits<uint32_t>::max() + 10)) {
1539+
int64_t first_weight = add_weight / 3;
1540+
add_weight -= first_weight;
1541+
1542+
first_weight -= GetSizeOfCompactSize(first_weight);
1543+
txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), first_weight, 0);
1544+
}
1545+
1546+
add_weight -= GetSizeOfCompactSize(add_weight);
1547+
txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), add_weight, 0);
1548+
assert(GetTransactionInputWeight(txin) == target_weight);
1549+
1550+
return true;
1551+
}
1552+
15101553
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
15111554
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
15121555
{
@@ -1515,6 +1558,14 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
15151558
for (const auto& txout : txouts)
15161559
{
15171560
CTxIn& txin = txNew.vin[nIn];
1561+
// If weight was provided, fill the input to that weight
1562+
if (coin_control && coin_control->HasInputWeight(txin.prevout)) {
1563+
if (!FillInputToWeight(txin, coin_control->GetInputWeight(txin.prevout))) {
1564+
return false;
1565+
}
1566+
nIn++;
1567+
continue;
1568+
}
15181569
// Use max sig if watch only inputs were used or if this particular input is an external input
15191570
// to ensure a sufficient fee is attained for the requested feerate.
15201571
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));

src/wallet/wallet.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,8 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
939939
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
940940

941941
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig);
942+
943+
bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
942944
} // namespace wallet
943945

944946
#endif // BITCOIN_WALLET_WALLET_H

0 commit comments

Comments
 (0)