Repository navigation
Expand file tree
/
Copy pathabc-wallet-standardness.py
More file actions
executable file
·193 lines (164 loc) · 7.81 KB
/
Copy pathabc-wallet-standardness.py
File metadata and controls
executable file
·193 lines (164 loc) · 7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/usr/bin/env python3
# Copyright (c) 2019 The Bitcoin developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the response of wallet to a variety of weird / nonstandard coins
that it might try to spend."""
from decimal import Decimal
from test_framework.script import (
CScript,
OP_1,
OP_5,
OP_CHECKSIG,
OP_CHECKMULTISIG,
OP_DUP,
OP_EQUALVERIFY,
OP_HASH160,
OP_PUSHDATA1,
hash160,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_raises_rpc_error,
assert_equal,
)
from test_framework.messages import (
CTransaction,
CTxOut,
FromHex,
ToHex,
)
SATOSHI = Decimal('0.00000001')
class WalletStandardnessTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [['-acceptnonstdtxn=0'], ['-acceptnonstdtxn=1']]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
std_node, nonstd_node = self.nodes
address_nonstd = nonstd_node.getnewaddress()
# make and mature some coins for the nonstandard node
self.generate(nonstd_node, 120)
self.sync_blocks()
def fund_and_test_wallet(scriptPubKey, shouldBeStandard, shouldBeInWallet,
amount=10000, spendfee=500, nonstd_error="scriptpubkey (code 64)", sign_error=None):
""" Get the nonstandard node to fund a transaction, test its
standardness by trying to broadcast on the standard node, then
mine it and see if it ended up in the standard node's wallet.
Finally, it attempts to spend the coin.
"""
self.log.info("Trying script {}".format(scriptPubKey.hex(),))
# get nonstandard node to fund the script
tx = CTransaction()
tx.vout.append(CTxOut(max(amount, 10000), scriptPubKey))
rawtx = nonstd_node.fundrawtransaction(
ToHex(tx), {'lockUnspents': True, 'changePosition': 1})['hex']
# fundrawtransaction doesn't like to fund dust outputs, so we
# have to manually override the amount.
FromHex(tx, rawtx)
tx.vout[0].nValue = min(amount, 10000)
rawtx = nonstd_node.signrawtransactionwithwallet(ToHex(tx))['hex']
# ensure signing process did not disturb scriptPubKey
signedtx = FromHex(CTransaction(), rawtx)
assert_equal(scriptPubKey, signedtx.vout[0].scriptPubKey)
txid = signedtx.rehash()
balance_initial = std_node.getbalance()
# try broadcasting it on the standard node
if shouldBeStandard:
std_node.sendrawtransaction(rawtx)
assert txid in std_node.getrawmempool()
else:
assert_raises_rpc_error(-26, nonstd_error,
std_node.sendrawtransaction, rawtx)
assert txid not in std_node.getrawmempool()
# make sure it's in nonstandard node's mempool, then mine it
nonstd_node.sendrawtransaction(rawtx)
assert txid in nonstd_node.getrawmempool()
[blockhash] = self.generate(nonstd_node, 1)
# make sure it was mined
assert txid in nonstd_node.getblock(blockhash)["tx"]
self.sync_blocks()
wallet_outpoints = {(entry['txid'], entry['vout'])
for entry in std_node.listunspent()}
# calculate wallet balance change just as a double check
balance_change = std_node.getbalance() - balance_initial
if shouldBeInWallet:
assert (txid, 0) in wallet_outpoints
assert balance_change == amount * SATOSHI
else:
assert (txid, 0) not in wallet_outpoints
assert balance_change == 0
# try spending the funds using the wallet.
outamount = (amount - spendfee) * SATOSHI
if outamount < 546 * SATOSHI:
# If the final amount would be too small, then just donate
# to miner fees.
outputs = [{"data": b"to miner, with love".hex()}]
else:
outputs = [{address_nonstd: outamount}]
spendtx = std_node.createrawtransaction(
[{'txid': txid, 'vout': 0}], outputs)
signresult = std_node.signrawtransactionwithwallet(spendtx)
if sign_error is None:
assert_equal(signresult['complete'], True)
txid = std_node.sendrawtransaction(signresult['hex'])
[blockhash] = self.generate(std_node, 1)
# make sure it was mined
assert txid in std_node.getblock(blockhash)["tx"]
self.sync_blocks()
else:
assert_equal(signresult['complete'], False)
assert_equal(signresult['errors'][0]['error'], sign_error)
# we start with an empty wallet
assert_equal(std_node.getbalance(), 0)
address = std_node.getnewaddress()
pubkey = bytes.fromhex(std_node.getaddressinfo(address)['pubkey'])
pubkeyhash = hash160(pubkey)
# P2PK
fund_and_test_wallet(CScript([pubkey, OP_CHECKSIG]), True, True)
fund_and_test_wallet(
CScript([OP_PUSHDATA1, pubkey, OP_CHECKSIG]), False, False,
sign_error='Data push larger than necessary')
# P2PKH
fund_and_test_wallet(CScript(
[OP_DUP, OP_HASH160, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG]), True, True)
# The signing error changes here since the script check (with empty
# scriptSig) hits OP_DUP before it hits the nonminimal push; in all
# other cases we hit the nonminimal push first.
fund_and_test_wallet(CScript(
[OP_DUP, OP_HASH160, OP_PUSHDATA1, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG]), False, False,
sign_error='Unable to sign input, invalid stack size (possibly missing key)')
# Bare multisig
fund_and_test_wallet(
CScript([OP_1, pubkey, OP_1, OP_CHECKMULTISIG]), True, False)
fund_and_test_wallet(
CScript([OP_1, OP_PUSHDATA1, pubkey, OP_1,
OP_CHECKMULTISIG]), False, False,
sign_error='Data push larger than necessary')
fund_and_test_wallet(
CScript([OP_1, pubkey, b'\x01', OP_CHECKMULTISIG]), False, False,
sign_error='Data push larger than necessary')
fund_and_test_wallet(
CScript([b'\x01', pubkey, OP_1, OP_CHECKMULTISIG]), False, False,
sign_error='Data push larger than necessary')
# Note: 1-of-5 is nonstandard to fund yet is standard to spend. However,
# trying to spend it with our wallet in particular will generate
# too-dense sigchecks since our wallet currently only signs with ECDSA
# (Schnorr would not have this issue).
fund_and_test_wallet(
CScript([OP_1, pubkey, pubkey, pubkey, pubkey, pubkey,
OP_5, OP_CHECKMULTISIG]), False, False,
sign_error='Input SigChecks limit exceeded')
fund_and_test_wallet(
CScript([OP_1, pubkey, pubkey, pubkey, OP_PUSHDATA1,
pubkey, pubkey, OP_5, OP_CHECKMULTISIG]), False, False,
sign_error='Data push larger than necessary')
# Dust also is nonstandard to fund but standard to spend.
fund_and_test_wallet(
CScript([pubkey, OP_CHECKSIG]), False, True, amount=200, nonstd_error="dust (code 64)")
# and we end with an empty wallet
assert_equal(std_node.getbalance(), 0)
if __name__ == '__main__':
WalletStandardnessTest().main()