mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 10:38:42 +01:00
Merge bitcoin/bitcoin#27171: test: add coverage for sigop limit policy (-bytespersigop
setting)
89cd20cbed
test: add coverage for sigop limit policy (`-bytespersigop` setting) (Sebastian Falbesoner) Pull request description: This PR adds missing test coverage for the `-bytespersigop` option, which determines how pre-taproot signature operations (OP_CHECKSIG{VERIFY}, OP_CHECKMULTIGSIG{VERIFY}) affect fee handling calculations. The setting was introduced in PR #7081 for mitigating the [sigop spam attack](https://bitcointalk.org/index.php?topic=1166928.0); the initial implementation rejected txs exceeding the limit, but was changed in #8365 later to account for higher sizes in the mempool (i.e. exceeding the sigop limit is possible, but has to be compensated by higher fees). For each combination of `-bytespersigop` setting and sigops count, the test first creates a P2WSH spending transaction with a witness script that puts sigops in a non-executing branch (OP_FALSE OP_IF OP_CHECKMULTISIG ... OP_CHECKSIG ... OP_ENDIF). This tx is then bumped up to reach exactly the _sig-op limit equivalent vsize_ by padding its datacarrier output. Based on that, increasing the tx's vsize should still reflect a vsize increase in the mempool, while a decrease of the tx's vsize should lead to the mempool treating the tx's vsize to be the _sig-op limit equivalent vsize_, since the limit was exceeded. I assume that this parameter is almost never set explicitly by users (also it is not relevant for taproot spends), but it doesn't hurt to have a test for it. See also https://bitcoin.stackexchange.com/a/87958 for another explanation. ACKs for top commit: glozow: light review ACK89cd20cbed
MarcoFalke: nice ACK89cd20cbed
📁 Tree-SHA512: 06998ce93bf9d5ce6143db2996a43f13990c415f97afe684227ad469349e73952bf4f6c871c1e6349e07606f4d45db64408848873a86a89481cdca5a134e5e60
This commit is contained in:
commit
3e7dd4ff33
125
test/functional/mempool_sigoplimit.py
Executable file
125
test/functional/mempool_sigoplimit.py
Executable file
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2023 The Bitcoin Core developers
|
||||
# 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 math import ceil
|
||||
|
||||
from test_framework.messages import (
|
||||
COutPoint,
|
||||
CTransaction,
|
||||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
WITNESS_SCALE_FACTOR,
|
||||
)
|
||||
from test_framework.script import (
|
||||
CScript,
|
||||
OP_CHECKMULTISIG,
|
||||
OP_CHECKSIG,
|
||||
OP_ENDIF,
|
||||
OP_FALSE,
|
||||
OP_IF,
|
||||
OP_RETURN,
|
||||
OP_TRUE,
|
||||
)
|
||||
from test_framework.script_util import (
|
||||
script_to_p2wsh_script,
|
||||
)
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than_or_equal,
|
||||
)
|
||||
from test_framework.wallet import MiniWallet
|
||||
|
||||
|
||||
DEFAULT_BYTES_PER_SIGOP = 20 # default setting
|
||||
|
||||
|
||||
class BytesPerSigOpTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
# allow large datacarrier output to pad transactions
|
||||
self.extra_args = [['-datacarriersize=100000']]
|
||||
|
||||
def create_p2wsh_spending_tx(self, witness_script, output_script):
|
||||
"""Create a 1-input-1-output P2WSH spending transaction with only the
|
||||
witness script in the witness stack and the given output script."""
|
||||
# create P2WSH address and fund it via MiniWallet first
|
||||
txid, vout = self.wallet.send_to(
|
||||
from_node=self.nodes[0],
|
||||
scriptPubKey=script_to_p2wsh_script(witness_script),
|
||||
amount=1000000,
|
||||
)
|
||||
|
||||
# create spending transaction
|
||||
tx = CTransaction()
|
||||
tx.vin = [CTxIn(COutPoint(int(txid, 16), vout))]
|
||||
tx.wit.vtxinwit = [CTxInWitness()]
|
||||
tx.wit.vtxinwit[0].scriptWitness.stack = [bytes(witness_script)]
|
||||
tx.vout = [CTxOut(500000, output_script)]
|
||||
return tx
|
||||
|
||||
def test_sigops_limit(self, bytes_per_sigop, num_sigops):
|
||||
sigop_equivalent_vsize = ceil(num_sigops * bytes_per_sigop / WITNESS_SCALE_FACTOR)
|
||||
self.log.info(f"- {num_sigops} sigops (equivalent size of {sigop_equivalent_vsize} vbytes)")
|
||||
|
||||
# create a template tx with the specified sigop cost in the witness script
|
||||
# (note that the sigops count even though being in a branch that's not executed)
|
||||
num_multisigops = num_sigops // 20
|
||||
num_singlesigops = num_sigops % 20
|
||||
witness_script = CScript(
|
||||
[OP_FALSE, OP_IF] +
|
||||
[OP_CHECKMULTISIG]*num_multisigops +
|
||||
[OP_CHECKSIG]*num_singlesigops +
|
||||
[OP_ENDIF, OP_TRUE]
|
||||
)
|
||||
# use a 256-byte data-push as lower bound in the output script, in order
|
||||
# to avoid having to compensate for tx size changes caused by varying
|
||||
# length serialization sizes (both for scriptPubKey and data-push lengths)
|
||||
tx = self.create_p2wsh_spending_tx(witness_script, CScript([OP_RETURN, b'X'*256]))
|
||||
|
||||
# bump the tx to reach the sigop-limit equivalent size by padding the datacarrier output
|
||||
assert_greater_than_or_equal(sigop_equivalent_vsize, tx.get_vsize())
|
||||
vsize_to_pad = sigop_equivalent_vsize - tx.get_vsize()
|
||||
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad)])
|
||||
assert_equal(sigop_equivalent_vsize, tx.get_vsize())
|
||||
|
||||
res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
|
||||
assert_equal(res['allowed'], True)
|
||||
assert_equal(res['vsize'], sigop_equivalent_vsize)
|
||||
|
||||
# increase the tx's vsize to be right above the sigop-limit equivalent size
|
||||
# => tx's vsize in mempool should also grow accordingly
|
||||
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad+1)])
|
||||
res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
|
||||
assert_equal(res['allowed'], True)
|
||||
assert_equal(res['vsize'], sigop_equivalent_vsize+1)
|
||||
|
||||
# decrease the tx's vsize to be right below the sigop-limit equivalent size
|
||||
# => tx's vsize in mempool should stick at the sigop-limit equivalent
|
||||
# bytes level, as it is higher than the tx's serialized vsize
|
||||
# (the maximum of both is taken)
|
||||
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad-1)])
|
||||
res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
|
||||
assert_equal(res['allowed'], True)
|
||||
assert_equal(res['vsize'], sigop_equivalent_vsize)
|
||||
|
||||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
|
||||
for bytes_per_sigop in (DEFAULT_BYTES_PER_SIGOP, 43, 81, 165, 327, 649, 1072):
|
||||
if bytes_per_sigop == DEFAULT_BYTES_PER_SIGOP:
|
||||
self.log.info(f"Test default sigops limit setting ({bytes_per_sigop} bytes per sigop)...")
|
||||
else:
|
||||
bytespersigop_parameter = f"-bytespersigop={bytes_per_sigop}"
|
||||
self.log.info(f"Test sigops limit setting {bytespersigop_parameter}...")
|
||||
self.restart_node(0, extra_args=[bytespersigop_parameter] + self.extra_args[0])
|
||||
|
||||
for num_sigops in (69, 101, 142, 183, 222):
|
||||
self.test_sigops_limit(bytes_per_sigop, num_sigops)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BytesPerSigOpTest().main()
|
@ -323,6 +323,7 @@ BASE_SCRIPTS = [
|
||||
'mempool_compatibility.py',
|
||||
'mempool_accept_wtxid.py',
|
||||
'mempool_dust.py',
|
||||
'mempool_sigoplimit.py',
|
||||
'rpc_deriveaddresses.py',
|
||||
'rpc_deriveaddresses.py --usecli',
|
||||
'p2p_ping.py',
|
||||
|
Loading…
Reference in New Issue
Block a user