Skip to content

Commit

Permalink
[test] Use deterministic addrman in addrman info tests
Browse files Browse the repository at this point in the history
The nodes are restarted with an empty addrman and populated
with addresses from different networks using a helper function.
We can safely add multiple addresses to addrman tables without
worrying about unpredictable collisions since bucket:position
is fixed in a deterministic addrman.
  • Loading branch information
stratospher committed Dec 22, 2023
1 parent b07be27 commit b7ec1fc
Showing 1 changed file with 99 additions and 70 deletions.
169 changes: 99 additions & 70 deletions test/functional/rpc_net.py
Expand Up @@ -13,7 +13,6 @@
import time

import test_framework.messages
from test_framework.netutil import ADDRMAN_NEW_BUCKET_COUNT, ADDRMAN_TRIED_BUCKET_COUNT, ADDRMAN_BUCKET_SIZE
from test_framework.p2p import (
P2PInterface,
P2P_SERVICES,
Expand Down Expand Up @@ -42,6 +41,20 @@ def assert_net_servicesnames(servicesflag, servicenames):
assert servicesflag_generated == servicesflag


def seed_addrman(node):
""" Populate the addrman with addresses from different networks.
Here 2 ipv4, 2 ipv6, 1 cjdns, 2 onion and 1 i2p addresses are added.
"""
node.addpeeraddress(address="1.2.3.4", tried=True, port=8333)
node.addpeeraddress(address="2.0.0.0", port=8333)
node.addpeeraddress(address="1233:3432:2434:2343:3234:2345:6546:4534", tried=True, port=8333)
node.addpeeraddress(address="2803:0:1234:abcd::1", port=45324)
node.addpeeraddress(address="fc00:1:2:3:4:5:6:7", port=8333)
node.addpeeraddress(address="pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion", tried=True, port=8333)
node.addpeeraddress(address="nrfj6inpyf73gpkyool35hcmne5zwfmse3jl3aw23vk7chdemalyaqad.onion", port=45324, tried=True)
node.addpeeraddress(address="c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p", port=8333)


class NetTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
Expand Down Expand Up @@ -376,25 +389,33 @@ def test_sendmsgtopeer(self):

def test_getaddrmaninfo(self):
self.log.info("Test getaddrmaninfo")
self.restart_node(1, extra_args=["-cjdnsreachable", "-test=addrman"], clear_addrman=True)
node = self.nodes[1]
seed_addrman(node)

expected_network_count = {
'all_networks': {'new': 4, 'tried': 4, 'total': 8},
'ipv4': {'new': 1, 'tried': 1, 'total': 2},
'ipv6': {'new': 1, 'tried': 1, 'total': 2},
'onion': {'new': 0, 'tried': 2, 'total': 2},
'i2p': {'new': 1, 'tried': 0, 'total': 1},
'cjdns': {'new': 1, 'tried': 0, 'total': 1},
}

# current count of ipv4 addresses in addrman is {'new':1, 'tried':1}
self.log.info("Test that count of addresses in addrman match expected values")
self.log.debug("Test that count of addresses in addrman match expected values")
res = node.getaddrmaninfo()
assert_equal(res["ipv4"]["new"], 1)
assert_equal(res["ipv4"]["tried"], 1)
assert_equal(res["ipv4"]["total"], 2)
assert_equal(res["all_networks"]["new"], 1)
assert_equal(res["all_networks"]["tried"], 1)
assert_equal(res["all_networks"]["total"], 2)
for net in ["ipv6", "onion", "i2p", "cjdns"]:
assert_equal(res[net]["new"], 0)
assert_equal(res[net]["tried"], 0)
assert_equal(res[net]["total"], 0)
for network, count in expected_network_count.items():
assert_equal(res[network]['new'], count['new'])
assert_equal(res[network]['tried'], count['tried'])
assert_equal(res[network]['total'], count['total'])

def test_getrawaddrman(self):
self.log.info("Test getrawaddrman")
self.restart_node(1, extra_args=["-cjdnsreachable", "-test=addrman"], clear_addrman=True)
node = self.nodes[1]
self.addr_time = int(time.time())
node.setmocktime(self.addr_time)
seed_addrman(node)

self.log.debug("Test that getrawaddrman is a hidden RPC")
# It is hidden from general help, but its detailed help may be called directly.
Expand All @@ -416,88 +437,96 @@ def check_getrawaddrman_entries(expected):
getrawaddrman = node.getrawaddrman()
getaddrmaninfo = node.getaddrmaninfo()
for (table_name, table_info) in expected.items():
assert_equal(len(getrawaddrman[table_name]), len(table_info["entries"]))
assert_equal(len(getrawaddrman[table_name]), len(table_info))
assert_equal(len(getrawaddrman[table_name]), getaddrmaninfo["all_networks"][table_name])

for bucket_position in getrawaddrman[table_name].keys():
bucket = int(bucket_position.split("/")[0])
position = int(bucket_position.split("/")[1])

# bucket and position only be sanity checked here as the
# test-addrman isn't deterministic
assert 0 <= int(bucket) < table_info["bucket_count"]
assert 0 <= int(position) < ADDRMAN_BUCKET_SIZE

entry = getrawaddrman[table_name][bucket_position]
expected_entry = list(filter(lambda e: e["address"] == entry["address"], table_info["entries"]))[0]
expected_entry = list(filter(lambda e: e["address"] == entry["address"], table_info))[0]
assert bucket_position == expected_entry["bucket_position"]
check_addr_information(entry, expected_entry)

# we expect one addrman new and tried table entry, which were added in a previous test
# we expect 4 new and 4 tried table entries in the addrman which were added using seed_addrman()
expected = {
"new": {
"bucket_count": ADDRMAN_NEW_BUCKET_COUNT,
"entries": [
"new": [
{
"bucket_position": "82/8",
"address": "2.0.0.0",
"port": 8333,
"services": 9,
"network": "ipv4",
"source": "2.0.0.0",
"source_network": "ipv4",
},
{
"bucket_position": "336/24",
"address": "fc00:1:2:3:4:5:6:7",
"port": 8333,
"services": 9,
"network": "cjdns",
"source": "fc00:1:2:3:4:5:6:7",
"source_network": "cjdns",
},
{
"bucket_position": "963/46",
"address": "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p",
"port": 8333,
"services": 9,
"network": "i2p",
"source": "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p",
"source_network": "i2p",
},
{
"bucket_position": "613/6",
"address": "2803:0:1234:abcd::1",
"services": 9,
"network": "ipv6",
"source": "2803:0:1234:abcd::1",
"source_network": "ipv6",
"port": 45324,
}
]
},
"tried": {
"bucket_count": ADDRMAN_TRIED_BUCKET_COUNT,
"entries": [
],
"tried": [
{
"bucket_position": "6/33",
"address": "1.2.3.4",
"port": 8333,
"services": 9,
"network": "ipv4",
"source": "1.2.3.4",
"source_network": "ipv4",
},
{
"bucket_position": "197/34",
"address": "1233:3432:2434:2343:3234:2345:6546:4534",
"port": 8333,
"services": 9,
"network": "ipv6",
"source": "1233:3432:2434:2343:3234:2345:6546:4534",
"source_network": "ipv6",
},
{
"bucket_position": "72/61",
"address": "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
"port": 8333,
"services": 9,
"network": "onion",
"source": "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion",
"source_network": "onion"
},
{
"bucket_position": "139/46",
"address": "nrfj6inpyf73gpkyool35hcmne5zwfmse3jl3aw23vk7chdemalyaqad.onion",
"services": 9,
"network": "onion",
"source": "nrfj6inpyf73gpkyool35hcmne5zwfmse3jl3aw23vk7chdemalyaqad.onion",
"source_network": "onion",
"port": 45324,
}
]
}
]
}

self.log.debug("Test that the getrawaddrman contains information about the addresses added in a previous test")
check_getrawaddrman_entries(expected)

self.log.debug("Add one new address to each addrman table")
expected["new"]["entries"].append({
"address": "2803:0:1234:abcd::1",
"services": 9,
"network": "ipv6",
"source": "2803:0:1234:abcd::1",
"source_network": "ipv6",
"port": -1, # set once addpeeraddress is successful
})
expected["tried"]["entries"].append({
"address": "nrfj6inpyf73gpkyool35hcmne5zwfmse3jl3aw23vk7chdemalyaqad.onion",
"services": 9,
"network": "onion",
"source": "nrfj6inpyf73gpkyool35hcmne5zwfmse3jl3aw23vk7chdemalyaqad.onion",
"source_network": "onion",
"port": -1, # set once addpeeraddress is successful
})

port = 0
for (table_name, table_info) in expected.items():
# There's a slight chance that the to-be-added address collides with an already
# present table entry. To avoid this, we increment the port until an address has been
# added. Incrementing the port changes the position in the new table bucket (bucket
# stays the same) and changes both the bucket and the position in the tried table.
while True:
if node.addpeeraddress(address=table_info["entries"][1]["address"], port=port, tried=table_name == "tried")["success"]:
table_info["entries"][1]["port"] = port
self.log.debug(f"Added {table_info['entries'][1]['address']} to {table_name} table")
break
else:
port += 1

self.log.debug("Test that the newly added addresses appear in getrawaddrman")
self.log.debug("Test that getrawaddrman contains information about newly added addresses in each addrman table")
check_getrawaddrman_entries(expected)


Expand Down

0 comments on commit b7ec1fc

Please sign in to comment.