mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Merge bitcoin/bitcoin#23371: test: MiniWallet: add P2TR support and use it per default
041abfebe4
test: MiniWallet: add P2TR support and use it per default (Sebastian Falbesoner)4a2edf2bf7
test: generate blocks to MiniWallet address in rpc_blockchain.py (Sebastian Falbesoner) Pull request description: Taproot activates in [about 19 days](https://taproot.watch/) (2716 blocks), and it'd be nice if we set a good example and also support it in our MiniWallet. This PR changes the default mode from P2WSH (segwit v0 output, bech32 address) to P2TR (segwit v1 output, bech32m address) transactions type with the _anyone-can-spend_ policy, i.e. a witness script of `OP_TRUE`. The transition is actually quite painless, one only needs one extra piece in the form of a internal public key that is passed in the control block on the witness stack, in order to trigger script-path spending. To keep things simple, the lowest possible valid x-only-public key with the value of 1 was chosen as internal key. Since many tests expect to find outputs for the default scriptPubKey of MiniWallet in the pre-mined chain of the test framework, the generation address is also changed from `ADDRESS_BCRT1_P2WSH_OP_TRUE` to `create_deterministic_address_bcrt1_p2tr_op_true()[0]` accordingly (see method `BitcoinTestFramework._initialize_chain(...)`). Note that the pre-mined chain is cached locally, so you probably have to delete the `./test/cache` folder first for the tests to pass again. In order to avoid unnecessary renames, the import of `ADDRESS_BCRT1_P2WSH_OP_TRUE` is eliminated in rpc_blockchain.py by generating blocks directly to the MiniWallet address by using the `self.generate(self.wallet, ...)` interface (see first commit). ACKs for top commit: laanwj: Code review ACK041abfebe4
Tree-SHA512: 876a5b0595333f9c96c68d5ecf2b4530aee2715aebb75a953f4f75ca12258bd7239210fcfa1ae044bee91489804c9c2f2a6a335bd46c3ac701873d32e3a4f49d
This commit is contained in:
commit
2539980e1d
7 changed files with 52 additions and 28 deletions
|
@ -47,8 +47,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
|||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
# the pre-mined test framework chain contains coinbase outputs to the
|
||||
# MiniWallet's default address ADDRESS_BCRT1_P2WSH_OP_TRUE in blocks
|
||||
# 76-100 (see method BitcoinTestFramework._initialize_chain())
|
||||
# MiniWallet's default address in blocks 76-100 (see method
|
||||
# BitcoinTestFramework._initialize_chain())
|
||||
self.wallet.rescan_utxos()
|
||||
|
||||
self.log.info("Running test simple doublespend...")
|
||||
|
|
|
@ -69,8 +69,8 @@ class UTXOSetHashTest(BitcoinTestFramework):
|
|||
assert_equal(finalized[::-1].hex(), node_muhash)
|
||||
|
||||
self.log.info("Test deterministic UTXO set hash results")
|
||||
assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "5b1b44097406226c0eb8e1362cd17a1f346522cf9390a8175a57a5262cb1963f")
|
||||
assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "4b8803075d7151d06fad3e88b68ba726886794873fbfa841d12aefb2cc2b881b")
|
||||
assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "221f245cf4c9010eeb7f5183d342c002ae6c1c27e98aa357dccb788c21d98049")
|
||||
assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "7c0890c68501f7630d36aeb3999dc924e63af084ae1bbfba11dd462144637635")
|
||||
|
||||
def run_test(self):
|
||||
self.test_muhash_implementation()
|
||||
|
|
|
@ -7,14 +7,17 @@
|
|||
NOTE: The test is designed to prevent cases when compatibility is broken accidentally.
|
||||
In case we need to break mempool compatibility we can continue to use the test by just bumping the version number.
|
||||
|
||||
The previous release v0.15.2 is required by this test, see test/README.md.
|
||||
The previous release v0.19.1 is required by this test, see test/README.md.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from test_framework.blocktools import COINBASE_MATURITY
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.wallet import MiniWallet
|
||||
from test_framework.wallet import (
|
||||
MiniWallet,
|
||||
MiniWalletMode,
|
||||
)
|
||||
|
||||
|
||||
class MempoolCompatibilityTest(BitcoinTestFramework):
|
||||
|
@ -37,7 +40,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
|
|||
self.log.info("Test that mempool.dat is compatible between versions")
|
||||
|
||||
old_node, new_node = self.nodes
|
||||
new_wallet = MiniWallet(new_node)
|
||||
new_wallet = MiniWallet(new_node, mode=MiniWalletMode.RAW_P2PK)
|
||||
self.generate(new_wallet, 1, sync_fun=self.no_op)
|
||||
self.generate(new_node, COINBASE_MATURITY, sync_fun=self.no_op)
|
||||
# Sync the nodes to ensure old_node has the block that contains the coinbase that new_wallet will spend.
|
||||
|
|
|
@ -25,7 +25,6 @@ import http.client
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
|
||||
from test_framework.blocktools import (
|
||||
create_block,
|
||||
create_coinbase,
|
||||
|
@ -64,6 +63,7 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
self.supports_cli = False
|
||||
|
||||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
self.mine_chain()
|
||||
self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) # Set extra args with pruning after rescan is complete
|
||||
|
||||
|
@ -82,7 +82,7 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
self.log.info(f"Generate {HEIGHT} blocks after the genesis block in ten-minute steps")
|
||||
for t in range(TIME_GENESIS_BLOCK, TIME_RANGE_END, TIME_RANGE_STEP):
|
||||
self.nodes[0].setmocktime(t)
|
||||
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_P2WSH_OP_TRUE)
|
||||
self.generate(self.wallet, 1)
|
||||
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], HEIGHT)
|
||||
|
||||
def _test_getblockchaininfo(self):
|
||||
|
@ -371,12 +371,12 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
def _test_stopatheight(self):
|
||||
self.log.info("Test stopping at height")
|
||||
assert_equal(self.nodes[0].getblockcount(), HEIGHT)
|
||||
self.generatetoaddress(self.nodes[0], 6, ADDRESS_BCRT1_P2WSH_OP_TRUE)
|
||||
self.generate(self.wallet, 6)
|
||||
assert_equal(self.nodes[0].getblockcount(), HEIGHT + 6)
|
||||
self.log.debug('Node should not stop at this height')
|
||||
assert_raises(subprocess.TimeoutExpired, lambda: self.nodes[0].process.wait(timeout=3))
|
||||
try:
|
||||
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_P2WSH_OP_TRUE, sync_fun=self.no_op)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.wallet.get_address(), sync_fun=self.no_op)
|
||||
except (ConnectionError, http.client.BadStatusLine):
|
||||
pass # The node already shut down before response
|
||||
self.log.debug('Node should stop at this height...')
|
||||
|
@ -424,14 +424,10 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
|
||||
def _test_getblock(self):
|
||||
node = self.nodes[0]
|
||||
|
||||
miniwallet = MiniWallet(node)
|
||||
miniwallet.rescan_utxos()
|
||||
|
||||
fee_per_byte = Decimal('0.00000010')
|
||||
fee_per_kb = 1000 * fee_per_byte
|
||||
|
||||
miniwallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
|
||||
self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
|
||||
blockhash = self.generate(node, 1)[0]
|
||||
|
||||
def assert_fee_not_in_block(verbosity):
|
||||
|
|
|
@ -5,12 +5,21 @@
|
|||
"""Encode and decode Bitcoin addresses.
|
||||
|
||||
- base58 P2PKH and P2SH addresses.
|
||||
- bech32 segwit v0 P2WPKH and P2WSH addresses."""
|
||||
- bech32 segwit v0 P2WPKH and P2WSH addresses.
|
||||
- bech32m segwit v1 P2TR addresses."""
|
||||
|
||||
import enum
|
||||
import unittest
|
||||
|
||||
from .script import hash256, hash160, sha256, CScript, OP_0
|
||||
from .script import (
|
||||
CScript,
|
||||
OP_0,
|
||||
OP_TRUE,
|
||||
hash160,
|
||||
hash256,
|
||||
sha256,
|
||||
taproot_construct,
|
||||
)
|
||||
from .segwit_addr import encode_segwit_address
|
||||
from .util import assert_equal
|
||||
|
||||
|
@ -29,6 +38,21 @@ class AddressType(enum.Enum):
|
|||
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
|
||||
def create_deterministic_address_bcrt1_p2tr_op_true():
|
||||
"""
|
||||
Generates a deterministic bech32m address (segwit v1 output) that
|
||||
can be spent with a witness stack of OP_TRUE and the control block
|
||||
with internal public key (script-path spending).
|
||||
|
||||
Returns a tuple with the generated address and the internal key.
|
||||
"""
|
||||
internal_key = (1).to_bytes(32, 'big')
|
||||
scriptPubKey = taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).scriptPubKey
|
||||
address = encode_segwit_address("bcrt", 1, scriptPubKey[2:])
|
||||
assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka')
|
||||
return (address, internal_key)
|
||||
|
||||
|
||||
def byte_to_base58(b, version):
|
||||
result = ''
|
||||
str = b.hex()
|
||||
|
|
|
@ -19,7 +19,7 @@ import tempfile
|
|||
import time
|
||||
|
||||
from typing import List
|
||||
from .address import ADDRESS_BCRT1_P2WSH_OP_TRUE
|
||||
from .address import create_deterministic_address_bcrt1_p2tr_op_true
|
||||
from .authproxy import JSONRPCException
|
||||
from . import coverage
|
||||
from .p2p import NetworkThread
|
||||
|
@ -777,7 +777,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
# block in the cache does not age too much (have an old tip age).
|
||||
# This is needed so that we are out of IBD when the test starts,
|
||||
# see the tip age check in IsInitialBlockDownload().
|
||||
gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [ADDRESS_BCRT1_P2WSH_OP_TRUE]
|
||||
gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [create_deterministic_address_bcrt1_p2tr_op_true()[0]]
|
||||
assert_equal(len(gen_addresses), 4)
|
||||
for i in range(8):
|
||||
self.generatetoaddress(
|
||||
|
|
|
@ -9,7 +9,7 @@ from decimal import Decimal
|
|||
from enum import Enum
|
||||
from random import choice
|
||||
from typing import Optional
|
||||
from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
|
||||
from test_framework.address import create_deterministic_address_bcrt1_p2tr_op_true
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.key import ECKey
|
||||
from test_framework.messages import (
|
||||
|
@ -24,8 +24,9 @@ from test_framework.messages import (
|
|||
from test_framework.script import (
|
||||
CScript,
|
||||
LegacySignatureHash,
|
||||
OP_TRUE,
|
||||
LEAF_VERSION_TAPSCRIPT,
|
||||
OP_NOP,
|
||||
OP_TRUE,
|
||||
SIGHASH_ALL,
|
||||
)
|
||||
from test_framework.script_util import (
|
||||
|
@ -43,7 +44,7 @@ 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 bech32 P2WSH address whose coins are spent with a
|
||||
it simply uses a fixed bech32m P2TR 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
|
||||
|
@ -53,7 +54,7 @@ class MiniWalletMode(Enum):
|
|||
| output | | tx is | can modify | needs
|
||||
mode | description | address | standard | scriptSig | signing
|
||||
----------------+-------------------+-----------+----------+------------+----------
|
||||
ADDRESS_OP_TRUE | anyone-can-spend | bech32 | yes | no | no
|
||||
ADDRESS_OP_TRUE | anyone-can-spend | bech32m | yes | no | no
|
||||
RAW_OP_TRUE | anyone-can-spend | - (raw) | no | yes | no
|
||||
RAW_P2PK | pay-to-public-key | - (raw) | yes | yes | yes
|
||||
"""
|
||||
|
@ -79,7 +80,7 @@ class MiniWallet:
|
|||
pub_key = self._priv_key.get_pubkey()
|
||||
self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes())
|
||||
elif mode == MiniWalletMode.ADDRESS_OP_TRUE:
|
||||
self._address = ADDRESS_BCRT1_P2WSH_OP_TRUE
|
||||
self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true()
|
||||
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
|
||||
|
||||
def rescan_utxos(self):
|
||||
|
@ -174,7 +175,7 @@ class MiniWallet:
|
|||
self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height']))
|
||||
utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee
|
||||
if self._priv_key is None:
|
||||
vsize = Decimal(96) # anyone-can-spend
|
||||
vsize = Decimal(104) # anyone-can-spend
|
||||
else:
|
||||
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
|
||||
send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000)))
|
||||
|
@ -191,10 +192,10 @@ class MiniWallet:
|
|||
self.sign_tx(tx)
|
||||
else:
|
||||
# anyone-can-spend
|
||||
tx.vin[0].scriptSig = CScript([OP_NOP] * 35) # pad to identical size
|
||||
tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size
|
||||
else:
|
||||
tx.wit.vtxinwit = [CTxInWitness()]
|
||||
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
|
||||
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
|
||||
tx_hex = tx.serialize().hex()
|
||||
|
||||
tx_info = from_node.testmempoolaccept([tx_hex])[0]
|
||||
|
|
Loading…
Add table
Reference in a new issue