Merge bitcoin/bitcoin#26657: test: Run feature_bip68_sequence.py with MiniWallet

4159ccd031 test: Run feature_bip68_sequence.py with MiniWallet (Miles Liu)
fc0caaf4aa test: Add "include mempool" flag to MiniWallet rescan_utxos (Miles Liu)
d0a909ae54 test: Add "include immature coinbase" flag to MiniWallet get_utxos (Miles Liu)
e5b9127d9e test: Add signs P2TR and RAWSCRIPT to MiniWallet sign_tx (Miles Liu)

Pull request description:

  This PR enables one more of the non-wallet functional tests (feature_bip68_sequence.py) to be run even when no wallet is compiled in by using the MiniWallet instead, as proposed in #20078.

ACKs for top commit:
  achow101:
    ACK 4159ccd031
  MarcoFalke:
    review ACK 4159ccd031 🤸

Tree-SHA512: e891b189381e961c840b45fc30d058363707fd54c1c4bdc3d29623b03309981f1d3ebfac27e6aecf621bdbcd7e31754a3ef7c53f86346f7dd241c137e64c92bd
This commit is contained in:
MarcoFalke
2023-01-16 16:26:02 +01:00
2 changed files with 93 additions and 92 deletions

View File

@@ -55,6 +55,7 @@ from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
)
from test_framework.blocktools import COINBASE_MATURITY
DEFAULT_FEE = Decimal("0.0001")
@@ -100,8 +101,8 @@ class MiniWallet:
self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true()
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
def _create_utxo(self, *, txid, vout, value, height):
return {"txid": txid, "vout": vout, "value": value, "height": height}
def _create_utxo(self, *, txid, vout, value, height, coinbase, confirmations):
return {"txid": txid, "vout": vout, "value": value, "height": height, "coinbase": coinbase, "confirmations": confirmations}
def _bulk_tx(self, tx, target_weight):
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
@@ -118,13 +119,25 @@ class MiniWallet:
def get_balance(self):
return sum(u['value'] for u in self._utxos)
def rescan_utxos(self):
def rescan_utxos(self, *, include_mempool=True):
"""Drop all utxos and rescan the utxo set"""
self._utxos = []
res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()])
assert_equal(True, res['success'])
for utxo in res['unspents']:
self._utxos.append(self._create_utxo(txid=utxo["txid"], vout=utxo["vout"], value=utxo["amount"], height=utxo["height"]))
self._utxos.append(
self._create_utxo(txid=utxo["txid"],
vout=utxo["vout"],
value=utxo["amount"],
height=utxo["height"],
coinbase=utxo["coinbase"],
confirmations=res["height"] - utxo["height"] + 1))
if include_mempool:
mempool = self._test_node.getrawmempool(verbose=True)
# Sort tx by ancestor count. See BlockAssembler::SortForBlock in src/node/miner.cpp
sorted_mempool = sorted(mempool.items(), key=lambda item: (item[1]["ancestorcount"], int(item[0], 16)))
for txid, _ in sorted_mempool:
self.scan_tx(self._test_node.getrawtransaction(txid=txid, verbose=True))
def scan_tx(self, tx):
"""Scan the tx and adjust the internal list of owned utxos"""
@@ -139,27 +152,35 @@ class MiniWallet:
pass
for out in tx['vout']:
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0))
self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0, coinbase=False, confirmations=0))
def scan_txs(self, txs):
for tx in txs:
self.scan_tx(tx)
def sign_tx(self, tx, fixed_length=True):
"""Sign tx that has been created by MiniWallet in P2PK mode"""
assert_equal(self._mode, MiniWalletMode.RAW_P2PK)
(sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
assert err is None
# 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]))])
tx.rehash()
if self._mode == MiniWalletMode.RAW_P2PK:
(sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
assert err is None
# 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]))])
tx.rehash()
elif self._mode == MiniWalletMode.RAW_OP_TRUE:
for i in range(len(tx.vin)):
tx.vin[i].scriptSig = CScript([OP_NOP] * 43) # pad to identical size
elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE:
tx.wit.vtxinwit = [CTxInWitness()] * len(tx.vin)
for i in range(len(tx.vin)):
tx.wit.vtxinwit[i].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
else:
assert False
def generate(self, num_blocks, **kwargs):
"""Generate blocks with coinbase outputs to the internal address, and call rescan_utxos"""
@@ -204,9 +225,13 @@ class MiniWallet:
else:
return self._utxos[index]
def get_utxos(self, *, mark_as_spent=True):
def get_utxos(self, *, include_immature_coinbase=False, mark_as_spent=True):
"""Returns the list of all utxos and optionally mark them as spent"""
utxos = deepcopy(self._utxos)
if not include_immature_coinbase:
utxo_filter = filter(lambda utxo: not utxo['coinbase'] or COINBASE_MATURITY <= utxo['confirmations'], self._utxos)
else:
utxo_filter = self._utxos
utxos = deepcopy(list(utxo_filter))
if mark_as_spent:
self._utxos = []
return utxos
@@ -273,17 +298,7 @@ class MiniWallet:
tx.vout = [CTxOut(amount_per_output, bytearray(self._scriptPubKey)) for _ in range(num_outputs)]
tx.nLockTime = locktime
if self._mode == MiniWalletMode.RAW_P2PK:
self.sign_tx(tx)
elif self._mode == MiniWalletMode.RAW_OP_TRUE:
for i in range(len(utxos_to_spend)):
tx.vin[i].scriptSig = CScript([OP_NOP] * 43) # pad to identical size
elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE:
tx.wit.vtxinwit = [CTxInWitness()] * len(utxos_to_spend)
for i in range(len(utxos_to_spend)):
tx.wit.vtxinwit[i].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
else:
assert False
self.sign_tx(tx)
if target_weight:
self._bulk_tx(tx, target_weight)
@@ -295,6 +310,8 @@ class MiniWallet:
vout=i,
value=Decimal(tx.vout[i].nValue) / COIN,
height=0,
coinbase=False,
confirmations=0,
) for i in range(len(tx.vout))],
"txid": txid,
"hex": tx.serialize().hex(),