mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 19:42:46 +01:00
merge bitcoin#27452: cover addrv2 anchors by adding TorV3 to CAddress in messages.py
This commit is contained in:
parent
779e4295ad
commit
7dcf561306
@ -7,11 +7,14 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from test_framework.p2p import P2PInterface
|
from test_framework.p2p import P2PInterface
|
||||||
|
from test_framework.socks5 import Socks5Configuration, Socks5Server
|
||||||
|
from test_framework.messages import CAddress, hash256, NODE_NETWORK
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
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
|
INBOUND_CONNECTIONS = 5
|
||||||
BLOCK_RELAY_CONNECTIONS = 2
|
BLOCK_RELAY_CONNECTIONS = 2
|
||||||
|
ONION_ADDR = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333"
|
||||||
|
|
||||||
|
|
||||||
class AnchorsTest(BitcoinTestFramework):
|
class AnchorsTest(BitcoinTestFramework):
|
||||||
@ -55,7 +58,7 @@ class AnchorsTest(BitcoinTestFramework):
|
|||||||
else:
|
else:
|
||||||
inbound_nodes_port.append(hex(int(addr_split[1]))[2:])
|
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)
|
self.stop_node(0)
|
||||||
|
|
||||||
# It should contain only the block-relay-only addresses
|
# It should contain only the block-relay-only addresses
|
||||||
@ -79,12 +82,64 @@ class AnchorsTest(BitcoinTestFramework):
|
|||||||
tweaked_contents[20:20] = b'1'
|
tweaked_contents[20:20] = b'1'
|
||||||
out_file_handler.write(bytes(tweaked_contents))
|
out_file_handler.write(bytes(tweaked_contents))
|
||||||
|
|
||||||
self.log.info("Start node")
|
self.log.debug("Start node")
|
||||||
self.start_node(0)
|
self.start_node(0)
|
||||||
|
|
||||||
self.log.info("When node starts, check if anchors.dat doesn't exist anymore")
|
self.log.info("When node starts, check if anchors.dat doesn't exist anymore")
|
||||||
assert not os.path.exists(node_anchors_path)
|
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] = NODE_NETWORK
|
||||||
|
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__":
|
if __name__ == "__main__":
|
||||||
AnchorsTest().main()
|
AnchorsTest().main()
|
||||||
|
@ -18,6 +18,7 @@ from test_framework.test_framework import BitcoinTestFramework
|
|||||||
from test_framework.util import assert_equal
|
from test_framework.util import assert_equal
|
||||||
|
|
||||||
I2P_ADDR = "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p"
|
I2P_ADDR = "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p"
|
||||||
|
ONION_ADDR = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"
|
||||||
|
|
||||||
ADDRS: List[CAddress] = []
|
ADDRS: List[CAddress] = []
|
||||||
|
|
||||||
@ -37,6 +38,16 @@ class AddrReceiver(P2PInterface):
|
|||||||
def wait_for_addrv2(self):
|
def wait_for_addrv2(self):
|
||||||
self.wait_until(lambda: "addrv2" in self.last_message)
|
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):
|
class AddrTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
@ -48,14 +59,18 @@ class AddrTest(BitcoinTestFramework):
|
|||||||
for i in range(10):
|
for i in range(10):
|
||||||
addr = CAddress()
|
addr = CAddress()
|
||||||
addr.time = int(self.mocktime) + i
|
addr.time = int(self.mocktime) + i
|
||||||
|
addr.port = 8333 + i
|
||||||
addr.nServices = NODE_NETWORK
|
addr.nServices = NODE_NETWORK
|
||||||
# Add one I2P address at an arbitrary position.
|
# Add one I2P and one onion V3 address at an arbitrary position.
|
||||||
if i == 5:
|
if i == 5:
|
||||||
addr.net = addr.NET_I2P
|
addr.net = addr.NET_I2P
|
||||||
addr.ip = I2P_ADDR
|
addr.ip = I2P_ADDR
|
||||||
|
addr.port = 0
|
||||||
|
elif i == 8:
|
||||||
|
addr.net = addr.NET_TORV3
|
||||||
|
addr.ip = ONION_ADDR
|
||||||
else:
|
else:
|
||||||
addr.ip = f"123.123.123.{i % 256}"
|
addr.ip = f"123.123.123.{i % 256}"
|
||||||
addr.port = 8333 + i
|
|
||||||
ADDRS.append(addr)
|
ADDRS.append(addr)
|
||||||
|
|
||||||
self.log.info('Create connection that sends addrv2 messages')
|
self.log.info('Create connection that sends addrv2 messages')
|
||||||
@ -73,14 +88,15 @@ class AddrTest(BitcoinTestFramework):
|
|||||||
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
|
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
|
||||||
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
|
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
|
||||||
msg.addrs = ADDRS
|
msg.addrs = ADDRS
|
||||||
|
msg_size = calc_addrv2_msg_size(ADDRS)
|
||||||
with self.nodes[0].assert_debug_log([
|
with self.nodes[0].assert_debug_log([
|
||||||
'received: addrv2 (159 bytes) peer=1',
|
f'received: addrv2 ({msg_size} bytes) peer=1',
|
||||||
]):
|
]):
|
||||||
addr_source.send_and_ping(msg)
|
addr_source.send_and_ping(msg)
|
||||||
|
|
||||||
# Wait until "Added ..." before bumping mocktime to make sure addv2 is (almost) fully processed
|
# Wait until "Added ..." before bumping mocktime to make sure addv2 is (almost) fully processed
|
||||||
with self.nodes[0].assert_debug_log([
|
with self.nodes[0].assert_debug_log([
|
||||||
'sending addrv2 (159 bytes) peer=2',
|
f'sending addrv2 ({msg_size} bytes) peer=2',
|
||||||
]):
|
]):
|
||||||
self.bump_mocktime(30 * 60)
|
self.bump_mocktime(30 * 60)
|
||||||
addr_receiver.wait_for_addrv2()
|
addr_receiver.wait_for_addrv2()
|
||||||
|
@ -25,6 +25,7 @@ import random
|
|||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
from test_framework.crypto.siphash import siphash256
|
from test_framework.crypto.siphash import siphash256
|
||||||
from test_framework.util import assert_equal
|
from test_framework.util import assert_equal
|
||||||
@ -74,6 +75,9 @@ def sha256(s):
|
|||||||
return hashlib.sha256(s).digest()
|
return hashlib.sha256(s).digest()
|
||||||
|
|
||||||
|
|
||||||
|
def sha3(s):
|
||||||
|
return hashlib.sha3_256(s).digest()
|
||||||
|
|
||||||
|
|
||||||
def hash256(s):
|
def hash256(s):
|
||||||
return sha256(sha256(s))
|
return sha256(sha256(s))
|
||||||
@ -249,16 +253,25 @@ class CAddress:
|
|||||||
|
|
||||||
# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
|
# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
|
||||||
NET_IPV4 = 1
|
NET_IPV4 = 1
|
||||||
|
NET_IPV6 = 2
|
||||||
|
NET_TORV3 = 4
|
||||||
NET_I2P = 5
|
NET_I2P = 5
|
||||||
|
NET_CJDNS = 6
|
||||||
|
|
||||||
ADDRV2_NET_NAME = {
|
ADDRV2_NET_NAME = {
|
||||||
NET_IPV4: "IPv4",
|
NET_IPV4: "IPv4",
|
||||||
NET_I2P: "I2P"
|
NET_IPV6: "IPv6",
|
||||||
|
NET_TORV3: "TorV3",
|
||||||
|
NET_I2P: "I2P",
|
||||||
|
NET_CJDNS: "CJDNS"
|
||||||
}
|
}
|
||||||
|
|
||||||
ADDRV2_ADDRESS_LENGTH = {
|
ADDRV2_ADDRESS_LENGTH = {
|
||||||
NET_IPV4: 4,
|
NET_IPV4: 4,
|
||||||
NET_I2P: 32
|
NET_IPV6: 16,
|
||||||
|
NET_TORV3: 32,
|
||||||
|
NET_I2P: 32,
|
||||||
|
NET_CJDNS: 16
|
||||||
}
|
}
|
||||||
|
|
||||||
I2P_PAD = "===="
|
I2P_PAD = "===="
|
||||||
@ -305,7 +318,7 @@ class CAddress:
|
|||||||
self.nServices = deser_compact_size(f)
|
self.nServices = deser_compact_size(f)
|
||||||
|
|
||||||
self.net = struct.unpack("B", f.read(1))[0]
|
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)
|
address_length = deser_compact_size(f)
|
||||||
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
|
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
|
||||||
@ -313,14 +326,25 @@ class CAddress:
|
|||||||
addr_bytes = f.read(address_length)
|
addr_bytes = f.read(address_length)
|
||||||
if self.net == self.NET_IPV4:
|
if self.net == self.NET_IPV4:
|
||||||
self.ip = socket.inet_ntoa(addr_bytes)
|
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"
|
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]
|
self.port = struct.unpack(">H", f.read(2))[0]
|
||||||
|
|
||||||
def serialize_v2(self):
|
def serialize_v2(self):
|
||||||
"""Serialize in addrv2 format (BIP155)"""
|
"""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 = b""
|
||||||
r += struct.pack("<I", self.time)
|
r += struct.pack("<I", self.time)
|
||||||
r += ser_compact_size(self.nServices)
|
r += ser_compact_size(self.nServices)
|
||||||
@ -328,10 +352,20 @@ class CAddress:
|
|||||||
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
|
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
|
||||||
if self.net == self.NET_IPV4:
|
if self.net == self.NET_IPV4:
|
||||||
r += socket.inet_aton(self.ip)
|
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"
|
sfx = ".b32.i2p"
|
||||||
assert self.ip.endswith(sfx)
|
assert self.ip.endswith(sfx)
|
||||||
r += b32decode(self.ip[0:-len(sfx)] + self.I2P_PAD, True)
|
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)
|
r += struct.pack(">H", self.port)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@ -2592,3 +2626,19 @@ class msg_sendtxrcncl:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "msg_sendtxrcncl(version=%lu, salt=%lu)" %\
|
return "msg_sendtxrcncl(version=%lu, salt=%lu)" %\
|
||||||
(self.version, self.salt)
|
(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)
|
||||||
|
@ -40,6 +40,7 @@ class Socks5Configuration():
|
|||||||
self.af = socket.AF_INET # Bind address family
|
self.af = socket.AF_INET # Bind address family
|
||||||
self.unauth = False # Support unauthenticated
|
self.unauth = False # Support unauthenticated
|
||||||
self.auth = False # Support authentication
|
self.auth = False # Support authentication
|
||||||
|
self.keep_alive = False # Do not automatically close connections
|
||||||
|
|
||||||
class Socks5Command():
|
class Socks5Command():
|
||||||
"""Information about an incoming socks5 command."""
|
"""Information about an incoming socks5 command."""
|
||||||
@ -115,13 +116,14 @@ class Socks5Connection():
|
|||||||
|
|
||||||
cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
|
cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
|
||||||
self.serv.queue.put(cmdin)
|
self.serv.queue.put(cmdin)
|
||||||
logger.info('Proxy: %s', cmdin)
|
logger.debug('Proxy: %s', cmdin)
|
||||||
# Fall through to disconnect
|
# Fall through to disconnect
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("socks5 request handling failed.")
|
logger.exception("socks5 request handling failed.")
|
||||||
self.serv.queue.put(e)
|
self.serv.queue.put(e)
|
||||||
finally:
|
finally:
|
||||||
self.conn.close()
|
if not self.serv.keep_alive:
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
class Socks5Server():
|
class Socks5Server():
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
@ -133,6 +135,7 @@ class Socks5Server():
|
|||||||
self.running = False
|
self.running = False
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self.queue = queue.Queue() # report connections and exceptions to client
|
self.queue = queue.Queue() # report connections and exceptions to client
|
||||||
|
self.keep_alive = conf.keep_alive
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while self.running:
|
while self.running:
|
||||||
@ -157,4 +160,3 @@ class Socks5Server():
|
|||||||
s.connect(self.conf.addr)
|
s.connect(self.conf.addr)
|
||||||
s.close()
|
s.close()
|
||||||
self.thread.join()
|
self.thread.join()
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ TEST_FRAMEWORK_MODULES = [
|
|||||||
"crypto.chacha20",
|
"crypto.chacha20",
|
||||||
"crypto.ellswift",
|
"crypto.ellswift",
|
||||||
"key",
|
"key",
|
||||||
|
"messages",
|
||||||
"crypto.muhash",
|
"crypto.muhash",
|
||||||
"crypto.poly1305",
|
"crypto.poly1305",
|
||||||
"crypto.ripemd160",
|
"crypto.ripemd160",
|
||||||
|
Loading…
Reference in New Issue
Block a user