forked from TrueCloudLab/neoneo-go
251 lines
6.1 KiB
Go
251 lines
6.1 KiB
Go
package network
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/anthdm/neo-go/pkg/network/payload"
|
|
"github.com/anthdm/neo-go/pkg/util"
|
|
)
|
|
|
|
const (
|
|
// node version
|
|
version = "2.6.0"
|
|
// official ports according to the protocol.
|
|
portMainNet = 10333
|
|
portTestNet = 20333
|
|
)
|
|
|
|
var (
|
|
// rpcLogger used for debugging RPC messages between nodes.
|
|
rpcLogger = log.New(os.Stdout, "RPC :: ", 0)
|
|
)
|
|
|
|
type messageTuple struct {
|
|
peer *Peer
|
|
msg *Message
|
|
}
|
|
|
|
// Server is the representation of a full working NEO TCP node.
|
|
type Server struct {
|
|
logger *log.Logger
|
|
|
|
// id of the server
|
|
id uint32
|
|
|
|
// the port the TCP listener is listening on.
|
|
port uint16
|
|
|
|
// userAgent of the server.
|
|
userAgent string
|
|
// The "magic" mode the server is currently running on.
|
|
// This can either be 0x00746e41 or 0x74746e41 for main or test net.
|
|
// Or 56753 to work with the docker privnet.
|
|
net NetMode
|
|
// map that holds all connected peers to this server.
|
|
peers map[*Peer]bool
|
|
|
|
register chan *Peer
|
|
unregister chan *Peer
|
|
|
|
// channel for coordinating messages.
|
|
message chan messageTuple
|
|
|
|
// channel used to gracefull shutdown the server.
|
|
quit chan struct{}
|
|
|
|
// Whether this server will receive and forward messages.
|
|
relay bool
|
|
|
|
// TCP listener of the server
|
|
listener net.Listener
|
|
}
|
|
|
|
// NewServer returns a pointer to a new server.
|
|
func NewServer(net NetMode) *Server {
|
|
logger := log.New(os.Stdout, "NEO SERVER :: ", 0)
|
|
|
|
if net != ModeTestNet && net != ModeMainNet && net != ModeDevNet {
|
|
logger.Fatalf("invalid network mode %d", net)
|
|
}
|
|
|
|
s := &Server{
|
|
// It is important to have this user agent correct. Otherwise we will get
|
|
// disconnected.
|
|
id: util.RandUint32(1111111, 9999999),
|
|
userAgent: fmt.Sprintf("\v/NEO:%s/", version),
|
|
logger: logger,
|
|
peers: make(map[*Peer]bool),
|
|
register: make(chan *Peer),
|
|
unregister: make(chan *Peer),
|
|
message: make(chan messageTuple),
|
|
relay: true,
|
|
net: net,
|
|
quit: make(chan struct{}),
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Start run's the server.
|
|
func (s *Server) Start(port string, seeds []string) {
|
|
p, err := strconv.Atoi(port[1:len(port)])
|
|
if err != nil {
|
|
s.logger.Fatalf("could not convert port to integer: %s", err)
|
|
}
|
|
s.port = uint16(p)
|
|
|
|
fmt.Println(logo())
|
|
fmt.Println(string(s.userAgent))
|
|
fmt.Println("")
|
|
s.logger.Printf("NET: %s - TCP: %d - RELAY: %v - ID: %d",
|
|
s.net, int(s.port), s.relay, s.id)
|
|
|
|
go listenTCP(s, port)
|
|
|
|
if len(seeds) > 0 {
|
|
connectToSeeds(s, seeds)
|
|
}
|
|
|
|
s.loop()
|
|
}
|
|
|
|
// Stop the server, attemping a gracefull shutdown.
|
|
func (s *Server) Stop() { s.quit <- struct{}{} }
|
|
|
|
// shutdown the server, disconnecting all peers.
|
|
func (s *Server) shutdown() {
|
|
s.logger.Println("attemping a quitefull shutdown.")
|
|
s.listener.Close()
|
|
|
|
// disconnect and remove all connected peers.
|
|
for peer := range s.peers {
|
|
peer.conn.Close()
|
|
s.unregister <- peer
|
|
}
|
|
}
|
|
|
|
func (s *Server) disconnect(p *Peer) {
|
|
p.conn.Close()
|
|
close(p.send)
|
|
s.unregister <- p
|
|
}
|
|
|
|
func (s *Server) loop() {
|
|
for {
|
|
select {
|
|
case peer := <-s.register:
|
|
s.logger.Printf("peer registered from address %s", peer.conn.RemoteAddr())
|
|
|
|
s.peers[peer] = true
|
|
|
|
// only respond with the version mesage if the peer initiated the connection.
|
|
if peer.initiator {
|
|
resp, err := s.handlePeerConnected()
|
|
if err != nil {
|
|
s.logger.Fatalf("handling initial peer connection failed: %s", err)
|
|
}
|
|
peer.send <- resp
|
|
}
|
|
case peer := <-s.unregister:
|
|
if _, ok := s.peers[peer]; ok {
|
|
delete(s.peers, peer)
|
|
s.logger.Printf("peer %s disconnected", peer.conn.RemoteAddr())
|
|
}
|
|
case tuple := <-s.message:
|
|
if err := s.processMessage(tuple.msg, tuple.peer); err != nil {
|
|
s.logger.Fatalf("failed to process message: %s", err)
|
|
s.disconnect(tuple.peer)
|
|
}
|
|
case <-s.quit:
|
|
s.shutdown()
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: unregister peers on error.
|
|
// processMessage processes the received message from a remote node.
|
|
func (s *Server) processMessage(msg *Message, peer *Peer) error {
|
|
rpcLogger.Printf("IN :: %s", msg.commandType())
|
|
if msg.Length > 0 {
|
|
rpcLogger.Printf("IN :: %+v", msg.Payload)
|
|
}
|
|
|
|
switch msg.commandType() {
|
|
case cmdVersion:
|
|
return s.handleVersionCmd(msg.Payload.(*payload.Version), peer)
|
|
case cmdVerack:
|
|
case cmdGetAddr:
|
|
return s.handleGetAddrCmd(msg, peer)
|
|
case cmdAddr:
|
|
case cmdGetHeaders:
|
|
case cmdHeaders:
|
|
case cmdGetBlocks:
|
|
case cmdInv:
|
|
case cmdGetData:
|
|
case cmdBlock:
|
|
case cmdTX:
|
|
default:
|
|
return errors.New("invalid RPC command received: " + string(msg.commandType()))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// When a new peer is connected we respond with the version command.
|
|
// No further communication should been made before both sides has received
|
|
// the version of eachother.
|
|
func (s *Server) handlePeerConnected() (*Message, error) {
|
|
payload := payload.NewVersion(s.id, s.port, s.userAgent, 0, s.relay)
|
|
msg := newMessage(s.net, cmdVersion, payload)
|
|
return msg, nil
|
|
}
|
|
|
|
// Version declares the server's version.
|
|
func (s *Server) handleVersionCmd(v *payload.Version, peer *Peer) error {
|
|
// TODO: check version and verify to trust that node.
|
|
|
|
payload := payload.NewVersion(s.id, s.port, s.userAgent, 0, s.relay)
|
|
// we respond with our version.
|
|
versionMsg := newMessage(s.net, cmdVersion, payload)
|
|
peer.send <- versionMsg
|
|
|
|
// we respond with a verack, we successfully received peer's version
|
|
// at this point.
|
|
peer.verack = true
|
|
verackMsg := newMessage(s.net, cmdVerack, nil)
|
|
peer.send <- verackMsg
|
|
|
|
return nil
|
|
}
|
|
|
|
// After receiving the "getaddr" the server needs to respond with an "addr" message.
|
|
// providing information about the other nodes in the network.
|
|
// e.g. this server's connected peers.
|
|
func (s *Server) handleGetAddrCmd(msg *Message, peer *Peer) error {
|
|
// payload := NewAddrPayload()
|
|
// b, err := payload.encode()
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// var addrList []AddrWithTimestamp
|
|
// for peer := range s.peers {
|
|
// addrList = append(addrList, newAddrWithTimestampFromPeer(peer))
|
|
// }
|
|
|
|
return nil
|
|
}
|
|
|
|
func logo() string {
|
|
return `
|
|
_ ____________ __________
|
|
/ | / / ____/ __ \ / ____/ __ \
|
|
/ |/ / __/ / / / /_____/ / __/ / / /
|
|
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
|
|
/_/ |_/_____/\____/ \____/\____/
|
|
`
|
|
}
|