Merge #7822: Add listunspent() test for spendable/unspendable UTXO

5d217de Add test to check spendable and unspendable UTXO on RPC listunspent (Joao Fonseca)
fa942c7 Move method to check matches within arrays on util.py (Joao Fonseca)
This commit is contained in:
Wladimir J. van der Laan 2016-04-19 14:57:45 +02:00
commit 0e6fd5e4af
No known key found for this signature in database
GPG key ID: 74810B012346C9A6
7 changed files with 98 additions and 145 deletions

View file

@ -6,28 +6,6 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import * from test_framework.util import *
def check_array_result(object_array, to_match, expected):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
"""
num_matched = 0
for item in object_array:
all_match = True
for key,value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
for key,value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
num_matched = num_matched+1
if num_matched == 0:
raise AssertionError("No objects matched %s"%(str(to_match)))
import threading import threading
class LongpollThread(threading.Thread): class LongpollThread(threading.Thread):

View file

@ -10,28 +10,6 @@ from binascii import a2b_hex, b2a_hex
from hashlib import sha256 from hashlib import sha256
from struct import pack from struct import pack
def check_array_result(object_array, to_match, expected):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
"""
num_matched = 0
for item in object_array:
all_match = True
for key,value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
for key,value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
num_matched = num_matched+1
if num_matched == 0:
raise AssertionError("No objects matched %s"%(str(to_match)))
def b2x(b): def b2x(b):
return b2a_hex(b).decode('ascii') return b2a_hex(b).decode('ascii')

View file

@ -10,28 +10,6 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import * from test_framework.util import *
def check_array_result(object_array, to_match, expected):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
"""
num_matched = 0
for item in object_array:
all_match = True
for key,value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
for key,value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
num_matched = num_matched+1
if num_matched == 0:
raise AssertionError("No objects matched %s"%(str(to_match)))
class KeyPoolTest(BitcoinTestFramework): class KeyPoolTest(BitcoinTestFramework):
def run_test(self): def run_test(self):

View file

@ -16,27 +16,6 @@ def txFromHex(hexstring):
tx.deserialize(f) tx.deserialize(f)
return tx return tx
def check_array_result(object_array, to_match, expected):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
"""
num_matched = 0
for item in object_array:
all_match = True
for key,value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
for key,value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
num_matched = num_matched+1
if num_matched == 0:
raise AssertionError("No objects matched %s"%(str(to_match)))
class ListTransactionsTest(BitcoinTestFramework): class ListTransactionsTest(BitcoinTestFramework):
def setup_nodes(self): def setup_nodes(self):
@ -48,28 +27,28 @@ class ListTransactionsTest(BitcoinTestFramework):
# Simple send, 0 to 1: # Simple send, 0 to 1:
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
self.sync_all() self.sync_all()
check_array_result(self.nodes[0].listtransactions(), assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid}, {"txid":txid},
{"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":0}) {"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":0})
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"txid":txid}, {"txid":txid},
{"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":0}) {"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":0})
# mine a block, confirmations should change: # mine a block, confirmations should change:
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
check_array_result(self.nodes[0].listtransactions(), assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid}, {"txid":txid},
{"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":1}) {"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":1})
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"txid":txid}, {"txid":txid},
{"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":1}) {"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":1})
# send-to-self: # send-to-self:
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2)
check_array_result(self.nodes[0].listtransactions(), assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid, "category":"send"}, {"txid":txid, "category":"send"},
{"amount":Decimal("-0.2")}) {"amount":Decimal("-0.2")})
check_array_result(self.nodes[0].listtransactions(), assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid, "category":"receive"}, {"txid":txid, "category":"receive"},
{"amount":Decimal("0.2")}) {"amount":Decimal("0.2")})
@ -80,28 +59,28 @@ class ListTransactionsTest(BitcoinTestFramework):
self.nodes[1].getaccountaddress("toself") : 0.44 } self.nodes[1].getaccountaddress("toself") : 0.44 }
txid = self.nodes[1].sendmany("", send_to) txid = self.nodes[1].sendmany("", send_to)
self.sync_all() self.sync_all()
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.11")}, {"category":"send","amount":Decimal("-0.11")},
{"txid":txid} ) {"txid":txid} )
check_array_result(self.nodes[0].listtransactions(), assert_array_result(self.nodes[0].listtransactions(),
{"category":"receive","amount":Decimal("0.11")}, {"category":"receive","amount":Decimal("0.11")},
{"txid":txid} ) {"txid":txid} )
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.22")}, {"category":"send","amount":Decimal("-0.22")},
{"txid":txid} ) {"txid":txid} )
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"category":"receive","amount":Decimal("0.22")}, {"category":"receive","amount":Decimal("0.22")},
{"txid":txid} ) {"txid":txid} )
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.33")}, {"category":"send","amount":Decimal("-0.33")},
{"txid":txid} ) {"txid":txid} )
check_array_result(self.nodes[0].listtransactions(), assert_array_result(self.nodes[0].listtransactions(),
{"category":"receive","amount":Decimal("0.33")}, {"category":"receive","amount":Decimal("0.33")},
{"txid":txid, "account" : "from1"} ) {"txid":txid, "account" : "from1"} )
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.44")}, {"category":"send","amount":Decimal("-0.44")},
{"txid":txid, "account" : ""} ) {"txid":txid, "account" : ""} )
check_array_result(self.nodes[1].listtransactions(), assert_array_result(self.nodes[1].listtransactions(),
{"category":"receive","amount":Decimal("0.44")}, {"category":"receive","amount":Decimal("0.44")},
{"txid":txid, "account" : "toself"} ) {"txid":txid, "account" : "toself"} )
@ -111,7 +90,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.nodes[1].generate(1) self.nodes[1].generate(1)
self.sync_all() self.sync_all()
assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0) assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0)
check_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True), assert_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True),
{"category":"receive","amount":Decimal("0.1")}, {"category":"receive","amount":Decimal("0.1")},
{"txid":txid, "account" : "watchonly"} ) {"txid":txid, "account" : "watchonly"} )
@ -139,9 +118,9 @@ class ListTransactionsTest(BitcoinTestFramework):
# 1. Chain a few transactions that don't opt-in. # 1. Chain a few transactions that don't opt-in.
txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1) txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
assert(not is_opt_in(self.nodes[0], txid_1)) assert(not is_opt_in(self.nodes[0], txid_1))
check_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
sync_mempools(self.nodes) sync_mempools(self.nodes)
check_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
# Tx2 will build off txid_1, still not opting in to RBF. # Tx2 will build off txid_1, still not opting in to RBF.
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1) utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
@ -155,9 +134,9 @@ class ListTransactionsTest(BitcoinTestFramework):
# ...and check the result # ...and check the result
assert(not is_opt_in(self.nodes[1], txid_2)) assert(not is_opt_in(self.nodes[1], txid_2))
check_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"})
sync_mempools(self.nodes) sync_mempools(self.nodes)
check_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"}) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"})
# Tx3 will opt-in to RBF # Tx3 will opt-in to RBF
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2) utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2)
@ -171,9 +150,9 @@ class ListTransactionsTest(BitcoinTestFramework):
txid_3 = self.nodes[0].sendrawtransaction(tx3_signed) txid_3 = self.nodes[0].sendrawtransaction(tx3_signed)
assert(is_opt_in(self.nodes[0], txid_3)) assert(is_opt_in(self.nodes[0], txid_3))
check_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"}) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"})
sync_mempools(self.nodes) sync_mempools(self.nodes)
check_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"})
# Tx4 will chain off tx3. Doesn't signal itself, but depends on one # Tx4 will chain off tx3. Doesn't signal itself, but depends on one
# that does. # that does.
@ -185,9 +164,9 @@ class ListTransactionsTest(BitcoinTestFramework):
txid_4 = self.nodes[1].sendrawtransaction(tx4_signed) txid_4 = self.nodes[1].sendrawtransaction(tx4_signed)
assert(not is_opt_in(self.nodes[1], txid_4)) assert(not is_opt_in(self.nodes[1], txid_4))
check_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"})
sync_mempools(self.nodes) sync_mempools(self.nodes)
check_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"}) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"})
# Replace tx3, and check that tx4 becomes unknown # Replace tx3, and check that tx4 becomes unknown
tx3_b = tx3_modified tx3_b = tx3_modified
@ -197,9 +176,9 @@ class ListTransactionsTest(BitcoinTestFramework):
txid_3b = self.nodes[0].sendrawtransaction(tx3_b_signed, True) txid_3b = self.nodes[0].sendrawtransaction(tx3_b_signed, True)
assert(is_opt_in(self.nodes[0], txid_3b)) assert(is_opt_in(self.nodes[0], txid_3b))
check_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"}) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"})
sync_mempools(self.nodes) sync_mempools(self.nodes)
check_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"})
# Check gettransaction as well: # Check gettransaction as well:
for n in self.nodes[0:2]: for n in self.nodes[0:2]:

View file

@ -25,32 +25,6 @@ def get_sub_array_from_array(object_array, to_match):
return item return item
return [] return []
def check_array_result(object_array, to_match, expected, should_not_find = False):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
If the should_not_find flag is true, to_match should not be found in object_array
"""
if should_not_find == True:
expected = { }
num_matched = 0
for item in object_array:
all_match = True
for key,value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
for key,value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
num_matched = num_matched+1
if num_matched == 0 and should_not_find != True:
raise AssertionError("No objects matched %s"%(str(to_match)))
if num_matched > 0 and should_not_find == True:
raise AssertionError("Objects was matched %s"%(str(to_match)))
class ReceivedByTest(BitcoinTestFramework): class ReceivedByTest(BitcoinTestFramework):
def setup_nodes(self): def setup_nodes(self):
@ -68,26 +42,26 @@ class ReceivedByTest(BitcoinTestFramework):
self.sync_all() self.sync_all()
#Check not listed in listreceivedbyaddress because has 0 confirmations #Check not listed in listreceivedbyaddress because has 0 confirmations
check_array_result(self.nodes[1].listreceivedbyaddress(), assert_array_result(self.nodes[1].listreceivedbyaddress(),
{"address":addr}, {"address":addr},
{ }, { },
True) True)
#Bury Tx under 10 block so it will be returned by listreceivedbyaddress #Bury Tx under 10 block so it will be returned by listreceivedbyaddress
self.nodes[1].generate(10) self.nodes[1].generate(10)
self.sync_all() self.sync_all()
check_array_result(self.nodes[1].listreceivedbyaddress(), assert_array_result(self.nodes[1].listreceivedbyaddress(),
{"address":addr}, {"address":addr},
{"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]}) {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]})
#With min confidence < 10 #With min confidence < 10
check_array_result(self.nodes[1].listreceivedbyaddress(5), assert_array_result(self.nodes[1].listreceivedbyaddress(5),
{"address":addr}, {"address":addr},
{"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]}) {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]})
#With min confidence > 10, should not find Tx #With min confidence > 10, should not find Tx
check_array_result(self.nodes[1].listreceivedbyaddress(11),{"address":addr},{ },True) assert_array_result(self.nodes[1].listreceivedbyaddress(11),{"address":addr},{ },True)
#Empty Tx #Empty Tx
addr = self.nodes[1].getnewaddress() addr = self.nodes[1].getnewaddress()
check_array_result(self.nodes[1].listreceivedbyaddress(0,True), assert_array_result(self.nodes[1].listreceivedbyaddress(0,True),
{"address":addr}, {"address":addr},
{"address":addr, "account":"", "amount":0, "confirmations":0, "txids":[]}) {"address":addr, "account":"", "amount":0, "confirmations":0, "txids":[]})
@ -131,7 +105,7 @@ class ReceivedByTest(BitcoinTestFramework):
self.sync_all() self.sync_all()
# listreceivedbyaccount should return received_by_account_json because of 0 confirmations # listreceivedbyaccount should return received_by_account_json because of 0 confirmations
check_array_result(self.nodes[1].listreceivedbyaccount(), assert_array_result(self.nodes[1].listreceivedbyaccount(),
{"account":account}, {"account":account},
received_by_account_json) received_by_account_json)
@ -143,7 +117,7 @@ class ReceivedByTest(BitcoinTestFramework):
self.nodes[1].generate(10) self.nodes[1].generate(10)
self.sync_all() self.sync_all()
# listreceivedbyaccount should return updated account balance # listreceivedbyaccount should return updated account balance
check_array_result(self.nodes[1].listreceivedbyaccount(), assert_array_result(self.nodes[1].listreceivedbyaccount(),
{"account":account}, {"account":account},
{"account":received_by_account_json["account"], "amount":(received_by_account_json["amount"] + Decimal("0.1"))}) {"account":received_by_account_json["account"], "amount":(received_by_account_json["amount"] + Decimal("0.1"))})

View file

@ -478,6 +478,35 @@ def assert_is_hash_string(string, length=64):
raise AssertionError( raise AssertionError(
"String %r contains invalid characters for a hash." % string) "String %r contains invalid characters for a hash." % string)
def assert_array_result(object_array, to_match, expected, should_not_find = False):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
If the should_not_find flag is true, to_match should not be found
in object_array
"""
if should_not_find == True:
expected = { }
num_matched = 0
for item in object_array:
all_match = True
for key,value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
elif should_not_find == True:
num_matched = num_matched+1
for key,value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
num_matched = num_matched+1
if num_matched == 0 and should_not_find != True:
raise AssertionError("No objects matched %s"%(str(to_match)))
if num_matched > 0 and should_not_find == True:
raise AssertionError("Objects were found %s"%(str(to_match)))
def satoshi_round(amount): def satoshi_round(amount):
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)

View file

@ -32,6 +32,12 @@ class WalletTest (BitcoinTestFramework):
self.sync_all() self.sync_all()
def run_test (self): def run_test (self):
# Check that there's no UTXO on none of the nodes
assert_equal(len(self.nodes[0].listunspent()), 0)
assert_equal(len(self.nodes[1].listunspent()), 0)
assert_equal(len(self.nodes[2].listunspent()), 0)
print "Mining blocks..." print "Mining blocks..."
self.nodes[0].generate(1) self.nodes[0].generate(1)
@ -48,6 +54,11 @@ class WalletTest (BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(), 50) assert_equal(self.nodes[1].getbalance(), 50)
assert_equal(self.nodes[2].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 0)
# Check that only first and second nodes have UTXOs
assert_equal(len(self.nodes[0].listunspent()), 1)
assert_equal(len(self.nodes[1].listunspent()), 1)
assert_equal(len(self.nodes[2].listunspent()), 0)
# Send 21 BTC from 0 to 2 using sendtoaddress call. # Send 21 BTC from 0 to 2 using sendtoaddress call.
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
@ -259,6 +270,32 @@ class WalletTest (BitcoinTestFramework):
except JSONRPCException as e: except JSONRPCException as e:
assert("not an integer" in e.error['message']) assert("not an integer" in e.error['message'])
# Import address and private key to check correct behavior of spendable unspents
# 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
self.nodes[0].generate(1)
self.sync_all()
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
# 3. Validate that the imported address is watch-only on node1
assert(self.nodes[1].validateaddress(address_to_import)["iswatchonly"])
# 4. Check that the unspents after import are not spendable
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": False})
# 5. Import private key of the previously imported address on node1
priv_key = self.nodes[2].dumpprivkey(address_to_import)
self.nodes[1].importprivkey(priv_key)
# 6. Check that the unspents are now spendable on node1
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": True})
# Mine a block from node0 to an address from node1 # Mine a block from node0 to an address from node1
cbAddr = self.nodes[1].getnewaddress() cbAddr = self.nodes[1].getnewaddress()