mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
* Change version from double to single SHA256 This invalidates serialized AddrMan data which forces re-bucketing. * Avoid reversing byte order for ASMap version in log Makes it easier to match it with externally computed hashes of the encoded file.
174 lines
7.2 KiB
Python
Executable File
174 lines
7.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2020-present 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 asmap config argument for ASN-based IP bucketing.
|
|
|
|
Verify node behaviour and debug log when launching bitcoind with different
|
|
`-asmap` and `-noasmap` arg values, including absolute and relative paths, and
|
|
with missing and unparseable files.
|
|
|
|
The tests are order-independent.
|
|
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import shutil
|
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
assert_raises_rpc_error,
|
|
)
|
|
|
|
ASMAP = 'src/test/data/asmap.raw' # path to unit test skeleton asmap
|
|
VERSION = '55dcec00c72b8a33a271dad20271e14cfbe5526aa1dc12e2559f30c376d85a35'
|
|
|
|
def expected_messages(filename):
|
|
return [f'Opened asmap file "{filename}" (59 bytes) from disk',
|
|
f'Using asmap version {VERSION} for IP bucketing']
|
|
|
|
class AsmapTest(BitcoinTestFramework):
|
|
def set_test_params(self):
|
|
self.num_nodes = 1
|
|
# Do addrman checks on all operations and use deterministic addrman
|
|
self.extra_args = [["-checkaddrman=1", "-test=addrman"]]
|
|
|
|
def fill_addrman(self, node_id):
|
|
"""Add 2 tried addresses to the addrman, followed by 2 new addresses."""
|
|
for addr, tried in [[0, True], [1, True], [2, False], [3, False]]:
|
|
self.nodes[node_id].addpeeraddress(address=f"101.{addr}.0.0", tried=tried, port=8333)
|
|
|
|
def test_without_asmap_arg(self):
|
|
self.log.info('Test bitcoind with no -asmap arg passed')
|
|
self.stop_node(0)
|
|
with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
|
|
self.start_node(0)
|
|
|
|
def test_noasmap_arg(self):
|
|
self.log.info('Test bitcoind with -noasmap arg passed')
|
|
self.stop_node(0)
|
|
with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
|
|
self.start_node(0, ["-noasmap"])
|
|
|
|
def test_asmap_with_absolute_path(self):
|
|
self.log.info('Test bitcoind -asmap=<absolute path>')
|
|
self.stop_node(0)
|
|
filename = os.path.join(self.datadir, 'my-map-file.map')
|
|
shutil.copyfile(self.asmap_raw, filename)
|
|
with self.node.assert_debug_log(expected_messages(filename)):
|
|
self.start_node(0, [f'-asmap={filename}'])
|
|
os.remove(filename)
|
|
|
|
def test_asmap_with_relative_path(self):
|
|
self.log.info('Test bitcoind -asmap=<relative path>')
|
|
self.stop_node(0)
|
|
name = 'ASN_map'
|
|
filename = os.path.join(self.datadir, name)
|
|
shutil.copyfile(self.asmap_raw, filename)
|
|
with self.node.assert_debug_log(expected_messages(filename)):
|
|
self.start_node(0, [f'-asmap={name}'])
|
|
os.remove(filename)
|
|
|
|
def test_embedded_asmap(self):
|
|
if self.is_embedded_asmap_compiled():
|
|
self.log.info('Test bitcoind -asmap (using embedded map data)')
|
|
for arg in ['-asmap', '-asmap=1']:
|
|
self.stop_node(0)
|
|
with self.node.assert_debug_log(["Opened asmap data", "from embedded byte array"]):
|
|
self.start_node(0, [arg])
|
|
else:
|
|
self.log.info('Test bitcoind -asmap (compiled without embedded map data)')
|
|
for arg in ['-asmap', '-asmap=1']:
|
|
self.stop_node(0)
|
|
msg = "Error: Embedded asmap data not available"
|
|
self.node.assert_start_raises_init_error(extra_args=[arg], expected_msg=msg)
|
|
|
|
def test_asmap_interaction_with_addrman_containing_entries(self):
|
|
self.log.info("Test bitcoind -asmap restart with addrman containing new and tried entries")
|
|
self.stop_node(0)
|
|
self.start_node(0, [f"-asmap={self.asmap_raw}", "-checkaddrman=1", "-test=addrman"])
|
|
self.fill_addrman(node_id=0)
|
|
self.restart_node(0, [f"-asmap={self.asmap_raw}", "-checkaddrman=1", "-test=addrman"])
|
|
with self.node.assert_debug_log(
|
|
expected_msgs=[
|
|
"CheckAddrman: new 2, tried 2, total 4 started",
|
|
"CheckAddrman: completed",
|
|
]
|
|
):
|
|
self.node.getnodeaddresses() # getnodeaddresses re-runs the addrman checks
|
|
|
|
def test_asmap_with_missing_file(self):
|
|
self.log.info('Test bitcoind -asmap with missing map file')
|
|
self.stop_node(0)
|
|
msg = f"Error: Could not find asmap file \"{self.datadir}{os.sep}missing\""
|
|
self.node.assert_start_raises_init_error(extra_args=['-asmap=missing'], expected_msg=msg)
|
|
|
|
def test_empty_asmap(self):
|
|
self.log.info('Test bitcoind -asmap with empty map file')
|
|
self.stop_node(0)
|
|
empty_asmap = os.path.join(self.datadir, "ip_asn.map")
|
|
with open(empty_asmap, "w") as f:
|
|
f.write("")
|
|
msg = f"Error: Could not parse asmap file \"{empty_asmap}\""
|
|
self.node.assert_start_raises_init_error(extra_args=[f'-asmap={empty_asmap}'], expected_msg=msg)
|
|
os.remove(empty_asmap)
|
|
|
|
def test_asmap_health_check(self):
|
|
self.log.info('Test bitcoind -asmap logs ASMap Health Check with basic stats')
|
|
msg = "ASMap Health Check: 4 clearnet peers are mapped to 3 ASNs with 0 peers being unmapped"
|
|
with self.node.assert_debug_log(expected_msgs=[msg]):
|
|
self.start_node(0, extra_args=[f'-asmap={self.asmap_raw}'])
|
|
raw_addrman = self.node.getrawaddrman()
|
|
asns = []
|
|
for _, entries in raw_addrman.items():
|
|
for _, entry in entries.items():
|
|
asn = entry['mapped_as']
|
|
if asn not in asns:
|
|
asns.append(asn)
|
|
assert_equal(len(asns), 3)
|
|
|
|
def test_export_embedded_asmap(self):
|
|
self.log.info('Test exportasmap RPC')
|
|
export_path = os.path.join(self.datadir, "asmap.dat")
|
|
|
|
if not self.is_embedded_asmap_compiled():
|
|
assert_raises_rpc_error(-1, "No embedded ASMap data available", self.node.exportasmap, export_path)
|
|
return
|
|
|
|
# Relative paths are resolved against the datadir.
|
|
result = self.node.exportasmap("asmap.dat")
|
|
assert_equal(result["path"], export_path)
|
|
|
|
with open(export_path, 'rb') as f:
|
|
data = f.read()
|
|
assert_equal(result["bytes_written"], len(data))
|
|
|
|
# Added in https://github.com/bitcoin/bitcoin/pull/34696
|
|
expected_hash = "478d61986c59365cf86cd244485bbbe76a9ca0c630864717286dd19949879074"
|
|
assert_equal(hashlib.sha256(data).hexdigest(), expected_hash)
|
|
assert_equal(result["file_hash"], expected_hash)
|
|
|
|
os.remove(export_path)
|
|
|
|
def run_test(self):
|
|
self.node = self.nodes[0]
|
|
self.datadir = self.node.chain_path
|
|
base_dir = self.config["environment"]["SRCDIR"]
|
|
self.asmap_raw = os.path.join(base_dir, ASMAP)
|
|
|
|
self.test_without_asmap_arg()
|
|
self.test_noasmap_arg()
|
|
self.test_asmap_with_absolute_path()
|
|
self.test_asmap_with_relative_path()
|
|
self.test_embedded_asmap()
|
|
self.test_asmap_interaction_with_addrman_containing_entries()
|
|
self.test_asmap_with_missing_file()
|
|
self.test_empty_asmap()
|
|
self.test_asmap_health_check()
|
|
self.test_export_embedded_asmap()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
AsmapTest(__file__).main()
|