Larry Ruane
5 years ago
17 changed files with 1198 additions and 23 deletions
@ -0,0 +1,357 @@ |
|||
#!/usr/bin/env python |
|||
# Copyright (c) 2019 The Zcash developers |
|||
# Distributed under the MIT software license, see the accompanying |
|||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
|||
|
|||
# |
|||
# Test addressindex generation and fetching for insightexplorer |
|||
# |
|||
# RPCs tested here: |
|||
# |
|||
# getaddresstxids |
|||
# getaddressbalance |
|||
# getaddressdeltas |
|||
# getaddressutxos |
|||
# getaddressmempool |
|||
# |
|||
|
|||
import sys; assert sys.version_info < (3,), ur"This script does not run under Python 3. Please use Python 2.7.x." |
|||
|
|||
from test_framework.test_framework import BitcoinTestFramework |
|||
|
|||
|
|||
from test_framework.util import ( |
|||
assert_equal, |
|||
initialize_chain_clean, |
|||
start_nodes, |
|||
stop_nodes, |
|||
connect_nodes, |
|||
) |
|||
|
|||
from test_framework.util import wait_bitcoinds |
|||
|
|||
from test_framework.script import ( |
|||
CScript, |
|||
OP_HASH160, |
|||
OP_EQUAL, |
|||
OP_DUP, |
|||
OP_DROP, |
|||
) |
|||
|
|||
from test_framework.mininode import COIN, CTransaction |
|||
from test_framework.mininode import CTxIn, CTxOut, COutPoint |
|||
|
|||
from binascii import hexlify |
|||
|
|||
|
|||
class AddressIndexTest(BitcoinTestFramework): |
|||
|
|||
def setup_chain(self): |
|||
print("Initializing test directory "+self.options.tmpdir) |
|||
initialize_chain_clean(self.options.tmpdir, 3) |
|||
|
|||
def setup_network(self): |
|||
# -insightexplorer causes addressindex to be enabled (fAddressIndex = true) |
|||
args = ('-debug', '-txindex', '-experimentalfeatures', '-insightexplorer') |
|||
self.nodes = start_nodes(3, self.options.tmpdir, [args] * 3) |
|||
connect_nodes(self.nodes[0], 1) |
|||
connect_nodes(self.nodes[0], 2) |
|||
|
|||
self.is_network_split = False |
|||
self.sync_all() |
|||
|
|||
def run_test(self): |
|||
|
|||
# helper functions |
|||
def getaddresstxids(node_index, addresses, start, end): |
|||
return self.nodes[node_index].getaddresstxids({ |
|||
'addresses': addresses, |
|||
'start': start, |
|||
'end': end |
|||
}) |
|||
|
|||
def getaddressdeltas(node_index, addresses, start, end, chainInfo=None): |
|||
params = { |
|||
'addresses': addresses, |
|||
'start': start, |
|||
'end': end, |
|||
} |
|||
if chainInfo is not None: |
|||
params.update({'chainInfo': chainInfo}) |
|||
return self.nodes[node_index].getaddressdeltas(params) |
|||
|
|||
# default received value is the balance value |
|||
def check_balance(node_index, address, expected_balance, expected_received=None): |
|||
if isinstance(address, list): |
|||
bal = self.nodes[node_index].getaddressbalance({'addresses': address}) |
|||
else: |
|||
bal = self.nodes[node_index].getaddressbalance(address) |
|||
assert_equal(bal['balance'], expected_balance) |
|||
if expected_received is None: |
|||
expected_received = expected_balance |
|||
assert_equal(bal['received'], expected_received) |
|||
|
|||
# begin test |
|||
|
|||
self.nodes[0].generate(105) |
|||
self.sync_all() |
|||
assert_equal(self.nodes[0].getbalance(), 5 * 10) |
|||
assert_equal(self.nodes[1].getblockcount(), 105) |
|||
assert_equal(self.nodes[1].getbalance(), 0) |
|||
|
|||
# only the oldest 5; subsequent are not yet mature |
|||
unspent_txids = [ u['txid'] for u in self.nodes[0].listunspent() ] |
|||
|
|||
# Currently our only unspents are coinbase transactions, choose any one |
|||
tx = self.nodes[0].getrawtransaction(unspent_txids[0], 1) |
|||
|
|||
# It just so happens that the first output is the mining reward, |
|||
# which has type pay-to-public-key-hash, and the second output |
|||
# is the founders' reward, which has type pay-to-script-hash. |
|||
addr_p2pkh = tx['vout'][0]['scriptPubKey']['addresses'][0] |
|||
addr_p2sh = tx['vout'][1]['scriptPubKey']['addresses'][0] |
|||
|
|||
# Check that balances from mining are correct (105 blocks mined); in |
|||
# regtest, all mining rewards from a single call to generate() are sent |
|||
# to the same pair of addresses. |
|||
check_balance(1, addr_p2pkh, 105 * 10 * COIN) |
|||
check_balance(1, addr_p2sh, 105 * 2.5 * COIN) |
|||
|
|||
# Multiple address arguments, results are the sum |
|||
check_balance(1, [addr_p2sh, addr_p2pkh], 105 * 12.5 * COIN) |
|||
|
|||
assert_equal(len(self.nodes[1].getaddresstxids(addr_p2pkh)), 105) |
|||
assert_equal(len(self.nodes[1].getaddresstxids(addr_p2sh)), 105) |
|||
|
|||
# only the oldest 5 transactions are in the unspent list, |
|||
# dup addresses are ignored |
|||
height_txids = getaddresstxids(1, [addr_p2pkh, addr_p2pkh], 1, 5) |
|||
assert_equal(sorted(height_txids), sorted(unspent_txids)) |
|||
|
|||
height_txids = getaddresstxids(1, [addr_p2sh], 1, 5) |
|||
assert_equal(sorted(height_txids), sorted(unspent_txids)) |
|||
|
|||
# each txid should appear only once |
|||
height_txids = getaddresstxids(1, [addr_p2pkh, addr_p2sh], 1, 5) |
|||
assert_equal(sorted(height_txids), sorted(unspent_txids)) |
|||
|
|||
# do some transfers, make sure balances are good |
|||
txids_a1 = [] |
|||
addr1 = self.nodes[1].getnewaddress() |
|||
expected = 0 |
|||
expected_deltas = [] # for checking getaddressdeltas (below) |
|||
for i in range(5): |
|||
# first transaction happens at height 105, mined in block 106 |
|||
txid = self.nodes[0].sendtoaddress(addr1, i + 1) |
|||
txids_a1.append(txid) |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
expected += i + 1 |
|||
expected_deltas.append({ |
|||
'height': 106 + i, |
|||
'satoshis': (i + 1) * COIN, |
|||
'txid': txid, |
|||
}) |
|||
check_balance(1, addr1, expected * COIN) |
|||
assert_equal(sorted(self.nodes[0].getaddresstxids(addr1)), sorted(txids_a1)) |
|||
assert_equal(sorted(self.nodes[1].getaddresstxids(addr1)), sorted(txids_a1)) |
|||
|
|||
# Restart all nodes to ensure indices are saved to disk and recovered |
|||
stop_nodes(self.nodes) |
|||
wait_bitcoinds() |
|||
self.setup_network() |
|||
|
|||
bal = self.nodes[1].getaddressbalance(addr1) |
|||
assert_equal(bal['balance'], expected * COIN) |
|||
assert_equal(bal['received'], expected * COIN) |
|||
assert_equal(sorted(self.nodes[0].getaddresstxids(addr1)), sorted(txids_a1)) |
|||
assert_equal(sorted(self.nodes[1].getaddresstxids(addr1)), sorted(txids_a1)) |
|||
|
|||
# Send 3 from addr1, but -- subtlety alert! -- addr1 at this |
|||
# time has 4 UTXOs, with values 1, 2, 3, 4. Sending value 3 requires |
|||
# using up the value 4 UTXO, because of the tx fee |
|||
# (the 3 UTXO isn't quite large enough). |
|||
# |
|||
# The txid from sending *from* addr1 is also added to the list of |
|||
# txids associated with that address (test will verify below). |
|||
|
|||
addr2 = self.nodes[2].getnewaddress() |
|||
txid = self.nodes[1].sendtoaddress(addr2, 3) |
|||
self.sync_all() |
|||
|
|||
# the one tx in the mempool refers to addresses addr1 and addr2, |
|||
# check that duplicate addresses are processed correctly |
|||
mempool = self.nodes[0].getaddressmempool({'addresses': [addr2, addr1, addr2]}) |
|||
assert_equal(len(mempool), 3) |
|||
|
|||
# addr2 (first arg) |
|||
assert_equal(mempool[0]['address'], addr2) |
|||
assert_equal(mempool[0]['satoshis'], 3 * COIN) |
|||
assert_equal(mempool[0]['txid'], txid) |
|||
|
|||
# addr1 (second arg) |
|||
assert_equal(mempool[1]['address'], addr1) |
|||
assert_equal(mempool[1]['satoshis'], (-4) * COIN) |
|||
assert_equal(mempool[1]['txid'], txid) |
|||
|
|||
# addr2 (third arg) |
|||
assert_equal(mempool[2]['address'], addr2) |
|||
assert_equal(mempool[2]['satoshis'], 3 * COIN) |
|||
assert_equal(mempool[2]['txid'], txid) |
|||
|
|||
# a single address can be specified as a string (not json object) |
|||
assert_equal([mempool[1]], self.nodes[0].getaddressmempool(addr1)) |
|||
|
|||
txids_a1.append(txid) |
|||
expected_deltas.append({ |
|||
'height': 111, |
|||
'satoshis': (-4) * COIN, |
|||
'txid': txid, |
|||
}) |
|||
self.sync_all() # ensure transaction is included in the next block |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
|
|||
# the send to addr2 tx is now in a mined block, no longer in the mempool |
|||
mempool = self.nodes[0].getaddressmempool({'addresses': [addr2, addr1]}) |
|||
assert_equal(len(mempool), 0) |
|||
|
|||
# Test DisconnectBlock() by invalidating the most recent mined block |
|||
tip = self.nodes[1].getchaintips()[0] |
|||
for i in range(3): |
|||
node = self.nodes[i] |
|||
# the value 4 UTXO is no longer in our balance |
|||
check_balance(i, addr1, (expected - 4) * COIN, expected * COIN) |
|||
check_balance(i, addr2, 3 * COIN) |
|||
|
|||
assert_equal(node.getblockcount(), 111) |
|||
node.invalidateblock(tip['hash']) |
|||
assert_equal(node.getblockcount(), 110) |
|||
|
|||
mempool = node.getaddressmempool({'addresses': [addr2, addr1]}) |
|||
assert_equal(len(mempool), 2) |
|||
|
|||
check_balance(i, addr1, expected * COIN) |
|||
check_balance(i, addr2, 0) |
|||
|
|||
# now re-mine the addr1 to addr2 send |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
for node in self.nodes: |
|||
assert_equal(node.getblockcount(), 111) |
|||
|
|||
mempool = self.nodes[0].getaddressmempool({'addresses': [addr2, addr1]}) |
|||
assert_equal(len(mempool), 0) |
|||
|
|||
# the value 4 UTXO is no longer in our balance |
|||
check_balance(2, addr1, (expected - 4) * COIN, expected * COIN) |
|||
|
|||
# Ensure the change from that transaction appears |
|||
tx = self.nodes[0].getrawtransaction(txid, 1) |
|||
change_vout = filter(lambda v: v['valueZat'] != 3 * COIN, tx['vout']) |
|||
change = change_vout[0]['scriptPubKey']['addresses'][0] |
|||
bal = self.nodes[2].getaddressbalance(change) |
|||
assert(bal['received'] > 0) |
|||
# the inequality is due to randomness in the tx fee |
|||
assert(bal['received'] < (4 - 3) * COIN) |
|||
assert_equal(bal['received'], bal['balance']) |
|||
assert_equal(self.nodes[2].getaddresstxids(change), [txid]) |
|||
|
|||
# Further checks that limiting by height works |
|||
|
|||
# various ranges |
|||
for i in range(5): |
|||
height_txids = getaddresstxids(1, [addr1], 106, 106 + i) |
|||
assert_equal(height_txids, txids_a1[0:i+1]) |
|||
|
|||
height_txids = getaddresstxids(1, [addr1], 1, 108) |
|||
assert_equal(height_txids, txids_a1[0:3]) |
|||
|
|||
# Further check specifying multiple addresses |
|||
txids_all = list(txids_a1) |
|||
txids_all += self.nodes[1].getaddresstxids(addr_p2pkh) |
|||
txids_all += self.nodes[1].getaddresstxids(addr_p2sh) |
|||
multitxids = self.nodes[1].getaddresstxids({ |
|||
'addresses': [addr1, addr_p2sh, addr_p2pkh] |
|||
}) |
|||
# No dups in return list from getaddresstxids |
|||
assert_equal(len(multitxids), len(set(multitxids))) |
|||
|
|||
# set(txids_all) removes its (expected) duplicates |
|||
assert_equal(set(multitxids), set(txids_all)) |
|||
|
|||
deltas = self.nodes[1].getaddressdeltas({'addresses': [addr1]}) |
|||
assert_equal(len(deltas), len(expected_deltas)) |
|||
for i in range(len(deltas)): |
|||
assert_equal(deltas[i]['address'], addr1) |
|||
assert_equal(deltas[i]['height'], expected_deltas[i]['height']) |
|||
assert_equal(deltas[i]['satoshis'], expected_deltas[i]['satoshis']) |
|||
assert_equal(deltas[i]['txid'], expected_deltas[i]['txid']) |
|||
|
|||
# 106-111 is the full range (also the default) |
|||
deltas_limited = getaddressdeltas(1, [addr1], 106, 111) |
|||
assert_equal(deltas_limited, deltas) |
|||
|
|||
# only the first element missing |
|||
deltas_limited = getaddressdeltas(1, [addr1], 107, 111) |
|||
assert_equal(deltas_limited, deltas[1:]) |
|||
|
|||
deltas_limited = getaddressdeltas(1, [addr1], 109, 109) |
|||
assert_equal(deltas_limited, deltas[3:4]) |
|||
|
|||
# the full range (also the default) |
|||
deltas_info = getaddressdeltas(1, [addr1], 106, 111, chainInfo=True) |
|||
assert_equal(deltas_info['deltas'], deltas) |
|||
|
|||
# check the additional items returned by chainInfo |
|||
assert_equal(deltas_info['start']['height'], 106) |
|||
block_hash = self.nodes[1].getblockhash(106) |
|||
assert_equal(deltas_info['start']['hash'], block_hash) |
|||
|
|||
assert_equal(deltas_info['end']['height'], 111) |
|||
block_hash = self.nodes[1].getblockhash(111) |
|||
assert_equal(deltas_info['end']['hash'], block_hash) |
|||
|
|||
# Test getaddressutxos by comparing results with deltas |
|||
utxos = self.nodes[1].getaddressutxos(addr1) |
|||
|
|||
# The value 4 note was spent, so won't show up in the utxo list, |
|||
# so for comparison, remove the 4 (and -4 for output) from the |
|||
# deltas list |
|||
deltas = self.nodes[1].getaddressdeltas({'addresses': [addr1]}) |
|||
deltas = filter(lambda d: abs(d['satoshis']) != 4 * COIN, deltas) |
|||
assert_equal(len(utxos), len(deltas)) |
|||
for i in range(len(utxos)): |
|||
assert_equal(utxos[i]['address'], addr1) |
|||
assert_equal(utxos[i]['height'], deltas[i]['height']) |
|||
assert_equal(utxos[i]['satoshis'], deltas[i]['satoshis']) |
|||
assert_equal(utxos[i]['txid'], deltas[i]['txid']) |
|||
|
|||
# Check that outputs with the same address in the same tx return one txid |
|||
# (can't use createrawtransaction() as it combines duplicate addresses) |
|||
addr = "t2LMJ6Arw9UWBMWvfUr2QLHM4Xd9w53FftS" |
|||
addressHash = "97643ce74b188f4fb6bbbb285e067a969041caf2".decode('hex') |
|||
scriptPubKey = CScript([OP_HASH160, addressHash, OP_EQUAL]) |
|||
# Add an unrecognized script type to vout[], a legal script that pays, |
|||
# but won't modify the addressindex (since the address can't be extracted). |
|||
# (This extra output has no effect on the rest of the test.) |
|||
scriptUnknown = CScript([OP_HASH160, OP_DUP, OP_DROP, addressHash, OP_EQUAL]) |
|||
unspent = filter(lambda u: u['amount'] >= 4, self.nodes[0].listunspent()) |
|||
tx = CTransaction() |
|||
tx.vin = [CTxIn(COutPoint(int(unspent[0]['txid'], 16), unspent[0]['vout']))] |
|||
tx.vout = [ |
|||
CTxOut(1 * COIN, scriptPubKey), |
|||
CTxOut(2 * COIN, scriptPubKey), |
|||
CTxOut(7 * COIN, scriptUnknown), |
|||
] |
|||
tx = self.nodes[0].signrawtransaction(hexlify(tx.serialize()).decode('utf-8')) |
|||
txid = self.nodes[0].sendrawtransaction(tx['hex'], True) |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
|
|||
assert_equal(self.nodes[1].getaddresstxids(addr), [txid]) |
|||
check_balance(2, addr, 3 * COIN) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
AddressIndexTest().main() |
Loading…
Reference in new issue