zcash: add address request handling
This commit is contained in:
parent
7d12f5e57d
commit
21b4c750a3
|
@ -0,0 +1,40 @@
|
||||||
|
package zcash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
netaddr *wire.NetAddress
|
||||||
|
valid bool
|
||||||
|
blacklist bool
|
||||||
|
lastTried time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Address) IsGood() bool {
|
||||||
|
return a.valid == true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Address) IsBad() bool {
|
||||||
|
return a.blacklist == true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Address) String() string {
|
||||||
|
portString := strconv.Itoa(int(a.netaddr.Port))
|
||||||
|
return net.JoinHostPort(a.netaddr.IP.String(), portString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addresses should be sortable by least-recently-tried
|
||||||
|
|
||||||
|
type AddrList []*Address
|
||||||
|
|
||||||
|
func (list AddrList) Len() int { return len(list) }
|
||||||
|
func (list AddrList) Swap(i, j int) { list[i], list[j] = list[j], list[i] }
|
||||||
|
func (list AddrList) Less(i, j int) bool { return list[i].lastTried.Before(list[j].lastTried) }
|
||||||
|
|
||||||
|
// GetShuffledAddressList returns a slice of n valid addresses in random order.
|
||||||
|
func GetShuffledAddressList(addrList []*Address, n int) []*Address { return nil }
|
184
zcash/client.go
184
zcash/client.go
|
@ -4,9 +4,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/addrmgr"
|
||||||
"github.com/btcsuite/btcd/peer"
|
"github.com/btcsuite/btcd/peer"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
|
@ -18,6 +20,7 @@ import (
|
||||||
var (
|
var (
|
||||||
ErrRepeatConnection = errors.New("attempted repeat connection to existing peer")
|
ErrRepeatConnection = errors.New("attempted repeat connection to existing peer")
|
||||||
ErrNoSuchPeer = errors.New("no record of requested peer")
|
ErrNoSuchPeer = errors.New("no record of requested peer")
|
||||||
|
ErrAddressTimeout = errors.New("wait for addreses timed out")
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultPeerConfig = &peer.Config{
|
var defaultPeerConfig = &peer.Config{
|
||||||
|
@ -34,14 +37,16 @@ type Seeder struct {
|
||||||
config *peer.Config
|
config *peer.Config
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
|
||||||
|
// Peer list handling
|
||||||
|
peerState sync.RWMutex
|
||||||
handshakeSignals *sync.Map
|
handshakeSignals *sync.Map
|
||||||
pendingPeers *sync.Map
|
pendingPeers *sync.Map
|
||||||
livePeers *sync.Map
|
livePeers *sync.Map
|
||||||
|
|
||||||
addrRecvChan chan *wire.NetAddress
|
// Address list handling
|
||||||
|
addrState sync.RWMutex
|
||||||
// For mutating the above
|
addrRecvCond *sync.Cond
|
||||||
peerState sync.RWMutex
|
addrList []*Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSeeder(network network.Network) (*Seeder, error) {
|
func NewSeeder(network network.Network) (*Seeder, error) {
|
||||||
|
@ -58,9 +63,11 @@ func NewSeeder(network network.Network) (*Seeder, error) {
|
||||||
handshakeSignals: new(sync.Map),
|
handshakeSignals: new(sync.Map),
|
||||||
pendingPeers: new(sync.Map),
|
pendingPeers: new(sync.Map),
|
||||||
livePeers: new(sync.Map),
|
livePeers: new(sync.Map),
|
||||||
addrRecvChan: make(chan *wire.NetAddress, 100),
|
addrList: make([]*Address, 0, 1000),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newSeeder.addrRecvCond = sync.NewCond(&newSeeder.addrState)
|
||||||
|
|
||||||
newSeeder.config.Listeners.OnVerAck = newSeeder.onVerAck
|
newSeeder.config.Listeners.OnVerAck = newSeeder.onVerAck
|
||||||
newSeeder.config.Listeners.OnAddr = newSeeder.onAddr
|
newSeeder.config.Listeners.OnAddr = newSeeder.onAddr
|
||||||
|
|
||||||
|
@ -85,9 +92,11 @@ func newTestSeeder(network network.Network) (*Seeder, error) {
|
||||||
handshakeSignals: new(sync.Map),
|
handshakeSignals: new(sync.Map),
|
||||||
pendingPeers: new(sync.Map),
|
pendingPeers: new(sync.Map),
|
||||||
livePeers: new(sync.Map),
|
livePeers: new(sync.Map),
|
||||||
addrRecvChan: make(chan *wire.NetAddress, 100),
|
addrList: make([]*Address, 0, 1000),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newSeeder.addrRecvCond = sync.NewCond(&newSeeder.addrState)
|
||||||
|
|
||||||
newSeeder.config.Listeners.OnVerAck = newSeeder.onVerAck
|
newSeeder.config.Listeners.OnVerAck = newSeeder.onVerAck
|
||||||
newSeeder.config.Listeners.OnAddr = newSeeder.onAddr
|
newSeeder.config.Listeners.OnAddr = newSeeder.onAddr
|
||||||
|
|
||||||
|
@ -134,10 +143,26 @@ func (s *Seeder) onVerAck(p *peer.Peer, msg *wire.MsgVerAck) {
|
||||||
// Signal successful connection
|
// Signal successful connection
|
||||||
if signal, ok := s.handshakeSignals.Load(p.Addr()); ok {
|
if signal, ok := s.handshakeSignals.Load(p.Addr()); ok {
|
||||||
signal.(chan struct{}) <- struct{}{}
|
signal.(chan struct{}) <- struct{}{}
|
||||||
|
} else {
|
||||||
|
s.logger.Printf("Got verack from peer without a callback channel: %s", p.Addr())
|
||||||
|
s.DisconnectPeer(p.Addr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Printf("Got verack from peer without a callback channel: %s", p.Addr())
|
// Add to list of known good addresses
|
||||||
|
newAddr := &Address{
|
||||||
|
netaddr: p.NA(),
|
||||||
|
valid: true,
|
||||||
|
lastTried: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Printf("Adding %s to address list", p.Addr())
|
||||||
|
|
||||||
|
s.addrState.Lock()
|
||||||
|
s.addrList = append(s.addrList, newAddr)
|
||||||
|
s.addrState.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectToPeer attempts to connect to a peer on the default port at the
|
// ConnectToPeer attempts to connect to a peer on the default port at the
|
||||||
|
@ -155,15 +180,30 @@ func (s *Seeder) Connect(addr, port string) error {
|
||||||
return errors.Wrap(err, "constructing outbound peer")
|
return errors.Wrap(err, "constructing outbound peer")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, alreadyPending := s.pendingPeers.LoadOrStore(p.Addr(), p)
|
_, alreadyPending := s.pendingPeers.Load(p.Addr())
|
||||||
_, alreadyHandshaking := s.handshakeSignals.LoadOrStore(p.Addr(), make(chan struct{}, 1))
|
_, alreadyHandshaking := s.handshakeSignals.Load(p.Addr())
|
||||||
_, alreadyLive := s.livePeers.Load(p.Addr())
|
_, alreadyLive := s.livePeers.Load(p.Addr())
|
||||||
|
|
||||||
if alreadyPending || alreadyHandshaking || alreadyLive {
|
if alreadyPending {
|
||||||
s.logger.Printf("Attempted repeat connection to peer %s", p.Addr())
|
s.logger.Printf("Peer is already pending: %s", p.Addr())
|
||||||
|
return ErrRepeatConnection
|
||||||
|
} else {
|
||||||
|
s.pendingPeers.Store(p.Addr(), p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if alreadyHandshaking {
|
||||||
|
s.logger.Printf("Peer is already handshaking: %s", p.Addr())
|
||||||
|
return ErrRepeatConnection
|
||||||
|
} else {
|
||||||
|
s.handshakeSignals.Store(p.Addr(), make(chan struct{}, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if alreadyLive {
|
||||||
|
s.logger.Printf("Peer is already live: %s", p.Addr())
|
||||||
return ErrRepeatConnection
|
return ErrRepeatConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO time out
|
||||||
conn, err := net.Dial("tcp", p.Addr())
|
conn, err := net.Dial("tcp", p.Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "dialing new peer address")
|
return errors.Wrap(err, "dialing new peer address")
|
||||||
|
@ -212,6 +252,7 @@ func (s *Seeder) DisconnectPeer(addr string) error {
|
||||||
// TODO: type safety and error handling
|
// TODO: type safety and error handling
|
||||||
|
|
||||||
v := p.(*peer.Peer)
|
v := p.(*peer.Peer)
|
||||||
|
s.logger.Printf("Disconnecting from peer %s", v.Addr())
|
||||||
v.Disconnect()
|
v.Disconnect()
|
||||||
v.WaitForDisconnect()
|
v.WaitForDisconnect()
|
||||||
s.livePeers.Delete(addr)
|
s.livePeers.Delete(addr)
|
||||||
|
@ -219,6 +260,39 @@ func (s *Seeder) DisconnectPeer(addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisconnectPeerDishonorably disconnects from a live peer identified by
|
||||||
|
// "host:port" string. It returns an error if we aren't connected to that peer.
|
||||||
|
// "Dishonorably" furthermore removes this peer from the list of known good
|
||||||
|
// addresses and adds them to a blacklist.
|
||||||
|
func (s *Seeder) DisconnectPeerDishonorably(addr string) error {
|
||||||
|
p, ok := s.livePeers.Load(addr)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return ErrNoSuchPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: type safety and error handling
|
||||||
|
|
||||||
|
v := p.(*peer.Peer)
|
||||||
|
s.logger.Printf("Disconnecting from peer %s", v.Addr())
|
||||||
|
v.Disconnect()
|
||||||
|
v.WaitForDisconnect()
|
||||||
|
s.livePeers.Delete(addr)
|
||||||
|
|
||||||
|
s.addrState.Lock()
|
||||||
|
for i := 0; i < len(s.addrList); i++ {
|
||||||
|
address := s.addrList[i]
|
||||||
|
if address.String() == addr {
|
||||||
|
s.logger.Printf("Blacklisting peer %s", v.Addr())
|
||||||
|
address.valid = false
|
||||||
|
address.blacklist = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.addrState.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DisconnectAllPeers terminates the connections to all live and pending peers.
|
// DisconnectAllPeers terminates the connections to all live and pending peers.
|
||||||
func (s *Seeder) DisconnectAllPeers() {
|
func (s *Seeder) DisconnectAllPeers() {
|
||||||
s.pendingPeers.Range(func(key, value interface{}) bool {
|
s.pendingPeers.Range(func(key, value interface{}) bool {
|
||||||
|
@ -227,7 +301,6 @@ func (s *Seeder) DisconnectAllPeers() {
|
||||||
s.logger.Printf("Invalid peer in pendingPeers")
|
s.logger.Printf("Invalid peer in pendingPeers")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
s.logger.Printf("Disconnecting from pending peer %s", p.Addr())
|
|
||||||
p.Disconnect()
|
p.Disconnect()
|
||||||
p.WaitForDisconnect()
|
p.WaitForDisconnect()
|
||||||
s.pendingPeers.Delete(key)
|
s.pendingPeers.Delete(key)
|
||||||
|
@ -240,7 +313,6 @@ func (s *Seeder) DisconnectAllPeers() {
|
||||||
s.logger.Printf("Invalid peer in livePeers")
|
s.logger.Printf("Invalid peer in livePeers")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
s.logger.Printf("Disconnecting from live peer %s", p.Addr())
|
|
||||||
s.DisconnectPeer(p.Addr())
|
s.DisconnectPeer(p.Addr())
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -259,8 +331,55 @@ func (s *Seeder) RequestAddresses() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Seeder) WaitForMoreAddresses() {
|
// WaitForAddresses waits for n addresses to be received and their initial
|
||||||
<-s.addrRecvChan
|
// connection attempts to resolve. There is no escape if that does not happen -
|
||||||
|
// this is intended for test runners.
|
||||||
|
func (s *Seeder) WaitForAddresses(n int) error {
|
||||||
|
s.addrState.Lock()
|
||||||
|
for {
|
||||||
|
addrCount := len(s.addrList)
|
||||||
|
if addrCount < n {
|
||||||
|
s.addrRecvCond.Wait()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.addrState.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Seeder) alreadyKnowsAddress(na *wire.NetAddress) bool {
|
||||||
|
s.addrState.RLock()
|
||||||
|
defer s.addrState.RUnlock()
|
||||||
|
|
||||||
|
ref := &Address{
|
||||||
|
netaddr: na,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s.addrList); i++ {
|
||||||
|
if s.addrList[i].String() == ref.String() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Seeder) isBlacklistedAddress(na *wire.NetAddress) bool {
|
||||||
|
s.addrState.RLock()
|
||||||
|
defer s.addrState.RUnlock()
|
||||||
|
|
||||||
|
ref := &Address{
|
||||||
|
netaddr: na,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s.addrList); i++ {
|
||||||
|
if s.addrList[i].String() == ref.String() {
|
||||||
|
return s.addrList[i].IsBad()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Seeder) onAddr(p *peer.Peer, msg *wire.MsgAddr) {
|
func (s *Seeder) onAddr(p *peer.Peer, msg *wire.MsgAddr) {
|
||||||
|
@ -271,7 +390,38 @@ func (s *Seeder) onAddr(p *peer.Peer, msg *wire.MsgAddr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Printf("Got %d addrs from peer %s", len(msg.AddrList), p.Addr())
|
s.logger.Printf("Got %d addrs from peer %s", len(msg.AddrList), p.Addr())
|
||||||
for _, addr := range msg.AddrList {
|
|
||||||
s.addrRecvChan <- addr
|
for _, na := range msg.AddrList {
|
||||||
|
s.logger.Printf("Trying %s:%d from peer %s", na.IP, na.Port, p.Addr())
|
||||||
|
go func(na *wire.NetAddress) {
|
||||||
|
if !addrmgr.IsRoutable(na) && !s.config.AllowSelfConns {
|
||||||
|
s.logger.Printf("Got bad addr %s:%d from peer %s", na.IP, na.Port, p.Addr())
|
||||||
|
s.DisconnectPeerDishonorably(p.Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.alreadyKnowsAddress(na) {
|
||||||
|
s.logger.Printf("Already knew about address %s:%d", na.IP, na.Port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.isBlacklistedAddress(na) {
|
||||||
|
s.logger.Printf("Address %s:%d is blacklisted", na.IP, na.Port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
portString := strconv.Itoa(int(na.Port))
|
||||||
|
err := s.Connect(na.IP.String(), portString)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Printf("Got unusable peer %s:%d from peer %s. Error: ", na.IP, na.Port, p.Addr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerString := net.JoinHostPort(na.IP.String(), portString)
|
||||||
|
s.DisconnectPeer(peerString)
|
||||||
|
|
||||||
|
s.addrRecvCond.Broadcast()
|
||||||
|
}(na)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,15 @@ func startMockLoop() {
|
||||||
time.Now(),
|
time.Now(),
|
||||||
0,
|
0,
|
||||||
net.ParseIP("127.0.0.1"),
|
net.ParseIP("127.0.0.1"),
|
||||||
uint16(8233),
|
uint16(18233),
|
||||||
)
|
)
|
||||||
cache = append(cache, addr)
|
addr2 := wire.NewNetAddressTimestamp(
|
||||||
|
time.Now(),
|
||||||
|
0,
|
||||||
|
net.ParseIP("127.0.0.1"),
|
||||||
|
uint16(18344),
|
||||||
|
)
|
||||||
|
cache = append(cache, addr, addr2)
|
||||||
_, err := p.PushAddrMsg(cache)
|
_, err := p.PushAddrMsg(cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mockPeerLogger.Error(err)
|
mockPeerLogger.Error(err)
|
||||||
|
@ -154,5 +160,8 @@ func TestRequestAddresses(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
regSeeder.RequestAddresses()
|
regSeeder.RequestAddresses()
|
||||||
regSeeder.WaitForMoreAddresses()
|
regSeeder.WaitForAddresses(1)
|
||||||
|
|
||||||
|
// TODO It isn't possible to test this wait on a local mock peer without
|
||||||
|
// carving a path through absolutely all of the bad connection logic.
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue