Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_block.py
Show First 20 Lines • Show All 117 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
self.sync_blocks([b1, b2]) | self.sync_blocks([b1, b2]) | ||||
# Fork like this: | # Fork like this: | ||||
# | # | ||||
# genesis -> b1 (0) -> b2 (1) | # genesis -> b1 (0) -> b2 (1) | ||||
# \-> b3 (1) | # \-> b3 (1) | ||||
# | # | ||||
# Nothing should happen at this point. We saw b2 first so it takes priority. | # Nothing should happen at this point. We saw b2 first so it takes | ||||
# priority. | |||||
self.log.info("Don't reorg to a chain of the same length") | self.log.info("Don't reorg to a chain of the same length") | ||||
self.move_tip(1) | self.move_tip(1) | ||||
b3 = self.next_block(3, spend=out[1]) | b3 = self.next_block(3, spend=out[1]) | ||||
txout_b3 = b3.vtx[1] | txout_b3 = b3.vtx[1] | ||||
self.sync_blocks([b3], False) | self.sync_blocks([b3], False) | ||||
# Now we add another block to make the alternative chain longer. | # Now we add another block to make the alternative chain longer. | ||||
# | # | ||||
▲ Show 20 Lines • Show All 203 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# Check spending of a transaction in a block which failed to connect | # Check spending of a transaction in a block which failed to connect | ||||
# | # | ||||
# b6 (3) | # b6 (3) | ||||
# b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) | # b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) | ||||
# \-> b37 (11) | # \-> b37 (11) | ||||
# \-> b38 (11/37) | # \-> b38 (11/37) | ||||
# | # | ||||
# save 37's spendable output, but then double-spend out11 to invalidate the block | # save 37's spendable output, but then double-spend out11 to invalidate | ||||
# the block | |||||
self.log.info( | self.log.info( | ||||
"Reject a block spending transaction from a block which failed to connect") | "Reject a block spending transaction from a block which failed to connect") | ||||
self.move_tip(35) | self.move_tip(35) | ||||
b37 = self.next_block(37, spend=out[11]) | b37 = self.next_block(37, spend=out[11]) | ||||
txout_b37 = b37.vtx[1] | txout_b37 = b37.vtx[1] | ||||
tx = self.create_and_sign_transaction(out[11], 0) | tx = self.create_and_sign_transaction(out[11], 0) | ||||
b37 = self.update_block(37, [tx]) | b37 = self.update_block(37, [tx]) | ||||
self.sync_blocks([b37], success=False, | self.sync_blocks([b37], success=False, | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | ||||
# attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid | # attempt to spend b37's first non-coinbase tx, at which point b37 was | ||||
# still considered valid | |||||
self.move_tip(35) | self.move_tip(35) | ||||
b38 = self.next_block(38, spend=txout_b37) | b38 = self.next_block(38, spend=txout_b37) | ||||
self.sync_blocks([b38], success=False, | self.sync_blocks([b38], success=False, | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | ||||
self.log.info("Skipped sigops tests") | self.log.info("Skipped sigops tests") | ||||
# tests were moved to feature_block_sigops.py | # tests were moved to feature_block_sigops.py | ||||
self.move_tip(35) | self.move_tip(35) | ||||
Show All 16 Lines | def run_test(self): | ||||
self.sync_blocks([b42, b43], True) | self.sync_blocks([b42, b43], True) | ||||
# Test a number of really invalid scenarios | # Test a number of really invalid scenarios | ||||
# | # | ||||
# -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14) | # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14) | ||||
# \-> ??? (15) | # \-> ??? (15) | ||||
# The next few blocks are going to be created "by hand" since they'll do funky things, such as having | # The next few blocks are going to be created "by hand" since they'll do funky things, such as having | ||||
# the first transaction be non-coinbase, etc. The purpose of b44 is to make sure this works. | # the first transaction be non-coinbase, etc. The purpose of b44 is to | ||||
# make sure this works. | |||||
self.log.info("Build block 44 manually") | self.log.info("Build block 44 manually") | ||||
height = self.block_heights[self.tip.sha256] + 1 | height = self.block_heights[self.tip.sha256] + 1 | ||||
coinbase = create_coinbase(height, self.coinbase_pubkey) | coinbase = create_coinbase(height, self.coinbase_pubkey) | ||||
b44 = CBlock() | b44 = CBlock() | ||||
b44.nTime = self.tip.nTime + 1 | b44.nTime = self.tip.nTime + 1 | ||||
b44.hashPrevBlock = self.tip.sha256 | b44.hashPrevBlock = self.tip.sha256 | ||||
b44.nBits = 0x207fffff | b44.nBits = 0x207fffff | ||||
b44.vtx.append(coinbase) | b44.vtx.append(coinbase) | ||||
▲ Show 20 Lines • Show All 415 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | ||||
# Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) | # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) | ||||
# | # | ||||
# -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) | # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) | ||||
# \-> b71 (21) | # \-> b71 (21) | ||||
# | # | ||||
# b72 is a good block. | # b72 is a good block. | ||||
# b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72. | # b71 is a copy of 72, but re-adds one of its transactions. However, | ||||
# it has the same hash as b72. | |||||
self.log.info( | self.log.info( | ||||
"Reject a block containing a duplicate transaction but with the same Merkle root (Merkle tree malleability") | "Reject a block containing a duplicate transaction but with the same Merkle root (Merkle tree malleability") | ||||
self.move_tip(69) | self.move_tip(69) | ||||
b72 = self.next_block(72) | b72 = self.next_block(72) | ||||
tx1 = self.create_and_sign_transaction(out[21], 2) | tx1 = self.create_and_sign_transaction(out[21], 2) | ||||
tx2 = self.create_and_sign_transaction(tx1, 1) | tx2 = self.create_and_sign_transaction(tx1, 1) | ||||
b72 = self.update_block(72, [tx1, tx2]) # now tip is 72 | b72 = self.update_block(72, [tx1, tx2]) # now tip is 72 | ||||
b71 = copy.deepcopy(b72) | b71 = copy.deepcopy(b72) | ||||
▲ Show 20 Lines • Show All 199 Lines • ▼ Show 20 Lines | class FullBlockTest(BitcoinTestFramework): | ||||
################ | ################ | ||||
def add_transactions_to_block(self, block, tx_list): | def add_transactions_to_block(self, block, tx_list): | ||||
[tx.rehash() for tx in tx_list] | [tx.rehash() for tx in tx_list] | ||||
block.vtx.extend(tx_list) | block.vtx.extend(tx_list) | ||||
# this is a little handier to use than the version in blocktools.py | # this is a little handier to use than the version in blocktools.py | ||||
def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): | def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): | ||||
return create_tx_with_script(spend_tx, n, amount=value, script_pub_key=script) | return create_tx_with_script( | ||||
spend_tx, n, amount=value, script_pub_key=script) | |||||
# sign a transaction, using the key we know about | # sign a transaction, using the key we know about | ||||
# this signs input 0 in tx, which is assumed to be spending output n in spend_tx | # this signs input 0 in tx, which is assumed to be spending output n in | ||||
# spend_tx | |||||
def sign_tx(self, tx, spend_tx): | def sign_tx(self, tx, spend_tx): | ||||
scriptPubKey = bytearray(spend_tx.vout[0].scriptPubKey) | scriptPubKey = bytearray(spend_tx.vout[0].scriptPubKey) | ||||
if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend | if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend | ||||
tx.vin[0].scriptSig = CScript() | tx.vin[0].scriptSig = CScript() | ||||
return | return | ||||
sighash = SignatureHashForkId( | sighash = SignatureHashForkId( | ||||
spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[0].nValue) | spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[0].nValue) | ||||
tx.vin[0].scriptSig = CScript( | tx.vin[0].scriptSig = CScript( | ||||
[self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) | [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) | ||||
def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): | def create_and_sign_transaction( | ||||
self, spend_tx, value, script=CScript([OP_TRUE])): | |||||
tx = self.create_tx(spend_tx, 0, value, script) | tx = self.create_tx(spend_tx, 0, value, script) | ||||
self.sign_tx(tx, spend_tx) | self.sign_tx(tx, spend_tx) | ||||
tx.rehash() | tx.rehash() | ||||
return tx | return tx | ||||
def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True): | def next_block(self, number, spend=None, additional_coinbase_value=0, | ||||
script=CScript([OP_TRUE]), solve=True): | |||||
if self.tip is None: | if self.tip is None: | ||||
base_block_hash = self.genesis_hash | base_block_hash = self.genesis_hash | ||||
block_time = int(time.time()) + 1 | block_time = int(time.time()) + 1 | ||||
else: | else: | ||||
base_block_hash = self.tip.sha256 | base_block_hash = self.tip.sha256 | ||||
block_time = self.tip.nTime + 1 | block_time = self.tip.nTime + 1 | ||||
# First create the coinbase | # First create the coinbase | ||||
height = self.block_heights[base_block_hash] + 1 | height = self.block_heights[base_block_hash] + 1 | ||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | |||||
def reconnect_p2p(self): | def reconnect_p2p(self): | ||||
"""Tear down and bootstrap the P2P connection to the node. | """Tear down and bootstrap the P2P connection to the node. | ||||
The node gets disconnected several times in this test. This helper | The node gets disconnected several times in this test. This helper | ||||
method reconnects the p2p and restarts the network thread.""" | method reconnects the p2p and restarts the network thread.""" | ||||
self.nodes[0].disconnect_p2ps() | self.nodes[0].disconnect_p2ps() | ||||
self.bootstrap_p2p() | self.bootstrap_p2p() | ||||
def sync_blocks(self, blocks, success=True, reject_reason=None, request_block=True, reconnect=False, timeout=60): | def sync_blocks(self, blocks, success=True, reject_reason=None, | ||||
request_block=True, reconnect=False, timeout=60): | |||||
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. | """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. | ||||
Call with success = False if the tip shouldn't advance to the most recent block.""" | Call with success = False if the tip shouldn't advance to the most recent block.""" | ||||
self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, | self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, | ||||
reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect) | reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect) | ||||
if reconnect: | if reconnect: | ||||
self.reconnect_p2p() | self.reconnect_p2p() | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
FullBlockTest().main() | FullBlockTest().main() |