neobytes/qa/rpc-tests/dip3-deterministicmns.py
Alexander Block 9da9d575aa Allow collaterals for non-DIP3 MNs which were created after DIP3/BIP9 activation (#2412)
* Remove reference to now removed "protocolVersion"

* Allow to use non-DIP3 collaterals after DIP3 activation

This removes a rule which only made sense when we assumed all collaterals
would remain in DIP3 ProRegTx transactions after DIP3 activation.

As we allow to refer external collaterals now, this rule should vanish as
well.

* Sleep in case block time is ahead of real time

See comment. This was not a problem before as we had enough time passed
between the last block that caused this and the start-alias call.
Now we added another start-alias call which is directly after we generated
many blocks.
2018-11-04 14:55:50 +03:00

767 lines
31 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2015-2018 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
# Test deterministic masternodes
#
from test_framework.blocktools import create_block, create_coinbase, get_masternode_payment
from test_framework.mininode import CTransaction, ToHex, FromHex, CTxOut, COIN, CCbTx
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
class Masternode(object):
pass
class DIP3Test(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.num_initial_mn = 11 # Should be >= 11 to make sure quorums are not always the same MNs
self.num_nodes = 1 + self.num_initial_mn + 2 # +1 for controller, +1 for mn-qt, +1 for mn created after dip3 activation
self.setup_clean_chain = True
self.extra_args = ["-budgetparams=240:100:240"]
self.extra_args += ["-sporkkey=cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK"]
def setup_network(self):
disable_mocktime()
self.start_controller_node()
self.is_network_split = False
def start_controller_node(self, extra_args=None):
print("starting controller node")
if self.nodes is None:
self.nodes = [None]
args = self.extra_args
if extra_args is not None:
args += extra_args
self.nodes[0] = start_node(0, self.options.tmpdir, extra_args=args)
for i in range(1, self.num_nodes):
if i < len(self.nodes) and self.nodes[i] is not None:
connect_nodes_bi(self.nodes, 0, i)
def stop_controller_node(self):
print("stopping controller node")
stop_node(self.nodes[0], 0)
def restart_controller_node(self):
self.stop_controller_node()
self.start_controller_node()
def run_test(self):
print("funding controller node")
while self.nodes[0].getbalance() < (self.num_initial_mn + 3) * 1000:
self.nodes[0].generate(1) # generate enough for collaterals
print("controller node has {} dash".format(self.nodes[0].getbalance()))
# Make sure we're below block 143 (which activates dip3)
print("testing rejection of ProTx before dip3 activation")
assert(self.nodes[0].getblockchaininfo()['blocks'] < 143)
dip3_deployment = self.nodes[0].getblockchaininfo()['bip9_softforks']['dip0003']
assert_equal(dip3_deployment['status'], 'defined')
self.test_fail_create_protx(self.nodes[0])
mns = []
mn_idx = 1
for i in range(self.num_initial_mn):
mn = self.create_mn(self.nodes[0], mn_idx, 'mn-%d' % (mn_idx))
mn_idx += 1
mns.append(mn)
# mature collaterals
for i in range(3):
self.nodes[0].generate(1)
time.sleep(1)
self.write_mnconf(mns)
self.restart_controller_node()
for mn in mns:
self.start_mn(mn)
self.sync_all()
# force finishing of mnsync
for node in self.nodes:
self.force_finish_mnsync(node)
# start MNs
print("start mns")
for mn in mns:
self.start_alias(self.nodes[0], mn.alias)
print("wait for MNs to appear in MN lists")
self.wait_for_mnlists(mns, True, False)
print("testing MN payment votes")
self.test_mn_votes(10)
print("testing instant send")
self.test_instantsend(10, 5)
print("testing rejection of ProTx before dip3 activation (in states defined, started and locked_in)")
while self.nodes[0].getblockchaininfo()['bip9_softforks']['dip0003']['status'] == 'defined':
self.nodes[0].generate(1)
self.test_fail_create_protx(self.nodes[0])
while self.nodes[0].getblockchaininfo()['bip9_softforks']['dip0003']['status'] == 'started':
self.nodes[0].generate(1)
self.test_fail_create_protx(self.nodes[0])
# prepare mn which should still be accepted later when dip3 activates
print("creating collateral for mn-before-dip3")
before_dip3_mn = self.create_mn(self.nodes[0], mn_idx, 'mn-before-dip3')
mn_idx += 1
while self.nodes[0].getblockchaininfo()['bip9_softforks']['dip0003']['status'] == 'locked_in':
self.nodes[0].generate(1)
# We have hundreds of blocks to sync here, give it more time
print("syncing blocks for all nodes")
sync_blocks(self.nodes, timeout=120)
# DIP3 has activated here
print("testing rejection of ProTx right before dip3 activation")
best_block = self.nodes[0].getbestblockhash()
self.nodes[0].invalidateblock(best_block)
self.test_fail_create_protx(self.nodes[0])
self.nodes[0].reconsiderblock(best_block)
# Now it should be possible to mine ProTx
self.sync_all()
self.test_success_create_protx(self.nodes[0])
print("creating collateral for mn-after-dip3")
after_dip3_mn = self.create_mn(self.nodes[0], mn_idx, 'mn-after-dip3')
# mature collaterals
for i in range(3):
self.nodes[0].generate(1)
time.sleep(1)
print("testing if we can start a mn which was created before dip3 activation")
self.write_mnconf(mns + [before_dip3_mn, after_dip3_mn])
self.restart_controller_node()
self.force_finish_mnsync(self.nodes[0])
print("start MN %s" % before_dip3_mn.alias)
mns.append(before_dip3_mn)
self.start_mn(before_dip3_mn)
self.wait_for_sporks()
self.force_finish_mnsync_list(before_dip3_mn.node)
self.start_alias(self.nodes[0], before_dip3_mn.alias)
self.wait_for_mnlists(mns)
self.wait_for_mnlists_same()
# Test if nodes still allow creating new non-ProTx MNs now
print("testing if MN start succeeds when using collateral which was created after dip3 activation")
print("start MN %s" % after_dip3_mn.alias)
mns.append(after_dip3_mn)
self.start_mn(after_dip3_mn)
self.wait_for_sporks()
self.force_finish_mnsync_list(after_dip3_mn.node)
self.start_alias(self.nodes[0], after_dip3_mn.alias)
self.wait_for_mnlists(mns)
self.wait_for_mnlists_same()
first_upgrade_count = 5
mns_after_upgrade = []
mns_to_restart = []
mns_protx = []
print("upgrading first %d MNs to use ProTx (but not deterministic MN lists)" % first_upgrade_count)
for i in range(first_upgrade_count):
# let a few of the protx MNs refer to the old collaterals
fund = (i % 2) == 0
mns[i] = self.upgrade_mn_protx(mns[i], fund)
self.nodes[0].generate(1)
if fund:
# collateral has moved, so we need to start it again
mns_to_restart.append(mns[i])
else:
# collateral has not moved, so it should still be in the masternode list even after upgrade
mns_after_upgrade.append(mns[i])
mns_protx.append(mns[i])
for i in range(first_upgrade_count, len(mns)):
mns_after_upgrade.append(mns[i])
self.write_mnconf(mns)
print("wait for freshly funded and upgraded MNs to disappear from MN lists (their collateral was spent)")
self.wait_for_mnlists(mns_after_upgrade, check=True)
self.wait_for_mnlists_same()
print("restarting controller and upgraded MNs")
self.restart_controller_node()
self.force_finish_mnsync_list(self.nodes[0])
for mn in mns_to_restart:
print("restarting MN %s" % mn.alias)
self.stop_node(mn.idx)
self.start_mn(mn)
self.force_finish_mnsync_list(mn.node)
print('start-alias on upgraded nodes')
for mn in mns_to_restart:
self.start_alias(self.nodes[0], mn.alias)
print("wait for upgraded MNs to appear in MN list")
self.wait_for_mnlists(mns)
self.wait_for_mnlists_same()
print("testing MN payment votes (with mixed ProTx and legacy nodes)")
self.test_mn_votes(10, test_enforcement=True)
print("testing instant send (with mixed ProTx and legacy nodes)")
self.test_instantsend(10, 5)
print("activating spork15")
height = self.nodes[0].getblockchaininfo()['blocks']
spork15_offset = 10
self.nodes[0].spork('SPORK_15_DETERMINISTIC_MNS_ENABLED', height + spork15_offset)
self.wait_for_sporks()
print("test that MN list does not change before final spork15 activation")
for i in range(spork15_offset - 1):
self.nodes[0].generate(1)
self.sync_all()
self.wait_for_mnlists(mns)
self.wait_for_mnlists_same()
print("mining final block which should switch network to deterministic lists")
self.nodes[0].generate(1)
self.sync_all()
##### WOW...we made it...we are in deterministic MN lists mode now.
##### From now on, we don't wait for mnlists to become correct anymore, we always assert that they are correct immediately
print("assert that not upgraded MNs disappeared from MN list")
self.assert_mnlists(mns_protx)
# enable enforcement and keep it on from now on
self.nodes[0].spork('SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT', 0)
self.wait_for_sporks()
print("test that MNs disappear from the list when the ProTx collateral is spent")
spend_mns_count = 3
mns_tmp = [] + mns_protx
dummy_txins = []
for i in range(spend_mns_count):
dummy_txin = self.spend_mn_collateral(mns_protx[i], with_dummy_input_output=True)
dummy_txins.append(dummy_txin)
self.nodes[0].generate(1)
self.sync_all()
mns_tmp.remove(mns_protx[i])
self.assert_mnlists(mns_tmp)
print("test that reverting the blockchain on a single node results in the mnlist to be reverted as well")
for i in range(spend_mns_count):
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
mns_tmp.append(mns_protx[spend_mns_count - 1 - i])
self.assert_mnlist(self.nodes[0], mns_tmp)
print("cause a reorg with a double spend and check that mnlists are still correct on all nodes")
self.mine_double_spend(self.nodes[0], dummy_txins, self.nodes[0].getnewaddress(), use_mnmerkleroot_from_tip=True)
self.nodes[0].generate(spend_mns_count)
self.sync_all()
self.assert_mnlists(mns_tmp)
print("upgrade remaining MNs to ProTx")
for i in range(first_upgrade_count, len(mns)):
mns[i] = self.upgrade_mn_protx(mns[i], True)
mn = mns[i]
self.nodes[0].generate(1)
mns_protx.append(mn)
print("restarting MN %s" % mn.alias)
self.stop_node(mn.idx)
self.start_mn(mn)
self.sync_all()
self.force_finish_mnsync(mn.node)
self.assert_mnlists(mns_protx)
self.assert_mnlists(mns_protx)
print("test mn payment enforcement with deterministic MNs")
for i in range(20):
node = self.nodes[i % len(self.nodes)]
self.test_invalid_mn_payment(node)
node.generate(1)
self.sync_all()
print("testing instant send with deterministic MNs")
self.test_instantsend(20, 5)
print("testing ProUpServTx")
for mn in mns_protx:
self.test_protx_update_service(mn)
def create_mn(self, node, idx, alias):
mn = Masternode()
mn.idx = idx
mn.alias = alias
mn.is_protx = False
mn.p2p_port = p2p_port(mn.idx)
blsKey = node.bls('generate')
mn.legacyMnkey = node.masternode('genkey')
mn.blsMnkey = blsKey['secret']
mn.collateral_address = node.getnewaddress()
mn.collateral_txid = node.sendtoaddress(mn.collateral_address, 1000)
rawtx = node.getrawtransaction(mn.collateral_txid, 1)
mn.collateral_vout = -1
for txout in rawtx['vout']:
if txout['value'] == Decimal(1000):
mn.collateral_vout = txout['n']
break
assert(mn.collateral_vout != -1)
lock = node.lockunspent(False, [{'txid': mn.collateral_txid, 'vout': mn.collateral_vout}])
return mn
def create_mn_protx_base(self, node, idx, alias):
mn = Masternode()
mn.idx = idx
mn.alias = alias
mn.is_protx = True
mn.p2p_port = p2p_port(mn.idx)
blsKey = node.bls('generate')
mn.ownerAddr = node.getnewaddress()
mn.operatorAddr = blsKey['public']
mn.votingAddr = mn.ownerAddr
mn.legacyMnkey = node.masternode('genkey')
mn.blsMnkey = blsKey['secret']
return mn
# create a protx MN and also fund it (using collateral inside ProRegTx)
def create_mn_protx_fund(self, node, idx, alias):
mn = self.create_mn_protx_base(node, idx, alias)
mn.collateral_address = node.getnewaddress()
mn.protx_hash = node.protx('register_fund', mn.collateral_address, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.collateral_address)
mn.collateral_txid = mn.protx_hash
mn.collateral_vout = -1
rawtx = node.getrawtransaction(mn.collateral_txid, 1)
for txout in rawtx['vout']:
if txout['value'] == Decimal(1000):
mn.collateral_vout = txout['n']
break
assert(mn.collateral_vout != -1)
return mn
# create a protx MN which refers to an existing collateral
def create_mn_protx(self, node, idx, alias, collateral_txid, collateral_vout):
mn = self.create_mn_protx_base(node, idx, alias)
mn.rewards_address = node.getnewaddress()
mn.protx_hash = node.protx('register', collateral_txid, collateral_vout, '127.0.0.1:%d' % mn.p2p_port, mn.ownerAddr, mn.operatorAddr, mn.votingAddr, 0, mn.rewards_address)
mn.collateral_txid = collateral_txid
mn.collateral_vout = collateral_vout
return mn
def start_mn(self, mn):
while len(self.nodes) <= mn.idx:
self.nodes.append(None)
extra_args = ['-masternode=1', '-masternodeprivkey=%s' % mn.legacyMnkey, '-masternodeblsprivkey=%s' % mn.blsMnkey]
n = start_node(mn.idx, self.options.tmpdir, self.extra_args + extra_args, redirect_stderr=True)
self.nodes[mn.idx] = n
for i in range(0, self.num_nodes):
if i < len(self.nodes) and self.nodes[i] is not None and i != mn.idx:
connect_nodes_bi(self.nodes, mn.idx, i)
mn.node = self.nodes[mn.idx]
self.sync_all()
def spend_mn_collateral(self, mn, with_dummy_input_output=False):
return self.spend_input(mn.collateral_txid, mn.collateral_vout, 1000, with_dummy_input_output)
def upgrade_mn_protx(self, mn, refund):
if refund:
self.spend_mn_collateral(mn)
mn = self.create_mn_protx_fund(self.nodes[0], mn.idx, 'mn-protx-%d' % mn.idx)
else:
mn = self.create_mn_protx(self.nodes[0], mn.idx, 'mn-protx-%d' % mn.idx, mn.collateral_txid, mn.collateral_vout)
return mn
def test_protx_update_service(self, mn):
self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.2:%d' % mn.p2p_port, mn.blsMnkey)
self.nodes[0].generate(1)
self.sync_all()
for node in self.nodes:
protx_info = node.protx('info', mn.protx_hash)
mn_list = node.masternode('list')
assert_equal(protx_info['state']['addr'], '127.0.0.2:%d' % mn.p2p_port)
assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.p2p_port)
# undo
self.nodes[0].protx('update_service', mn.protx_hash, '127.0.0.1:%d' % mn.p2p_port, mn.blsMnkey)
self.nodes[0].generate(1)
def force_finish_mnsync(self, node):
while True:
s = node.mnsync('next')
if s == 'sync updated to MASTERNODE_SYNC_FINISHED':
break
time.sleep(0.1)
def force_finish_mnsync_list(self, node):
if node.mnsync('status')['AssetName'] == 'MASTERNODE_SYNC_WAITING':
node.mnsync('next')
while True:
mnlist = node.masternode('list', 'status')
if len(mnlist) != 0:
time.sleep(0.5)
self.force_finish_mnsync(node)
return
time.sleep(0.1)
def write_mnconf_line(self, mn, f):
conf_line = "%s %s:%d %s %s %d\n" % (mn.alias, '127.0.0.1', mn.p2p_port, mn.legacyMnkey, mn.collateral_txid, mn.collateral_vout)
f.write(conf_line)
def write_mnconf(self, mns):
mnconf_file = os.path.join(self.options.tmpdir, "node0/regtest/masternode.conf")
with open(mnconf_file, 'w') as f:
for mn in mns:
self.write_mnconf_line(mn, f)
def start_alias(self, node, alias, should_fail=False):
# When generating blocks very fast, the logic in miner.cpp:UpdateTime might result in block times ahead of the real time
# This can easily accumulate to 30 seconds or more, which results in start-alias to fail as it expects the sigTime
# to be less or equal to the confirmation block time
# Solution is to sleep in this case.
lastblocktime = node.getblock(node.getbestblockhash())['time']
sleeptime = lastblocktime - time.time()
if sleeptime > 0:
time.sleep(sleeptime + 1) # +1 to be extra sure
start_result = node.masternode('start-alias', alias)
if not should_fail:
assert_equal(start_result, {'result': 'successful', 'alias': alias})
else:
assert_equal(start_result, {'result': 'failed', 'alias': alias, 'errorMessage': 'Failed to verify MNB'})
def generate_blocks_until_winners(self, node, count, timeout=60):
# Winner lists are pretty much messed up when too many blocks were generated in a short time
# To allow proper testing of winners list, we need to slowly generate a few blocks until the list stabilizes
good_count = 0
st = time.time()
while time.time() < st + timeout:
height = node.getblockchaininfo()['blocks'] + 10
winners = node.masternode('winners')
if str(height) in winners:
if re.match('[0-9a-zA-Z]*:10', winners[str(height)]):
good_count += 1
if good_count >= count:
return
else:
good_count = 0
node.generate(1)
self.sync_all()
time.sleep(1)
raise AssertionError("generate_blocks_until_winners timed out: {}".format(node.masternode('winners')))
def test_mn_votes(self, block_count, test_enforcement=False):
self.generate_blocks_until_winners(self.nodes[0], self.num_nodes)
if test_enforcement:
self.nodes[0].spork('SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT', 0)
self.wait_for_sporks()
self.test_invalid_mn_payment(self.nodes[0])
cur_block = 0
while cur_block < block_count:
for n1 in self.nodes:
if cur_block >= block_count:
break
if n1 is None:
continue
if test_enforcement:
self.test_invalid_mn_payment(n1)
n1.generate(1)
cur_block += 1
self.sync_all()
height = n1.getblockchaininfo()['blocks']
winners = self.wait_for_winners(n1, height + 10)
for n2 in self.nodes:
if n1 is n2 or n2 is None:
continue
winners2 = self.wait_for_winners(n2, height + 10)
if winners[str(height + 10)] != winners2[str(height + 10)]:
print("winner1: " + str(winners[str(height + 10)]))
print("winner2: " + str(winners2[str(height + 10)]))
raise AssertionError("winners did not match")
if test_enforcement:
self.nodes[0].spork('SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT', 4070908800)
def test_instantsend(self, tx_count, repeat):
self.nodes[0].spork('SPORK_2_INSTANTSEND_ENABLED', 0)
self.wait_for_sporks()
# give all nodes some coins first
for i in range(tx_count):
outputs = {}
for node in self.nodes[1:]:
outputs[node.getnewaddress()] = 1
rawtx = self.nodes[0].createrawtransaction([], outputs)
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
rawtx = self.nodes[0].signrawtransaction(rawtx)['hex']
self.nodes[0].sendrawtransaction(rawtx)
self.nodes[0].generate(1)
self.sync_all()
for j in range(repeat):
for i in range(tx_count):
while True:
from_node_idx = random.randint(0, len(self.nodes) - 1)
from_node = self.nodes[from_node_idx]
if from_node is not None:
break
while True:
to_node_idx = random.randint(0, len(self.nodes) - 1)
to_node = self.nodes[to_node_idx]
if to_node is not None and from_node is not to_node:
break
to_address = to_node.getnewaddress()
txid = from_node.instantsendtoaddress(to_address, 0.01)
self.wait_for_instant_lock(to_node, to_node_idx, txid)
self.nodes[0].generate(6)
self.sync_all()
def wait_for_instant_lock(self, node, node_idx, txid, timeout=10):
st = time.time()
while time.time() < st + timeout:
try:
tx = node.gettransaction(txid)
except:
continue
if tx is None:
continue
if tx['instantlock']:
return
time.sleep(0.5)
raise AssertionError("wait_for_instant_lock timed out for: {} on node {}".format(txid, node_idx))
def wait_for_winners(self, node, height, timeout=5):
st = time.time()
while time.time() < st + timeout:
winners = node.masternode('winners')
if str(height) in winners:
if re.match('[0-9a-zA-Z]*:10', winners[str(height)]):
return winners
time.sleep(0.5)
raise AssertionError("wait_for_winners for height {} timed out: {}".format(height, node.masternode('winners')))
def wait_for_mnlists(self, mns, timeout=30, check=False):
for node in self.nodes:
self.wait_for_mnlist(node, mns, timeout, check=check)
def wait_for_mnlist(self, node, mns, timeout=30, check=False):
st = time.time()
while time.time() < st + timeout:
if check:
node.masternode('check')
if self.compare_mnlist(node, mns):
return
time.sleep(0.5)
raise AssertionError("wait_for_mnlist timed out")
def assert_mnlists(self, mns):
for node in self.nodes:
self.assert_mnlist(node, mns)
def assert_mnlist(self, node, mns):
if not self.compare_mnlist(node, mns):
expected = []
for mn in mns:
expected.append('%s-%d' % (mn.collateral_txid, mn.collateral_vout))
print('mnlist: ' + str(node.masternode('list', 'status')))
print('expected: ' + str(expected))
raise AssertionError("mnlists does not match provided mns")
def wait_for_sporks(self, timeout=30):
st = time.time()
while time.time() < st + timeout:
if self.compare_sporks():
return
time.sleep(0.5)
raise AssertionError("wait_for_sporks timed out")
def compare_sporks(self):
sporks = self.nodes[0].spork('show')
for node in self.nodes[1:]:
sporks2 = node.spork('show')
if sporks != sporks2:
return False
return True
def compare_mnlist(self, node, mns):
mnlist = node.masternode('list', 'status')
for mn in mns:
s = '%s-%d' % (mn.collateral_txid, mn.collateral_vout)
in_list = s in mnlist
if not in_list:
return False
mnlist.pop(s, None)
if len(mnlist) != 0:
return False
return True
def wait_for_mnlists_same(self, timeout=30):
st = time.time()
while time.time() < st + timeout:
mnlist = self.nodes[0].masternode('list', 'status')
all_match = True
for node in self.nodes[1:]:
mnlist2 = node.masternode('list', 'status')
if mnlist != mnlist2:
all_match = False
break
if all_match:
return
time.sleep(0.5)
raise AssertionError("wait_for_mnlists_same timed out")
def test_fail_create_protx(self, node):
# Try to create ProTx (should still fail)
address = node.getnewaddress()
key = node.getnewaddress()
blsKey = node.bls('generate')
assert_raises_jsonrpc(None, "bad-tx-type", node.protx, 'register_fund', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address)
def test_success_create_protx(self, node):
address = node.getnewaddress()
key = node.getnewaddress()
blsKey = node.bls('generate')
txid = node.protx('register_fund', address, '127.0.0.1:10000', key, blsKey['public'], key, 0, address)
rawtx = node.getrawtransaction(txid, 1)
self.mine_double_spend(node, rawtx['vin'], address, use_mnmerkleroot_from_tip=True)
self.sync_all()
def spend_input(self, txid, vout, amount, with_dummy_input_output=False):
# with_dummy_input_output is useful if you want to test reorgs with double spends of the TX without touching the actual txid/vout
address = self.nodes[0].getnewaddress()
target = {address: amount}
if with_dummy_input_output:
dummyaddress = self.nodes[0].getnewaddress()
target[dummyaddress] = 1
rawtx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], target)
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
rawtx = self.nodes[0].signrawtransaction(rawtx)['hex']
new_txid = self.nodes[0].sendrawtransaction(rawtx)
if with_dummy_input_output:
decoded = self.nodes[0].decoderawtransaction(rawtx)
for i in range(len(decoded['vout'])):
# make sure this one can only be spent when explicitely creating a rawtx with these outputs as inputs
# this ensures that no other TX is chaining on top of this TX
lock = self.nodes[0].lockunspent(False, [{'txid': new_txid, 'vout': i}])
for txin in decoded['vin']:
if txin['txid'] != txid or txin['vout'] != vout:
return txin
return None
def mine_block(self, node, vtx=[], miner_address=None, mn_payee=None, mn_amount=None, use_mnmerkleroot_from_tip=False, expected_error=None):
bt = node.getblocktemplate()
height = bt['height']
tip_hash = bt['previousblockhash']
tip_block = node.getblock(tip_hash)
coinbasevalue = bt['coinbasevalue']
if miner_address is None:
miner_address = node.getnewaddress()
if mn_payee is None:
if isinstance(bt['masternode'], list):
mn_payee = bt['masternode'][0]['payee']
else:
mn_payee = bt['masternode']['payee']
# we can't take the masternode payee amount from the template here as we might have additional fees in vtx
# calculate fees that the block template included (we'll have to remove it from the coinbase as we won't
# include the template's transactions
bt_fees = 0
for tx in bt['transactions']:
bt_fees += tx['fee']
new_fees = 0
for tx in vtx:
in_value = 0
out_value = 0
for txin in tx.vin:
txout = node.gettxout("%064x" % txin.prevout.hash, txin.prevout.n, False)
in_value += int(txout['value'] * COIN)
for txout in tx.vout:
out_value += txout.nValue
new_fees += in_value - out_value
# fix fees
coinbasevalue -= bt_fees
coinbasevalue += new_fees
if mn_amount is None:
mn_amount = get_masternode_payment(height, coinbasevalue)
miner_amount = coinbasevalue - mn_amount
outputs = {miner_address: str(Decimal(miner_amount) / COIN)}
if mn_amount > 0:
outputs[mn_payee] = str(Decimal(mn_amount) / COIN)
coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs))
coinbase.vin = create_coinbase(height).vin
# We can't really use this one as it would result in invalid merkle roots for masternode lists
if len(bt['coinbase_payload']) != 0:
cbtx = FromHex(CCbTx(), bt['coinbase_payload'])
if use_mnmerkleroot_from_tip:
if 'cbTx' in tip_block:
cbtx.merkleRootMNList = int(tip_block['cbTx']['merkleRootMNList'], 16)
else:
cbtx.merkleRootMNList = 0
coinbase.nVersion = 3
coinbase.nType = 5 # CbTx
coinbase.vExtraPayload = cbtx.serialize()
coinbase.calc_sha256()
block = create_block(int(tip_hash, 16), coinbase)
block.vtx += vtx
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
result = node.submitblock(ToHex(block))
if expected_error is not None and result != expected_error:
raise AssertionError('mining the block should have failed with error %s, but submitblock returned %s' % (expected_error, result))
elif expected_error is None and result is not None:
raise AssertionError('submitblock returned %s' % (result))
def mine_double_spend(self, node, txins, target_address, use_mnmerkleroot_from_tip=False):
amount = Decimal(0)
for txin in txins:
txout = node.gettxout(txin['txid'], txin['vout'], False)
amount += txout['value']
amount -= Decimal("0.001") # fee
rawtx = node.createrawtransaction(txins, {target_address: amount})
rawtx = node.signrawtransaction(rawtx)['hex']
tx = FromHex(CTransaction(), rawtx)
self.mine_block(node, [tx], use_mnmerkleroot_from_tip=use_mnmerkleroot_from_tip)
def test_invalid_mn_payment(self, node):
mn_payee = self.nodes[0].getnewaddress()
self.mine_block(node, mn_payee=mn_payee, expected_error='bad-cb-payee')
self.mine_block(node, mn_amount=1, expected_error='bad-cb-payee')
if __name__ == '__main__':
DIP3Test().main()