Skip to content

Commit

Permalink
protocol: add capabilities to version payload
Browse files Browse the repository at this point in the history
closes #871
  • Loading branch information
AnnaShaleva committed May 25, 2020
1 parent e6f617a commit 902cfd5
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 92 deletions.
71 changes: 71 additions & 0 deletions pkg/network/capability/capability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package capability

import (
"errors"

"github.com/nspcc-dev/neo-go/pkg/io"
)

// MaxCapabilities is the maximum number of capabilities per payload
const MaxCapabilities = 32

// Capability describes network service available for node
type Capability struct {
Type Type
Data io.Serializable
}

// DecodeBinary implements Serializable interface.
func (c *Capability) DecodeBinary(br *io.BinReader) {
c.Type = Type(br.ReadB())
switch c.Type {
case FullNode:
c.Data = &Node{}
case TCPServer, WSServer:
c.Data = &Server{}
default:
br.Err = errors.New("unknown node capability type")
}
c.Data.DecodeBinary(br)
}

// EncodeBinary implements Serializable interface.
func (c *Capability) EncodeBinary(bw *io.BinWriter) {
bw.WriteB(byte(c.Type))
if c.Data == nil {
bw.Err = errors.New("capability has no data")
return
}
c.Data.EncodeBinary(bw)
}

// Node represents full node capability with start height
type Node struct {
StartHeight uint32
}

// DecodeBinary implements Serializable interface.
func (n *Node) DecodeBinary(br *io.BinReader) {
n.StartHeight = br.ReadU32LE()
}

// EncodeBinary implements Serializable interface.
func (n *Node) EncodeBinary(bw *io.BinWriter) {
bw.WriteU32LE(n.StartHeight)
}

// Server represents TCP or WS server capability with port
type Server struct {
// Port is the port this server is listening on
Port uint16
}

// DecodeBinary implements Serializable interface.
func (s *Server) DecodeBinary(br *io.BinReader) {
s.Port = br.ReadU16LE()
}

// EncodeBinary implements Serializable interface.
func (s *Server) EncodeBinary(bw *io.BinWriter) {
bw.WriteU16LE(s.Port)
}
13 changes: 13 additions & 0 deletions pkg/network/capability/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package capability

// Type represents node capability type
type Type byte

const (
// TCPServer represents TCP node capability type
TCPServer Type = 0x01
// WSServer represents WebSocket node capability type
WSServer Type = 0x02
// FullNode represents full node capability type
FullNode Type = 0x10
)
3 changes: 3 additions & 0 deletions pkg/network/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ func (ft *fakeTransp) Accept() {
func (ft *fakeTransp) Proto() string {
return ""
}
func (ft *fakeTransp) Address() string {
return ""
}
func (ft *fakeTransp) Close() {
}
func TestDefaultDiscoverer(t *testing.T) {
Expand Down
21 changes: 6 additions & 15 deletions pkg/network/helper_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package network

import (
"fmt"
"math/rand"
"net"
"sync/atomic"
"testing"
"time"

"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block"
Expand Down Expand Up @@ -163,15 +163,6 @@ func (d testDiscovery) RequestRemote(n int) {}
func (d testDiscovery) BadPeers() []string { return []string{} }
func (d testDiscovery) GoodPeers() []string { return []string{} }

type localTransport struct{}

func (t localTransport) Dial(addr string, timeout time.Duration) error {
return nil
}
func (t localTransport) Accept() {}
func (t localTransport) Proto() string { return "local" }
func (t localTransport) Close() {}

var defaultMessageHandler = func(t *testing.T, msg *Message) {}

type localPeer struct {
Expand Down Expand Up @@ -267,11 +258,10 @@ func (p *localPeer) Handshaked() bool {
return p.handshaked
}

func newTestServer(t *testing.T) *Server {
return &Server{
ServerConfig: ServerConfig{},
func newTestServer(t *testing.T, serverConfig ServerConfig) *Server {
s := &Server{
ServerConfig: serverConfig,
chain: &testChain{},
transport: localTransport{},
discovery: testDiscovery{},
id: rand.Uint32(),
quit: make(chan struct{}),
Expand All @@ -280,5 +270,6 @@ func newTestServer(t *testing.T) *Server {
peers: make(map[Peer]bool),
log: zaptest.NewLogger(t),
}

s.transport = NewTCPTransport(s, fmt.Sprintf("%s:%d", s.ServerConfig.Address, s.ServerConfig.Port), s.log)
return s
}
59 changes: 20 additions & 39 deletions pkg/network/payload/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,7 @@ import (

"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/io"
)

// Size of the payload not counting UserAgent encoding (which is at least 1 byte
// for zero-length string).
const minVersionSize = 27

// List of Services offered by the node.
const (
nodePeerService uint64 = 1
// BloomFilerService uint64 = 2 // Not implemented
// PrunedNode uint64 = 3 // Not implemented
// LightNode uint64 = 4 // Not implemented

"github.com/nspcc-dev/neo-go/pkg/network/capability"
)

// Version payload.
Expand All @@ -26,60 +14,53 @@ type Version struct {
Magic config.NetMode
// currently the version of the protocol is 0
Version uint32
// currently 1
Services uint64
// timestamp
Timestamp uint32
// port this server is listening on
Port uint16
// it's used to distinguish the node from public IP
Nonce uint32
// client id
UserAgent []byte
// Height of the block chain
StartHeight uint32
// Whether to receive and forward
Relay bool
// List of available network services
Capabilities []capability.Capability
}

// NewVersion returns a pointer to a Version payload.
func NewVersion(magic config.NetMode, id uint32, p uint16, ua string, h uint32, r bool) *Version {
func NewVersion(magic config.NetMode, id uint32, ua string, c []capability.Capability) *Version {
return &Version{
Magic: magic,
Version: 0,
Services: nodePeerService,
Timestamp: uint32(time.Now().UTC().Unix()),
Port: p,
Nonce: id,
UserAgent: []byte(ua),
StartHeight: h,
Relay: r,
Magic: magic,
Version: 0,
Timestamp: uint32(time.Now().UTC().Unix()),
Nonce: id,
UserAgent: []byte(ua),
Capabilities: c,
}
}

// DecodeBinary implements Serializable interface.
func (p *Version) DecodeBinary(br *io.BinReader) {
p.Magic = config.NetMode(br.ReadU32LE())
p.Version = br.ReadU32LE()
p.Services = br.ReadU64LE()
p.Timestamp = br.ReadU32LE()
p.Port = br.ReadU16LE()
p.Nonce = br.ReadU32LE()
p.UserAgent = br.ReadVarBytes()
p.StartHeight = br.ReadU32LE()
p.Relay = br.ReadBool()
br.ReadArray(&p.Capabilities, capability.MaxCapabilities)
unique := make(map[capability.Type]bool)
for _, cap := range p.Capabilities {
if unique[cap.Type] == false {
unique[cap.Type] = true
} else {
panic("capabilities with the same type are not allowed")
}
}
}

// EncodeBinary implements Serializable interface.
func (p *Version) EncodeBinary(br *io.BinWriter) {
br.WriteU32LE(uint32(p.Magic))
br.WriteU32LE(p.Version)
br.WriteU64LE(p.Services)
br.WriteU32LE(p.Timestamp)
br.WriteU16LE(p.Port)
br.WriteU32LE(p.Nonce)

br.WriteVarBytes(p.UserAgent)
br.WriteU32LE(p.StartHeight)
br.WriteBool(p.Relay)
br.WriteArray(p.Capabilities)
}
31 changes: 25 additions & 6 deletions pkg/network/payload/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,44 @@ import (

"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/network/capability"
"github.com/stretchr/testify/assert"
)

func TestVersionEncodeDecode(t *testing.T) {
var magic config.NetMode = 56753
var port uint16 = 3000
var tcpPort uint16 = 3000
var wsPort uint16 = 3001
var id uint32 = 13337
useragent := "/NEO:0.0.1/"
var height uint32 = 100500
var relay = true
var capabilities = []capability.Capability{
{
Type: capability.TCPServer,
Data: &capability.Server{
Port: tcpPort,
},
},
{
Type: capability.WSServer,
Data: &capability.Server{
Port: wsPort,
},
},
{
Type: capability.FullNode,
Data: &capability.Node{
StartHeight: height,
},
},
}

version := NewVersion(magic, id, port, useragent, height, relay)
version := NewVersion(magic, id, useragent, capabilities)
versionDecoded := &Version{}
testserdes.EncodeDecodeBinary(t, version, versionDecoded)

assert.Equal(t, versionDecoded.Nonce, id)
assert.Equal(t, versionDecoded.Port, port)
assert.ElementsMatch(t, capabilities, versionDecoded.Capabilities)
assert.Equal(t, versionDecoded.UserAgent, []byte(useragent))
assert.Equal(t, versionDecoded.StartHeight, height)
assert.Equal(t, versionDecoded.Relay, relay)
assert.Equal(t, version, versionDecoded)
}
36 changes: 32 additions & 4 deletions pkg/network/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/network/capability"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/util"
"go.uber.org/atomic"
Expand Down Expand Up @@ -347,13 +348,33 @@ func (s *Server) HandshakedPeersCount() int {

// getVersionMsg returns current version message.
func (s *Server) getVersionMsg() *Message {
_, portStr, err := net.SplitHostPort(s.transport.Address())
if err != nil {
panic(err)
}
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
panic(err)
}
capabilities := []capability.Capability{
{
Type: capability.FullNode,
Data: &capability.Node{
StartHeight: s.chain.BlockHeight(),
},
},
{
Type: capability.TCPServer,
Data: &capability.Server{
Port: uint16(port),
},
},
}
payload := payload.NewVersion(
s.Net,
s.id,
s.Port,
s.UserAgent,
s.chain.BlockHeight(),
s.Relay,
capabilities,
)
return NewMessage(CMDVersion, payload)
}
Expand Down Expand Up @@ -836,7 +857,14 @@ func (s *Server) broadcastTxHashes(hs []util.Uint256) {
// We need to filter out non-relaying nodes, so plain broadcast
// functions don't fit here.
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, func(p Peer) bool {
return p.Handshaked() && p.Version().Relay
var isFullNode bool
for _, c := range p.Version().Capabilities {
if c.Type == capability.FullNode {
isFullNode = true
break
}
}
return p.Handshaked() && isFullNode
})
}

Expand Down
Loading

0 comments on commit 902cfd5

Please sign in to comment.