Skip to content

Commit 8bd5e0a

Browse files
author
MarcoFalke
committedAug 15, 2019
Merge bitcoin#16465: test: Test p2sh-witness and bech32 in wallet_import_rescan
fa3c657 lint: Add false positive to python dead code linter (MarcoFalke) fa25668 test: Test p2sh-witness and bech32 in wallet_import_rescan (MarcoFalke) fa79af2 test: Replace fragile "rng" with call to random() (MarcoFalke) fac3dcf test: Generate one block for each send in wallet_import_rescan (MarcoFalke) Pull request description: This adds test coverage for segwit in the `wallet_import_rescan` test, among other cleanups. ACKs for top commit: jnewbery: ACK fa3c657 Tree-SHA512: 877741763c62c1bf9d868864a1e3f0699857e8c028e9fcd65c7eeb73600c22cbe97b7b51093737743d9e87bcb991c1fe1086f673e18765aef0fcfe27951402f0
2 parents e00501e + fa3c657 commit 8bd5e0a

File tree

5 files changed

+113
-28
lines changed

5 files changed

+113
-28
lines changed
 

‎.travis/lint_04_install.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
export LC_ALL=C
88

9-
travis_retry pip install codespell==1.15.0
10-
travis_retry pip install flake8==3.5.0
11-
travis_retry pip install vulture==0.29
9+
travis_retry pip3 install codespell==1.15.0
10+
travis_retry pip3 install flake8==3.5.0
11+
travis_retry pip3 install vulture==0.29
1212

1313
SHELLCHECK_VERSION=v0.6.0
1414
curl -s "https://storage.googleapis.com/shellcheck/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/

‎test/functional/test_framework/address.py

+9
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Encode and decode BASE58, P2PKH and P2SH addresses."""
66

7+
import enum
8+
79
from .script import hash256, hash160, sha256, CScript, OP_0
810
from .util import hex_str_to_bytes
911

1012
from . import segwit_addr
1113

1214
ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj'
1315

16+
17+
class AddressType(enum.Enum):
18+
bech32 = 'bech32'
19+
p2sh_segwit = 'p2sh-segwit'
20+
legacy = 'legacy' # P2PKH
21+
22+
1423
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
1524

1625

‎test/functional/wallet_import_rescan.py

+54-23
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,38 @@
2020
"""
2121

2222
from test_framework.test_framework import BitcoinTestFramework
23+
from test_framework.address import AddressType
2324
from test_framework.util import (
2425
connect_nodes,
2526
assert_equal,
2627
set_node_times,
2728
)
2829

2930
import collections
31+
from decimal import Decimal
3032
import enum
3133
import itertools
34+
import random
3235

3336
Call = enum.Enum("Call", "single multiaddress multiscript")
3437
Data = enum.Enum("Data", "address pub priv")
3538
Rescan = enum.Enum("Rescan", "no yes late_timestamp")
3639

3740

38-
class Variant(collections.namedtuple("Variant", "call data rescan prune")):
41+
class Variant(collections.namedtuple("Variant", "call data address_type rescan prune")):
3942
"""Helper for importing one key and verifying scanned transactions."""
4043

4144
def do_import(self, timestamp):
4245
"""Call one key import RPC."""
4346
rescan = self.rescan == Rescan.yes
4447

48+
assert_equal(self.address["solvable"], True)
49+
assert_equal(self.address["isscript"], self.address_type == AddressType.p2sh_segwit)
50+
assert_equal(self.address["iswitness"], self.address_type == AddressType.bech32)
51+
if self.address["isscript"]:
52+
assert_equal(self.address["embedded"]["isscript"], False)
53+
assert_equal(self.address["embedded"]["iswitness"], True)
54+
4555
if self.call == Call.single:
4656
if self.data == Data.address:
4757
response = self.node.importaddress(address=self.address["address"], label=self.label, rescan=rescan)
@@ -52,7 +62,7 @@ def do_import(self, timestamp):
5262
assert_equal(response, None)
5363

5464
elif self.call in (Call.multiaddress, Call.multiscript):
55-
response = self.node.importmulti([{
65+
request = {
5666
"scriptPubKey": {
5767
"address": self.address["address"]
5868
} if self.call == Call.multiaddress else self.address["scriptPubKey"],
@@ -61,13 +71,21 @@ def do_import(self, timestamp):
6171
"keys": [self.key] if self.data == Data.priv else [],
6272
"label": self.label,
6373
"watchonly": self.data != Data.priv
64-
}], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
74+
}
75+
if self.address_type == AddressType.p2sh_segwit and self.data != Data.address:
76+
# We need solving data when providing a pubkey or privkey as data
77+
request.update({"redeemscript": self.address['embedded']['scriptPubKey']})
78+
response = self.node.importmulti(
79+
requests=[request],
80+
options={"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)},
81+
)
6582
assert_equal(response, [{"success": True}])
6683

67-
def check(self, txid=None, amount=None, confirmations=None):
84+
def check(self, txid=None, amount=None, confirmation_height=None):
6885
"""Verify that listtransactions/listreceivedbyaddress return expected values."""
6986

7087
txs = self.node.listtransactions(label=self.label, count=10000, include_watchonly=True)
88+
current_height = self.node.getblockcount()
7189
assert_equal(len(txs), self.expected_txs)
7290

7391
addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address'])
@@ -82,13 +100,13 @@ def check(self, txid=None, amount=None, confirmations=None):
82100
assert_equal(tx["category"], "receive")
83101
assert_equal(tx["label"], self.label)
84102
assert_equal(tx["txid"], txid)
85-
assert_equal(tx["confirmations"], confirmations)
103+
assert_equal(tx["confirmations"], 1 + current_height - confirmation_height)
86104
assert_equal("trusted" not in tx, True)
87105

88106
address, = [ad for ad in addresses if txid in ad["txids"]]
89107
assert_equal(address["address"], self.address["address"])
90108
assert_equal(address["amount"], self.expected_balance)
91-
assert_equal(address["confirmations"], confirmations)
109+
assert_equal(address["confirmations"], 1 + current_height - confirmation_height)
92110
# Verify the transaction is correctly marked watchonly depending on
93111
# whether the transaction pays to an imported public key or
94112
# imported private key. The test setup ensures that transaction
@@ -102,7 +120,7 @@ def check(self, txid=None, amount=None, confirmations=None):
102120

103121

104122
# List of Variants for each way a key or address could be imported.
105-
IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))]
123+
IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, AddressType, Rescan, (False, True))]
106124

107125
# List of nodes to import keys to. Half the nodes will have pruning disabled,
108126
# half will have it enabled. Different nodes will be used for imports that are
@@ -116,6 +134,13 @@ def check(self, txid=None, amount=None, confirmations=None):
116134
# Rescans start at the earliest block up to 2 hours before the key timestamp.
117135
TIMESTAMP_WINDOW = 2 * 60 * 60
118136

137+
AMOUNT_DUST = 0.00000546
138+
139+
140+
def get_rand_amount():
141+
r = random.uniform(AMOUNT_DUST, 1)
142+
return Decimal(str(round(r, 8)))
143+
119144

120145
class ImportRescanTest(BitcoinTestFramework):
121146
def set_test_params(self):
@@ -125,12 +150,12 @@ def skip_test_if_missing_module(self):
125150
self.skip_if_no_wallet()
126151

127152
def setup_network(self):
128-
extra_args = [["-addresstype=legacy"] for _ in range(self.num_nodes)]
153+
self.extra_args = [[]] * self.num_nodes
129154
for i, import_node in enumerate(IMPORT_NODES, 2):
130155
if import_node.prune:
131-
extra_args[i] += ["-prune=1"]
156+
self.extra_args[i] += ["-prune=1"]
132157

133-
self.add_nodes(self.num_nodes, extra_args=extra_args)
158+
self.add_nodes(self.num_nodes, extra_args=self.extra_args)
134159

135160
# Import keys with pruning disabled
136161
self.start_nodes(extra_args=[[]] * self.num_nodes)
@@ -147,17 +172,23 @@ def run_test(self):
147172
# each possible type of wallet import RPC.
148173
for i, variant in enumerate(IMPORT_VARIANTS):
149174
variant.label = "label {} {}".format(i, variant)
150-
variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label))
175+
variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(
176+
label=variant.label,
177+
address_type=variant.address_type.value,
178+
))
151179
variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
152-
variant.initial_amount = 1 - (i + 1) / 64
180+
variant.initial_amount = get_rand_amount()
153181
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
182+
self.nodes[0].generate(1) # Generate one block for each send
183+
variant.confirmation_height = self.nodes[0].getblockcount()
184+
variant.timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
154185

155-
# Generate a block containing the initial transactions, then another
156-
# block further in the future (past the rescan window).
157-
self.nodes[0].generate(1)
186+
# Generate a block further in the future (past the rescan window).
158187
assert_equal(self.nodes[0].getrawmempool(), [])
159-
timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
160-
set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1)
188+
set_node_times(
189+
self.nodes,
190+
self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] + TIMESTAMP_WINDOW + 1,
191+
)
161192
self.nodes[0].generate(1)
162193
self.sync_all()
163194

@@ -167,23 +198,23 @@ def run_test(self):
167198
self.log.info('Run import for variant {}'.format(variant))
168199
expect_rescan = variant.rescan == Rescan.yes
169200
variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
170-
variant.do_import(timestamp)
201+
variant.do_import(variant.timestamp)
171202
if expect_rescan:
172203
variant.expected_balance = variant.initial_amount
173204
variant.expected_txs = 1
174-
variant.check(variant.initial_txid, variant.initial_amount, 2)
205+
variant.check(variant.initial_txid, variant.initial_amount, variant.confirmation_height)
175206
else:
176207
variant.expected_balance = 0
177208
variant.expected_txs = 0
178209
variant.check()
179210

180211
# Create new transactions sending to each address.
181212
for i, variant in enumerate(IMPORT_VARIANTS):
182-
variant.sent_amount = 1 - (2 * i + 1) / 128
213+
variant.sent_amount = get_rand_amount()
183214
variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
215+
self.nodes[0].generate(1) # Generate one block for each send
216+
variant.confirmation_height = self.nodes[0].getblockcount()
184217

185-
# Generate a block containing the new transactions.
186-
self.nodes[0].generate(1)
187218
assert_equal(self.nodes[0].getrawmempool(), [])
188219
self.sync_all()
189220

@@ -192,7 +223,7 @@ def run_test(self):
192223
self.log.info('Run check for variant {}'.format(variant))
193224
variant.expected_balance += variant.sent_amount
194225
variant.expected_txs += 1
195-
variant.check(variant.sent_txid, variant.sent_amount, 1)
226+
variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height)
196227

197228
if __name__ == "__main__":
198229
ImportRescanTest().main()
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
BadInputOutpointIndex # unused class (test/functional/data/invalid_txs.py)
2+
_.carbon_path # unused attribute (contrib/macdeploy/custom_dsstore.py)
3+
connection_lost # unused function (test/functional/test_framework/mininode.py)
4+
connection_made # unused function (test/functional/test_framework/mininode.py)
5+
_.converter # unused attribute (test/functional/test_framework/test_framework.py)
6+
_.daemon # unused attribute (test/functional/test_framework/socks5.py)
7+
data_received # unused function (test/functional/test_framework/mininode.py)
8+
DuplicateInput # unused class (test/functional/data/invalid_txs.py)
9+
_.filename # unused attribute (contrib/macdeploy/custom_dsstore.py)
10+
InvalidOPIFConstruction # unused class (test/functional/data/invalid_txs.py)
11+
_.is_compressed # unused property (test/functional/test_framework/key.py)
12+
legacy # unused variable (test/functional/test_framework/address.py)
13+
msg_generic # unused class (test/functional/test_framework/messages.py)
14+
NonexistentInput # unused class (test/functional/data/invalid_txs.py)
15+
on_addr # unused function (test/functional/test_framework/mininode.py)
16+
on_blocktxn # unused function (test/functional/test_framework/mininode.py)
17+
on_block # unused function (test/functional/test_framework/mininode.py)
18+
on_cmpctblock # unused function (test/functional/test_framework/mininode.py)
19+
on_feefilter # unused function (test/functional/test_framework/mininode.py)
20+
on_getaddr # unused function (test/functional/test_framework/mininode.py)
21+
on_getblocks # unused function (test/functional/test_framework/mininode.py)
22+
on_getblocktxn # unused function (test/functional/test_framework/mininode.py)
23+
on_getdata # unused function (test/functional/test_framework/mininode.py)
24+
on_getheaders # unused function (test/functional/test_framework/mininode.py)
25+
on_headers # unused function (test/functional/test_framework/mininode.py)
26+
on_inv # unused function (test/functional/test_framework/mininode.py)
27+
on_mempool # unused function (test/functional/test_framework/mininode.py)
28+
on_notfound # unused function (test/functional/test_framework/mininode.py)
29+
on_ping # unused function (test/functional/test_framework/mininode.py)
30+
on_pong # unused function (test/functional/test_framework/mininode.py)
31+
on_reject # unused function (test/functional/test_framework/mininode.py)
32+
on_sendcmpct # unused function (test/functional/test_framework/mininode.py)
33+
on_sendheaders # unused function (test/functional/test_framework/mininode.py)
34+
on_tx # unused function (test/functional/test_framework/mininode.py)
35+
on_verack # unused function (test/functional/test_framework/mininode.py)
36+
on_version # unused function (test/functional/test_framework/mininode.py)
37+
_.optionxform # unused attribute (test/util/bitcoin-util-test.py)
38+
OutputMissing # unused class (test/functional/data/invalid_txs.py)
39+
_.posix_path # unused attribute (contrib/macdeploy/custom_dsstore.py)
40+
profile_with_perf # unused function (test/functional/test_framework/test_node.py)
41+
SizeTooSmall # unused class (test/functional/data/invalid_txs.py)
42+
SpendNegative # unused class (test/functional/data/invalid_txs.py)
43+
SpendTooMuch # unused class (test/functional/data/invalid_txs.py)
44+
TooManySigops # unused class (test/functional/data/invalid_txs.py)
45+
verify_ecdsa # unused function (test/functional/test_framework/key.py)

‎test/lint/lint-python-dead-code.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ fi
1515

1616
vulture \
1717
--min-confidence 60 \
18-
--ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,is_compressed,is_valid,verify_ecdsa,msg_generic,on_*,optionxform,restype,profile_with_perf" \
19-
$(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py")
18+
$(git rev-parse --show-toplevel) \
19+
$(dirname "${BASH_SOURCE[0]}")/lint-python-dead-code-whitelist

0 commit comments

Comments
 (0)
Please sign in to comment.