Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-segwit-recovery-activation.py
Show First 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | |||||
# First blocks (initial coinbases, pre-fork test blocks) happen 1 day before. | # First blocks (initial coinbases, pre-fork test blocks) happen 1 day before. | ||||
FIRST_BLOCK_TIME = GREAT_WALL_START_TIME - 86400 | FIRST_BLOCK_TIME = GREAT_WALL_START_TIME - 86400 | ||||
# Error due to non clean stack | # Error due to non clean stack | ||||
CLEANSTACK_ERROR = b'non-mandatory-script-verify-flag (Script did not clean its stack)' | CLEANSTACK_ERROR = b'non-mandatory-script-verify-flag (Script did not clean its stack)' | ||||
RPC_CLEANSTACK_ERROR = "64: " + \ | RPC_CLEANSTACK_ERROR = "64: " + \ | ||||
CLEANSTACK_ERROR.decode("utf-8") | CLEANSTACK_ERROR.decode("utf-8") | ||||
EVAL_FALSE_ERROR = b'non-mandatory-script-verify-flag (Script evaluated without error but finished with a false/empty top stack elem' | |||||
RPC_EVAL_FALSE_ERROR = "64: " + \ | |||||
EVAL_FALSE_ERROR.decode("utf-8") | |||||
class PreviousSpendableOutput(object): | class PreviousSpendableOutput(object): | ||||
def __init__(self, tx=CTransaction(), n=-1): | def __init__(self, tx=CTransaction(), n=-1): | ||||
self.tx = tx | self.tx = tx | ||||
self.n = n | self.n = n | ||||
▲ Show 20 Lines • Show All 110 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
self.tip = block | self.tip = block | ||||
if block.sha256 != old_sha256: | if block.sha256 != old_sha256: | ||||
self.block_heights[ | self.block_heights[ | ||||
block.sha256] = self.block_heights[old_sha256] | block.sha256] = self.block_heights[old_sha256] | ||||
del self.block_heights[old_sha256] | del self.block_heights[old_sha256] | ||||
self.blocks[block_number] = block | self.blocks[block_number] = block | ||||
return block | return block | ||||
# checks the mempool has exactly the same txns as in the provided list | |||||
def check_mempool_equal(node, txns): | |||||
assert set(node.getrawmempool()) == set(tx.hash for tx in txns) | |||||
# Returns 2 transactions: | # Returns 2 transactions: | ||||
# 1) txfund: create outputs in segwit addresses | # 1) txfund: create outputs in segwit addresses | ||||
# 2) txspend: spends outputs from segwit addresses | # 2) txspend: spends outputs from segwit addresses | ||||
def create_segwit_fund_and_spend_tx(spend): | def create_segwit_fund_and_spend_tx(spend, case0=False): | ||||
if not case0: | |||||
# To make sure we'll be able to recover coins sent to segwit addresses, | # To make sure we'll be able to recover coins sent to segwit addresses, | ||||
# we test using historical recoveries from btc.com: | # we test using historical recoveries from btc.com: | ||||
# Spending from a P2SH-P2WPKH coin, | # Spending from a P2SH-P2WPKH coin, | ||||
# txhash:a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 | # txhash:a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 | ||||
redeem_script0 = bytearray.fromhex( | redeem_script0 = bytearray.fromhex( | ||||
'0014fcf9969ce1c98a135ed293719721fb69f0b686cb') | '0014fcf9969ce1c98a135ed293719721fb69f0b686cb') | ||||
# Spending from a P2SH-P2WSH coin, | # Spending from a P2SH-P2WSH coin, | ||||
# txhash:6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f | # txhash:6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f | ||||
redeem_script1 = bytearray.fromhex( | redeem_script1 = bytearray.fromhex( | ||||
'0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4') | '0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4') | ||||
else: | |||||
redeem_script0 = bytearray.fromhex('51020000') | |||||
redeem_script1 = bytearray.fromhex('53020080') | |||||
redeem_scripts = [redeem_script0, redeem_script1] | redeem_scripts = [redeem_script0, redeem_script1] | ||||
# Fund transaction to segwit addresses | # Fund transaction to segwit addresses | ||||
txfund = CTransaction() | txfund = CTransaction() | ||||
txfund.vin = [CTxIn(COutPoint(spend.tx.sha256, spend.n))] | txfund.vin = [CTxIn(COutPoint(spend.tx.sha256, spend.n))] | ||||
amount = (50 * COIN - 1000) // len(redeem_scripts) | amount = (50 * COIN - 1000) // len(redeem_scripts) | ||||
for redeem_script in redeem_scripts: | for redeem_script in redeem_scripts: | ||||
txfund.vout.append( | txfund.vout.append( | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
# Create a new block | # Create a new block | ||||
block(0) | block(0) | ||||
save_spendable_output() | save_spendable_output() | ||||
yield accepted() | yield accepted() | ||||
# Now we need that block to mature so we can spend the coinbase. | # Now we need that block to mature so we can spend the coinbase. | ||||
test = TestInstance(sync_every_block=False) | test = TestInstance(sync_every_block=False) | ||||
for i in range(99): | for i in range(100): | ||||
block(5000 + i) | block(5000 + i) | ||||
test.blocks_and_transactions.append([self.tip, True]) | test.blocks_and_transactions.append([self.tip, True]) | ||||
save_spendable_output() | save_spendable_output() | ||||
yield test | yield test | ||||
# collect spendable outputs now to avoid cluttering the code later on | # collect spendable outputs now to avoid cluttering the code later on | ||||
out = [] | out = [] | ||||
for i in range(100): | for i in range(100): | ||||
out.append(get_spendable_output()) | out.append(get_spendable_output()) | ||||
# Create segwit funding and spending transactions | # Create segwit funding and spending transactions | ||||
txfund, txspend = create_segwit_fund_and_spend_tx(out[0]) | txfund, txspend = create_segwit_fund_and_spend_tx(out[0]) | ||||
txfund_case0, txspend_case0 = create_segwit_fund_and_spend_tx( | |||||
out[1], True) | |||||
# Create blocks to get closer to activate the fork. | # Create blocks to get closer to activate the fork. | ||||
# Mine txfund, as it can't go into node_std mempool because it's | # Mine txfund, as it can't go into node_std mempool because it's | ||||
# nonstandard. | # nonstandard. | ||||
b = block(5555) | b = block(5555) | ||||
b.nTime = GREAT_WALL_START_TIME - 1 | b.nTime = GREAT_WALL_START_TIME - 1 | ||||
update_block(5555, [txfund]) | update_block(5555, [txfund, txfund_case0]) | ||||
yield accepted() | yield accepted() | ||||
for i in range(5): | for i in range(5): | ||||
block(5100 + i) | block(5100 + i) | ||||
test.blocks_and_transactions.append([self.tip, True]) | test.blocks_and_transactions.append([self.tip, True]) | ||||
yield test | yield test | ||||
# Since the TestManager is not connected to node_std, we must check | # Since the TestManager is not connected to node_std, we must check | ||||
# both nodes are synchronized before continuing. | # both nodes are synchronized before continuing. | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# Check we are just before the activation time | # Check we are just before the activation time | ||||
assert_equal(node_nonstd.getblockheader( | assert_equal(node_nonstd.getblockheader( | ||||
node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) | node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) | ||||
assert_equal(node_std.getblockheader( | assert_equal(node_std.getblockheader( | ||||
node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) | node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) | ||||
# Before the fork, segwit spending txns are rejected. | # Before the fork, segwit spending txns are rejected. | ||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | ||||
node_nonstd.sendrawtransaction, ToHex(txspend)) | node_nonstd.sendrawtransaction, ToHex(txspend)) | ||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | ||||
node_std.sendrawtransaction, ToHex(txspend)) | node_std.sendrawtransaction, ToHex(txspend)) | ||||
assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | |||||
node_nonstd.sendrawtransaction, ToHex(txspend_case0)) | |||||
assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | |||||
node_std.sendrawtransaction, ToHex(txspend_case0)) | |||||
# Blocks containing segwit spending txns are rejected as well. | # Blocks containing segwit spending txns are rejected as well. | ||||
block(2) | block(2) | ||||
update_block(2, [txspend]) | update_block(2, [txspend, txspend_case0]) | ||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | yield rejected(RejectResult(16, b'blk-bad-inputs')) | ||||
# Rewind bad block | # Rewind bad block | ||||
tip(5104) | tip(5104) | ||||
# Check that non-upgraded nodes checking for standardness are not | # Check that non-upgraded nodes checking for standardness are not | ||||
# banning nodes sending segwit spending txns. | # banning nodes sending segwit spending txns. | ||||
check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | ||||
check_for_no_ban_on_rejected_tx(txspend_case0, 64, EVAL_FALSE_ERROR) | |||||
# Activate the fork in both nodes! | # Activate the fork in both nodes! | ||||
forkblock = block(5556) | forkblock = block(5556) | ||||
yield accepted() | yield accepted() | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# Check we just activated the fork | # Check we just activated the fork | ||||
assert_equal(node_nonstd.getblockheader( | assert_equal(node_nonstd.getblockheader( | ||||
node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) | node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) | ||||
assert_equal(node_std.getblockheader( | assert_equal(node_std.getblockheader( | ||||
node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) | node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) | ||||
# Check that upgraded nodes checking for standardness are not banning | |||||
# nodes sending segwit spending txns. | |||||
check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | |||||
check_for_no_ban_on_rejected_tx(txspend_case0, 64, EVAL_FALSE_ERROR) | |||||
# Segwit spending txns are accepted in the mempool of nodes not checking | # Segwit spending txns are accepted in the mempool of nodes not checking | ||||
# for standardness, but rejected in nodes that check. | # for standardness, but rejected in nodes that check. | ||||
node_nonstd.sendrawtransaction(ToHex(txspend)) | node_nonstd.sendrawtransaction(ToHex(txspend)) | ||||
assert(txspend.hash in node_nonstd.getrawmempool()) | node_nonstd.sendrawtransaction(ToHex(txspend_case0)) | ||||
check_mempool_equal(node_nonstd, [txspend, txspend_case0]) | |||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | ||||
node_std.sendrawtransaction, ToHex(txspend)) | node_std.sendrawtransaction, ToHex(txspend)) | ||||
assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | |||||
# Check that upgraded nodes checking for standardness are not banning | node_std.sendrawtransaction, ToHex(txspend_case0)) | ||||
# nodes sending segwit spending txns. | |||||
check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | |||||
# Blocks containing segwit spending txns are now accepted in both | # Blocks containing segwit spending txns are now accepted in both | ||||
# nodes. | # nodes. | ||||
block(5) | block(5) | ||||
postforkblock = update_block(5, [txspend]) | postforkblock = update_block(5, [txspend, txspend_case0]) | ||||
yield accepted() | yield accepted() | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# Ok, now we check if a reorg work properly accross the activation. | # Ok, now we check if a reorg work properly accross the activation. | ||||
node_nonstd.invalidateblock(postforkblock.hash) | node_nonstd.invalidateblock(postforkblock.hash) | ||||
assert(txspend.hash in node_nonstd.getrawmempool()) | check_mempool_equal(node_nonstd, [txspend, txspend_case0]) | ||||
# Also check that nodes checking for standardness don't return a segwit | # Also check that nodes checking for standardness don't return a segwit | ||||
# spending txn into the mempool when disconnecting a block. | # spending txn into the mempool when disconnecting a block. | ||||
node_std.invalidateblock(postforkblock.hash) | node_std.invalidateblock(postforkblock.hash) | ||||
assert(txspend.hash not in node_std.getrawmempool()) | assert(len(node_std.getrawmempool()) == 0) | ||||
# Deactivate the fork. The spending tx has been evicted from the | # Deactivate the fork. The spending tx has been evicted from the | ||||
# mempool | # mempool | ||||
node_nonstd.invalidateblock(forkblock.hash) | node_nonstd.invalidateblock(forkblock.hash) | ||||
assert(len(node_nonstd.getrawmempool()) == 0) | assert(len(node_nonstd.getrawmempool()) == 0) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
SegwitRecoveryActivationTest().main() | SegwitRecoveryActivationTest().main() |