aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_anchors.py63
-rwxr-xr-xtest/functional/p2p_addrv2_relay.py25
-rwxr-xr-xtest/functional/test_framework/messages.py63
-rw-r--r--test/functional/test_framework/socks5.py8
-rwxr-xr-xtest/functional/test_runner.py1
5 files changed, 143 insertions, 17 deletions
diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py
index 0961f21a40..3b75a06d9e 100755
--- a/test/functional/feature_anchors.py
+++ b/test/functional/feature_anchors.py
@@ -6,12 +6,15 @@
import os
-from test_framework.p2p import P2PInterface
+from test_framework.p2p import P2PInterface, P2P_SERVICES
+from test_framework.socks5 import Socks5Configuration, Socks5Server
+from test_framework.messages import CAddress, hash256
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import check_node_connections
+from test_framework.util import check_node_connections, assert_equal, p2p_port
INBOUND_CONNECTIONS = 5
BLOCK_RELAY_CONNECTIONS = 2
+ONION_ADDR = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333"
class AnchorsTest(BitcoinTestFramework):
@@ -54,7 +57,7 @@ class AnchorsTest(BitcoinTestFramework):
else:
inbound_nodes_port.append(hex(int(addr_split[1]))[2:])
- self.log.info("Stop node 0")
+ self.log.debug("Stop node")
self.stop_node(0)
# It should contain only the block-relay-only addresses
@@ -78,12 +81,64 @@ class AnchorsTest(BitcoinTestFramework):
tweaked_contents[20:20] = b'1'
out_file_handler.write(bytes(tweaked_contents))
- self.log.info("Start node")
+ self.log.debug("Start node")
self.start_node(0)
self.log.info("When node starts, check if anchors.dat doesn't exist anymore")
assert not os.path.exists(node_anchors_path)
+ self.log.info("Ensure addrv2 support")
+ # Use proxies to catch outbound connections to networks with 256-bit addresses
+ onion_conf = Socks5Configuration()
+ onion_conf.auth = True
+ onion_conf.unauth = True
+ onion_conf.addr = ('127.0.0.1', p2p_port(self.num_nodes))
+ onion_conf.keep_alive = True
+ onion_proxy = Socks5Server(onion_conf)
+ onion_proxy.start()
+ self.restart_node(0, extra_args=[f"-onion={onion_conf.addr[0]}:{onion_conf.addr[1]}"])
+
+ self.log.info("Add 256-bit-address block-relay-only connections to node")
+ self.nodes[0].addconnection(ONION_ADDR, 'block-relay-only')
+
+ self.log.debug("Stop node")
+ with self.nodes[0].assert_debug_log([f"DumpAnchors: Flush 1 outbound block-relay-only peer addresses to anchors.dat"]):
+ self.stop_node(0)
+ # Manually close keep_alive proxy connection
+ onion_proxy.stop()
+
+ self.log.info("Check for addrv2 addresses in anchors.dat")
+ caddr = CAddress()
+ caddr.net = CAddress.NET_TORV3
+ caddr.ip, port_str = ONION_ADDR.split(":")
+ caddr.port = int(port_str)
+ # TorV3 addrv2 serialization:
+ # time(4) | services(1) | networkID(1) | address length(1) | address(32)
+ expected_pubkey = caddr.serialize_v2()[7:39].hex()
+
+ # position of services byte of first addr in anchors.dat
+ # network magic, vector length, version, nTime
+ services_index = 4 + 1 + 4 + 4
+ data = bytes()
+ with open(node_anchors_path, "rb") as file_handler:
+ data = file_handler.read()
+ assert_equal(data[services_index], 0x00) # services == NONE
+ anchors2 = data.hex()
+ assert expected_pubkey in anchors2
+
+ with open(node_anchors_path, "wb") as file_handler:
+ # Modify service flags for this address even though we never connected to it.
+ # This is necessary because on restart we will not attempt an anchor connection
+ # to a host without our required services, even if its address is in the anchors.dat file
+ new_data = bytearray(data)[:-32]
+ new_data[services_index] = P2P_SERVICES
+ new_data_hash = hash256(new_data)
+ file_handler.write(new_data + new_data_hash)
+
+ self.log.info("Restarting node attempts to reconnect to anchors")
+ with self.nodes[0].assert_debug_log([f"Trying to make an anchor connection to {ONION_ADDR}"]):
+ self.start_node(0, extra_args=[f"-onion={onion_conf.addr[0]}:{onion_conf.addr[1]}"])
+
if __name__ == "__main__":
AnchorsTest().main()
diff --git a/test/functional/p2p_addrv2_relay.py b/test/functional/p2p_addrv2_relay.py
index 9ab190871f..f9a8c44be2 100755
--- a/test/functional/p2p_addrv2_relay.py
+++ b/test/functional/p2p_addrv2_relay.py
@@ -20,19 +20,24 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
I2P_ADDR = "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p"
+ONION_ADDR = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"
ADDRS = []
for i in range(10):
addr = CAddress()
addr.time = int(time.time()) + i
+ addr.port = 8333 + i
addr.nServices = P2P_SERVICES
- # Add one I2P address at an arbitrary position.
+ # Add one I2P and one onion V3 address at an arbitrary position.
if i == 5:
addr.net = addr.NET_I2P
addr.ip = I2P_ADDR
+ addr.port = 0
+ elif i == 8:
+ addr.net = addr.NET_TORV3
+ addr.ip = ONION_ADDR
else:
addr.ip = f"123.123.123.{i % 256}"
- addr.port = 8333 + i
ADDRS.append(addr)
@@ -52,6 +57,17 @@ class AddrReceiver(P2PInterface):
self.wait_until(lambda: "addrv2" in self.last_message)
+def calc_addrv2_msg_size(addrs):
+ size = 1 # vector length byte
+ for addr in addrs:
+ size += 4 # time
+ size += 1 # services, COMPACTSIZE(P2P_SERVICES)
+ size += 1 # network id
+ size += 1 # address length byte
+ size += addr.ADDRV2_ADDRESS_LENGTH[addr.net] # address
+ size += 2 # port
+ return size
+
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -71,9 +87,10 @@ class AddrTest(BitcoinTestFramework):
self.log.info('Check that addrv2 message content is relayed and added to addrman')
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
msg.addrs = ADDRS
+ msg_size = calc_addrv2_msg_size(ADDRS)
with self.nodes[0].assert_debug_log([
- 'received: addrv2 (159 bytes) peer=0',
- 'sending addrv2 (159 bytes) peer=1',
+ f'received: addrv2 ({msg_size} bytes) peer=0',
+ f'sending addrv2 ({msg_size} bytes) peer=1',
]):
addr_source.send_and_ping(msg)
self.nodes[0].setmocktime(int(time.time()) + 30 * 60)
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index a6764365c5..4d635556f4 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -27,6 +27,7 @@ import random
import socket
import struct
import time
+import unittest
from test_framework.siphash import siphash256
from test_framework.util import assert_equal
@@ -77,6 +78,10 @@ def sha256(s):
return hashlib.sha256(s).digest()
+def sha3(s):
+ return hashlib.sha3_256(s).digest()
+
+
def hash256(s):
return sha256(sha256(s))
@@ -229,16 +234,25 @@ class CAddress:
# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
NET_IPV4 = 1
+ NET_IPV6 = 2
+ NET_TORV3 = 4
NET_I2P = 5
+ NET_CJDNS = 6
ADDRV2_NET_NAME = {
NET_IPV4: "IPv4",
- NET_I2P: "I2P"
+ NET_IPV6: "IPv6",
+ NET_TORV3: "TorV3",
+ NET_I2P: "I2P",
+ NET_CJDNS: "CJDNS"
}
ADDRV2_ADDRESS_LENGTH = {
NET_IPV4: 4,
- NET_I2P: 32
+ NET_IPV6: 16,
+ NET_TORV3: 32,
+ NET_I2P: 32,
+ NET_CJDNS: 16
}
I2P_PAD = "===="
@@ -285,7 +299,7 @@ class CAddress:
self.nServices = deser_compact_size(f)
self.net = struct.unpack("B", f.read(1))[0]
- assert self.net in (self.NET_IPV4, self.NET_I2P)
+ assert self.net in self.ADDRV2_NET_NAME
address_length = deser_compact_size(f)
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
@@ -293,14 +307,25 @@ class CAddress:
addr_bytes = f.read(address_length)
if self.net == self.NET_IPV4:
self.ip = socket.inet_ntoa(addr_bytes)
- else:
+ elif self.net == self.NET_IPV6:
+ self.ip = socket.inet_ntop(socket.AF_INET6, addr_bytes)
+ elif self.net == self.NET_TORV3:
+ prefix = b".onion checksum"
+ version = bytes([3])
+ checksum = sha3(prefix + addr_bytes + version)[:2]
+ self.ip = b32encode(addr_bytes + checksum + version).decode("ascii").lower() + ".onion"
+ elif self.net == self.NET_I2P:
self.ip = b32encode(addr_bytes)[0:-len(self.I2P_PAD)].decode("ascii").lower() + ".b32.i2p"
+ elif self.net == self.NET_CJDNS:
+ self.ip = socket.inet_ntop(socket.AF_INET6, addr_bytes)
+ else:
+ raise Exception(f"Address type not supported")
self.port = struct.unpack(">H", f.read(2))[0]
def serialize_v2(self):
"""Serialize in addrv2 format (BIP155)"""
- assert self.net in (self.NET_IPV4, self.NET_I2P)
+ assert self.net in self.ADDRV2_NET_NAME
r = b""
r += struct.pack("<I", self.time)
r += ser_compact_size(self.nServices)
@@ -308,10 +333,20 @@ class CAddress:
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
if self.net == self.NET_IPV4:
r += socket.inet_aton(self.ip)
- else:
+ elif self.net == self.NET_IPV6:
+ r += socket.inet_pton(socket.AF_INET6, self.ip)
+ elif self.net == self.NET_TORV3:
+ sfx = ".onion"
+ assert self.ip.endswith(sfx)
+ r += b32decode(self.ip[0:-len(sfx)], True)[0:32]
+ elif self.net == self.NET_I2P:
sfx = ".b32.i2p"
assert self.ip.endswith(sfx)
r += b32decode(self.ip[0:-len(sfx)] + self.I2P_PAD, True)
+ elif self.net == self.NET_CJDNS:
+ r += socket.inet_pton(socket.AF_INET6, self.ip)
+ else:
+ raise Exception(f"Address type not supported")
r += struct.pack(">H", self.port)
return r
@@ -1852,3 +1887,19 @@ class msg_sendtxrcncl:
def __repr__(self):
return "msg_sendtxrcncl(version=%lu, salt=%lu)" %\
(self.version, self.salt)
+
+class TestFrameworkScript(unittest.TestCase):
+ def test_addrv2_encode_decode(self):
+ def check_addrv2(ip, net):
+ addr = CAddress()
+ addr.net, addr.ip = net, ip
+ ser = addr.serialize_v2()
+ actual = CAddress()
+ actual.deserialize_v2(BytesIO(ser))
+ self.assertEqual(actual, addr)
+
+ check_addrv2("1.65.195.98", CAddress.NET_IPV4)
+ check_addrv2("2001:41f0::62:6974:636f:696e", CAddress.NET_IPV6)
+ check_addrv2("2bqghnldu6mcug4pikzprwhtjjnsyederctvci6klcwzepnjd46ikjyd.onion", CAddress.NET_TORV3)
+ check_addrv2("255fhcp6ajvftnyo7bwz3an3t4a4brhopm3bamyh2iu5r3gnr2rq.b32.i2p", CAddress.NET_I2P)
+ check_addrv2("fc32:17ea:e415:c3bf:9808:149d:b5a2:c9aa", CAddress.NET_CJDNS)
diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py
index 799b1c74b8..0ca06a7396 100644
--- a/test/functional/test_framework/socks5.py
+++ b/test/functional/test_framework/socks5.py
@@ -40,6 +40,7 @@ class Socks5Configuration():
self.af = socket.AF_INET # Bind address family
self.unauth = False # Support unauthenticated
self.auth = False # Support authentication
+ self.keep_alive = False # Do not automatically close connections
class Socks5Command():
"""Information about an incoming socks5 command."""
@@ -115,13 +116,14 @@ class Socks5Connection():
cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
self.serv.queue.put(cmdin)
- logger.info('Proxy: %s', cmdin)
+ logger.debug('Proxy: %s', cmdin)
# Fall through to disconnect
except Exception as e:
logger.exception("socks5 request handling failed.")
self.serv.queue.put(e)
finally:
- self.conn.close()
+ if not self.serv.keep_alive:
+ self.conn.close()
class Socks5Server():
def __init__(self, conf):
@@ -133,6 +135,7 @@ class Socks5Server():
self.running = False
self.thread = None
self.queue = queue.Queue() # report connections and exceptions to client
+ self.keep_alive = conf.keep_alive
def run(self):
while self.running:
@@ -157,4 +160,3 @@ class Socks5Server():
s.connect(self.conf.addr)
s.close()
self.thread.join()
-
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index e627a40e10..d93e6fd6da 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -76,6 +76,7 @@ TEST_FRAMEWORK_MODULES = [
"blocktools",
"ellswift",
"key",
+ "messages",
"muhash",
"ripemd160",
"script",