-
Notifications
You must be signed in to change notification settings - Fork 35.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix virtual size limit enforcement in transaction package context #28471
Changes from all commits
533660c
bc013fe
1a579f9
eb8f58f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
glozow marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -197,12 +197,28 @@ util::Result<CTxMemPool::setEntries> CTxMemPool::CalculateAncestorsAndCheckLimit | |
} | ||
|
||
bool CTxMemPool::CheckPackageLimits(const Package& package, | ||
const int64_t total_vsize, | ||
std::string &errString) const | ||
{ | ||
size_t pack_count = package.size(); | ||
|
||
// Package itself is busting mempool limits; should be rejected even if no staged_ancestors exist | ||
if (pack_count > static_cast<uint64_t>(m_limits.ancestor_count)) { | ||
errString = strprintf("package count %u exceeds ancestor count limit [limit: %u]", pack_count, m_limits.ancestor_count); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Following diff doesn’t sound to break functional test
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. going to reserve for follow-up test writing |
||
} else if (pack_count > static_cast<uint64_t>(m_limits.descendant_count)) { | ||
errString = strprintf("package count %u exceeds descendant count limit [limit: %u]", pack_count, m_limits.descendant_count); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. going to reserve for follow-up test writing |
||
} else if (total_vsize > m_limits.ancestor_size_vbytes) { | ||
errString = strprintf("package size %u exceeds ancestor size limit [limit: %u]", total_vsize, m_limits.ancestor_size_vbytes); | ||
return false; | ||
} else if (total_vsize > m_limits.descendant_size_vbytes) { | ||
errString = strprintf("package size %u exceeds descendant size limit [limit: %u]", total_vsize, m_limits.descendant_size_vbytes); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here
(Mutating the third check on ancestor size vbytes break the functional test) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. going to reserve for follow-up test writing |
||
} | ||
|
||
CTxMemPoolEntry::Parents staged_ancestors; | ||
int64_t total_size = 0; | ||
for (const auto& tx : package) { | ||
total_size += GetVirtualTransactionSize(*tx); | ||
for (const auto& input : tx->vin) { | ||
std::optional<txiter> piter = GetIter(input.prevout.hash); | ||
if (piter) { | ||
|
@@ -217,7 +233,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, | |
// When multiple transactions are passed in, the ancestors and descendants of all transactions | ||
// considered together must be within limits even if they are not interdependent. This may be | ||
// stricter than the limits for each individual transaction. | ||
const auto ancestors{CalculateAncestorsAndCheckLimits(total_size, package.size(), | ||
const auto ancestors{CalculateAncestorsAndCheckLimits(total_vsize, package.size(), | ||
staged_ancestors, m_limits)}; | ||
// It's possible to overestimate the ancestor/descendant totals. | ||
if (!ancestors.has_value()) errString = "possibly " + util::ErrorString(ancestors).original; | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,6 +3,7 @@ | |||||
# Distributed under the MIT software license, see the accompanying | ||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||||
"""Test sigop limit mempool policy (`-bytespersigop` parameter)""" | ||||||
from decimal import Decimal | ||||||
from math import ceil | ||||||
|
||||||
from test_framework.messages import ( | ||||||
|
@@ -25,16 +26,18 @@ | |||||
OP_TRUE, | ||||||
) | ||||||
from test_framework.script_util import ( | ||||||
keys_to_multisig_script, | ||||||
script_to_p2wsh_script, | ||||||
) | ||||||
from test_framework.test_framework import BitcoinTestFramework | ||||||
from test_framework.util import ( | ||||||
assert_equal, | ||||||
assert_greater_than, | ||||||
assert_greater_than_or_equal, | ||||||
assert_raises_rpc_error, | ||||||
) | ||||||
from test_framework.wallet import MiniWallet | ||||||
|
||||||
from test_framework.wallet_util import generate_keypair | ||||||
|
||||||
DEFAULT_BYTES_PER_SIGOP = 20 # default setting | ||||||
|
||||||
|
@@ -133,6 +136,45 @@ def test_sigops_limit(self, bytes_per_sigop, num_sigops): | |||||
assert_equal(entry_parent['descendantcount'], 2) | ||||||
assert_equal(entry_parent['descendantsize'], parent_tx.get_vsize() + sigop_equivalent_vsize) | ||||||
|
||||||
def test_sigops_package(self): | ||||||
self.log.info("Test a overly-large sigops-vbyte hits package limits") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit eb8f58f
Suggested change
|
||||||
# Make a 2-transaction package which fails vbyte checks even though | ||||||
# separately they would work. | ||||||
self.restart_node(0, extra_args=["-bytespersigop=5000"] + self.extra_args[0]) | ||||||
glozow marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
def create_bare_multisig_tx(utxo_to_spend=None): | ||||||
_, pubkey = generate_keypair() | ||||||
amount_for_bare = 50000 | ||||||
tx_dict = self.wallet.create_self_transfer(fee=Decimal("3"), utxo_to_spend=utxo_to_spend) | ||||||
tx_utxo = tx_dict["new_utxo"] | ||||||
tx = tx_dict["tx"] | ||||||
tx.vout.append(CTxOut(amount_for_bare, keys_to_multisig_script([pubkey], k=1))) | ||||||
tx.vout[0].nValue -= amount_for_bare | ||||||
tx_utxo["txid"] = tx.rehash() | ||||||
tx_utxo["value"] -= Decimal("0.00005000") | ||||||
return (tx_utxo, tx) | ||||||
|
||||||
tx_parent_utxo, tx_parent = create_bare_multisig_tx() | ||||||
tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit eb8f58f: unused
Suggested change
|
||||||
|
||||||
# Separately, the parent tx is ok | ||||||
parent_individual_testres = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex()])[0] | ||||||
assert parent_individual_testres["allowed"] | ||||||
# Multisig is counted as MAX_PUBKEYS_PER_MULTISIG = 20 sigops | ||||||
assert_equal(parent_individual_testres["vsize"], 5000 * 20) | ||||||
|
||||||
# But together, it's exceeding limits in the *package* context. If sigops adjusted vsize wasn't being checked | ||||||
# here, it would get further in validation and give too-long-mempool-chain error instead. | ||||||
packet_test = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex(), tx_child.serialize().hex()]) | ||||||
assert_equal([x["package-error"] for x in packet_test], ["package-mempool-limits", "package-mempool-limits"]) | ||||||
|
||||||
# When we actually try to submit, the parent makes it into the mempool, but the child would exceed ancestor vsize limits | ||||||
assert_raises_rpc_error(-26, "too-long-mempool-chain", self.nodes[0].submitpackage, [tx_parent.serialize().hex(), tx_child.serialize().hex()]) | ||||||
glozow marked this conversation as resolved.
Show resolved
Hide resolved
glozow marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
assert tx_parent.rehash() in self.nodes[0].getrawmempool() | ||||||
|
||||||
# Transactions are tiny in weight | ||||||
assert_greater_than(2000, tx_parent.get_weight() + tx_child.get_weight()) | ||||||
|
||||||
def run_test(self): | ||||||
self.wallet = MiniWallet(self.nodes[0]) | ||||||
|
||||||
|
@@ -149,6 +191,8 @@ def run_test(self): | |||||
|
||||||
self.generate(self.wallet, 1) | ||||||
|
||||||
self.test_sigops_package() | ||||||
|
||||||
|
||||||
if __name__ == '__main__': | ||||||
BytesPerSigOpTest().main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Playing with the boundary values
403’999
or404’001
doesn’t breakpackage_sanitization_tests
as one could expect.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should break if you make it significantly larger, I think, since it's making txns that aren't 1 WU. leaving as is for now