mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 23:33:37 +00:00
b6d8271b8d
* headers can now sync till infinity * fixed empty hashStop getBlock payload + test * added more test + more binary decoding/encoding * bump version
309 lines
6.7 KiB
Go
309 lines
6.7 KiB
Go
package network
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/core"
|
|
"github.com/CityOfZion/neo-go/pkg/network/payload"
|
|
"github.com/CityOfZion/neo-go/pkg/util"
|
|
)
|
|
|
|
func listenTCP(s *Server, port int) error {
|
|
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.listener = ln
|
|
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go handleConnection(s, conn)
|
|
}
|
|
}
|
|
|
|
func connectToRemoteNode(s *Server, address string) {
|
|
conn, err := net.Dial("tcp", address)
|
|
if err != nil {
|
|
s.logger.Printf("failed to connect to remote node %s", address)
|
|
if conn != nil {
|
|
conn.Close()
|
|
}
|
|
return
|
|
}
|
|
go handleConnection(s, conn)
|
|
}
|
|
|
|
func connectToSeeds(s *Server, addrs []string) {
|
|
for _, addr := range addrs {
|
|
go connectToRemoteNode(s, addr)
|
|
}
|
|
}
|
|
|
|
func handleConnection(s *Server, conn net.Conn) {
|
|
peer := NewTCPPeer(conn, s)
|
|
s.register <- peer
|
|
|
|
// remove the peer from connected peers and cleanup the connection.
|
|
defer func() {
|
|
peer.disconnect()
|
|
}()
|
|
|
|
// Start a goroutine that will handle all outgoing messages.
|
|
go peer.writeLoop()
|
|
// Start a goroutine that will handle all incomming messages.
|
|
go handleMessage(s, peer)
|
|
|
|
// Read from the connection and decode it into a Message ready for processing.
|
|
for {
|
|
msg := &Message{}
|
|
if err := msg.decode(conn); err != nil {
|
|
s.logger.Printf("decode error: %s", err)
|
|
break
|
|
}
|
|
|
|
peer.receive <- msg
|
|
}
|
|
}
|
|
|
|
// handleMessage multiplexes the message received from a TCP connection to a server command.
|
|
func handleMessage(s *Server, p *TCPPeer) {
|
|
var err error
|
|
|
|
for {
|
|
msg := <-p.receive
|
|
command := msg.commandType()
|
|
|
|
// s.logger.Printf("IN :: %d :: %s :: %v", p.id(), command, msg)
|
|
|
|
switch command {
|
|
case cmdVersion:
|
|
version := msg.Payload.(*payload.Version)
|
|
if err = s.handleVersionCmd(version, p); err != nil {
|
|
break
|
|
}
|
|
p.nonce = version.Nonce
|
|
p.pVersion = version
|
|
|
|
// When a node receives a connection request, it declares its version immediately.
|
|
// There will be no other communication until both sides are getting versions of each other.
|
|
// When a node receives the version message, it replies to a verack as a response immediately.
|
|
// NOTE: The current official NEO nodes dont mimic this behaviour. There is small chance that the
|
|
// official nodes will not respond directly with a verack after we sended our version.
|
|
// is this a bug? - anthdm 02/02/2018
|
|
msgVerack := <-p.receive
|
|
if msgVerack.commandType() != cmdVerack {
|
|
err = errors.New("expected verack after sended out version")
|
|
break
|
|
}
|
|
|
|
// start the protocol
|
|
go s.startProtocol(p)
|
|
case cmdAddr:
|
|
addrList := msg.Payload.(*payload.AddressList)
|
|
err = s.handleAddrCmd(addrList, p)
|
|
case cmdGetAddr:
|
|
err = s.handleGetaddrCmd(msg, p)
|
|
case cmdInv:
|
|
inv := msg.Payload.(*payload.Inventory)
|
|
err = s.handleInvCmd(inv, p)
|
|
case cmdBlock:
|
|
block := msg.Payload.(*core.Block)
|
|
err = s.handleBlockCmd(block, p)
|
|
case cmdConsensus:
|
|
case cmdTX:
|
|
case cmdVerack:
|
|
// If we receive a verack here we disconnect. We already handled the verack
|
|
// when we sended our version.
|
|
err = errors.New("verack already received")
|
|
case cmdGetHeaders:
|
|
case cmdGetBlocks:
|
|
case cmdGetData:
|
|
case cmdHeaders:
|
|
headers := msg.Payload.(*payload.Headers)
|
|
err = s.handleHeadersCmd(headers, p)
|
|
default:
|
|
// This command is unknown by the server.
|
|
err = fmt.Errorf("unknown command received %v", msg.Command)
|
|
break
|
|
}
|
|
|
|
// catch all errors here and disconnect.
|
|
if err != nil {
|
|
s.logger.Printf("processing message failed: %s", err)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Disconnect the peer when breaked out of the loop.
|
|
p.disconnect()
|
|
}
|
|
|
|
type sendTuple struct {
|
|
msg *Message
|
|
err chan error
|
|
}
|
|
|
|
// TCPPeer represents a remote node, backed by TCP transport.
|
|
type TCPPeer struct {
|
|
s *Server
|
|
// nonce (id) of the peer.
|
|
nonce uint32
|
|
// underlying TCP connection
|
|
conn net.Conn
|
|
// host and port information about this peer.
|
|
endpoint util.Endpoint
|
|
// channel to coordinate messages writen back to the connection.
|
|
send chan sendTuple
|
|
// channel to receive from underlying connection.
|
|
receive chan *Message
|
|
// the version sended out by the peer when connected.
|
|
pVersion *payload.Version
|
|
}
|
|
|
|
// NewTCPPeer returns a pointer to a TCP Peer.
|
|
func NewTCPPeer(conn net.Conn, s *Server) *TCPPeer {
|
|
e, _ := util.EndpointFromString(conn.RemoteAddr().String())
|
|
|
|
return &TCPPeer{
|
|
conn: conn,
|
|
send: make(chan sendTuple),
|
|
receive: make(chan *Message),
|
|
endpoint: e,
|
|
s: s,
|
|
}
|
|
}
|
|
|
|
func (p *TCPPeer) callVersion(msg *Message) error {
|
|
t := sendTuple{
|
|
msg: msg,
|
|
err: make(chan error),
|
|
}
|
|
|
|
p.send <- t
|
|
|
|
return <-t.err
|
|
}
|
|
|
|
func (p *TCPPeer) version() *payload.Version {
|
|
return p.pVersion
|
|
}
|
|
|
|
// id implements the peer interface
|
|
func (p *TCPPeer) id() uint32 {
|
|
return p.nonce
|
|
}
|
|
|
|
// endpoint implements the peer interface
|
|
func (p *TCPPeer) addr() util.Endpoint {
|
|
return p.endpoint
|
|
}
|
|
|
|
// callGetaddr will send the "getaddr" command to the remote.
|
|
func (p *TCPPeer) callGetaddr(msg *Message) error {
|
|
t := sendTuple{
|
|
msg: msg,
|
|
err: make(chan error),
|
|
}
|
|
|
|
p.send <- t
|
|
|
|
return <-t.err
|
|
}
|
|
|
|
// callGetblocks will send the "getblocks" command to the remote.
|
|
func (p *TCPPeer) callGetblocks(msg *Message) error {
|
|
t := sendTuple{
|
|
msg: msg,
|
|
err: make(chan error),
|
|
}
|
|
|
|
p.send <- t
|
|
|
|
return <-t.err
|
|
}
|
|
|
|
// callGetheaders will send the "getheaders" command to the remote.
|
|
func (p *TCPPeer) callGetheaders(msg *Message) error {
|
|
t := sendTuple{
|
|
msg: msg,
|
|
err: make(chan error),
|
|
}
|
|
|
|
p.send <- t
|
|
|
|
return <-t.err
|
|
}
|
|
|
|
func (p *TCPPeer) callVerack(msg *Message) error {
|
|
t := sendTuple{
|
|
msg: msg,
|
|
err: make(chan error),
|
|
}
|
|
|
|
p.send <- t
|
|
|
|
return <-t.err
|
|
}
|
|
|
|
func (p *TCPPeer) callGetdata(msg *Message) error {
|
|
t := sendTuple{
|
|
msg: msg,
|
|
err: make(chan error),
|
|
}
|
|
|
|
p.send <- t
|
|
|
|
return <-t.err
|
|
}
|
|
|
|
// disconnect disconnects the peer, cleaning up all its resources.
|
|
// 3 goroutines needs to be cleanup (writeLoop, handleConnection and handleMessage)
|
|
func (p *TCPPeer) disconnect() {
|
|
select {
|
|
case <-p.send:
|
|
case <-p.receive:
|
|
default:
|
|
close(p.send)
|
|
close(p.receive)
|
|
p.s.unregister <- p
|
|
p.conn.Close()
|
|
}
|
|
}
|
|
|
|
// writeLoop writes messages to the underlying TCP connection.
|
|
// A goroutine writeLoop is started for each connection.
|
|
// There should be at most one writer to a connection executing
|
|
// all writes from this goroutine.
|
|
func (p *TCPPeer) writeLoop() {
|
|
// clean up the connection.
|
|
defer func() {
|
|
p.disconnect()
|
|
}()
|
|
|
|
// resuse this buffer
|
|
buf := new(bytes.Buffer)
|
|
for {
|
|
t := <-p.send
|
|
if t.msg == nil {
|
|
break // send probably closed.
|
|
}
|
|
|
|
// p.s.logger.Printf("OUT :: %s :: %+v", t.msg.commandType(), t.msg.Payload)
|
|
|
|
if err := t.msg.encode(buf); err != nil {
|
|
t.err <- err
|
|
}
|
|
_, err := p.conn.Write(buf.Bytes())
|
|
t.err <- err
|
|
|
|
buf.Reset()
|
|
}
|
|
}
|