r1
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Seeds
|
||||
|
||||
Utility to generate the seeds.txt list that is compiled into the client
|
||||
(see [src/chainparamsseeds.h](/src/chainparamsseeds.h) and other utilities in [contrib/seeds](/contrib/seeds)).
|
||||
|
||||
Be sure to update `PATTERN_AGENT` in `makeseeds.py` to include the current version,
|
||||
and remove old versions as necessary.
|
||||
|
||||
The seeds compiled into the release are created from fuzzbawls' DNS seed data, like this:
|
||||
|
||||
curl -s http://seeder.fuzzbawls.pw/agrarian-mainnet.txt > seeds_main.txt
|
||||
python3 makeseeds.py < seeds_main.txt > nodes_main.txt
|
||||
python3 generate-seeds.py . > ../../src/chainparamsseeds.h
|
||||
|
||||
## Dependencies
|
||||
|
||||
Ubuntu:
|
||||
|
||||
sudo apt-get install python3-dnspython
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014-2017 Wladimir J. van der Laan
|
||||
# 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>
|
||||
<ip>:<port>
|
||||
[<ipv6>]
|
||||
[<ipv6>]:<port>
|
||||
<onion>.onion
|
||||
0xDDBBCCAA (IPv4 little-endian old pnSeeds format)
|
||||
|
||||
The output will be two data structures with the peers in binary format:
|
||||
|
||||
static SeedSpec6 pnSeed6_main[]={
|
||||
...
|
||||
}
|
||||
static SeedSpec6 pnSeed6_test[]={
|
||||
...
|
||||
}
|
||||
|
||||
These should be pasted into `src/chainparamsseeds.h`.
|
||||
'''
|
||||
|
||||
from base64 import b32decode
|
||||
from binascii import a2b_hex
|
||||
import sys, os
|
||||
import re
|
||||
|
||||
# ipv4 in ipv6 prefix
|
||||
pchIPv4 = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff])
|
||||
# tor-specific ipv6 prefix
|
||||
pchOnionCat = bytearray([0xFD,0x87,0xD8,0x7E,0xEB,0x43])
|
||||
|
||||
def name_to_ipv6(addr):
|
||||
if len(addr)>6 and addr.endswith('.onion'):
|
||||
vchAddr = b32decode(addr[0:-6], True)
|
||||
if len(vchAddr) != 16-len(pchOnionCat):
|
||||
raise ValueError('Invalid onion %s' % s)
|
||||
return pchOnionCat + vchAddr
|
||||
elif '.' in addr: # IPv4
|
||||
return pchIPv4 + bytearray((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 bytearray(sub[0] + ([0] * nullbytes) + sub[1])
|
||||
elif addr.startswith('0x'): # IPv4-in-little-endian
|
||||
return pchIPv4 + bytearray(reversed(a2b_hex(addr[2:])))
|
||||
else:
|
||||
raise ValueError('Could not parse address %s' % addr)
|
||||
|
||||
def parse_spec(s, defaultport):
|
||||
match = re.match('\[([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 = defaultport
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
host = name_to_ipv6(host)
|
||||
|
||||
return (host,port)
|
||||
|
||||
def process_nodes(g, f, structname, defaultport):
|
||||
g.write('static SeedSpec6 %s[] = {\n' % structname)
|
||||
first = True
|
||||
for line in f:
|
||||
comment = line.find('#')
|
||||
if comment != -1:
|
||||
line = line[0:comment]
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if not first:
|
||||
g.write(',\n')
|
||||
first = False
|
||||
|
||||
(host,port) = parse_spec(line, defaultport)
|
||||
hoststr = ','.join(('0x%02x' % b) for b in host)
|
||||
g.write(' {{%s}, %i}' % (hoststr, port))
|
||||
g.write('\n};\n')
|
||||
|
||||
def main():
|
||||
if len(sys.argv)<2:
|
||||
print(('Usage: %s <path_to_nodes_txt>' % sys.argv[0]), file=sys.stderr)
|
||||
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 16-byte IPv6 address and a port.\n')
|
||||
g.write(' * IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly.\n')
|
||||
g.write(' */\n')
|
||||
with open(os.path.join(indir,'nodes_main.txt'),'r') as f:
|
||||
process_nodes(g, f, 'pnSeed6_main', 51336)
|
||||
g.write('\n')
|
||||
with open(os.path.join(indir,'nodes_test.txt'),'r') as f:
|
||||
process_nodes(g, f, 'pnSeed6_test', 61336)
|
||||
g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2013-2017 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#
|
||||
# Generate seeds.txt from Pieter's DNS seeder
|
||||
#
|
||||
|
||||
NSEEDS=512
|
||||
|
||||
MAX_SEEDS_PER_ASN=2
|
||||
|
||||
MIN_BLOCKS = 615801
|
||||
|
||||
# These are hosts that have been observed to be behaving strangely (e.g.
|
||||
# aggressively connecting to every node).
|
||||
SUSPICIOUS_HOSTS = {
|
||||
""
|
||||
}
|
||||
|
||||
import re
|
||||
import sys
|
||||
import dns.resolver
|
||||
import collections
|
||||
|
||||
PATTERN_IPV4 = re.compile(r"^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})):(\d+)$")
|
||||
PATTERN_IPV6 = re.compile(r"^\[([0-9a-z:]+)\]:(\d+)$")
|
||||
PATTERN_ONION = re.compile(r"^([abcdefghijklmnopqrstuvwxyz234567]{16}\.onion):(\d+)$")
|
||||
PATTERN_AGENT = re.compile(r"^(/AgrarianCore:2.2.(0|1|99)/)$")
|
||||
|
||||
def parseline(line):
|
||||
sline = line.split()
|
||||
if len(sline) < 11:
|
||||
return None
|
||||
m = PATTERN_IPV4.match(sline[0])
|
||||
sortkey = None
|
||||
ip = None
|
||||
if m is None:
|
||||
m = PATTERN_IPV6.match(sline[0])
|
||||
if m is None:
|
||||
m = PATTERN_ONION.match(sline[0])
|
||||
if m is None:
|
||||
return None
|
||||
else:
|
||||
net = 'onion'
|
||||
ipstr = sortkey = m.group(1)
|
||||
port = int(m.group(2))
|
||||
else:
|
||||
net = 'ipv6'
|
||||
if m.group(1) in ['::']: # Not interested in localhost
|
||||
return None
|
||||
ipstr = m.group(1)
|
||||
sortkey = ipstr # XXX parse IPv6 into number, could use name_to_ipv6 from generate-seeds
|
||||
port = int(m.group(2))
|
||||
else:
|
||||
# Do IPv4 sanity check
|
||||
ip = 0
|
||||
for i in range(0,4):
|
||||
if int(m.group(i+2)) < 0 or int(m.group(i+2)) > 255:
|
||||
return None
|
||||
ip = ip + (int(m.group(i+2)) << (8*(3-i)))
|
||||
if ip == 0:
|
||||
return None
|
||||
net = 'ipv4'
|
||||
sortkey = ip
|
||||
ipstr = m.group(1)
|
||||
port = int(m.group(6))
|
||||
# Skip bad results.
|
||||
if sline[1] == 0:
|
||||
return None
|
||||
# Extract uptime %.
|
||||
uptime30 = float(sline[7][:-1])
|
||||
# Extract Unix timestamp of last success.
|
||||
lastsuccess = int(sline[2])
|
||||
# Extract protocol version.
|
||||
version = int(sline[10])
|
||||
# Extract user agent.
|
||||
if len(sline) > 11:
|
||||
agent = sline[11][1:] + sline[12][:-1]
|
||||
else:
|
||||
agent = sline[11][1:-1]
|
||||
# Extract service flags.
|
||||
service = int(sline[9], 16)
|
||||
# Extract blocks.
|
||||
blocks = int(sline[8])
|
||||
# Construct result.
|
||||
return {
|
||||
'net': net,
|
||||
'ip': ipstr,
|
||||
'port': port,
|
||||
'ipnum': ip,
|
||||
'uptime': uptime30,
|
||||
'lastsuccess': lastsuccess,
|
||||
'version': version,
|
||||
'agent': agent,
|
||||
'service': service,
|
||||
'blocks': blocks,
|
||||
'sortkey': sortkey,
|
||||
}
|
||||
|
||||
def filtermultiport(ips):
|
||||
'''Filter out hosts with more nodes per IP'''
|
||||
hist = collections.defaultdict(list)
|
||||
for ip in ips:
|
||||
hist[ip['sortkey']].append(ip)
|
||||
return [value[0] for (key,value) in list(hist.items()) if len(value)==1]
|
||||
|
||||
# Based on Greg Maxwell's seed_filter.py
|
||||
def filterbyasn(ips, max_per_asn, max_total):
|
||||
# Sift out ips by type
|
||||
ips_ipv4 = [ip for ip in ips if ip['net'] == 'ipv4']
|
||||
ips_ipv6 = [ip for ip in ips if ip['net'] == 'ipv6']
|
||||
ips_onion = [ip for ip in ips if ip['net'] == 'onion']
|
||||
|
||||
# Filter IPv4 by ASN
|
||||
result = []
|
||||
asn_count = {}
|
||||
for ip in ips_ipv4:
|
||||
if len(result) == max_total:
|
||||
break
|
||||
try:
|
||||
asn = int([x.to_text() for x in dns.resolver.query('.'.join(reversed(ip['ip'].split('.'))) + '.origin.asn.cymru.com', 'TXT').response.answer][0].split('\"')[1].split(' ')[0])
|
||||
if asn not in asn_count:
|
||||
asn_count[asn] = 0
|
||||
if asn_count[asn] == max_per_asn:
|
||||
continue
|
||||
asn_count[asn] += 1
|
||||
result.append(ip)
|
||||
except:
|
||||
sys.stderr.write('ERR: Could not resolve ASN for "' + ip['ip'] + '"\n')
|
||||
|
||||
# TODO: filter IPv6 by ASN
|
||||
|
||||
# Add back non-IPv4
|
||||
result.extend(ips_ipv6)
|
||||
result.extend(ips_onion)
|
||||
return result
|
||||
|
||||
def main():
|
||||
lines = sys.stdin.readlines()
|
||||
ips = [parseline(line) for line in lines]
|
||||
|
||||
# Skip entries with valid address.
|
||||
ips = [ip for ip in ips if ip is not None]
|
||||
# Skip entries from suspicious hosts.
|
||||
ips = [ip for ip in ips if ip['ip'] not in SUSPICIOUS_HOSTS]
|
||||
# Enforce minimal number of blocks.
|
||||
ips = [ip for ip in ips if ip['blocks'] >= MIN_BLOCKS]
|
||||
# Require service bit 1.
|
||||
ips = [ip for ip in ips if (ip['service'] & 1) == 1]
|
||||
# Require at least 50% 30-day uptime.
|
||||
ips = [ip for ip in ips if ip['uptime'] > 50]
|
||||
# Require a known and recent user agent.
|
||||
ips = [ip for ip in ips if PATTERN_AGENT.match(re.sub(' ', '-', ip['agent']))]
|
||||
# Sort by availability (and use last success as tie breaker)
|
||||
ips.sort(key=lambda x: (x['uptime'], x['lastsuccess'], x['ip']), reverse=True)
|
||||
# Filter out hosts with multiple bitcoin ports, these are likely abusive
|
||||
ips = filtermultiport(ips)
|
||||
# Look up ASNs and limit results, both per ASN and globally.
|
||||
ips = filterbyasn(ips, MAX_SEEDS_PER_ASN, NSEEDS)
|
||||
# Sort the results by IP address (for deterministic output).
|
||||
ips.sort(key=lambda x: (x['net'], x['sortkey']))
|
||||
|
||||
for ip in ips:
|
||||
if ip['net'] == 'ipv6':
|
||||
print('[%s]:%i' % (ip['ip'], ip['port']))
|
||||
else:
|
||||
print('%s:%i' % (ip['ip'], ip['port']))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user