2018-03-09 15:55:25 +00:00
|
|
|
package network
|
|
|
|
|
|
|
|
import (
|
2019-09-13 12:43:22 +00:00
|
|
|
"errors"
|
2018-03-09 15:55:25 +00:00
|
|
|
"net"
|
2019-11-06 07:55:21 +00:00
|
|
|
"strconv"
|
2018-03-10 12:04:06 +00:00
|
|
|
"sync"
|
2020-01-15 14:03:42 +00:00
|
|
|
"time"
|
2018-03-09 15:55:25 +00:00
|
|
|
|
2019-09-16 09:18:13 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/io"
|
2018-03-09 15:55:25 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/network/payload"
|
2020-01-15 14:03:42 +00:00
|
|
|
"go.uber.org/zap"
|
2018-03-09 15:55:25 +00:00
|
|
|
)
|
|
|
|
|
2019-09-13 12:43:22 +00:00
|
|
|
type handShakeStage uint8
|
|
|
|
|
|
|
|
const (
|
2019-11-06 08:06:00 +00:00
|
|
|
versionSent handShakeStage = 1 << iota
|
|
|
|
versionReceived
|
|
|
|
verAckSent
|
|
|
|
verAckReceived
|
2019-09-13 12:43:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errStateMismatch = errors.New("tried to send protocol message before handshake completed")
|
|
|
|
)
|
|
|
|
|
2018-03-09 15:55:25 +00:00
|
|
|
// TCPPeer represents a connected remote node in the
|
|
|
|
// network over TCP.
|
|
|
|
type TCPPeer struct {
|
2018-03-14 09:36:59 +00:00
|
|
|
// underlying TCP connection.
|
2019-09-09 14:54:38 +00:00
|
|
|
conn net.Conn
|
2020-01-15 14:03:42 +00:00
|
|
|
// The server this peer belongs to.
|
|
|
|
server *Server
|
2018-03-14 09:36:59 +00:00
|
|
|
// The version of the peer.
|
2018-03-09 15:55:25 +00:00
|
|
|
version *payload.Version
|
2020-01-17 10:17:19 +00:00
|
|
|
// Index of the last block.
|
|
|
|
lastBlockIndex uint32
|
2018-03-09 15:55:25 +00:00
|
|
|
|
2019-11-06 08:06:00 +00:00
|
|
|
lock sync.RWMutex
|
2020-01-15 15:25:58 +00:00
|
|
|
finale sync.Once
|
2019-09-13 12:43:22 +00:00
|
|
|
handShake handShakeStage
|
|
|
|
|
2020-01-15 15:25:58 +00:00
|
|
|
done chan struct{}
|
2018-03-09 15:55:25 +00:00
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
wg sync.WaitGroup
|
2020-01-17 10:17:19 +00:00
|
|
|
|
|
|
|
// number of sent pings.
|
|
|
|
pingSent int
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
|
2019-09-03 14:51:37 +00:00
|
|
|
// NewTCPPeer returns a TCPPeer structure based on the given connection.
|
2020-01-15 14:03:42 +00:00
|
|
|
func NewTCPPeer(conn net.Conn, s *Server) *TCPPeer {
|
2018-03-09 15:55:25 +00:00
|
|
|
return &TCPPeer{
|
2020-01-15 14:03:42 +00:00
|
|
|
conn: conn,
|
|
|
|
server: s,
|
2020-01-15 15:25:58 +00:00
|
|
|
done: make(chan struct{}),
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 10:14:08 +00:00
|
|
|
// WriteMsg implements the Peer interface. This will write/encode the message
|
2019-09-13 12:43:22 +00:00
|
|
|
// to the underlying connection, this only works for messages other than Version
|
|
|
|
// or VerAck.
|
2018-04-13 10:14:08 +00:00
|
|
|
func (p *TCPPeer) WriteMsg(msg *Message) error {
|
2019-09-13 12:43:22 +00:00
|
|
|
if !p.Handshaked() {
|
|
|
|
return errStateMismatch
|
|
|
|
}
|
|
|
|
return p.writeMsg(msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *TCPPeer) writeMsg(msg *Message) error {
|
2020-01-15 15:25:58 +00:00
|
|
|
w := io.NewBufBinWriter()
|
|
|
|
if err := msg.Encode(w.BinWriter); err != nil {
|
2018-04-13 10:14:08 +00:00
|
|
|
return err
|
2020-01-15 15:25:58 +00:00
|
|
|
}
|
2019-11-16 09:42:03 +00:00
|
|
|
|
2020-01-15 15:25:58 +00:00
|
|
|
_, err := p.conn.Write(w.Bytes())
|
2019-11-16 09:42:03 +00:00
|
|
|
|
2020-01-15 15:25:58 +00:00
|
|
|
return err
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 14:03:42 +00:00
|
|
|
// handleConn handles the read side of the connection, it should be started as
|
|
|
|
// a goroutine right after the new peer setup.
|
|
|
|
func (p *TCPPeer) handleConn() {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
p.server.register <- p
|
|
|
|
|
|
|
|
// When a new peer is connected we send out our version immediately.
|
|
|
|
err = p.server.sendVersion(p)
|
|
|
|
if err == nil {
|
|
|
|
r := io.NewBinReaderFromIO(p.conn)
|
|
|
|
for {
|
|
|
|
msg := &Message{}
|
|
|
|
err = msg.Decode(r)
|
|
|
|
|
|
|
|
if err == payload.ErrTooManyHeaders {
|
|
|
|
p.server.log.Warn("not all headers were processed")
|
|
|
|
r.Err = nil
|
|
|
|
} else if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err = p.server.handleMessage(p, msg); err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.Disconnect(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartProtocol starts a long running background loop that interacts
|
|
|
|
// every ProtoTickInterval with the peer. It's only good to run after the
|
|
|
|
// handshake.
|
|
|
|
func (p *TCPPeer) StartProtocol() {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
p.server.log.Info("started protocol",
|
|
|
|
zap.Stringer("addr", p.RemoteAddr()),
|
|
|
|
zap.ByteString("userAgent", p.Version().UserAgent),
|
|
|
|
zap.Uint32("startHeight", p.Version().StartHeight),
|
|
|
|
zap.Uint32("id", p.Version().Nonce))
|
|
|
|
|
|
|
|
p.server.discovery.RegisterGoodAddr(p.PeerAddr().String())
|
|
|
|
if p.server.chain.HeaderHeight() < p.LastBlockIndex() {
|
|
|
|
err = p.server.requestHeaders(p)
|
|
|
|
if err != nil {
|
|
|
|
p.Disconnect(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
timer := time.NewTimer(p.server.ProtoTickInterval)
|
|
|
|
pingTimer := time.NewTimer(p.server.PingTimeout)
|
|
|
|
for {
|
|
|
|
select {
|
2020-01-15 15:25:58 +00:00
|
|
|
case <-p.done:
|
|
|
|
return
|
2020-01-15 14:03:42 +00:00
|
|
|
case m := <-p.server.addrReq:
|
|
|
|
err = p.WriteMsg(m)
|
|
|
|
case <-timer.C:
|
|
|
|
// Try to sync in headers and block with the peer if his block height is higher then ours.
|
|
|
|
if p.LastBlockIndex() > p.server.chain.BlockHeight() {
|
|
|
|
err = p.server.requestBlocks(p)
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
timer.Reset(p.server.ProtoTickInterval)
|
|
|
|
}
|
|
|
|
if p.server.chain.HeaderHeight() >= p.LastBlockIndex() {
|
|
|
|
block, errGetBlock := p.server.chain.GetBlock(p.server.chain.CurrentBlockHash())
|
|
|
|
if errGetBlock != nil {
|
|
|
|
err = errGetBlock
|
|
|
|
} else {
|
|
|
|
diff := uint32(time.Now().UTC().Unix()) - block.Timestamp
|
|
|
|
if diff > uint32(p.server.PingInterval/time.Second) {
|
|
|
|
p.UpdatePingSent(p.GetPingSent() + 1)
|
|
|
|
err = p.WriteMsg(NewMessage(p.server.Net, CMDPing, payload.NewPing(p.server.id, p.server.chain.HeaderHeight())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-pingTimer.C:
|
|
|
|
if p.GetPingSent() > defaultPingLimit {
|
|
|
|
err = errors.New("ping/pong timeout")
|
|
|
|
} else {
|
|
|
|
pingTimer.Reset(p.server.PingTimeout)
|
|
|
|
p.UpdatePingSent(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
timer.Stop()
|
|
|
|
p.Disconnect(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-13 12:43:22 +00:00
|
|
|
// Handshaked returns status of the handshake, whether it's completed or not.
|
|
|
|
func (p *TCPPeer) Handshaked() bool {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.lock.RLock()
|
|
|
|
defer p.lock.RUnlock()
|
|
|
|
return p.handShake == (verAckReceived | verAckSent | versionReceived | versionSent)
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendVersion checks for the handshake state and sends a message to the peer.
|
|
|
|
func (p *TCPPeer) SendVersion(msg *Message) error {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.lock.Lock()
|
|
|
|
defer p.lock.Unlock()
|
|
|
|
if p.handShake&versionSent != 0 {
|
|
|
|
return errors.New("invalid handshake: already sent Version")
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
|
|
|
err := p.writeMsg(msg)
|
|
|
|
if err == nil {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.handShake |= versionSent
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleVersion checks for the handshake state and version message contents.
|
|
|
|
func (p *TCPPeer) HandleVersion(version *payload.Version) error {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.lock.Lock()
|
|
|
|
defer p.lock.Unlock()
|
|
|
|
if p.handShake&versionReceived != 0 {
|
|
|
|
return errors.New("invalid handshake: already received Version")
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
|
|
|
p.version = version
|
2020-01-17 10:17:19 +00:00
|
|
|
p.lastBlockIndex = version.StartHeight
|
2019-11-06 08:06:00 +00:00
|
|
|
p.handShake |= versionReceived
|
2019-09-13 12:43:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendVersionAck checks for the handshake state and sends a message to the peer.
|
|
|
|
func (p *TCPPeer) SendVersionAck(msg *Message) error {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.lock.Lock()
|
|
|
|
defer p.lock.Unlock()
|
|
|
|
if p.handShake&versionReceived == 0 {
|
|
|
|
return errors.New("invalid handshake: tried to send VersionAck, but no version received yet")
|
|
|
|
}
|
2019-11-06 15:05:50 +00:00
|
|
|
if p.handShake&versionSent == 0 {
|
|
|
|
return errors.New("invalid handshake: tried to send VersionAck, but didn't send Version yet")
|
|
|
|
}
|
2019-11-06 08:06:00 +00:00
|
|
|
if p.handShake&verAckSent != 0 {
|
|
|
|
return errors.New("invalid handshake: already sent VersionAck")
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
|
|
|
err := p.writeMsg(msg)
|
|
|
|
if err == nil {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.handShake |= verAckSent
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleVersionAck checks handshake sequence correctness when VerAck message
|
|
|
|
// is received.
|
|
|
|
func (p *TCPPeer) HandleVersionAck() error {
|
2019-11-06 08:06:00 +00:00
|
|
|
p.lock.Lock()
|
|
|
|
defer p.lock.Unlock()
|
|
|
|
if p.handShake&versionSent == 0 {
|
|
|
|
return errors.New("invalid handshake: received VersionAck, but no version sent yet")
|
|
|
|
}
|
2019-11-06 15:05:50 +00:00
|
|
|
if p.handShake&versionReceived == 0 {
|
|
|
|
return errors.New("invalid handshake: received VersionAck, but no version received yet")
|
|
|
|
}
|
2019-11-06 08:06:00 +00:00
|
|
|
if p.handShake&verAckReceived != 0 {
|
|
|
|
return errors.New("invalid handshake: already received VersionAck")
|
2019-09-13 12:43:22 +00:00
|
|
|
}
|
2019-11-06 08:06:00 +00:00
|
|
|
p.handShake |= verAckReceived
|
2019-09-13 12:43:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 07:55:21 +00:00
|
|
|
// RemoteAddr implements the Peer interface.
|
|
|
|
func (p *TCPPeer) RemoteAddr() net.Addr {
|
|
|
|
return p.conn.RemoteAddr()
|
|
|
|
}
|
|
|
|
|
|
|
|
// PeerAddr implements the Peer interface.
|
|
|
|
func (p *TCPPeer) PeerAddr() net.Addr {
|
|
|
|
remote := p.conn.RemoteAddr()
|
|
|
|
// The network can be non-tcp in unit tests.
|
|
|
|
if !p.Handshaked() || remote.Network() != "tcp" {
|
|
|
|
return p.RemoteAddr()
|
|
|
|
}
|
|
|
|
host, _, err := net.SplitHostPort(remote.String())
|
|
|
|
if err != nil {
|
|
|
|
return p.RemoteAddr()
|
|
|
|
}
|
|
|
|
addrString := net.JoinHostPort(host, strconv.Itoa(int(p.version.Port)))
|
|
|
|
tcpAddr, err := net.ResolveTCPAddr("tcp", addrString)
|
|
|
|
if err != nil {
|
|
|
|
return p.RemoteAddr()
|
|
|
|
}
|
|
|
|
return tcpAddr
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
|
2018-04-13 10:14:08 +00:00
|
|
|
// Disconnect will fill the peer's done channel with the given error.
|
|
|
|
func (p *TCPPeer) Disconnect(err error) {
|
2020-01-15 15:25:58 +00:00
|
|
|
p.finale.Do(func() {
|
|
|
|
p.server.unregister <- peerDrop{p, err}
|
|
|
|
p.conn.Close()
|
|
|
|
close(p.done)
|
|
|
|
})
|
2018-04-13 10:14:08 +00:00
|
|
|
}
|
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
// Version implements the Peer interface.
|
|
|
|
func (p *TCPPeer) Version() *payload.Version {
|
|
|
|
return p.version
|
2018-03-10 12:04:06 +00:00
|
|
|
}
|
2020-01-17 10:17:19 +00:00
|
|
|
|
|
|
|
// LastBlockIndex returns last block index.
|
|
|
|
func (p *TCPPeer) LastBlockIndex() uint32 {
|
|
|
|
p.lock.RLock()
|
|
|
|
defer p.lock.RUnlock()
|
|
|
|
return p.lastBlockIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateLastBlockIndex updates last block index.
|
|
|
|
func (p *TCPPeer) UpdateLastBlockIndex(newIndex uint32) {
|
|
|
|
p.lock.Lock()
|
|
|
|
defer p.lock.Unlock()
|
|
|
|
p.lastBlockIndex = newIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPingSent returns flag whether ping was sent or not.
|
|
|
|
func (p *TCPPeer) GetPingSent() int {
|
|
|
|
p.lock.RLock()
|
|
|
|
defer p.lock.RUnlock()
|
|
|
|
return p.pingSent
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdatePingSent updates pingSent value.
|
|
|
|
func (p *TCPPeer) UpdatePingSent(newValue int) {
|
|
|
|
p.lock.Lock()
|
|
|
|
defer p.lock.Unlock()
|
|
|
|
p.pingSent = newValue
|
|
|
|
}
|