2020-09-09 09:06:16 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2020 The Bitcoin Core developers
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
"""A limited-functionality wallet, which may replace a real wallet in tests"""
|
|
|
|
|
Merge bitcoin/bitcoin#21800: mempool/validation: mempool ancestor/descendant limits for packages
accf3d5868460b4b14ab607fd66ac985b086fbb3 [test] mempool package ancestor/descendant limits (glozow)
2b6b26e57c24d2f0abd442c1c33098e3121572ce [test] parameterizable fee for make_chain and create_child_with_parents (glozow)
313c09f7b7beddfdb74c284720d209c81dfdb94f [test] helper function to increase transaction weight (glozow)
f8253d69d6f02850995a11eeb71fedc22e6f6575 extract/rename helper functions from rpc_packages.py (glozow)
3cd663a5d33aa7ef87994e452bced7f192d021a0 [policy] ancestor/descendant limits for packages (glozow)
c6e016aa139c8363e9b38bbc1ba0dca55700b8a7 [mempool] check ancestor/descendant limits for packages (glozow)
f551841d3ec080a2d7a7988c7b35088dff6c5830 [refactor] pass size/count instead of entry to CalculateAncestorsAndCheckLimits (glozow)
97dd1c729d2bbedf9527b914c0cc8267b8a7c21b MOVEONLY: add helper function for calculating ancestors and checking limits (glozow)
f95bbf58aaf72aab8a9c5827b1f162f3b8ac38f4 misc package validation doc improvements (glozow)
Pull request description:
This PR implements a function to calculate mempool ancestors for a package and enforces ancestor/descendant limits on them as a whole. It reuses a portion of `CalculateMemPoolAncestors()`; there's also a small refactor to move the reused code into a generic helper function. Instead of calculating ancestors and descendants on every single transaction in the package and their ancestors, we use a "worst case" heuristic, treating every transaction in the package as each other's ancestor and descendant. This may overestimate everyone's counts, but is still pretty accurate in the our main package use cases, in which at least one of the transactions in the package is directly related to all the others (e.g. 1 parent + 1 child, multiple parents with 1 child, or chains).
Note on Terminology: While "package" is often used to describe groups of related transactions _within_ the mempool, here, I only use package to mean the group of not-in-mempool transactions we are currently validating.
#### Motivation
It would be a potential DoS vector to allow submission of packages to mempool without a proper guard for mempool ancestors/descendants. In general, the purpose of mempool ancestor/descendant limits is to limit the computational complexity of dealing with families during removals and additions. We want to be able to validate multiple transactions on top of the mempool, but also avoid these scenarios:
- We underestimate the ancestors/descendants during package validation and end up with extremely complex families in our mempool (potentially a DoS vector).
- We expend an unreasonable amount of resources calculating everyone's ancestors and descendants during package validation.
ACKs for top commit:
JeremyRubin:
utACK accf3d5
ariard:
ACK accf3d5.
Tree-SHA512: 0d18ce4b77398fe872e0b7c2cc66d3aac2135e561b64029584339e1f4de2a6a16ebab3dd5784f376e119cbafc4d50168b28d3bd95d0b3d01158714ade2e3624d
Signed-off-by: Vijay <vijaydas.mp@gmail.com>
2021-08-09 05:53:10 +02:00
|
|
|
from copy import deepcopy
|
2020-09-09 09:06:16 +02:00
|
|
|
from decimal import Decimal
|
2021-05-25 07:26:18 +02:00
|
|
|
from enum import Enum
|
2020-09-09 09:06:16 +02:00
|
|
|
from test_framework.address import ADDRESS_BCRT1_P2SH_OP_TRUE
|
2021-05-24 08:29:05 +02:00
|
|
|
from test_framework.key import ECKey
|
Merge bitcoin/bitcoin#21800: mempool/validation: mempool ancestor/descendant limits for packages
accf3d5868460b4b14ab607fd66ac985b086fbb3 [test] mempool package ancestor/descendant limits (glozow)
2b6b26e57c24d2f0abd442c1c33098e3121572ce [test] parameterizable fee for make_chain and create_child_with_parents (glozow)
313c09f7b7beddfdb74c284720d209c81dfdb94f [test] helper function to increase transaction weight (glozow)
f8253d69d6f02850995a11eeb71fedc22e6f6575 extract/rename helper functions from rpc_packages.py (glozow)
3cd663a5d33aa7ef87994e452bced7f192d021a0 [policy] ancestor/descendant limits for packages (glozow)
c6e016aa139c8363e9b38bbc1ba0dca55700b8a7 [mempool] check ancestor/descendant limits for packages (glozow)
f551841d3ec080a2d7a7988c7b35088dff6c5830 [refactor] pass size/count instead of entry to CalculateAncestorsAndCheckLimits (glozow)
97dd1c729d2bbedf9527b914c0cc8267b8a7c21b MOVEONLY: add helper function for calculating ancestors and checking limits (glozow)
f95bbf58aaf72aab8a9c5827b1f162f3b8ac38f4 misc package validation doc improvements (glozow)
Pull request description:
This PR implements a function to calculate mempool ancestors for a package and enforces ancestor/descendant limits on them as a whole. It reuses a portion of `CalculateMemPoolAncestors()`; there's also a small refactor to move the reused code into a generic helper function. Instead of calculating ancestors and descendants on every single transaction in the package and their ancestors, we use a "worst case" heuristic, treating every transaction in the package as each other's ancestor and descendant. This may overestimate everyone's counts, but is still pretty accurate in the our main package use cases, in which at least one of the transactions in the package is directly related to all the others (e.g. 1 parent + 1 child, multiple parents with 1 child, or chains).
Note on Terminology: While "package" is often used to describe groups of related transactions _within_ the mempool, here, I only use package to mean the group of not-in-mempool transactions we are currently validating.
#### Motivation
It would be a potential DoS vector to allow submission of packages to mempool without a proper guard for mempool ancestors/descendants. In general, the purpose of mempool ancestor/descendant limits is to limit the computational complexity of dealing with families during removals and additions. We want to be able to validate multiple transactions on top of the mempool, but also avoid these scenarios:
- We underestimate the ancestors/descendants during package validation and end up with extremely complex families in our mempool (potentially a DoS vector).
- We expend an unreasonable amount of resources calculating everyone's ancestors and descendants during package validation.
ACKs for top commit:
JeremyRubin:
utACK accf3d5
ariard:
ACK accf3d5.
Tree-SHA512: 0d18ce4b77398fe872e0b7c2cc66d3aac2135e561b64029584339e1f4de2a6a16ebab3dd5784f376e119cbafc4d50168b28d3bd95d0b3d01158714ade2e3624d
Signed-off-by: Vijay <vijaydas.mp@gmail.com>
2021-08-09 05:53:10 +02:00
|
|
|
from random import choice
|
2021-06-07 07:05:05 +02:00
|
|
|
from typing import Optional
|
2020-09-09 09:06:16 +02:00
|
|
|
from test_framework.messages import (
|
|
|
|
COIN,
|
|
|
|
COutPoint,
|
|
|
|
CTransaction,
|
|
|
|
CTxIn,
|
|
|
|
CTxOut,
|
Merge bitcoin/bitcoin#21800: mempool/validation: mempool ancestor/descendant limits for packages
accf3d5868460b4b14ab607fd66ac985b086fbb3 [test] mempool package ancestor/descendant limits (glozow)
2b6b26e57c24d2f0abd442c1c33098e3121572ce [test] parameterizable fee for make_chain and create_child_with_parents (glozow)
313c09f7b7beddfdb74c284720d209c81dfdb94f [test] helper function to increase transaction weight (glozow)
f8253d69d6f02850995a11eeb71fedc22e6f6575 extract/rename helper functions from rpc_packages.py (glozow)
3cd663a5d33aa7ef87994e452bced7f192d021a0 [policy] ancestor/descendant limits for packages (glozow)
c6e016aa139c8363e9b38bbc1ba0dca55700b8a7 [mempool] check ancestor/descendant limits for packages (glozow)
f551841d3ec080a2d7a7988c7b35088dff6c5830 [refactor] pass size/count instead of entry to CalculateAncestorsAndCheckLimits (glozow)
97dd1c729d2bbedf9527b914c0cc8267b8a7c21b MOVEONLY: add helper function for calculating ancestors and checking limits (glozow)
f95bbf58aaf72aab8a9c5827b1f162f3b8ac38f4 misc package validation doc improvements (glozow)
Pull request description:
This PR implements a function to calculate mempool ancestors for a package and enforces ancestor/descendant limits on them as a whole. It reuses a portion of `CalculateMemPoolAncestors()`; there's also a small refactor to move the reused code into a generic helper function. Instead of calculating ancestors and descendants on every single transaction in the package and their ancestors, we use a "worst case" heuristic, treating every transaction in the package as each other's ancestor and descendant. This may overestimate everyone's counts, but is still pretty accurate in the our main package use cases, in which at least one of the transactions in the package is directly related to all the others (e.g. 1 parent + 1 child, multiple parents with 1 child, or chains).
Note on Terminology: While "package" is often used to describe groups of related transactions _within_ the mempool, here, I only use package to mean the group of not-in-mempool transactions we are currently validating.
#### Motivation
It would be a potential DoS vector to allow submission of packages to mempool without a proper guard for mempool ancestors/descendants. In general, the purpose of mempool ancestor/descendant limits is to limit the computational complexity of dealing with families during removals and additions. We want to be able to validate multiple transactions on top of the mempool, but also avoid these scenarios:
- We underestimate the ancestors/descendants during package validation and end up with extremely complex families in our mempool (potentially a DoS vector).
- We expend an unreasonable amount of resources calculating everyone's ancestors and descendants during package validation.
ACKs for top commit:
JeremyRubin:
utACK accf3d5
ariard:
ACK accf3d5.
Tree-SHA512: 0d18ce4b77398fe872e0b7c2cc66d3aac2135e561b64029584339e1f4de2a6a16ebab3dd5784f376e119cbafc4d50168b28d3bd95d0b3d01158714ade2e3624d
Signed-off-by: Vijay <vijaydas.mp@gmail.com>
2021-08-09 05:53:10 +02:00
|
|
|
tx_from_hex,
|
2020-09-09 09:06:16 +02:00
|
|
|
)
|
|
|
|
from test_framework.script import (
|
|
|
|
CScript,
|
2021-05-24 08:29:05 +02:00
|
|
|
SignatureHash,
|
|
|
|
OP_CHECKSIG,
|
2020-09-09 09:06:16 +02:00
|
|
|
OP_TRUE,
|
2024-07-23 19:39:49 +02:00
|
|
|
OP_NOP,
|
2021-05-24 08:29:05 +02:00
|
|
|
SIGHASH_ALL,
|
2020-09-09 09:06:16 +02:00
|
|
|
)
|
|
|
|
from test_framework.util import (
|
|
|
|
assert_equal,
|
Merge bitcoin/bitcoin#21800: mempool/validation: mempool ancestor/descendant limits for packages
accf3d5868460b4b14ab607fd66ac985b086fbb3 [test] mempool package ancestor/descendant limits (glozow)
2b6b26e57c24d2f0abd442c1c33098e3121572ce [test] parameterizable fee for make_chain and create_child_with_parents (glozow)
313c09f7b7beddfdb74c284720d209c81dfdb94f [test] helper function to increase transaction weight (glozow)
f8253d69d6f02850995a11eeb71fedc22e6f6575 extract/rename helper functions from rpc_packages.py (glozow)
3cd663a5d33aa7ef87994e452bced7f192d021a0 [policy] ancestor/descendant limits for packages (glozow)
c6e016aa139c8363e9b38bbc1ba0dca55700b8a7 [mempool] check ancestor/descendant limits for packages (glozow)
f551841d3ec080a2d7a7988c7b35088dff6c5830 [refactor] pass size/count instead of entry to CalculateAncestorsAndCheckLimits (glozow)
97dd1c729d2bbedf9527b914c0cc8267b8a7c21b MOVEONLY: add helper function for calculating ancestors and checking limits (glozow)
f95bbf58aaf72aab8a9c5827b1f162f3b8ac38f4 misc package validation doc improvements (glozow)
Pull request description:
This PR implements a function to calculate mempool ancestors for a package and enforces ancestor/descendant limits on them as a whole. It reuses a portion of `CalculateMemPoolAncestors()`; there's also a small refactor to move the reused code into a generic helper function. Instead of calculating ancestors and descendants on every single transaction in the package and their ancestors, we use a "worst case" heuristic, treating every transaction in the package as each other's ancestor and descendant. This may overestimate everyone's counts, but is still pretty accurate in the our main package use cases, in which at least one of the transactions in the package is directly related to all the others (e.g. 1 parent + 1 child, multiple parents with 1 child, or chains).
Note on Terminology: While "package" is often used to describe groups of related transactions _within_ the mempool, here, I only use package to mean the group of not-in-mempool transactions we are currently validating.
#### Motivation
It would be a potential DoS vector to allow submission of packages to mempool without a proper guard for mempool ancestors/descendants. In general, the purpose of mempool ancestor/descendant limits is to limit the computational complexity of dealing with families during removals and additions. We want to be able to validate multiple transactions on top of the mempool, but also avoid these scenarios:
- We underestimate the ancestors/descendants during package validation and end up with extremely complex families in our mempool (potentially a DoS vector).
- We expend an unreasonable amount of resources calculating everyone's ancestors and descendants during package validation.
ACKs for top commit:
JeremyRubin:
utACK accf3d5
ariard:
ACK accf3d5.
Tree-SHA512: 0d18ce4b77398fe872e0b7c2cc66d3aac2135e561b64029584339e1f4de2a6a16ebab3dd5784f376e119cbafc4d50168b28d3bd95d0b3d01158714ade2e3624d
Signed-off-by: Vijay <vijaydas.mp@gmail.com>
2021-08-09 05:53:10 +02:00
|
|
|
assert_greater_than_or_equal,
|
2020-09-09 09:06:16 +02:00
|
|
|
satoshi_round,
|
|
|
|
)
|
|
|
|
|
Merge bitcoin/bitcoin#21800: mempool/validation: mempool ancestor/descendant limits for packages
accf3d5868460b4b14ab607fd66ac985b086fbb3 [test] mempool package ancestor/descendant limits (glozow)
2b6b26e57c24d2f0abd442c1c33098e3121572ce [test] parameterizable fee for make_chain and create_child_with_parents (glozow)
313c09f7b7beddfdb74c284720d209c81dfdb94f [test] helper function to increase transaction weight (glozow)
f8253d69d6f02850995a11eeb71fedc22e6f6575 extract/rename helper functions from rpc_packages.py (glozow)
3cd663a5d33aa7ef87994e452bced7f192d021a0 [policy] ancestor/descendant limits for packages (glozow)
c6e016aa139c8363e9b38bbc1ba0dca55700b8a7 [mempool] check ancestor/descendant limits for packages (glozow)
f551841d3ec080a2d7a7988c7b35088dff6c5830 [refactor] pass size/count instead of entry to CalculateAncestorsAndCheckLimits (glozow)
97dd1c729d2bbedf9527b914c0cc8267b8a7c21b MOVEONLY: add helper function for calculating ancestors and checking limits (glozow)
f95bbf58aaf72aab8a9c5827b1f162f3b8ac38f4 misc package validation doc improvements (glozow)
Pull request description:
This PR implements a function to calculate mempool ancestors for a package and enforces ancestor/descendant limits on them as a whole. It reuses a portion of `CalculateMemPoolAncestors()`; there's also a small refactor to move the reused code into a generic helper function. Instead of calculating ancestors and descendants on every single transaction in the package and their ancestors, we use a "worst case" heuristic, treating every transaction in the package as each other's ancestor and descendant. This may overestimate everyone's counts, but is still pretty accurate in the our main package use cases, in which at least one of the transactions in the package is directly related to all the others (e.g. 1 parent + 1 child, multiple parents with 1 child, or chains).
Note on Terminology: While "package" is often used to describe groups of related transactions _within_ the mempool, here, I only use package to mean the group of not-in-mempool transactions we are currently validating.
#### Motivation
It would be a potential DoS vector to allow submission of packages to mempool without a proper guard for mempool ancestors/descendants. In general, the purpose of mempool ancestor/descendant limits is to limit the computational complexity of dealing with families during removals and additions. We want to be able to validate multiple transactions on top of the mempool, but also avoid these scenarios:
- We underestimate the ancestors/descendants during package validation and end up with extremely complex families in our mempool (potentially a DoS vector).
- We expend an unreasonable amount of resources calculating everyone's ancestors and descendants during package validation.
ACKs for top commit:
JeremyRubin:
utACK accf3d5
ariard:
ACK accf3d5.
Tree-SHA512: 0d18ce4b77398fe872e0b7c2cc66d3aac2135e561b64029584339e1f4de2a6a16ebab3dd5784f376e119cbafc4d50168b28d3bd95d0b3d01158714ade2e3624d
Signed-off-by: Vijay <vijaydas.mp@gmail.com>
2021-08-09 05:53:10 +02:00
|
|
|
DEFAULT_FEE = Decimal("0.0001")
|
2020-09-09 09:06:16 +02:00
|
|
|
|
2021-05-25 07:26:18 +02:00
|
|
|
class MiniWalletMode(Enum):
|
|
|
|
"""Determines the transaction type the MiniWallet is creating and spending.
|
|
|
|
|
|
|
|
For most purposes, the default mode ADDRESS_OP_TRUE should be sufficient;
|
|
|
|
it simply uses a fixed P2SH address whose coins are spent with a
|
|
|
|
witness stack of OP_TRUE, i.e. following an anyone-can-spend policy.
|
|
|
|
However, if the transactions need to be modified by the user (e.g. prepending
|
|
|
|
scriptSig for testing opcodes that are activated by a soft-fork), or the txs
|
|
|
|
should contain an actual signature, the raw modes RAW_OP_TRUE and RAW_P2PK
|
|
|
|
can be useful. Summary of modes:
|
|
|
|
|
|
|
|
| output | | tx is | can modify | needs
|
|
|
|
mode | description | address | standard | scriptSig | signing
|
|
|
|
----------------+-------------------+-----------+----------+------------+----------
|
|
|
|
ADDRESS_OP_TRUE | anyone-can-spend | bech32 | yes | no | no
|
|
|
|
RAW_OP_TRUE | anyone-can-spend | - (raw) | no | yes | no
|
|
|
|
RAW_P2PK | pay-to-public-key | - (raw) | yes | yes | yes
|
|
|
|
"""
|
|
|
|
ADDRESS_OP_TRUE = 1
|
|
|
|
RAW_OP_TRUE = 2
|
|
|
|
RAW_P2PK = 3
|
|
|
|
|
|
|
|
|
2020-09-09 09:06:16 +02:00
|
|
|
class MiniWallet:
|
2021-05-25 07:26:18 +02:00
|
|
|
def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE):
|
2020-09-09 09:06:16 +02:00
|
|
|
self._test_node = test_node
|
|
|
|
self._utxos = []
|
2021-05-24 08:29:05 +02:00
|
|
|
self._priv_key = None
|
|
|
|
self._address = None
|
|
|
|
|
2021-05-25 07:26:18 +02:00
|
|
|
assert isinstance(mode, MiniWalletMode)
|
|
|
|
if mode == MiniWalletMode.RAW_OP_TRUE:
|
2024-07-23 19:39:49 +02:00
|
|
|
self._scriptPubKey = bytes(CScript([OP_TRUE]))
|
2021-05-25 07:26:18 +02:00
|
|
|
elif mode == MiniWalletMode.RAW_P2PK:
|
2021-05-24 08:29:05 +02:00
|
|
|
# use simple deterministic private key (k=1)
|
|
|
|
self._priv_key = ECKey()
|
|
|
|
self._priv_key.set((1).to_bytes(32, 'big'), True)
|
|
|
|
pub_key = self._priv_key.get_pubkey()
|
|
|
|
self._scriptPubKey = bytes(CScript([pub_key.get_bytes(), OP_CHECKSIG]))
|
2021-05-25 07:26:18 +02:00
|
|
|
elif mode == MiniWalletMode.ADDRESS_OP_TRUE:
|
2024-07-23 19:39:49 +02:00
|
|
|
self._address = ADDRESS_BCRT1_P2SH_OP_TRUE
|
2021-07-31 21:23:16 +02:00
|
|
|
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
|
2020-09-09 09:06:16 +02:00
|
|
|
|
2021-02-25 09:48:28 +01:00
|
|
|
def scan_blocks(self, *, start=1, num):
|
|
|
|
"""Scan the blocks for self._address outputs and add them to self._utxos"""
|
|
|
|
for i in range(start, start + num):
|
|
|
|
block = self._test_node.getblock(blockhash=self._test_node.getblockhash(i), verbosity=2)
|
|
|
|
for tx in block['tx']:
|
2024-07-23 19:39:21 +02:00
|
|
|
self.scan_tx(tx)
|
|
|
|
|
|
|
|
def scan_tx(self, tx):
|
|
|
|
"""Scan the tx for self._scriptPubKey outputs and add them to self._utxos"""
|
|
|
|
for out in tx['vout']:
|
|
|
|
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
|
|
|
|
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']})
|
2021-02-25 09:48:28 +01:00
|
|
|
|
2021-06-21 16:11:09 +02:00
|
|
|
def sign_tx(self, tx, fixed_length=True):
|
2021-05-24 08:29:05 +02:00
|
|
|
"""Sign tx that has been created by MiniWallet in P2PK mode"""
|
|
|
|
assert self._priv_key is not None
|
|
|
|
(sighash, err) = SignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
|
|
|
|
assert err is None
|
2021-06-21 16:11:09 +02:00
|
|
|
# for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):
|
|
|
|
# 65 bytes: high-R val (33 bytes) + low-S val (32 bytes)
|
|
|
|
# with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes
|
|
|
|
der_sig = b''
|
|
|
|
while not len(der_sig) == 71:
|
|
|
|
der_sig = self._priv_key.sign_ecdsa(sighash)
|
|
|
|
if not fixed_length:
|
|
|
|
break
|
|
|
|
tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))])
|
2021-05-24 08:29:05 +02:00
|
|
|
|
2020-09-09 09:06:16 +02:00
|
|
|
def generate(self, num_blocks):
|
|
|
|
"""Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
|
2024-07-23 19:39:49 +02:00
|
|
|
blocks = self._test_node.generatetodescriptor(num_blocks, f'raw({self._scriptPubKey.hex()})')
|
2020-09-09 09:06:16 +02:00
|
|
|
for b in blocks:
|
|
|
|
cb_tx = self._test_node.getblock(blockhash=b, verbosity=2)['tx'][0]
|
|
|
|
self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value']})
|
|
|
|
return blocks
|
|
|
|
|
2021-03-26 08:52:51 +01:00
|
|
|
def get_address(self):
|
|
|
|
return self._address
|
|
|
|
|
Merge bitcoin/bitcoin#21900: test: use MiniWallet for feature_csv_activation.py
bd7f27d16dacf6f7de3b4f6bd052def41d9601be refactor: feature_csv_activation.py: move tx helper functions to methods (Sebastian Falbesoner)
2eca46b0aa0ecf4738500b53523d7013985b387d test: use MiniWallet for feature_csv_activation.py (Sebastian Falbesoner)
Pull request description:
This PR enables one more of the non-wallet functional tests (feature_csv_activation.py) to be run even with the Bitcoin Core wallet disabled by using the new MiniWallet instead, as proposed in #20078.
Short reviewers guideline:
- Since we exclusively work with anyone-can-spend outputs here (raw scriptPubKey = OP_TRUE), signing is not needed anymore. The function `sign_transaction` and its calls are removed, after changing a tx (e.g. its scriptSig or nVersion) a simple `.rehash()` call is sufficient. Also, generating an address `self.nodeaddress` (and with that, passing it to the the various test tx creation/sending helper methods) is not needed anymore and removed.
- The test repeatedly uses the same input for creating different txs (e.g. with different txversions 1 and 2). To let `MiniWallet` create a tx with a specific input, we have to call `.get_utxo()` before which also marks the UTXO as spent. The method is changed to also support keeping the UTXO in its internal list (`mark_as_spent=False`). With the behaviour on master, the second call to `.get_utxo()` with the same input would fail.
- To keep the diff in the first commit short, the `miniwallet` is set as a global variable, to avoid passing it on every tx creation/spending helper. The global is eliminated in the second (refactoring) commit, where all the helpers are moved to the test class as methods. By that, we can use `self.nodes[0]` directly in the helpers and don't have to pass it again and again. I think there could still be a lot of improvements/refactoring done in the test, but that should hopefully serve as a good basis.
ACKs for top commit:
laanwj:
Code review ACK bd7f27d16dacf6f7de3b4f6bd052def41d9601be
MarcoFalke:
review ACK bd7f27d16dacf6f7de3b4f6bd052def41d9601be 🐕
Tree-SHA512: 24fb6a0f7702bae40d5271d197119827067d4b597e954d182e4c1aa5d0fa870368eb3ffed469b26713fa8ff8eb3ecc06abc80b2449cd68156d5559e7ae8a2b11
2021-05-10 17:50:15 +02:00
|
|
|
def get_utxo(self, *, txid: Optional[str]='', mark_as_spent=True):
|
2020-11-19 16:39:56 +01:00
|
|
|
"""
|
|
|
|
Returns a utxo and marks it as spent (pops it from the internal list)
|
|
|
|
|
|
|
|
Args:
|
2021-06-07 07:05:05 +02:00
|
|
|
txid: get the first utxo we find from a specific transaction
|
2020-11-19 16:39:56 +01:00
|
|
|
|
|
|
|
Note: Can be used to get the change output immediately after a send_self_transfer
|
|
|
|
"""
|
|
|
|
index = -1 # by default the last utxo
|
|
|
|
if txid:
|
|
|
|
utxo = next(filter(lambda utxo: txid == utxo['txid'], self._utxos))
|
|
|
|
index = self._utxos.index(utxo)
|
Merge bitcoin/bitcoin#21900: test: use MiniWallet for feature_csv_activation.py
bd7f27d16dacf6f7de3b4f6bd052def41d9601be refactor: feature_csv_activation.py: move tx helper functions to methods (Sebastian Falbesoner)
2eca46b0aa0ecf4738500b53523d7013985b387d test: use MiniWallet for feature_csv_activation.py (Sebastian Falbesoner)
Pull request description:
This PR enables one more of the non-wallet functional tests (feature_csv_activation.py) to be run even with the Bitcoin Core wallet disabled by using the new MiniWallet instead, as proposed in #20078.
Short reviewers guideline:
- Since we exclusively work with anyone-can-spend outputs here (raw scriptPubKey = OP_TRUE), signing is not needed anymore. The function `sign_transaction` and its calls are removed, after changing a tx (e.g. its scriptSig or nVersion) a simple `.rehash()` call is sufficient. Also, generating an address `self.nodeaddress` (and with that, passing it to the the various test tx creation/sending helper methods) is not needed anymore and removed.
- The test repeatedly uses the same input for creating different txs (e.g. with different txversions 1 and 2). To let `MiniWallet` create a tx with a specific input, we have to call `.get_utxo()` before which also marks the UTXO as spent. The method is changed to also support keeping the UTXO in its internal list (`mark_as_spent=False`). With the behaviour on master, the second call to `.get_utxo()` with the same input would fail.
- To keep the diff in the first commit short, the `miniwallet` is set as a global variable, to avoid passing it on every tx creation/spending helper. The global is eliminated in the second (refactoring) commit, where all the helpers are moved to the test class as methods. By that, we can use `self.nodes[0]` directly in the helpers and don't have to pass it again and again. I think there could still be a lot of improvements/refactoring done in the test, but that should hopefully serve as a good basis.
ACKs for top commit:
laanwj:
Code review ACK bd7f27d16dacf6f7de3b4f6bd052def41d9601be
MarcoFalke:
review ACK bd7f27d16dacf6f7de3b4f6bd052def41d9601be 🐕
Tree-SHA512: 24fb6a0f7702bae40d5271d197119827067d4b597e954d182e4c1aa5d0fa870368eb3ffed469b26713fa8ff8eb3ecc06abc80b2449cd68156d5559e7ae8a2b11
2021-05-10 17:50:15 +02:00
|
|
|
if mark_as_spent:
|
|
|
|
return self._utxos.pop(index)
|
|
|
|
else:
|
|
|
|
return self._utxos[index]
|
2020-09-11 16:16:10 +02:00
|
|
|
|
2021-06-19 08:47:38 +02:00
|
|
|
def send_self_transfer(self, **kwargs):
|
2020-09-09 09:06:16 +02:00
|
|
|
"""Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
|
2021-06-19 08:47:38 +02:00
|
|
|
tx = self.create_self_transfer(**kwargs)
|
|
|
|
self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
|
2024-07-23 19:39:21 +02:00
|
|
|
return tx
|
|
|
|
|
2021-06-19 08:47:38 +02:00
|
|
|
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
|
2024-07-23 19:39:21 +02:00
|
|
|
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
|
2020-09-11 16:16:10 +02:00
|
|
|
self._utxos = sorted(self._utxos, key=lambda k: k['value'])
|
|
|
|
utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee
|
2021-06-21 16:11:09 +02:00
|
|
|
if self._priv_key is None:
|
|
|
|
vsize = Decimal(85) # anyone-can-spend
|
|
|
|
else:
|
|
|
|
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
|
2020-09-11 16:16:10 +02:00
|
|
|
send_value = satoshi_round(utxo_to_spend['value'] - fee_rate * (vsize / 1000))
|
|
|
|
fee = utxo_to_spend['value'] - send_value
|
|
|
|
assert send_value > 0
|
2020-09-09 09:06:16 +02:00
|
|
|
|
|
|
|
tx = CTransaction()
|
2021-06-19 08:47:38 +02:00
|
|
|
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
|
2020-09-09 09:06:16 +02:00
|
|
|
tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)]
|
2021-06-01 15:04:55 +02:00
|
|
|
tx.nLockTime = locktime
|
2024-07-23 19:39:49 +02:00
|
|
|
if not self._address:
|
|
|
|
# raw script
|
2021-05-24 08:29:05 +02:00
|
|
|
if self._priv_key is not None:
|
|
|
|
# P2PK, need to sign
|
|
|
|
self.sign_tx(tx)
|
|
|
|
else:
|
|
|
|
# anyone-can-spend
|
|
|
|
tx.vin[0].scriptSig = CScript([OP_NOP] * 24) # pad to identical size
|
2024-07-23 19:39:49 +02:00
|
|
|
else:
|
|
|
|
tx.vin[0].scriptSig = CScript([CScript([OP_TRUE])])
|
2020-09-09 09:06:16 +02:00
|
|
|
tx_hex = tx.serialize().hex()
|
|
|
|
|
2021-01-11 08:57:23 +01:00
|
|
|
tx_info = from_node.testmempoolaccept([tx_hex])[0]
|
2024-07-23 19:39:21 +02:00
|
|
|
assert_equal(mempool_valid, tx_info['allowed'])
|
|
|
|
if mempool_valid:
|
2021-06-21 16:11:09 +02:00
|
|
|
assert_equal(len(tx_hex) // 2, vsize) # 1 byte = 2 character
|
2024-07-23 19:39:21 +02:00
|
|
|
assert_equal(tx_info['fees']['base'], fee)
|
2024-07-23 19:39:49 +02:00
|
|
|
return {'txid': tx_info['txid'], 'hex': tx_hex, 'tx': tx}
|
2024-07-23 19:39:21 +02:00
|
|
|
|
|
|
|
def sendrawtransaction(self, *, from_node, tx_hex):
|
|
|
|
from_node.sendrawtransaction(tx_hex)
|
|
|
|
self.scan_tx(from_node.decoderawtransaction(tx_hex))
|
Merge bitcoin/bitcoin#21800: mempool/validation: mempool ancestor/descendant limits for packages
accf3d5868460b4b14ab607fd66ac985b086fbb3 [test] mempool package ancestor/descendant limits (glozow)
2b6b26e57c24d2f0abd442c1c33098e3121572ce [test] parameterizable fee for make_chain and create_child_with_parents (glozow)
313c09f7b7beddfdb74c284720d209c81dfdb94f [test] helper function to increase transaction weight (glozow)
f8253d69d6f02850995a11eeb71fedc22e6f6575 extract/rename helper functions from rpc_packages.py (glozow)
3cd663a5d33aa7ef87994e452bced7f192d021a0 [policy] ancestor/descendant limits for packages (glozow)
c6e016aa139c8363e9b38bbc1ba0dca55700b8a7 [mempool] check ancestor/descendant limits for packages (glozow)
f551841d3ec080a2d7a7988c7b35088dff6c5830 [refactor] pass size/count instead of entry to CalculateAncestorsAndCheckLimits (glozow)
97dd1c729d2bbedf9527b914c0cc8267b8a7c21b MOVEONLY: add helper function for calculating ancestors and checking limits (glozow)
f95bbf58aaf72aab8a9c5827b1f162f3b8ac38f4 misc package validation doc improvements (glozow)
Pull request description:
This PR implements a function to calculate mempool ancestors for a package and enforces ancestor/descendant limits on them as a whole. It reuses a portion of `CalculateMemPoolAncestors()`; there's also a small refactor to move the reused code into a generic helper function. Instead of calculating ancestors and descendants on every single transaction in the package and their ancestors, we use a "worst case" heuristic, treating every transaction in the package as each other's ancestor and descendant. This may overestimate everyone's counts, but is still pretty accurate in the our main package use cases, in which at least one of the transactions in the package is directly related to all the others (e.g. 1 parent + 1 child, multiple parents with 1 child, or chains).
Note on Terminology: While "package" is often used to describe groups of related transactions _within_ the mempool, here, I only use package to mean the group of not-in-mempool transactions we are currently validating.
#### Motivation
It would be a potential DoS vector to allow submission of packages to mempool without a proper guard for mempool ancestors/descendants. In general, the purpose of mempool ancestor/descendant limits is to limit the computational complexity of dealing with families during removals and additions. We want to be able to validate multiple transactions on top of the mempool, but also avoid these scenarios:
- We underestimate the ancestors/descendants during package validation and end up with extremely complex families in our mempool (potentially a DoS vector).
- We expend an unreasonable amount of resources calculating everyone's ancestors and descendants during package validation.
ACKs for top commit:
JeremyRubin:
utACK accf3d5
ariard:
ACK accf3d5.
Tree-SHA512: 0d18ce4b77398fe872e0b7c2cc66d3aac2135e561b64029584339e1f4de2a6a16ebab3dd5784f376e119cbafc4d50168b28d3bd95d0b3d01158714ade2e3624d
Signed-off-by: Vijay <vijaydas.mp@gmail.com>
2021-08-09 05:53:10 +02:00
|
|
|
|
|
|
|
def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):
|
|
|
|
"""Build a transaction that spends parent_txid.vout[n] and produces one output with
|
|
|
|
amount = parent_value with a fee deducted.
|
|
|
|
Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created).
|
|
|
|
"""
|
|
|
|
inputs = [{"txid": parent_txid, "vout": n}]
|
|
|
|
my_value = parent_value - fee
|
|
|
|
outputs = {address : my_value}
|
|
|
|
rawtx = node.createrawtransaction(inputs, outputs)
|
|
|
|
prevtxs = [{
|
|
|
|
"txid": parent_txid,
|
|
|
|
"vout": n,
|
|
|
|
"scriptPubKey": parent_locking_script,
|
|
|
|
"amount": parent_value,
|
|
|
|
}] if parent_locking_script else None
|
|
|
|
signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=privkeys, prevtxs=prevtxs)
|
|
|
|
assert signedtx["complete"]
|
|
|
|
tx = tx_from_hex(signedtx["hex"])
|
|
|
|
return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex())
|
|
|
|
|
|
|
|
def create_child_with_parents(node, address, privkeys, parents_tx, values, locking_scripts, fee=DEFAULT_FEE):
|
|
|
|
"""Creates a transaction that spends the first output of each parent in parents_tx."""
|
|
|
|
num_parents = len(parents_tx)
|
|
|
|
total_value = sum(values)
|
|
|
|
inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx]
|
|
|
|
outputs = {address : total_value - fee}
|
|
|
|
rawtx_child = node.createrawtransaction(inputs, outputs)
|
|
|
|
prevtxs = []
|
|
|
|
for i in range(num_parents):
|
|
|
|
prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]})
|
|
|
|
signedtx_child = node.signrawtransactionwithkey(hexstring=rawtx_child, privkeys=privkeys, prevtxs=prevtxs)
|
|
|
|
assert signedtx_child["complete"]
|
|
|
|
return signedtx_child["hex"]
|
|
|
|
|
|
|
|
def create_raw_chain(node, first_coin, address, privkeys, chain_length=25):
|
|
|
|
"""Helper function: create a "chain" of chain_length transactions. The nth transaction in the
|
|
|
|
chain is a child of the n-1th transaction and parent of the n+1th transaction.
|
|
|
|
"""
|
|
|
|
parent_locking_script = None
|
|
|
|
txid = first_coin["txid"]
|
|
|
|
chain_hex = []
|
|
|
|
chain_txns = []
|
|
|
|
value = first_coin["amount"]
|
|
|
|
|
|
|
|
for _ in range(chain_length):
|
|
|
|
(tx, txhex, value, parent_locking_script) = make_chain(node, address, privkeys, txid, value, 0, parent_locking_script)
|
|
|
|
txid = tx.rehash()
|
|
|
|
chain_hex.append(txhex)
|
|
|
|
chain_txns.append(tx)
|
|
|
|
|
|
|
|
return (chain_hex, chain_txns)
|
|
|
|
|
|
|
|
def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None):
|
|
|
|
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
|
|
|
|
returns CTransaction object
|
|
|
|
"""
|
|
|
|
tx_heavy = deepcopy(tx)
|
|
|
|
assert_greater_than_or_equal(target_weight, tx_heavy.get_weight())
|
|
|
|
while tx_heavy.get_weight() < target_weight:
|
|
|
|
random_spk = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
|
|
|
|
for _ in range(512*2):
|
|
|
|
random_spk += choice("0123456789ABCDEF")
|
|
|
|
tx_heavy.vout.append(CTxOut(0, bytes.fromhex(random_spk)))
|
|
|
|
# Re-sign the transaction
|
|
|
|
if privkeys:
|
|
|
|
signed = node.signrawtransactionwithkey(tx_heavy.serialize().hex(), privkeys, prevtxs)
|
|
|
|
return tx_from_hex(signed["hex"])
|
|
|
|
# OP_TRUE
|
|
|
|
tx_heavy.vin[0].scriptSig = CScript([CScript([OP_TRUE])])
|
|
|
|
return tx_heavy
|