diff options
author | glozow <gloriajzhao@gmail.com> | 2024-03-11 09:46:22 +0000 |
---|---|---|
committer | glozow <gloriajzhao@gmail.com> | 2024-03-11 09:54:18 +0000 |
commit | c33e83a53a863e55413bee2893716b93165bf656 (patch) | |
tree | efacfa32b84fbb4f2ebd0594180cae34094d1d2f /test/functional/p2p_mutated_blocks.py | |
parent | a718bfafe7ad38bab8a5782ae0db719480984238 (diff) | |
parent | c68d4d0ac5b8537ba5e1c0c512b807768e1c5c1f (diff) |
Merge bitcoin/bitcoin#29509: [26.x] backports and final changes for v26.1rc2v26.1rc2
c68d4d0ac5b8537ba5e1c0c512b807768e1c5c1f [doc] update manual pages for v26.1rc2 (glozow)
bd715bfb3030f392b3b19f9a05aada48e385a0d9 [build] bump version to v26.1rc2 (glozow)
b6d006d2a2b840e4a5af96c8d838e1cf589d3bce update release notes 26.1 (glozow)
fce992b38e59c90babe505eda0d72f05d79eb2f3 fuzz: restrict fopencookie usage to Linux & FreeBSD (fanquake)
40c56a4d1341017b02dcb71882b1b1f03f880b1d test: make sure keypool sizes do not change on `getrawchangeaddress`/`getnewaddress` failures (UdjinM6)
7c82b2758c6bcfb8a94d2086f0d40088286815e8 wallet: Avoid updating `ReserveDestination::nIndex` when `GetReservedDestination` fails (UdjinM6)
b5419ce6b621121bb1a0ec497968eb16cc012c39 p2p: Don't consider blocks mutated if they don't connect to known prev block (Greg Sanders)
0535c253fe71ae9d875827cafed41a8889f4a702 [test] IsBlockMutated unit tests (dergoegge)
8141498f3ad3ae9c42c32346ee73dc7f29e72cb5 [validation] Cache merkle root and witness commitment checks (dergoegge)
0c5c5962cbfdfd532cebc6706d5b838488b89d53 [test] Add regression test for #27608 (dergoegge)
24736350e932799c66c999470fa3837e25576fc7 [net processing] Don't process mutated blocks (dergoegge)
50c0b61a9d562240d5fe4bd79324b0c0e79caa5c [validation] Merkle root malleation should be caught by IsBlockMutated (dergoegge)
aff368fa817b065d99729186d304fff02f6e527b [validation] Introduce IsBlockMutated (dergoegge)
076c67c3aae424e58863dde3bc37cedecc496935 [refactor] Cleanup merkle root checks (dergoegge)
97a1d0a45959a29464ae73087c7a0adcdebd5a61 [validation] Isolate merkle root checks (dergoegge)
4ac0eb543d028379bb2b86ab08bbbb2f9f48d5b1 test: Drop `x` modifier in `fsbridge::fopen` call for mingw builds (Hennadii Stepanov)
Pull request description:
Includes:
- #29357
- #29412
- #29524
- #29510
- #29529
Also does:
- update to release notes
- bump to rc2
- manpages
- (no changes to bitcoin.conf)
ACKs for top commit:
achow101:
ACK c68d4d0ac5b8537ba5e1c0c512b807768e1c5c1f
Tree-SHA512: 2f8c3dd705e3f9f33403b3cc17e8006510ff827d7dbd609b09732a1669964e9b001cfecdc63d8d8daeb8f39c652e1e4ad0aac873d44d259c21803de85688ed2b
Diffstat (limited to 'test/functional/p2p_mutated_blocks.py')
-rwxr-xr-x | test/functional/p2p_mutated_blocks.py | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/test/functional/p2p_mutated_blocks.py b/test/functional/p2p_mutated_blocks.py new file mode 100755 index 0000000000..737edaf5bf --- /dev/null +++ b/test/functional/p2p_mutated_blocks.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (c) The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" +Test that an attacker can't degrade compact block relay by sending unsolicited +mutated blocks to clear in-flight blocktxn requests from other honest peers. +""" + +from test_framework.p2p import P2PInterface +from test_framework.messages import ( + BlockTransactions, + msg_cmpctblock, + msg_block, + msg_blocktxn, + HeaderAndShortIDs, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.blocktools import ( + COINBASE_MATURITY, + create_block, + add_witness_commitment, + NORMAL_GBT_REQUEST_PARAMS, +) +from test_framework.util import assert_equal +from test_framework.wallet import MiniWallet +import copy + +class MutatedBlocksTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [ + [ + "-testactivationheight=segwit@1", # causes unconnected headers/blocks to not have segwit considered deployed + ], + ] + + def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.generate(self.wallet, COINBASE_MATURITY) + + honest_relayer = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay") + attacker = self.nodes[0].add_p2p_connection(P2PInterface()) + + # Create new block with two transactions (coinbase + 1 self-transfer). + # The self-transfer transaction is needed to trigger a compact block + # `getblocktxn` roundtrip. + tx = self.wallet.create_self_transfer()["tx"] + block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[tx]) + add_witness_commitment(block) + block.solve() + + # Create mutated version of the block by changing the transaction + # version on the self-transfer. + mutated_block = copy.deepcopy(block) + mutated_block.vtx[1].nVersion = 4 + + # Announce the new block via a compact block through the honest relayer + cmpctblock = HeaderAndShortIDs() + cmpctblock.initialize_from_block(block, use_witness=True) + honest_relayer.send_message(msg_cmpctblock(cmpctblock.to_p2p())) + + # Wait for a `getblocktxn` that attempts to fetch the self-transfer + def self_transfer_requested(): + if not honest_relayer.last_message.get('getblocktxn'): + return False + + get_block_txn = honest_relayer.last_message['getblocktxn'] + return get_block_txn.block_txn_request.blockhash == block.sha256 and \ + get_block_txn.block_txn_request.indexes == [1] + honest_relayer.wait_until(self_transfer_requested, timeout=5) + + # Block at height 101 should be the only one in flight from peer 0 + peer_info_prior_to_attack = self.nodes[0].getpeerinfo() + assert_equal(peer_info_prior_to_attack[0]['id'], 0) + assert_equal([101], peer_info_prior_to_attack[0]["inflight"]) + + # Attempt to clear the honest relayer's download request by sending the + # mutated block (as the attacker). + with self.nodes[0].assert_debug_log(expected_msgs=["Block mutated: bad-txnmrklroot, hashMerkleRoot mismatch"]): + attacker.send_message(msg_block(mutated_block)) + # Attacker should get disconnected for sending a mutated block + attacker.wait_for_disconnect(timeout=5) + + # Block at height 101 should *still* be the only block in-flight from + # peer 0 + peer_info_after_attack = self.nodes[0].getpeerinfo() + assert_equal(peer_info_after_attack[0]['id'], 0) + assert_equal([101], peer_info_after_attack[0]["inflight"]) + + # The honest relayer should be able to complete relaying the block by + # sending the blocktxn that was requested. + block_txn = msg_blocktxn() + block_txn.block_transactions = BlockTransactions(blockhash=block.sha256, transactions=[tx]) + honest_relayer.send_and_ping(block_txn) + assert_equal(self.nodes[0].getbestblockhash(), block.hash) + + # Check that unexpected-witness mutation check doesn't trigger on a header that doesn't connect to anything + assert_equal(len(self.nodes[0].getpeerinfo()), 1) + attacker = self.nodes[0].add_p2p_connection(P2PInterface()) + block_missing_prev = copy.deepcopy(block) + block_missing_prev.hashPrevBlock = 123 + block_missing_prev.solve() + + # Attacker gets a DoS score of 10, not immediately disconnected, so we do it 10 times to get to 100 + for _ in range(10): + assert_equal(len(self.nodes[0].getpeerinfo()), 2) + with self.nodes[0].assert_debug_log(expected_msgs=["AcceptBlock FAILED (prev-blk-not-found)"]): + attacker.send_message(msg_block(block_missing_prev)) + attacker.wait_for_disconnect(timeout=5) + + +if __name__ == '__main__': + MutatedBlocksTest().main() |