Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_feature_minerfund.py
Show All 9 Lines | |||||
from test_framework.messages import XEC, CTxOut, ToHex | from test_framework.messages import XEC, CTxOut, ToHex | ||||
from test_framework.script import OP_EQUAL, OP_HASH160, CScript | from test_framework.script import OP_EQUAL, OP_HASH160, CScript | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.txtools import pad_tx | from test_framework.txtools import pad_tx | ||||
from test_framework.util import assert_equal, assert_greater_than_or_equal | from test_framework.util import assert_equal, assert_greater_than_or_equal | ||||
WELLINGTON_ACTIVATION_TIME = 2100000600 | WELLINGTON_ACTIVATION_TIME = 2100000600 | ||||
MINER_FUND_RATIO = 8 | MINER_FUND_RATIO = 8 | ||||
MINER_FUND_ADDR = 'ecregtest:prfhcnyqnl5cgrnmlfmms675w93ld7mvvq9jcw0zsn' | MINER_FUND_ADDR = "ecregtest:prfhcnyqnl5cgrnmlfmms675w93ld7mvvq9jcw0zsn" | ||||
MINER_FUND_ADDR_AXION = 'ecregtest:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdgz0wv9ltl' | MINER_FUND_ADDR_AXION = "ecregtest:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdgz0wv9ltl" | ||||
class MinerFundTest(BitcoinTestFramework): | class MinerFundTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [[ | self.extra_args = [ | ||||
'-enableminerfund', | [ | ||||
f'-wellingtonactivationtime={WELLINGTON_ACTIVATION_TIME}', | "-enableminerfund", | ||||
], [ | f"-wellingtonactivationtime={WELLINGTON_ACTIVATION_TIME}", | ||||
f'-wellingtonactivationtime={WELLINGTON_ACTIVATION_TIME}', | ], | ||||
]] | [ | ||||
f"-wellingtonactivationtime={WELLINGTON_ACTIVATION_TIME}", | |||||
], | |||||
] | |||||
def run_test(self): | def run_test(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
self.log.info('Create some history') | self.log.info("Create some history") | ||||
self.generate(node, 10) | self.generate(node, 10) | ||||
def get_best_coinbase(n): | def get_best_coinbase(n): | ||||
return n.getblock(n.getbestblockhash(), 2)['tx'][0] | return n.getblock(n.getbestblockhash(), 2)["tx"][0] | ||||
coinbase = get_best_coinbase(node) | coinbase = get_best_coinbase(node) | ||||
assert_greater_than_or_equal(len(coinbase['vout']), 2) | assert_greater_than_or_equal(len(coinbase["vout"]), 2) | ||||
block_reward = sum([vout['value'] for vout in coinbase['vout']]) | block_reward = sum([vout["value"] for vout in coinbase["vout"]]) | ||||
def check_miner_fund_output(expected_address): | def check_miner_fund_output(expected_address): | ||||
coinbase = get_best_coinbase(node) | coinbase = get_best_coinbase(node) | ||||
assert_equal(len(coinbase['vout']), 2) | assert_equal(len(coinbase["vout"]), 2) | ||||
assert_equal( | assert_equal( | ||||
coinbase['vout'][1]['scriptPubKey']['addresses'][0], | coinbase["vout"][1]["scriptPubKey"]["addresses"][0], expected_address | ||||
expected_address) | ) | ||||
total = Decimal() | total = Decimal() | ||||
for o in coinbase['vout']: | for o in coinbase["vout"]: | ||||
total += o['value'] | total += o["value"] | ||||
assert_equal(total, block_reward) | assert_equal(total, block_reward) | ||||
assert_greater_than_or_equal( | assert_greater_than_or_equal( | ||||
coinbase['vout'][1]['value'], | coinbase["vout"][1]["value"], (MINER_FUND_RATIO * total) / 100 | ||||
(MINER_FUND_RATIO * total) / 100) | ) | ||||
# The coinbase has an output to the miner fund address. | # The coinbase has an output to the miner fund address. | ||||
# Now we send part of the coinbase to the fund. | # Now we send part of the coinbase to the fund. | ||||
check_miner_fund_output(MINER_FUND_ADDR) | check_miner_fund_output(MINER_FUND_ADDR) | ||||
def check_bad_miner_fund(prev_hash, coinbase=None): | def check_bad_miner_fund(prev_hash, coinbase=None): | ||||
if coinbase is None: | if coinbase is None: | ||||
coinbase = create_coinbase(node.getblockcount() + 1) | coinbase = create_coinbase(node.getblockcount() + 1) | ||||
block_time = node.getblock(prev_hash)['time'] + 1 | block_time = node.getblock(prev_hash)["time"] + 1 | ||||
block = create_block(int(prev_hash, 16), | block = create_block(int(prev_hash, 16), coinbase, block_time, version=4) | ||||
coinbase, block_time, version=4) | |||||
block.solve() | block.solve() | ||||
assert_equal(node.submitblock(ToHex(block)), 'bad-cb-minerfund') | assert_equal(node.submitblock(ToHex(block)), "bad-cb-minerfund") | ||||
# A block with no miner fund coinbase should be rejected. | # A block with no miner fund coinbase should be rejected. | ||||
tip = node.getbestblockhash() | tip = node.getbestblockhash() | ||||
check_bad_miner_fund(tip) | check_bad_miner_fund(tip) | ||||
def create_cb_pay_to_address(address): | def create_cb_pay_to_address(address): | ||||
_, _, script_hash = decode(address) | _, _, script_hash = decode(address) | ||||
miner_fund_amount = int( | miner_fund_amount = int(block_reward * XEC * MINER_FUND_RATIO / 100) | ||||
block_reward * XEC * MINER_FUND_RATIO / 100) | |||||
# Build a coinbase with no miner fund | # Build a coinbase with no miner fund | ||||
cb = create_coinbase(node.getblockcount() + 1) | cb = create_coinbase(node.getblockcount() + 1) | ||||
# Keep only the block reward output | # Keep only the block reward output | ||||
cb.vout = cb.vout[:1] | cb.vout = cb.vout[:1] | ||||
# Change the block reward to account for the miner fund | # Change the block reward to account for the miner fund | ||||
cb.vout[0].nValue = int(block_reward * XEC - miner_fund_amount) | cb.vout[0].nValue = int(block_reward * XEC - miner_fund_amount) | ||||
# Add the miner fund output | # Add the miner fund output | ||||
cb.vout.append(CTxOut(nValue=miner_fund_amount, scriptPubKey=CScript( | cb.vout.append( | ||||
[OP_HASH160, script_hash, OP_EQUAL]))) | CTxOut( | ||||
nValue=miner_fund_amount, | |||||
scriptPubKey=CScript([OP_HASH160, script_hash, OP_EQUAL]), | |||||
) | |||||
) | |||||
pad_tx(cb) | pad_tx(cb) | ||||
cb.calc_sha256() | cb.calc_sha256() | ||||
return cb | return cb | ||||
# Build a custom coinbase that spend to the legacy miner fund address | # Build a custom coinbase that spend to the legacy miner fund address | ||||
# and check it is rejected. | # and check it is rejected. | ||||
check_bad_miner_fund( | check_bad_miner_fund(tip, create_cb_pay_to_address(MINER_FUND_ADDR_AXION)) | ||||
tip, | |||||
create_cb_pay_to_address(MINER_FUND_ADDR_AXION)) | |||||
# Build a custom coinbase that spend to the new miner fund address | # Build a custom coinbase that spend to the new miner fund address | ||||
# and check it is accepted. | # and check it is accepted. | ||||
good_block = create_block( | good_block = create_block( | ||||
int(tip, 16), | int(tip, 16), | ||||
create_cb_pay_to_address(MINER_FUND_ADDR), | create_cb_pay_to_address(MINER_FUND_ADDR), | ||||
node.getblock(tip)['time'] + 1, | node.getblock(tip)["time"] + 1, | ||||
version=4) | version=4, | ||||
) | |||||
good_block.solve() | good_block.solve() | ||||
node.submitblock(ToHex(good_block)) | node.submitblock(ToHex(good_block)) | ||||
assert_equal(node.getbestblockhash(), good_block.hash) | assert_equal(node.getbestblockhash(), good_block.hash) | ||||
# Move MTP forward to wellington activation. Next block will enforce | # Move MTP forward to wellington activation. Next block will enforce | ||||
# new rules. | # new rules. | ||||
address = node.get_deterministic_priv_key().address | address = node.get_deterministic_priv_key().address | ||||
for n in self.nodes: | for n in self.nodes: | ||||
n.setmocktime(WELLINGTON_ACTIVATION_TIME) | n.setmocktime(WELLINGTON_ACTIVATION_TIME) | ||||
self.generatetoaddress(node, nblocks=6, address=address) | self.generatetoaddress(node, nblocks=6, address=address) | ||||
assert_equal( | assert_equal(node.getblockchaininfo()["mediantime"], WELLINGTON_ACTIVATION_TIME) | ||||
node.getblockchaininfo()['mediantime'], | |||||
WELLINGTON_ACTIVATION_TIME) | |||||
# First block that does not have miner fund as a consensus requirement. | # First block that does not have miner fund as a consensus requirement. | ||||
# node0 still mines a block with a coinbase output to the miner fund. | # node0 still mines a block with a coinbase output to the miner fund. | ||||
first_block_has_miner_fund = self.generatetoaddress( | first_block_has_miner_fund = self.generatetoaddress( | ||||
node, nblocks=1, address=address)[0] | node, nblocks=1, address=address | ||||
)[0] | |||||
check_miner_fund_output(MINER_FUND_ADDR) | check_miner_fund_output(MINER_FUND_ADDR) | ||||
# Invalidate it | # Invalidate it | ||||
for n in self.nodes: | for n in self.nodes: | ||||
n.invalidateblock(first_block_has_miner_fund) | n.invalidateblock(first_block_has_miner_fund) | ||||
# node1 mines a block without a coinbase output to the miner fund. | # node1 mines a block without a coinbase output to the miner fund. | ||||
with node.assert_debug_log(expected_msgs=['policy-bad-miner-fund']): | with node.assert_debug_log(expected_msgs=["policy-bad-miner-fund"]): | ||||
first_block_no_miner_fund = self.generatetoaddress( | first_block_no_miner_fund = self.generatetoaddress( | ||||
self.nodes[1], | self.nodes[1], nblocks=1, address=address, sync_fun=self.no_op | ||||
nblocks=1, | )[0] | ||||
address=address, | |||||
sync_fun=self.no_op)[0] | |||||
coinbase = get_best_coinbase(self.nodes[1]) | coinbase = get_best_coinbase(self.nodes[1]) | ||||
assert_equal(len(coinbase['vout']), 1) | assert_equal(len(coinbase["vout"]), 1) | ||||
# node0 parks the block since the miner fund is enforced by policy. | # node0 parks the block since the miner fund is enforced by policy. | ||||
def parked_block(blockhash): | def parked_block(blockhash): | ||||
for tip in node.getchaintips(): | for tip in node.getchaintips(): | ||||
if tip["hash"] == blockhash: | if tip["hash"] == blockhash: | ||||
assert tip["status"] != "active" | assert tip["status"] != "active" | ||||
return tip["status"] == "parked" | return tip["status"] == "parked" | ||||
return False | return False | ||||
self.wait_until(lambda: parked_block(first_block_no_miner_fund)) | self.wait_until(lambda: parked_block(first_block_no_miner_fund)) | ||||
# Unpark the block | # Unpark the block | ||||
node.unparkblock(first_block_no_miner_fund) | node.unparkblock(first_block_no_miner_fund) | ||||
# Invalidate it | # Invalidate it | ||||
for n in self.nodes: | for n in self.nodes: | ||||
n.invalidateblock(first_block_no_miner_fund) | n.invalidateblock(first_block_no_miner_fund) | ||||
# Connecting the block again does not park because block policies are | # Connecting the block again does not park because block policies are | ||||
# only checked the first time a block is connected. | # only checked the first time a block is connected. | ||||
for n in self.nodes: | for n in self.nodes: | ||||
n.reconsiderblock(first_block_no_miner_fund) | n.reconsiderblock(first_block_no_miner_fund) | ||||
assert_equal(n.getbestblockhash(), first_block_no_miner_fund) | assert_equal(n.getbestblockhash(), first_block_no_miner_fund) | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
MinerFundTest().main() | MinerFundTest().main() |