A crawler for the Pirate Chain P2P network that exposes a list of reliable nodes via a built-in authoritative DNS server - the same role a Bitcoin-style "DNS seed" plays for any Bitcoin-derived network, run here for Pirate (ARRR).
It also crawls I2P peers directly through a local I2P router's SAM bridge
(e.g. TreasureChest's embedded pirate-i2pd), and can reach Tor peers
through a SOCKS5 proxy.
- Connects outbound to known peers over TLS 1.3 (TreasureChest enforces TLS
on P2P connections by default and drops plaintext ones), does the
version/verack handshake, and asks for their address book
(
getaddr/addr/addrv2) to discover more. - Understands modern Tor v3 (
.onion) and I2P (.b32.i2p) addresses viaaddrv2(BIP155), not just IPv4/IPv6. - Scores each peer's reliability over exponentially-weighted 2h/8h/1d/1w/1M windows, and bans or temporarily ignores consistently bad ones.
- Answers DNS
A/AAAA/NS/SOAqueries for a configured hostname with a random sample of currently-good peers, plus thex<hex-service-flags>.hostfiltered-query convention wallets use to ask for peers with specific service bits. - Event-driven (libevent) rather than one OS thread per crawled connection, so one process can have many crawls in flight at once; SOCKS5/SAM dials (which need a blocking handshake libevent has no native support for) run on a small dedicated worker pool and hand the connected socket off to the same async code path used for direct connections.
- Low memory and CPU footprint - this is a crawler and a tiny DNS server, not a full node.
Ubuntu/Debian:
sudo apt-get install build-essential pkg-config libssl-dev libevent-dev
make # release build -> ./pirate-seeder
make asan # debug build with ASan/UBSan, for development only
sudo make install PREFIX=/usr/local
pirate-seeder needs a subdomain delegated to it via DNS, not just an A
record pointing at it - delegation is what lets it answer every query for
that subdomain dynamically (rotating through whichever peers currently score
well) instead of a DNS provider just handing back one fixed IP forever. That
delegation happens at your DNS provider, once, before you ever run the
binary; the pieces then map directly onto its command-line flags as follows.
- Seed hostname - the name wallets/nodes will actually query, e.g.
dnsseed.example.com. This becomes-h. - Nameserver hostname - the name that identifies this VPS as a
nameserver, e.g.
ns-dnsseed.example.com. This becomes-n. It does not need to be (and usually isn't) the same as the seed hostname.
| # | Name | Type | Content | Notes |
|---|---|---|---|---|
| 1 | ns-dnsseed.example.com |
A | <this VPS's public IP> |
Must not be proxied/CDN'd (e.g. on Cloudflare, "DNS only" / grey cloud, not orange). A CDN proxy only forwards HTTP(S); it can't forward raw DNS queries, so a proxied record here would silently break everything downstream. |
| 2 | dnsseed.example.com |
NS | ns-dnsseed.example.com |
This is the actual delegation. NS records aren't proxiable, so there's no toggle to worry about here. |
Record 1 is glue - it's just how resolvers find the VPS at all. Record 2
is what actually hands authority for dnsseed.example.com over to that VPS;
after it propagates, your DNS provider no longer answers queries for that
name (or any name under it) itself, it just refers resolvers to record 1's
target.
Nothing else needs to exist in your DNS provider for this - no SOA record
(providers don't let you create one manually; see below), no separate record
for the -m mailbox, no wildcard.
./pirate-seeder -h dnsseed.example.com -n ns-dnsseed.example.com -m admin.example.com
| Flag | Value | Where it came from |
|---|---|---|
-h |
dnsseed.example.com |
the name you're serving (record 2's Name) |
-n |
ns-dnsseed.example.com |
the nameserver identity (record 2's Content, == record 1's Name) |
-m |
admin.example.com |
your contact e-mail with @ replaced by . - reported in SOA records, not a DNS record itself |
Run this on the VPS whose IP is in record 1 - pirate-seeder binds a
local UDP port; it isn't told the domain name, so it has to physically be
the machine that record 1 points at.
dig NS dnsseed.example.com # should show ns-dnsseed.example.com
dig A dnsseed.example.com # should return one or more live peer IPs
If step 4 returns nothing, double check record 1 isn't proxied/CDN'd (the most common cause) and that UDP port 53 is actually reachable on the VPS (see "Running as non-root" below if you're not running as root).
DNS providers only let you create record types that make sense as one entry
among many in a zone you own (A, NS, CNAME, ...). SOA is different - it's a
single, automatic property of owning a zone, not something you add to one,
so no provider exposes it as a creatable type. Once record 2 delegates
dnsseed.example.com away, pirate-seeder is the owner of that zone as
far as the rest of the internet is concerned, and it generates its own SOA
record on the fly from -n and -m (see WriteRecordSoa in
src/dns_server.cpp) - there's nothing to configure for it beyond those two
flags you already set in step 3.
By default, pirate-seeder tries to discover a local I2P router's SAM bridge
by reading -i2psam out of ~/.komodo/PIRATE/PIRATE.conf (the same key
lightwalletd reads for its own I2P support), falling back to i2pd's default
of 127.0.0.1:7656 if no config is found. Override with --i2p-sam <ip:port>, or point at a specific conf file with --pirate-conf <path>. If
no SAM bridge is reachable, I2P peers are simply skipped rather than
penalized - they're re-queued for whenever a bridge does become available.
Tor peers need a SOCKS5 proxy to dial out through, since this seeder doesn't
resolve .onion addresses itself - point -o at one:
./pirate-seeder -h dnsseed.example.com -n ns-dnsseed.example.com -m admin.example.com \
-o 127.0.0.1:9050
(TreasureChest's own embedded Tor currently only runs a control port, not a
SOCKS proxy, so this needs a separately-run Tor with SocksPort enabled -
either a system Tor package, or your own instance.)
Binding UDP port 53 needs root. To run pirate-seeder unprivileged, redirect port 53 to an unprivileged port instead of running the process as root:
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5353
./pirate-seeder -h dnsseed.example.com -n ns-dnsseed.example.com -m admin.example.com -p 5353
-h, --host <host> Hostname of the DNS seed
-n, --ns <ns> Hostname of the nameserver
-m, --mbox <mbox> Contact e-mail for SOA records ('@' replaced by '.')
-t, --threads <n> Concurrent P2P crawl sessions (default 64)
--dial-threads <n> Worker threads for SOCKS5/SAM dials (default 8)
-d, --dnsthreads <n> DNS server instances, SO_REUSEPORT (default 4)
-p, --port <port> UDP port for the DNS server (default 53)
-o, --onion <ip:port> SOCKS5 proxy for Tor peers
-i, --proxyipv4 <a:p> SOCKS5 proxy for IPv4 peers
-k, --proxyipv6 <a:p> SOCKS5 proxy for IPv6 peers
--i2p-sam <ip:port> I2P SAM bridge for crawling I2P peers
--pirate-conf <path> PIRATE.conf to read -i2psam from
-w, --filter <f1,f2,..> Allowed service-flag filters for x<hex>.host queries
--db <path> Address database path (default: dnsseed.dat)
--testnet Use testnet parameters
--wipeban Clear the persisted ban list on startup
--wipeignore Clear persisted ignore timers on startup
Mainnet/testnet magic bytes, default ports, and protocol version are defined
in src/wire.h, copied from TreasureChest's src/chainparams.cpp and
src/version.h. If Pirate's network parameters ever change, update both
places together.
This is a from-scratch modernization of the design pioneered by Pieter
Wuille's original bitcoin-seeder: a crawler plus a small hand-rolled
authoritative DNS server, since a general-purpose DNS server can't
dynamically rotate its answers based on which peers are currently reachable.
That overall shape carries over well to any Bitcoin-derived network and
didn't need reinventing - what changed is the implementation: modern C++17
throughout, libevent instead of a thread per crawled connection, BIP155
(addrv2) address support so Tor v3 and I2P peers can actually be
represented and gossiped (the old fixed 16-byte address format they came
from physically can't hold either), an I2P SAM client for crawling I2P
peers directly, and TLS on every direct/proxied P2P connection (via
libevent's OpenSSL bufferevent wrapper) since TreasureChest's net.cpp
enforces it by default and closes the socket on a failed handshake with no
plaintext fallback - a plaintext-only crawler simply can't reach real peers
on this network.
MIT, see LICENSE.