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.
This commit is contained in:
Andrew Chow 2021-10-05 15:54:11 -04:00
parent 4060c50d7e
commit 808068e90e
4 changed files with 110 additions and 4 deletions

View file

@ -455,15 +455,17 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
}
input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false);
txout = wtx.tx->vout.at(outpoint.n);
}
if (input_bytes == -1) {
// 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
} else {
// The input is external. We did not find the tx in mapWallet.
if (!coin_control.GetExternalOutput(outpoint, txout)) {
// Not ours, and we don't have solving data.
return std::nullopt;
}
input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
}
// If available, override calculated size with coin control specified size
if (coin_control.HasInputWeight(outpoint)) {
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
}
CInputCoin coin(outpoint, txout, input_bytes);
if (coin.m_input_bytes == -1) {

View file

@ -63,5 +63,56 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
BOOST_CHECK_EQUAL(fee, check_tx(fee + 123));
}
static void TestFillInputToWeight(int64_t additional_weight, std::vector<int64_t> expected_stack_sizes)
{
static const int64_t EMPTY_INPUT_WEIGHT = GetTransactionInputWeight(CTxIn());
CTxIn input;
int64_t target_weight = EMPTY_INPUT_WEIGHT + additional_weight;
BOOST_CHECK(FillInputToWeight(input, target_weight));
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), target_weight);
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), expected_stack_sizes.size());
for (unsigned int i = 0; i < expected_stack_sizes.size(); ++i) {
BOOST_CHECK_EQUAL(input.scriptWitness.stack[i].size(), expected_stack_sizes[i]);
}
}
BOOST_FIXTURE_TEST_CASE(FillInputToWeightTest, BasicTestingSetup)
{
{
// Less than or equal minimum of 165 should not add any witness data
CTxIn input;
BOOST_CHECK(!FillInputToWeight(input, -1));
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
BOOST_CHECK(!FillInputToWeight(input, 0));
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
BOOST_CHECK(!FillInputToWeight(input, 164));
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
BOOST_CHECK(FillInputToWeight(input, 165));
BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
}
// Make sure we can add at least one weight
TestFillInputToWeight(1, {0});
// 1 byte compact size uint boundary
TestFillInputToWeight(252, {251});
TestFillInputToWeight(253, {83, 168});
TestFillInputToWeight(262, {86, 174});
TestFillInputToWeight(263, {260});
// 3 byte compact size uint boundary
TestFillInputToWeight(65535, {65532});
TestFillInputToWeight(65536, {21842, 43688});
TestFillInputToWeight(65545, {21845, 43694});
TestFillInputToWeight(65546, {65541});
// Note: We don't test the next boundary because of memory allocation constraints.
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet

View file

@ -1507,6 +1507,49 @@ bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut
return true;
}
bool FillInputToWeight(CTxIn& txin, int64_t target_weight)
{
assert(txin.scriptSig.empty());
assert(txin.scriptWitness.IsNull());
int64_t txin_weight = GetTransactionInputWeight(txin);
// Do nothing if the weight that should be added is less than the weight that already exists
if (target_weight < txin_weight) {
return false;
}
if (target_weight == txin_weight) {
return true;
}
// Subtract current txin weight, which should include empty witness stack
int64_t add_weight = target_weight - txin_weight;
assert(add_weight > 0);
// We will want to subtract the size of the Compact Size UInt that will also be serialized.
// However doing so when the size is near a boundary can result in a problem where it is not
// possible to have a stack element size and combination to exactly equal a target.
// To avoid this possibility, if the weight to add is less than 10 bytes greater than
// a boundary, the size will be split so that 2/3rds will be in one stack element, and
// the remaining 1/3rd in another. Using 3rds allows us to avoid additional boundaries.
// 10 bytes is used because that accounts for the maximum size. This does not need to be super precise.
if ((add_weight >= 253 && add_weight < 263)
|| (add_weight > std::numeric_limits<uint16_t>::max() && add_weight <= std::numeric_limits<uint16_t>::max() + 10)
|| (add_weight > std::numeric_limits<uint32_t>::max() && add_weight <= std::numeric_limits<uint32_t>::max() + 10)) {
int64_t first_weight = add_weight / 3;
add_weight -= first_weight;
first_weight -= GetSizeOfCompactSize(first_weight);
txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), first_weight, 0);
}
add_weight -= GetSizeOfCompactSize(add_weight);
txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), add_weight, 0);
assert(GetTransactionInputWeight(txin) == target_weight);
return true;
}
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
{
@ -1515,6 +1558,14 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
for (const auto& txout : txouts)
{
CTxIn& txin = txNew.vin[nIn];
// If weight was provided, fill the input to that weight
if (coin_control && coin_control->HasInputWeight(txin.prevout)) {
if (!FillInputToWeight(txin, coin_control->GetInputWeight(txin.prevout))) {
return false;
}
nIn++;
continue;
}
// Use max sig if watch only inputs were used or if this particular input is an external input
// to ensure a sufficient fee is attained for the requested feerate.
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));

View file

@ -939,6 +939,8 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig);
bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
} // namespace wallet
#endif // BITCOIN_WALLET_WALLET_H