You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.2 KiB
171 lines
5.2 KiB
#!/usr/bin/env python3 |
|
# Copyright (c) 2014-2021 The Bitcoin Core developers |
|
# Distributed under the MIT software license, see the accompanying |
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
|
''' |
|
Script to generate list of seed nodes for chainparams.cpp. |
|
|
|
This script expects two text files in the directory that is passed as an |
|
argument: |
|
|
|
nodes_main.txt |
|
nodes_test.txt |
|
|
|
These files must consist of lines in the format |
|
|
|
<ip>:<port> |
|
[<ipv6>]:<port> |
|
<onion>.onion:<port> |
|
<i2p>.b32.i2p:<port> |
|
|
|
The output will be two data structures with the peers in binary format: |
|
|
|
static const uint8_t chainparams_seed_{main,test}[]={ |
|
... |
|
} |
|
|
|
These should be pasted into `src/chainparamsseeds.h`. |
|
''' |
|
|
|
from base64 import b32decode |
|
from enum import Enum |
|
import struct |
|
import sys |
|
import os |
|
import re |
|
|
|
class BIP155Network(Enum): |
|
IPV4 = 1 |
|
IPV6 = 2 |
|
TORV2 = 3 # no longer supported |
|
TORV3 = 4 |
|
I2P = 5 |
|
CJDNS = 6 |
|
|
|
def name_to_bip155(addr): |
|
'''Convert address string to BIP155 (networkID, addr) tuple.''' |
|
if addr.endswith('.onion'): |
|
vchAddr = b32decode(addr[0:-6], True) |
|
if len(vchAddr) == 35: |
|
assert vchAddr[34] == 3 |
|
return (BIP155Network.TORV3, vchAddr[:32]) |
|
elif len(vchAddr) == 10: |
|
return (BIP155Network.TORV2, vchAddr) |
|
else: |
|
raise ValueError('Invalid onion %s' % vchAddr) |
|
elif addr.endswith('.b32.i2p'): |
|
vchAddr = b32decode(addr[0:-8] + '====', True) |
|
if len(vchAddr) == 32: |
|
return (BIP155Network.I2P, vchAddr) |
|
else: |
|
raise ValueError(f'Invalid I2P {vchAddr}') |
|
elif '.' in addr: # IPv4 |
|
return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.')))) |
|
elif ':' in addr: # IPv6 |
|
sub = [[], []] # prefix, suffix |
|
x = 0 |
|
addr = addr.split(':') |
|
for i,comp in enumerate(addr): |
|
if comp == '': |
|
if i == 0 or i == (len(addr)-1): # skip empty component at beginning or end |
|
continue |
|
x += 1 # :: skips to suffix |
|
assert(x < 2) |
|
else: # two bytes per component |
|
val = int(comp, 16) |
|
sub[x].append(val >> 8) |
|
sub[x].append(val & 0xff) |
|
nullbytes = 16 - len(sub[0]) - len(sub[1]) |
|
assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)) |
|
return (BIP155Network.IPV6, bytes(sub[0] + ([0] * nullbytes) + sub[1])) |
|
else: |
|
raise ValueError('Could not parse address %s' % addr) |
|
|
|
def parse_spec(s): |
|
'''Convert endpoint string to BIP155 (networkID, addr, port) tuple.''' |
|
match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) |
|
if match: # ipv6 |
|
host = match.group(1) |
|
port = match.group(2) |
|
elif s.count(':') > 1: # ipv6, no port |
|
host = s |
|
port = '' |
|
else: |
|
(host,_,port) = s.partition(':') |
|
|
|
if not port: |
|
port = 0 |
|
else: |
|
port = int(port) |
|
|
|
host = name_to_bip155(host) |
|
|
|
if host[0] == BIP155Network.TORV2: |
|
return None # TORV2 is no longer supported, so we ignore it |
|
else: |
|
return host + (port, ) |
|
|
|
def ser_compact_size(l): |
|
r = b"" |
|
if l < 253: |
|
r = struct.pack("B", l) |
|
elif l < 0x10000: |
|
r = struct.pack("<BH", 253, l) |
|
elif l < 0x100000000: |
|
r = struct.pack("<BI", 254, l) |
|
else: |
|
r = struct.pack("<BQ", 255, l) |
|
return r |
|
|
|
def bip155_serialize(spec): |
|
''' |
|
Serialize (networkID, addr, port) tuple to BIP155 binary format. |
|
''' |
|
r = b"" |
|
r += struct.pack('B', spec[0].value) |
|
r += ser_compact_size(len(spec[1])) |
|
r += spec[1] |
|
r += struct.pack('>H', spec[2]) |
|
return r |
|
|
|
def process_nodes(g, f, structname): |
|
g.write('static const uint8_t %s[] = {\n' % structname) |
|
for line in f: |
|
comment = line.find('#') |
|
if comment != -1: |
|
line = line[0:comment] |
|
line = line.strip() |
|
if not line: |
|
continue |
|
|
|
spec = parse_spec(line) |
|
if spec is None: # ignore this entry (e.g. no longer supported addresses like TORV2) |
|
continue |
|
blob = bip155_serialize(spec) |
|
hoststr = ','.join(('0x%02x' % b) for b in blob) |
|
g.write(f' {hoststr},\n') |
|
g.write('};\n') |
|
|
|
def main(): |
|
if len(sys.argv)<2: |
|
print(('Usage: %s <path_to_nodes_txt>' % sys.argv[0]), file=sys.stderr) |
|
sys.exit(1) |
|
g = sys.stdout |
|
indir = sys.argv[1] |
|
g.write('#ifndef BITCOIN_CHAINPARAMSSEEDS_H\n') |
|
g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n') |
|
g.write('/**\n') |
|
g.write(' * List of fixed seed nodes for the bitcoin network\n') |
|
g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n') |
|
g.write(' *\n') |
|
g.write(' * Each line contains a BIP155 serialized (networkID, addr, port) tuple.\n') |
|
g.write(' */\n') |
|
with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f: |
|
process_nodes(g, f, 'chainparams_seed_main') |
|
g.write('\n') |
|
with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: |
|
process_nodes(g, f, 'chainparams_seed_test') |
|
g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n') |
|
|
|
if __name__ == '__main__': |
|
main()
|
|
|