mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-05-30 07:48:09 +08:00
expand UTXO-consumption integration test to verify that no "small" UTXOs will be consumed by any transaction, even though they're available. Also, expand the test to check renewal and revoke.
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
import testlib
|
||||
import pybitcoin
|
||||
import json
|
||||
import simplejson
|
||||
import blockstack_client
|
||||
|
||||
wallets = [
|
||||
@@ -36,92 +37,305 @@ wallets = [
|
||||
|
||||
debug = False
|
||||
consensus = "17ac43c1d8549c3181b200f1bf97eb7d"
|
||||
small_unspents = []
|
||||
|
||||
def check_utxo_consumption( name_or_ns, payment_wallet, owner_wallet, data_wallet, operations, recipient_address, **kw):
|
||||
|
||||
global small_unspents
|
||||
|
||||
wallet = testlib.blockstack_client_initialize_wallet( "0123456789abcdef", payment_wallet.privkey, owner_wallet.privkey, data_wallet.privkey )
|
||||
test_proxy = testlib.TestAPIProxy()
|
||||
blockstack_client.set_default_proxy( test_proxy )
|
||||
|
||||
# estimate the fee for the operation sequence
|
||||
fees = testlib.blockstack_cli_price(name_or_ns, "0123456789abcdef", recipient_address=recipient_address, operations=operations)
|
||||
if 'error' in fees:
|
||||
return fees
|
||||
|
||||
print "without UTXOs:"
|
||||
print simplejson.dumps(fees, indent=4, sort_keys=True)
|
||||
|
||||
# make a few small UTXOs for the payment address
|
||||
# count up the number of UTXOs that exist for this payment address
|
||||
payment_utxos = testlib.get_utxos(payment_wallet.addr)
|
||||
expected_utxo_count = len(payment_utxos)
|
||||
for i in xrange(0, 1):
|
||||
senders = wallets
|
||||
for sender in senders:
|
||||
if sender.privkey == payment_wallet.privkey:
|
||||
continue
|
||||
|
||||
res = testlib.send_funds( sender.privkey, 10000, payment_wallet.addr )
|
||||
if 'error' in res:
|
||||
print simplejson.dumps(res, indent=4, sort_keys=True)
|
||||
return res
|
||||
|
||||
expected_utxo_count += 1
|
||||
small_unspents.append(res['transaction_hash'])
|
||||
|
||||
testlib.next_block(**kw)
|
||||
|
||||
# estimate the fee with all the UTXOs
|
||||
fees_utxos = testlib.blockstack_cli_price(name_or_ns, "0123456789abcdef", recipient_address=recipient_address, operations=operations)
|
||||
if 'error' in fees_utxos:
|
||||
return fees_utxos
|
||||
|
||||
print "with UTXOs:"
|
||||
print simplejson.dumps(fees_utxos, indent=4, sort_keys=True)
|
||||
|
||||
# all our operations should have similar fees, regardless of the UTXO set
|
||||
for tx_fee_key in ['{}_tx_fee'.format(op) for op in operations]:
|
||||
fee_diff = abs(fees[tx_fee_key]['satoshis'] - fees_utxos[tx_fee_key]['satoshis'])
|
||||
if fee_diff > 5500:
|
||||
print 'tx fees for {} disagree by {}'.format(tx_fee_key, fee_diff)
|
||||
return {'error': 'No appreciable fee change'}
|
||||
|
||||
return {'status': True, 'expected_utxo_count': expected_utxo_count}
|
||||
|
||||
|
||||
def spent_small_transaction(txhash):
|
||||
"""
|
||||
Did we spend a "small" tx by mistake?
|
||||
"""
|
||||
|
||||
# verify that all the small UTXOs are NOT consumed
|
||||
bitcoind = testlib.connect_bitcoind()
|
||||
bitcoind.ping()
|
||||
|
||||
txdata = bitcoind.getrawtransaction(txhash, 1)
|
||||
for vin in txdata['vin']:
|
||||
input_tx = bitcoind.getrawtransaction(vin['txid'], 1)
|
||||
consumed_out = input_tx['vout'][vin['vout']]
|
||||
if consumed_out['value'] <= 0.00010001 and consumed_out['value'] >= 0.00009999:
|
||||
print '\n{} spent small UTXO {}\n{}'.format(txhash, vin['txid'], simplejson.dumps(bitcoind.getrawtransaction(vin['txid'], 1), indent=4, sort_keys=True))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def scenario( wallets, **kw ):
|
||||
|
||||
global debug, consensus
|
||||
global debug, consensus, small_unspents
|
||||
|
||||
res = check_utxo_consumption("test", wallets[0], wallets[1], wallets[2], ['namespace_preorder', 'namespace_reveal', 'namespace_ready'], wallets[1].addr, **kw)
|
||||
if 'error' in res:
|
||||
return False
|
||||
|
||||
expected_utxo_count = res['expected_utxo_count']
|
||||
|
||||
# do the preorder
|
||||
resp = testlib.blockstack_namespace_preorder( "test", wallets[1].addr, wallets[0].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
# verify that all the small UTXOs are NOT consumed
|
||||
bitcoind = testlib.connect_bitcoind()
|
||||
bitcoind.ping()
|
||||
|
||||
txdata = bitcoind.getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 1:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "wrong number of inputs: {} != 1".format(len(txdata['vin']))
|
||||
return False
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
# finish ordering the namespace
|
||||
resp = testlib.blockstack_namespace_reveal( "test", wallets[1].addr, 52595, 250, 4, [6,5,4,3,2,1,0,0,0,0,0,0,0,0,0,0], 10, 10, wallets[0].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
resp = testlib.blockstack_namespace_ready( "test", wallets[1].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
res = check_utxo_consumption("foo.test", wallets[2], wallets[3], wallets[4], ['preorder', 'register', 'update', 'transfer'], wallets[4].addr, **kw)
|
||||
if 'error' in res:
|
||||
return False
|
||||
|
||||
expected_utxo_count = res['expected_utxo_count']
|
||||
|
||||
resp = testlib.blockstack_name_preorder( "foo.test", wallets[2].privkey, wallets[3].addr )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
# verify that all the small UTXOs are NOT consumed
|
||||
bitcoind = testlib.connect_bitcoind()
|
||||
bitcoind.ping()
|
||||
|
||||
txdata = bitcoind.getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 1:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "wrong number of inputs: {} != {}".format(len(txdata['vin']), expected_utxo_count)
|
||||
return False
|
||||
|
||||
# proceed to register
|
||||
resp = testlib.blockstack_name_register( "foo.test", wallets[2].privkey, wallets[3].addr )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
# verify that all the UTXOs are consumed
|
||||
bitcoind = testlib.connect_bitcoind()
|
||||
bitcoind.ping()
|
||||
|
||||
txdata = bitcoind.getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 1:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "wrong number of inputs: {} != {}".format(len(txdata['vin']), expected_utxo_count)
|
||||
return False
|
||||
|
||||
# make a few small UTXOs for the preorder payment addr
|
||||
for i in xrange(0, 3):
|
||||
res = testlib.send_funds( wallets[1].privkey, 100000, testlib.get_default_payment_wallet().addr)
|
||||
res = testlib.send_funds( wallets[1].privkey, 10000, testlib.get_default_payment_wallet().addr)
|
||||
if 'error' in res:
|
||||
print json.dumps(res, indent=4, sort_keys=True)
|
||||
print simplejson.dumps(res, indent=4, sort_keys=True)
|
||||
return False
|
||||
|
||||
testlib.next_block(**kw)
|
||||
small_unspents.append(res['transaction_hash'])
|
||||
|
||||
utxos = testlib.get_utxos(testlib.get_default_payment_wallet().addr)
|
||||
assert len(utxos) > 3
|
||||
|
||||
resp = testlib.blockstack_name_update( "foo.test", "11" * 20, wallets[3].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
consensus = testlib.get_consensus_at( testlib.get_current_block(**kw), **kw)
|
||||
testlib.next_block( **kw )
|
||||
|
||||
# inspect the transaction: only one UTXO should have been consumed
|
||||
txdata = testlib.get_bitcoind().getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) > 3:
|
||||
print json.dumps(txdata, indent=4)
|
||||
# inspect the transaction: only 3 UTXOs should have been consumed (2 owner UTXOs and 1 payment UTXO)
|
||||
txdata = testlib.connect_bitcoind().getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 3:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "too many inputs"
|
||||
return False
|
||||
|
||||
# make a few more small UTXOs for the preorder payment addr
|
||||
for i in xrange(0, 3):
|
||||
res = testlib.send_funds( wallets[1].privkey, 100000, testlib.get_default_payment_wallet().addr )
|
||||
res = testlib.send_funds( wallets[1].privkey, 10000, testlib.get_default_payment_wallet().addr )
|
||||
if 'error' in res:
|
||||
print json.dumps(res, indent=4, sort_keys=True)
|
||||
print simplejson.dumps(res, indent=4, sort_keys=True)
|
||||
return False
|
||||
|
||||
testlib.next_block(**kw)
|
||||
small_unspents.append(res['transaction_hash'])
|
||||
|
||||
utxos = testlib.get_utxos(testlib.get_default_payment_wallet().addr)
|
||||
assert len(utxos) > 3
|
||||
|
||||
resp = testlib.blockstack_name_transfer( "foo.test", wallets[4].addr, True, wallets[3].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print json.dumps( resp, indent=4 )
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
# inspect the transaction: only one UTXO should have been consumed
|
||||
txdata = testlib.get_bitcoind().getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) > 2:
|
||||
print json.dumps(txdata, indent=4)
|
||||
# inspect the transaction: only 2 UTXOs should have been consumed (1 owner UTXO and 1 payment UTXO)
|
||||
txdata = testlib.connect_bitcoind().getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 2:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "too many inputs"
|
||||
return False
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
# make a few more small UTXOs for the preorder payment addr
|
||||
for i in xrange(0, 3):
|
||||
res = testlib.send_funds( wallets[1].privkey, 10000, testlib.get_default_payment_wallet().addr )
|
||||
if 'error' in res:
|
||||
print simplejson.dumps(res, indent=4, sort_keys=True)
|
||||
return False
|
||||
|
||||
testlib.next_block(**kw)
|
||||
small_unspents.append(res['transaction_hash'])
|
||||
|
||||
utxos = testlib.get_utxos(testlib.get_default_payment_wallet().addr)
|
||||
assert len(utxos) > 3
|
||||
|
||||
resp = testlib.blockstack_name_renew( "foo.test", wallets[4].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
# inspect the transaction: only 3 UTXOs should have been consumed (2 owner UTXO and 1 payment UTXO)
|
||||
# NOTE: produces two UTXOs: an "owner" utxo and the change for the owner address
|
||||
txdata = testlib.connect_bitcoind().getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 3:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "too many inputs"
|
||||
return False
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
|
||||
# make a few more small UTXOs for the preorder payment addr
|
||||
for i in xrange(0, 3):
|
||||
res = testlib.send_funds( wallets[1].privkey, 10000, testlib.get_default_payment_wallet().addr )
|
||||
if 'error' in res:
|
||||
print simplejson.dumps(res, indent=4, sort_keys=True)
|
||||
return False
|
||||
|
||||
testlib.next_block(**kw)
|
||||
small_unspents.append(res['transaction_hash'])
|
||||
|
||||
utxos = testlib.get_utxos(testlib.get_default_payment_wallet().addr)
|
||||
assert len(utxos) > 3
|
||||
|
||||
resp = testlib.blockstack_name_revoke( "foo.test", wallets[4].privkey )
|
||||
if debug or 'error' in resp:
|
||||
print simplejson.dumps( resp, indent=4 )
|
||||
|
||||
# inspect the transaction: only 3 UTXOs should have been consumed (2 owner UTXO and 1 payment UTXO)
|
||||
txdata = testlib.connect_bitcoind().getrawtransaction(resp['transaction_hash'], 1)
|
||||
if len(txdata['vin']) != 3:
|
||||
print simplejson.dumps(txdata, indent=4)
|
||||
print "too many inputs"
|
||||
return False
|
||||
|
||||
if spent_small_transaction(resp['transaction_hash']):
|
||||
return False
|
||||
|
||||
testlib.next_block( **kw )
|
||||
'''
|
||||
# all unspents should be unspent
|
||||
all_unspents = testlib.connect_bitcoind().listunspent()
|
||||
all_unspent_txids = [u['txid'] for u in all_unspents]
|
||||
valid = True
|
||||
for i in xrange(0, len(small_unspents)):
|
||||
unspent_txid = small_unspents[i]
|
||||
if unspent_txid not in all_unspent_txids:
|
||||
print "Spent small transaction {}: {}".format(i, unspent_txid)
|
||||
valid = False
|
||||
'''
|
||||
|
||||
|
||||
def check( state_engine ):
|
||||
|
||||
@@ -152,8 +366,8 @@ def check( state_engine ):
|
||||
print "'foo.test' not registered"
|
||||
return False
|
||||
|
||||
# updated, and data is preserved
|
||||
if name_rec['value_hash'] != '11' * 20:
|
||||
# updated, but revoked
|
||||
if name_rec['value_hash'] is not None:
|
||||
print "'foo.test' invalid value hash"
|
||||
return False
|
||||
|
||||
@@ -167,4 +381,9 @@ def check( state_engine ):
|
||||
print "quirk not preserved: current consensus %s != %s" % (name_rec['consensus_hash'], consensus)
|
||||
return False
|
||||
|
||||
# revoked
|
||||
if not name_rec['revoked']:
|
||||
print 'Name is not revoked'
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user