initial commit
This commit is contained in:
commit
bdb049e215
|
@ -0,0 +1,5 @@
|
|||
## Zcash Network Crawler
|
||||
|
||||
This is a CoreDNS plugin that scrapes addresses of peers from a Zcash network. It's intended as a safer and more scalable replacement for the [zcash-seeder](https://github.com/zcash/zcash-seeder) project.
|
||||
|
||||
It uses [btcsuite](https://github.com/btcsuite) for networking and [crawshaw/sqlite](https://crawshaw.io/blog/go-and-sqlite) for storing peer data.
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/gtank/coredns-zcash
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.0.0-20190926002857-ba530c4abb35
|
||||
github.com/pkg/errors v0.8.1
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.0.0-20190926002857-ba530c4abb35 h1:o2mPiVrkVzpBg/Q+lSfuf/92pEgsSIJvsQ13DyHs/3A=
|
||||
github.com/btcsuite/btcd v0.0.0-20190926002857-ba530c4abb35/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,148 @@
|
|||
package zcash
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/peer"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/gtank/coredns-zcash/zcash/network"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var defaultPeerConfig = &peer.Config{
|
||||
UserAgentName: "MagicBean",
|
||||
UserAgentVersion: "2.0.7",
|
||||
ChainParams: nil,
|
||||
Services: 0,
|
||||
TrickleInterval: time.Second * 10,
|
||||
ProtocolVersion: 170009, // Blossom
|
||||
}
|
||||
|
||||
var logger = log.New(os.Stdout, "zcash_client: ", log.Ldate|log.Ltime|log.Lshortfile|log.LUTC)
|
||||
|
||||
type Seeder struct {
|
||||
peer *peer.Peer
|
||||
config *peer.Config
|
||||
|
||||
handshakeComplete chan *peer.Peer
|
||||
handshakePendingPeers map[string]*peer.Peer
|
||||
livePeers map[string]*peer.Peer
|
||||
|
||||
// For mutating the above
|
||||
peerState sync.RWMutex
|
||||
}
|
||||
|
||||
func newSeederPeerConfig(magic network.Network, template *peer.Config) (*peer.Config, error) {
|
||||
var newPeerConfig peer.Config
|
||||
|
||||
// Load the default values
|
||||
if template != nil {
|
||||
newPeerConfig = *template
|
||||
}
|
||||
|
||||
params, err := network.GetNetworkParams(magic)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "couldn't construct peer config")
|
||||
}
|
||||
newPeerConfig.ChainParams = params
|
||||
|
||||
return &newPeerConfig, nil
|
||||
}
|
||||
|
||||
func (s *Seeder) OnVerAck(p *peer.Peer, msg *wire.MsgVerAck) {
|
||||
s.peerState.RLock()
|
||||
if s.handshakePendingPeers[p.Addr()] == nil {
|
||||
logger.Printf("Got verack from unexpected peer %s", p.Addr())
|
||||
s.peerState.RUnlock()
|
||||
return
|
||||
}
|
||||
s.peerState.RUnlock()
|
||||
|
||||
logger.Printf("Handshake completed with new peer %s", p.Addr())
|
||||
|
||||
s.peerState.Lock()
|
||||
delete(s.handshakePendingPeers, p.Addr())
|
||||
s.livePeers[p.Addr()] = p
|
||||
s.peerState.Unlock()
|
||||
|
||||
s.handshakeComplete <- p
|
||||
}
|
||||
|
||||
func NewSeeder(network network.Network) (*Seeder, error) {
|
||||
config, err := newSeederPeerConfig(network, defaultPeerConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not construct seeder")
|
||||
}
|
||||
|
||||
newSeeder := Seeder{
|
||||
config: config,
|
||||
handshakeComplete: make(chan *peer.Peer, 1),
|
||||
handshakePendingPeers: make(map[string]*peer.Peer),
|
||||
livePeers: make(map[string]*peer.Peer),
|
||||
}
|
||||
|
||||
newSeeder.config.Listeners.OnVerAck = newSeeder.OnVerAck
|
||||
|
||||
return &newSeeder, nil
|
||||
}
|
||||
|
||||
// ConnectToPeer attempts to connect to a peer on the default port at the
|
||||
// specified address. It returns either a live peer connection or an error.
|
||||
func (s *Seeder) ConnectToPeer(addr string) (*peer.Peer, error) {
|
||||
connectionString := net.JoinHostPort(addr, s.config.ChainParams.DefaultPort)
|
||||
|
||||
p, err := peer.NewOutboundPeer(s.config, connectionString)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "constructing outbound peer")
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", p.Addr())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "dialing new peer address")
|
||||
}
|
||||
|
||||
s.peerState.Lock()
|
||||
s.handshakePendingPeers[p.Addr()] = p
|
||||
s.peerState.Unlock()
|
||||
|
||||
p.AssociateConnection(conn)
|
||||
|
||||
for {
|
||||
select {
|
||||
case verackPeer := <-s.handshakeComplete:
|
||||
if verackPeer.Addr() == p.Addr() {
|
||||
return p, nil
|
||||
}
|
||||
case <-time.After(time.Second * 1):
|
||||
return nil, errors.New("peer handshake timed out")
|
||||
}
|
||||
}
|
||||
|
||||
panic("This should be unreachable")
|
||||
}
|
||||
|
||||
func (s *Seeder) GracefulDisconnect() {
|
||||
s.peerState.Lock()
|
||||
|
||||
for _, v := range s.handshakePendingPeers {
|
||||
logger.Printf("Disconnecting from peer %s", v.Addr())
|
||||
v.Disconnect()
|
||||
v.WaitForDisconnect()
|
||||
}
|
||||
s.handshakePendingPeers = make(map[string]*peer.Peer)
|
||||
|
||||
for _, v := range s.livePeers {
|
||||
logger.Printf("Disconnecting from peer %s", v.Addr())
|
||||
v.Disconnect()
|
||||
v.WaitForDisconnect()
|
||||
}
|
||||
s.livePeers = make(map[string]*peer.Peer)
|
||||
|
||||
s.peerState.Unlock()
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package zcash
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gtank/coredns-zcash/zcash/network"
|
||||
)
|
||||
|
||||
func TestOutboundPeer(t *testing.T) {
|
||||
regSeeder, err := NewSeeder(network.Regtest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = regSeeder.ConnectToPeer("127.0.0.1")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
regSeeder.GracefulDisconnect()
|
||||
}
|
||||
|
||||
func TestOutboundPeerAsync(t *testing.T) {
|
||||
regSeeder, err := NewSeeder(network.Regtest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, err := regSeeder.ConnectToPeer("127.0.0.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
regSeeder.GracefulDisconnect()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second * 1):
|
||||
t.Error("timed out")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
/// Network represents the byte sequences used to identify different Zcash networks.
|
||||
type Network uint32
|
||||
|
||||
const (
|
||||
// Mainnet identifies the Zcash mainnet
|
||||
Mainnet Network = 0x6427e924
|
||||
// Testnet identifies ECC's public testnet
|
||||
Testnet Network = 0xbff91afa
|
||||
// Regtest identifies the regression test network
|
||||
Regtest Network = 0x5f3fe8aa
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidMagic = errors.New("invalid network magic")
|
||||
)
|
||||
|
||||
// Marshal appends the 4-byte, little endian encoding of a network identifier
|
||||
// to the dst slice and returns the resulting slice. If there is sufficient room
|
||||
// in the dst slice, Marshal does not allocate.
|
||||
func (m Network) Marshal(dst []byte) (out []byte) {
|
||||
if n := len(dst) + 4; cap(dst) >= n {
|
||||
out = dst[:n]
|
||||
} else {
|
||||
out = make([]byte, n)
|
||||
copy(out, dst)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(out[len(dst):], uint32(m))
|
||||
return
|
||||
}
|
||||
|
||||
// Decode parses a valid network identifier from a byte slice. It
|
||||
// returns the identifier on success, zero and an error on failure.
|
||||
func Decode(data []byte) (Network, error) {
|
||||
if len(data) != 4 {
|
||||
return 0, ErrInvalidMagic
|
||||
}
|
||||
|
||||
number := Network(binary.LittleEndian.Uint32(data))
|
||||
|
||||
switch number {
|
||||
case Mainnet:
|
||||
return Mainnet, nil
|
||||
case Testnet:
|
||||
return Testnet, nil
|
||||
case Regtest:
|
||||
return Regtest, nil
|
||||
default:
|
||||
return 0, ErrInvalidMagic
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMainnetMagic(t *testing.T) {
|
||||
// Zcash mainnet, src/chainparams.cpp
|
||||
var pchMessageStart [4]byte
|
||||
pchMessageStart[0] = 0x24
|
||||
pchMessageStart[1] = 0xe9
|
||||
pchMessageStart[2] = 0x27
|
||||
pchMessageStart[3] = 0x64
|
||||
|
||||
magicBytes := Mainnet.Marshal(nil)
|
||||
|
||||
if !bytes.Equal(magicBytes, pchMessageStart[:]) {
|
||||
t.Error("encoding failed")
|
||||
}
|
||||
|
||||
magic, err := Decode(pchMessageStart[:])
|
||||
|
||||
if err != nil || magic != Mainnet {
|
||||
t.Error("decoding failed")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// These are not fully valid chainparams, but they'll do for a seeder.
|
||||
regtestParams = chaincfg.Params{
|
||||
Name: "regtest",
|
||||
Net: wire.BitcoinNet(Regtest),
|
||||
DefaultPort: "18344",
|
||||
}
|
||||
|
||||
// These are not fully valid chainparams, but they'll do for a seeder.
|
||||
mainnetParams = chaincfg.Params{
|
||||
Name: "mainnet",
|
||||
Net: wire.BitcoinNet(Mainnet),
|
||||
DefaultPort: "8233",
|
||||
}
|
||||
|
||||
// These are not fully valid chainparams, but they'll do for a seeder.
|
||||
testnetParams = chaincfg.Params{
|
||||
Name: "testnet",
|
||||
Net: wire.BitcoinNet(Testnet),
|
||||
DefaultPort: "18233",
|
||||
}
|
||||
)
|
||||
|
||||
func GetNetworkParams(magic Network) (*chaincfg.Params, error) {
|
||||
var cfg chaincfg.Params
|
||||
|
||||
switch magic {
|
||||
case Regtest:
|
||||
cfg = regtestParams
|
||||
case Mainnet:
|
||||
cfg = mainnetParams
|
||||
case Testnet:
|
||||
cfg = testnetParams
|
||||
default:
|
||||
return nil, errors.Wrap(ErrInvalidMagic, "no network params")
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package zcash
|
||||
|
||||
// sqlite storage: resultFn writes to resp buffer
|
Loading…
Reference in New Issue