|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2017 The Bitcoin Core developers |
| 3 | +# Distributed under the MIT software license, see the accompanying |
| 4 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 5 | +"""Test that the wallet can send and receive using all combinations of address types. |
| 6 | +
|
| 7 | +There are 4 nodes-under-test: |
| 8 | + - node0 uses legacy addresses |
| 9 | + - node1 uses p2sh/segwit addresses |
| 10 | + - node2 uses p2sh/segwit addresses and bech32 addresses for change |
| 11 | + - node3 uses bech32 addresses |
| 12 | +
|
| 13 | +node4 exists to generate new blocks. |
| 14 | +
|
| 15 | +The script is a series of tests, iterating over the 4 nodes. In each iteration |
| 16 | +of the test, one node sends: |
| 17 | + - 10/101th of its balance to itself (using getrawchangeaddress for single key addresses) |
| 18 | + - 20/101th to the next node |
| 19 | + - 30/101th to the node after that |
| 20 | + - 40/101th to the remaining node |
| 21 | + - 1/101th remains as fee+change |
| 22 | +
|
| 23 | +Iterate over each node for single key addresses, and then over each node for |
| 24 | +multisig addresses. In a second iteration, the same is done, but with explicit address_type |
| 25 | +parameters passed to getnewaddress and getrawchangeaddress. Node0 and node3 send to p2sh, |
| 26 | +node 1 sends to bech32, and node2 sends to legacy. As every node sends coins after receiving, |
| 27 | +this also verifies that spending coins sent to all these address types works.""" |
| 28 | + |
| 29 | +from decimal import Decimal |
| 30 | +import itertools |
| 31 | + |
| 32 | +from test_framework.test_framework import BitcoinTestFramework |
| 33 | +from test_framework.util import assert_equal, assert_greater_than, connect_nodes_bi, sync_blocks, sync_mempools |
| 34 | + |
| 35 | +class AddressTypeTest(BitcoinTestFramework): |
| 36 | + def set_test_params(self): |
| 37 | + self.num_nodes = 5 |
| 38 | + self.extra_args = [["-addresstype=legacy"], ["-addresstype=p2sh-segwit"], ["-addresstype=p2sh-segwit", "-changetype=bech32"], ["-addresstype=bech32"], []] |
| 39 | + |
| 40 | + def setup_network(self): |
| 41 | + self.setup_nodes() |
| 42 | + |
| 43 | + # Fully mesh-connect nodes for faster mempool sync |
| 44 | + for i, j in itertools.product(range(self.num_nodes), repeat=2): |
| 45 | + if i > j: |
| 46 | + connect_nodes_bi(self.nodes, i, j) |
| 47 | + self.sync_all() |
| 48 | + |
| 49 | + def get_balances(self, confirmed=True): |
| 50 | + """Return a list of confirmed or unconfirmed balances.""" |
| 51 | + if confirmed: |
| 52 | + return [self.nodes[i].getbalance() for i in range(4)] |
| 53 | + else: |
| 54 | + return [self.nodes[i].getunconfirmedbalance() for i in range(4)] |
| 55 | + |
| 56 | + def test_address(self, node, address, multisig, typ): |
| 57 | + """Run sanity checks on an address.""" |
| 58 | + info = self.nodes[node].validateaddress(address) |
| 59 | + assert(info['isvalid']) |
| 60 | + if not multisig and typ == 'legacy': |
| 61 | + # P2PKH |
| 62 | + assert(not info['isscript']) |
| 63 | + assert(not info['iswitness']) |
| 64 | + assert('pubkey' in info) |
| 65 | + elif not multisig and typ == 'p2sh-segwit': |
| 66 | + # P2SH-P2WPKH |
| 67 | + assert(info['isscript']) |
| 68 | + assert(not info['iswitness']) |
| 69 | + assert_equal(info['script'], 'witness_v0_keyhash') |
| 70 | + assert('pubkey' in info) |
| 71 | + elif not multisig and typ == 'bech32': |
| 72 | + # P2WPKH |
| 73 | + assert(not info['isscript']) |
| 74 | + assert(info['iswitness']) |
| 75 | + assert_equal(info['witness_version'], 0) |
| 76 | + assert_equal(len(info['witness_program']), 40) |
| 77 | + assert('pubkey' in info) |
| 78 | + elif typ == 'legacy': |
| 79 | + # P2SH-multisig |
| 80 | + assert(info['isscript']) |
| 81 | + assert_equal(info['script'], 'multisig') |
| 82 | + assert(not info['iswitness']) |
| 83 | + assert('pubkeys' in info) |
| 84 | + elif typ == 'p2sh-segwit': |
| 85 | + # P2SH-P2WSH-multisig |
| 86 | + assert(info['isscript']) |
| 87 | + assert_equal(info['script'], 'witness_v0_scripthash') |
| 88 | + assert(not info['iswitness']) |
| 89 | + assert(info['embedded']['isscript']) |
| 90 | + assert_equal(info['embedded']['script'], 'multisig') |
| 91 | + assert(info['embedded']['iswitness']) |
| 92 | + assert_equal(info['embedded']['witness_version'], 0) |
| 93 | + assert_equal(len(info['embedded']['witness_program']), 64) |
| 94 | + assert('pubkeys' in info['embedded']) |
| 95 | + elif typ == 'bech32': |
| 96 | + # P2WSH-multisig |
| 97 | + assert(info['isscript']) |
| 98 | + assert_equal(info['script'], 'multisig') |
| 99 | + assert(info['iswitness']) |
| 100 | + assert_equal(info['witness_version'], 0) |
| 101 | + assert_equal(len(info['witness_program']), 64) |
| 102 | + assert('pubkeys' in info) |
| 103 | + else: |
| 104 | + # Unknown type |
| 105 | + assert(False) |
| 106 | + |
| 107 | + def run_test(self): |
| 108 | + # Mine 101 blocks on node4 to bring nodes out of IBD and make sure that |
| 109 | + # no coinbases are maturing for the nodes-under-test during the test |
| 110 | + self.nodes[4].generate(101) |
| 111 | + sync_blocks(self.nodes) |
| 112 | + |
| 113 | + uncompressed_1 = "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee" |
| 114 | + uncompressed_2 = "047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77" |
| 115 | + compressed_1 = "0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52" |
| 116 | + compressed_2 = "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073" |
| 117 | + |
| 118 | + # addmultisigaddress with at least 1 uncompressed key should return a legacy address. |
| 119 | + for node in range(4): |
| 120 | + self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2]), True, 'legacy') |
| 121 | + self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2]), True, 'legacy') |
| 122 | + self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2]), True, 'legacy') |
| 123 | + # addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours). |
| 124 | + self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'legacy') |
| 125 | + self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'p2sh-segwit') |
| 126 | + self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'p2sh-segwit') |
| 127 | + self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'bech32') |
| 128 | + |
| 129 | + for explicit_type, multisig, from_node in itertools.product([False, True], [False, True], range(4)): |
| 130 | + address_type = None |
| 131 | + if explicit_type and not multisig: |
| 132 | + if from_node == 1: |
| 133 | + address_type = 'bech32' |
| 134 | + elif from_node == 0 or from_node == 3: |
| 135 | + address_type = 'p2sh-segwit' |
| 136 | + else: |
| 137 | + address_type = 'legacy' |
| 138 | + self.log.info("Sending from node {} ({}) with{} multisig using {}".format(from_node, self.extra_args[from_node], "" if multisig else "out", "default" if address_type is None else address_type)) |
| 139 | + old_balances = self.get_balances() |
| 140 | + self.log.debug("Old balances are {}".format(old_balances)) |
| 141 | + to_send = (old_balances[from_node] / 101).quantize(Decimal("0.00000001")) |
| 142 | + sends = {} |
| 143 | + |
| 144 | + self.log.debug("Prepare sends") |
| 145 | + for n, to_node in enumerate(range(from_node, from_node + 4)): |
| 146 | + to_node %= 4 |
| 147 | + change = False |
| 148 | + if not multisig: |
| 149 | + if from_node == to_node: |
| 150 | + # When sending non-multisig to self, use getrawchangeaddress |
| 151 | + address = self.nodes[to_node].getrawchangeaddress(address_type=address_type) |
| 152 | + change = True |
| 153 | + else: |
| 154 | + address = self.nodes[to_node].getnewaddress(address_type=address_type) |
| 155 | + else: |
| 156 | + addr1 = self.nodes[to_node].getnewaddress() |
| 157 | + addr2 = self.nodes[to_node].getnewaddress() |
| 158 | + address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2]) |
| 159 | + |
| 160 | + # Do some sanity checking on the created address |
| 161 | + if address_type is not None: |
| 162 | + typ = address_type |
| 163 | + elif to_node == 0: |
| 164 | + typ = 'legacy' |
| 165 | + elif to_node == 1 or (to_node == 2 and not change): |
| 166 | + typ = 'p2sh-segwit' |
| 167 | + else: |
| 168 | + typ = 'bech32' |
| 169 | + self.test_address(to_node, address, multisig, typ) |
| 170 | + |
| 171 | + # Output entry |
| 172 | + sends[address] = to_send * 10 * (1 + n) |
| 173 | + |
| 174 | + self.log.debug("Sending: {}".format(sends)) |
| 175 | + self.nodes[from_node].sendmany("", sends) |
| 176 | + sync_mempools(self.nodes) |
| 177 | + |
| 178 | + unconf_balances = self.get_balances(False) |
| 179 | + self.log.debug("Check unconfirmed balances: {}".format(unconf_balances)) |
| 180 | + assert_equal(unconf_balances[from_node], 0) |
| 181 | + for n, to_node in enumerate(range(from_node + 1, from_node + 4)): |
| 182 | + to_node %= 4 |
| 183 | + assert_equal(unconf_balances[to_node], to_send * 10 * (2 + n)) |
| 184 | + |
| 185 | + # node4 collects fee and block subsidy to keep accounting simple |
| 186 | + self.nodes[4].generate(1) |
| 187 | + sync_blocks(self.nodes) |
| 188 | + |
| 189 | + new_balances = self.get_balances() |
| 190 | + self.log.debug("Check new balances: {}".format(new_balances)) |
| 191 | + # We don't know what fee was set, so we can only check bounds on the balance of the sending node |
| 192 | + assert_greater_than(new_balances[from_node], to_send * 10) |
| 193 | + assert_greater_than(to_send * 11, new_balances[from_node]) |
| 194 | + for n, to_node in enumerate(range(from_node + 1, from_node + 4)): |
| 195 | + to_node %= 4 |
| 196 | + assert_equal(new_balances[to_node], old_balances[to_node] + to_send * 10 * (2 + n)) |
| 197 | + |
| 198 | +if __name__ == '__main__': |
| 199 | + AddressTypeTest().main() |
0 commit comments