From 82b400700f4384944dad42b3d12f703b1f49918c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 27 Dec 2024 12:51:45 +0300 Subject: [PATCH] capability: add ArchivalNode, fix #3776 This will be used to differentiate full history nodes from state only. Currently it's just a definition, we can not use it safely on current networks because nodes will refuse this Version, so actual code using the attribute will be added in newer versions. Signed-off-by: Roman Khimov --- pkg/network/capability/capability.go | 25 ++++++++++++++++- pkg/network/capability/capability_test.go | 33 +++++++++++++++++++++++ pkg/network/capability/type.go | 5 ++++ pkg/network/payload/version_test.go | 4 +++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/network/capability/capability.go b/pkg/network/capability/capability.go index 7484ffbe7..130506761 100644 --- a/pkg/network/capability/capability.go +++ b/pkg/network/capability/capability.go @@ -31,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 @@ -65,6 +70,8 @@ type Capability struct { func (c *Capability) DecodeBinary(br *io.BinReader) { c.Type = Type(br.ReadB()) switch c.Type { + case ArchivalNode: + c.Data = &Archival{} case FullNode: c.Data = &Node{} case TCPServer, WSServer: @@ -116,6 +123,22 @@ 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 diff --git a/pkg/network/capability/capability_test.go b/pkg/network/capability/capability_test.go index 52649f9a8..696a4fe70 100644 --- a/pkg/network/capability/capability_test.go +++ b/pkg/network/capability/capability_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" + "github.com/stretchr/testify/require" ) func TestUnknownEncodeDecode(t *testing.T) { @@ -13,3 +14,35 @@ func TestUnknownEncodeDecode(t *testing.T) { ) 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)) + } +} diff --git a/pkg/network/capability/type.go b/pkg/network/capability/type.go index a20336e40..161a49f54 100644 --- a/pkg/network/capability/type.go +++ b/pkg/network/capability/type.go @@ -10,6 +10,11 @@ const ( WSServer Type = 0x02 // 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. ReservedFirst Type = 0xf0 diff --git a/pkg/network/payload/version_test.go b/pkg/network/payload/version_test.go index a18ac3d32..45315e698 100644 --- a/pkg/network/payload/version_test.go +++ b/pkg/network/payload/version_test.go @@ -29,6 +29,10 @@ func TestVersionEncodeDecode(t *testing.T) { Port: wsPort, }, }, + { + Type: capability.ArchivalNode, + Data: &capability.Archival{}, + }, { Type: 0xff, Data: &capability.Unknown{},