mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 06:52:36 +01:00
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:
parent
4060c50d7e
commit
808068e90e
4 changed files with 110 additions and 4 deletions
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue