Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Archival node #3778

Merged
merged 4 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions pkg/network/capability/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io"
)

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

// MaxDataSize is the maximum size of capability payload.
MaxDataSize = 1024
)

// Capabilities is a list of Capability.
type Capabilities []Capability
Expand All @@ -26,9 +31,14 @@ func (cs *Capabilities) EncodeBinary(br *io.BinWriter) {
// checkUniqueCapabilities checks whether payload capabilities have a unique type.
func (cs Capabilities) checkUniqueCapabilities() error {
err := errors.New("capabilities with the same type are not allowed")
var isFullNode, isTCP, isWS bool
var isFullNode, isArchived, isTCP, isWS bool
for _, cap := range cs {
switch cap.Type {
case ArchivalNode:
if isArchived {
return err
}
isArchived = true
case FullNode:
if isFullNode {
return err
Expand All @@ -44,6 +54,7 @@ func (cs Capabilities) checkUniqueCapabilities() error {
return err
}
isWS = true
default: /* OK to have duplicates */
}
}
return nil
Expand All @@ -59,13 +70,14 @@ type Capability struct {
func (c *Capability) DecodeBinary(br *io.BinReader) {
c.Type = Type(br.ReadB())
switch c.Type {
case ArchivalNode:
c.Data = &Archival{}
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
case FullNode:
c.Data = &Node{}
case TCPServer, WSServer:
c.Data = &Server{}
default:
br.Err = errors.New("unknown node capability type")
return
c.Data = &Unknown{}
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
}
c.Data.DecodeBinary(br)
}
Expand Down Expand Up @@ -110,3 +122,35 @@ func (s *Server) DecodeBinary(br *io.BinReader) {
func (s *Server) EncodeBinary(bw *io.BinWriter) {
bw.WriteU16LE(s.Port)
}

// Archival represents an archival node that stores all blocks.
type Archival struct{}

// DecodeBinary implements io.Serializable.
func (a *Archival) DecodeBinary(br *io.BinReader) {
var zero = br.ReadB() // Zero-length byte array as per Unknown.
if zero != 0 {
br.Err = errors.New("archival capability with non-zero data")
}
}

// EncodeBinary implements io.Serializable.
func (a *Archival) EncodeBinary(bw *io.BinWriter) {
bw.WriteB(0)
}

// Unknown represents an unknown capability with some data. Other nodes can
// decode it even if they can't interpret it. This is not expected to be used
// for sending data directly (proper new types should be used), but it allows
// for easier protocol extensibility (old nodes won't reject new capabilities).
type Unknown []byte

// DecodeBinary implements io.Serializable.
func (u *Unknown) DecodeBinary(br *io.BinReader) {
*u = br.ReadVarBytes()
}

// EncodeBinary implements io.Serializable.
func (u *Unknown) EncodeBinary(bw *io.BinWriter) {
bw.WriteVarBytes(*u)
}
48 changes: 48 additions & 0 deletions pkg/network/capability/capability_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package capability

import (
"testing"

"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/stretchr/testify/require"
)

func TestUnknownEncodeDecode(t *testing.T) {
var (
u = Unknown{0x55, 0xaa}
ud Unknown
)
testserdes.EncodeDecodeBinary(t, &u, &ud)
}

func TestArchivalEncodeDecode(t *testing.T) {
var (
a = Archival{}
ad Archival
)
testserdes.EncodeDecodeBinary(t, &a, &ad)

var bad = []byte{0x02, 0x55, 0xaa} // Two-byte var-encoded string.
require.Error(t, testserdes.DecodeBinary(bad, &ad))
}

func TestCheckUniqueError(t *testing.T) {
// Successful cases are already checked in Version payload test.
var caps Capabilities

for _, bad := range [][]byte{
{0x02, 0x11, 0x00, 0x11, 0x00}, // 2 Archival
{0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00}, // 2 FullNode
{0x02, 0x01, 0x55, 0xaa, 0x01, 0x55, 0xaa}, // 2 TCPServer
{0x02, 0x02, 0x55, 0xaa, 0x02, 0x55, 0xaa}, // 2 WSServer
} {
require.Error(t, testserdes.DecodeBinary(bad, &caps))
}
for _, good := range [][]byte{
{0x02, 0x11, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00}, // Archival + FullNode
{0x02, 0x01, 0x55, 0xaa, 0x02, 0x55, 0xaa}, // TCPServer + WSServer
{0x02, 0xf0, 0x00, 0xf0, 0x00}, // 2 Reserved 0xf0
} {
require.NoError(t, testserdes.DecodeBinary(good, &caps))
}
}
11 changes: 10 additions & 1 deletion pkg/network/capability/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ const (
TCPServer Type = 0x01
// WSServer represents WebSocket node capability type.
WSServer Type = 0x02
// FullNode represents full node capability type.
// FullNode represents a node that has complete current state.
FullNode Type = 0x10
// ArchivalNode represents a node that stores full block history.
// These nodes can be used for P2P synchronization from genesis
// (FullNode can cut the tail and may not respond to requests for
// old (wrt MaxTraceableBlocks) blocks).
ArchivalNode Type = 0x11

// 0xf0-0xff are reserved for private experiments.
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
ReservedFirst Type = 0xf0
ReservedLast Type = 0xff
)
12 changes: 12 additions & 0 deletions pkg/network/payload/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,24 @@ func TestVersionEncodeDecode(t *testing.T) {
Port: wsPort,
},
},
{
Type: capability.ArchivalNode,
Data: &capability.Archival{},
},
{
Type: 0xff,
Data: &capability.Unknown{},
},
{
Type: capability.FullNode,
Data: &capability.Node{
StartHeight: height,
},
},
{
Type: 0xf0,
Data: &capability.Unknown{0x55, 0xaa},
},
}

version := NewVersion(magic, id, useragent, capabilities)
Expand Down
Loading