forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathfeature_spentindex.py
More file actions
executable file
·176 lines (143 loc) · 7.04 KB
/
Copy pathfeature_spentindex.py
File metadata and controls
executable file
·176 lines (143 loc) · 7.04 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
#!/usr/bin/env python3
# Copyright (c) 2014-2015 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
# Test spentindex generation and fetching
#
from decimal import Decimal
from test_framework.messages import (
COutPoint,
CTransaction,
CTxIn,
CTxOut,
COIN,
)
from test_framework.script_util import (
keyhash_to_p2pkh_script,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
class SpentIndexTest(BitcoinTestFramework):
def add_options(self, parser):
self.add_wallet_options(parser)
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 4
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
self.add_nodes(self.num_nodes)
# Nodes 0/1 are "wallet" nodes
self.start_node(0)
self.start_node(1, ["-spentindex"])
# Nodes 2/3 are used for testing
self.start_node(2, ["-spentindex"])
self.start_node(3, ["-spentindex", "-txindex"])
self.connect_nodes(0, 1)
self.connect_nodes(0, 2)
self.connect_nodes(0, 3)
self.sync_all()
self.import_deterministic_coinbase_privkeys()
def run_test(self):
self.log.info("Test that settings can be disabled and re-enabled...")
self.restart_node(1, ["-spentindex=0"])
self.connect_nodes(0, 1)
self.sync_all()
# SpentIndex is now async, so it can be enabled without -reindex
self.restart_node(1, ["-spentindex"])
self.connect_nodes(0, 1)
self.sync_all()
self.log.info("Mining blocks...")
self.generate(self.nodes[0], 105)
chain_height = self.nodes[1].getblockcount()
assert_equal(chain_height, 105)
# Check that
self.log.info("Testing spent index...")
privkey = "cU4zhap7nPJAWeMFu4j6jLrfPmqakDAzy8zn8Fhb3oEevdm4e5Lc"
addressHash = bytes.fromhex("C5E4FB9171C22409809A3E8047A29C83886E325D")
scriptPubKey = keyhash_to_p2pkh_script(addressHash)
unspent = self.nodes[0].listunspent()
tx = CTransaction()
tx_fee = Decimal('0.00001')
tx_fee_sat = int(tx_fee * COIN)
amount = int(unspent[0]["amount"] * COIN) - tx_fee_sat
tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))]
tx.vout = [CTxOut(amount, scriptPubKey)]
tx.rehash()
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
self.generate(self.nodes[0], 1)
self.log.info("Testing getspentinfo method...")
# Check that the spentinfo works standalone
info = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]})
assert_equal(info["txid"], txid)
assert_equal(info["index"], 0)
assert_equal(info["height"], 106)
self.log.info("Testing getrawtransaction method...")
# Check that verbose raw transaction includes spent info
txVerbose = self.nodes[3].getrawtransaction(unspent[0]["txid"], 1)
assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentTxId"], txid)
assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentIndex"], 0)
assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentHeight"], 106)
# Check that verbose raw transaction includes input values
txVerbose2 = self.nodes[3].getrawtransaction(txid, 1)
assert_equal(txVerbose2["vin"][0]["value"], Decimal(unspent[0]["amount"]))
assert_equal(txVerbose2["vin"][0]["valueSat"] - tx_fee_sat, amount)
# Check that verbose raw transaction includes address values and input values
address2 = "yeMpGzMj3rhtnz48XsfpB8itPHhHtgxLc3"
addressHash2 = bytes.fromhex("C5E4FB9171C22409809A3E8047A29C83886E325D")
scriptPubKey2 = keyhash_to_p2pkh_script(addressHash2)
tx2 = CTransaction()
tx2.vin = [CTxIn(COutPoint(int(txid, 16), 0))]
tx2.vout = [CTxOut(amount - int(COIN / 10), scriptPubKey2)]
tx2.rehash()
self.nodes[0].importprivkey(privkey)
signed_tx2 = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())
txid2 = self.nodes[0].sendrawtransaction(signed_tx2["hex"], 0)
# Check the mempool index
self.sync_all()
txVerbose3 = self.nodes[1].getrawtransaction(txid2, 1)
assert_equal(txVerbose3["vin"][0]["address"], address2)
assert_equal(txVerbose3["vin"][0]["value"], Decimal(unspent[0]["amount"]) - tx_fee)
assert_equal(txVerbose3["vin"][0]["valueSat"], amount)
self.log.info("Testing getspentinfo with mempool data...")
# Check that getspentinfo returns mempool spend info for unconfirmed transactions
# The output from txid is now spent by txid2 (which is in mempool, not yet mined)
info_mempool = self.nodes[1].getspentinfo({"txid": txid, "index": 0})
assert_equal(info_mempool["txid"], txid2)
assert_equal(info_mempool["index"], 0)
assert_equal(info_mempool["height"], -1) # Height -1 indicates mempool transaction
# Check the database index
self.generate(self.nodes[0], 1)
txVerbose4 = self.nodes[3].getrawtransaction(txid2, 1)
assert_equal(txVerbose4["vin"][0]["address"], address2)
assert_equal(txVerbose4["vin"][0]["value"], Decimal(unspent[0]["amount"]) - tx_fee)
assert_equal(txVerbose4["vin"][0]["valueSat"], amount)
self.log.info("Testing reorg handling...")
# Get the block hash containing txid
block_hash = self.nodes[1].getblockhash(106)
# Verify spent info exists before reorg
info_before = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]})
assert_equal(info_before["txid"], txid)
# Invalidate the block containing the spending transaction on all nodes
for node in self.nodes:
node.invalidateblock(block_hash)
# After invalidation, transactions go back to mempool
# getspentinfo should still find the spend in mempool with height -1
info_after_invalidate = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]})
assert_equal(info_after_invalidate["txid"], txid)
assert_equal(info_after_invalidate["index"], 0)
assert_equal(info_after_invalidate["height"], -1) # Now in mempool
# Reconsider the block on all nodes
for node in self.nodes:
node.reconsiderblock(block_hash)
self.sync_all()
# Verify spent info is back after reconsider
info_after = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]})
assert_equal(info_after["txid"], txid)
assert_equal(info_after["index"], 0)
assert_equal(info_after["height"], 106)
self.log.info("Passed")
if __name__ == '__main__':
SpentIndexTest().main()