Skip to content

Commit

Permalink
Merge pull request #121
Browse files Browse the repository at this point in the history
Feature: Test mini wallet add P2TR support and use it per default
  • Loading branch information
madnadyka authored Feb 6, 2023
2 parents 02ed5dd + 9ed0a4a commit c557170
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 31 deletions.
14 changes: 7 additions & 7 deletions test/functional/feature_coinstatsindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,17 @@ def _test_coin_stats_index(self):

# Test an older block height that included a normal tx
res5 = index_node.gettxoutsetinfo(hash_option, 102)
assert_equal(Decimal(res5['total_unspendable_amount']), Decimal('200.0002592'))
assert_equal(Decimal(res5['total_unspendable_amount']), Decimal('200.00028080'))
assert_equal(res5['block_info'], {
'unspendable': Decimal('0.00025920'),
'unspendable': Decimal('0.00028080'),
'prevout_spent': 200,
'new_outputs_ex_coinbase': Decimal('199.99971200'),
'coinbase': Decimal('200.00002880'),
'new_outputs_ex_coinbase': Decimal('199.99968800'),
'coinbase': Decimal('200.00003120'),
'unspendables': {
'genesis_block': 0,
'bip30': 0,
'scripts': 0,
'unclaimed_rewards': Decimal('0.00025920'),
'unclaimed_rewards': Decimal('0.00028080'),
}
})
self.block_sanity_check(res5['block_info'])
Expand All @@ -166,7 +166,7 @@ def _test_coin_stats_index(self):
for hash_option in index_hash_options:
# Check all amounts were registered correctly
res6 = index_node.gettxoutsetinfo(hash_option, 108)
assert_equal(res6['total_unspendable_amount'], Decimal('220.99976820'))
assert_equal(res6['total_unspendable_amount'], Decimal('220.99978980'))
assert_equal(res6['block_info'], {
'unspendable': Decimal('20.99950900'),
'prevout_spent': Decimal('221.00000000'),
Expand Down Expand Up @@ -197,7 +197,7 @@ def _test_coin_stats_index(self):

for hash_option in index_hash_options:
res7 = index_node.gettxoutsetinfo(hash_option, 109)
assert_equal(res7['total_unspendable_amount'], Decimal('380.99976820'))
assert_equal(res7['total_unspendable_amount'], Decimal('380.99978980'))
assert_equal(res7['block_info'], {
'unspendable': Decimal('160.00000000'),
'prevout_spent': 0,
Expand Down
2 changes: 1 addition & 1 deletion test/functional/feature_maxuploadtarget.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def run_test(self):
assert_equal(len(self.nodes[0].getpeerinfo()), 3)
# At most a couple more tries should succeed (depending on how long
# the test has been running so far).
for _ in range(10):
for _ in range(100):
p2p_conns[0].send_message(getdata_request)
p2p_conns[0].wait_for_disconnect()
assert_equal(len(self.nodes[0].getpeerinfo()), 2)
Expand Down
4 changes: 2 additions & 2 deletions test/functional/feature_utxo_set_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ def test_muhash_implementation(self):
assert_equal(finalized[::-1].hex(), node_muhash)

self.log.info("Test deterministic UTXO set hash results")
assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "f4edcc42a569bfef13f56306cf32821774ce36b0868cabbf698859d453b3ee0a")
assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "6aebab920c5c6102e2ae8fe94122c0cf6546e48eb167a9278693c00f7b348436")
assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "4b7ea0bd09947f5d0d9f983dfce3f590a5a2e1a83aa2ab252561b849d3f23d73")
assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "88f5d8f48b36504974ce92cfd1f91a7000a5bb28288126b81d4788b7250b5b00")

def run_test(self):
self.test_muhash_implementation()
Expand Down
12 changes: 6 additions & 6 deletions test/functional/mempool_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +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.
Download node binaries:
test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2
Only v0.15.2 is required by this test. The rest is used in other backwards compatibility tests.
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 BGLTestFramework
from test_framework.wallet import MiniWallet
from test_framework.wallet import (
MiniWallet,
MiniWalletMode,
)


class MempoolCompatibilityTest(BGLTestFramework):
Expand All @@ -40,7 +40,7 @@ def run_test(self):
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.
Expand Down
28 changes: 26 additions & 2 deletions test/functional/test_framework/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
import sha3
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

Expand All @@ -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("rbgl", 1, scriptPubKey[2:])
assert_equal(address, 'rbgl1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsylg5sy')
return (address, internal_key)


def byte_to_base58(b, version):
result = ''
str = b.hex()
Expand Down
5 changes: 3 additions & 2 deletions test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
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
Expand Down Expand Up @@ -777,7 +777,8 @@ def _initialize_chain(self):
# 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(
cache_node,
Expand Down
22 changes: 11 additions & 11 deletions test/functional/test_framework/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
from copy import deepcopy
from decimal import Decimal
from enum import Enum
from random import choice
from typing import (
Any,
List,
Optional,
)
from test_framework.address import (
base58_to_byte,
ADDRESS_BCRT1_P2WSH_OP_TRUE,
create_deterministic_address_bcrt1_p2tr_op_true,
key_to_p2pkh,
key_to_p2sh_p2wpkh,
key_to_p2wpkh,
output_key_to_p2tr,
)

from random import choice
from typing import Optional
from test_framework.descriptors import descsum_create
from test_framework.key import (
ECKey,
Expand All @@ -39,8 +39,9 @@
from test_framework.script import (
CScript,
LegacySignatureHash,
OP_TRUE,
LEAF_VERSION_TAPSCRIPT,
OP_NOP,
OP_TRUE,
SIGHASH_ALL,
taproot_construct,
)
Expand All @@ -56,15 +57,14 @@
assert_equal,
assert_greater_than_or_equal,
)
from enum import Enum

DEFAULT_FEE = Decimal("0.0001")

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
Expand All @@ -74,7 +74,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
"""
Expand All @@ -100,7 +100,7 @@ def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE):
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 _create_utxo(self, *, txid, vout, value, height):
Expand Down Expand Up @@ -283,7 +283,7 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), u
"""Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed."""
utxo_to_spend = utxo_to_spend or self.get_utxo()
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 = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
Expand All @@ -300,10 +300,10 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), u
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()

assert_equal(tx.get_vsize(), vsize)
Expand Down

0 comments on commit c557170

Please sign in to comment.