From 3474a98f97dbdd083b41c63c4dd2f01ed383d110 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Feb 2026 19:34:12 +0000 Subject: [PATCH] feat(p2p): CORE_SYNC_DATA type and command ID constants Add CoreSyncData with MarshalSection/UnmarshalSection for portable storage roundtrip. Re-export Levin command IDs for the CryptoNote P2P protocol layer. Co-Authored-By: Charon --- go.mod | 7 +++-- go.sum | 8 +++++ p2p/commands.go | 22 +++++++++++++ p2p/sync.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ p2p/sync_test.go | 59 ++++++++++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 p2p/commands.go create mode 100644 p2p/sync.go create mode 100644 p2p/sync_test.go diff --git a/go.mod b/go.mod index 90aebfa..cec8d52 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module forge.lthn.ai/core/go-blockchain -go 1.25 +go 1.25.5 -require golang.org/x/crypto v0.48.0 +require ( + forge.lthn.ai/core/go-p2p v0.0.0-00010101000000-000000000000 + golang.org/x/crypto v0.48.0 +) require golang.org/x/sys v0.41.0 // indirect diff --git a/go.sum b/go.sum index f13e109..59fa593 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/p2p/commands.go b/p2p/commands.go new file mode 100644 index 0000000..9347def --- /dev/null +++ b/p2p/commands.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2026 Lethean (https://lt.hn) +// +// Licensed under the European Union Public Licence (EUPL) version 1.2. +// SPDX-License-Identifier: EUPL-1.2 + +// Package p2p implements the CryptoNote P2P protocol for the Lethean blockchain. +package p2p + +import "forge.lthn.ai/core/go-p2p/node/levin" + +// Re-export command IDs from the levin package for convenience. +const ( + CommandHandshake = levin.CommandHandshake + CommandTimedSync = levin.CommandTimedSync + CommandPing = levin.CommandPing + CommandNewBlock = levin.CommandNewBlock + CommandNewTransactions = levin.CommandNewTransactions + CommandRequestObjects = levin.CommandRequestObjects + CommandResponseObjects = levin.CommandResponseObjects + CommandRequestChain = levin.CommandRequestChain + CommandResponseChain = levin.CommandResponseChain +) diff --git a/p2p/sync.go b/p2p/sync.go new file mode 100644 index 0000000..449ad73 --- /dev/null +++ b/p2p/sync.go @@ -0,0 +1,82 @@ +// Copyright (c) 2017-2026 Lethean (https://lt.hn) +// +// Licensed under the European Union Public Licence (EUPL) version 1.2. +// SPDX-License-Identifier: EUPL-1.2 + +package p2p + +import ( + "forge.lthn.ai/core/go-blockchain/types" + "forge.lthn.ai/core/go-p2p/node/levin" +) + +// CoreSyncData is the blockchain state exchanged during handshake and timed sync. +type CoreSyncData struct { + CurrentHeight uint64 + TopID types.Hash + LastCheckpointHeight uint64 + CoreTime uint64 + ClientVersion string + NonPruningMode bool +} + +// MarshalSection encodes CoreSyncData into a portable storage Section. +func (d *CoreSyncData) MarshalSection() levin.Section { + return levin.Section{ + "current_height": levin.Uint64Val(d.CurrentHeight), + "top_id": levin.StringVal(d.TopID[:]), + "last_checkpoint_height": levin.Uint64Val(d.LastCheckpointHeight), + "core_time": levin.Uint64Val(d.CoreTime), + "client_version": levin.StringVal([]byte(d.ClientVersion)), + "non_pruning_mode_enabled": levin.BoolVal(d.NonPruningMode), + } +} + +// UnmarshalSection decodes CoreSyncData from a portable storage Section. +func (d *CoreSyncData) UnmarshalSection(s levin.Section) error { + if v, ok := s["current_height"]; ok { + val, err := v.AsUint64() + if err != nil { + return err + } + d.CurrentHeight = val + } + if v, ok := s["top_id"]; ok { + blob, err := v.AsString() + if err != nil { + return err + } + if len(blob) == 32 { + copy(d.TopID[:], blob) + } + } + if v, ok := s["last_checkpoint_height"]; ok { + val, err := v.AsUint64() + if err != nil { + return err + } + d.LastCheckpointHeight = val + } + if v, ok := s["core_time"]; ok { + val, err := v.AsUint64() + if err != nil { + return err + } + d.CoreTime = val + } + if v, ok := s["client_version"]; ok { + blob, err := v.AsString() + if err != nil { + return err + } + d.ClientVersion = string(blob) + } + if v, ok := s["non_pruning_mode_enabled"]; ok { + val, err := v.AsBool() + if err != nil { + return err + } + d.NonPruningMode = val + } + return nil +} diff --git a/p2p/sync_test.go b/p2p/sync_test.go new file mode 100644 index 0000000..a77de9b --- /dev/null +++ b/p2p/sync_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2017-2026 Lethean (https://lt.hn) +// +// Licensed under the European Union Public Licence (EUPL) version 1.2. +// SPDX-License-Identifier: EUPL-1.2 + +package p2p + +import ( + "testing" + + "forge.lthn.ai/core/go-blockchain/types" + "forge.lthn.ai/core/go-p2p/node/levin" +) + +func TestCoreSyncData_Good_Roundtrip(t *testing.T) { + var topID types.Hash + topID[0] = 0xCB + topID[31] = 0x63 + + original := CoreSyncData{ + CurrentHeight: 6300, + TopID: topID, + LastCheckpointHeight: 0, + CoreTime: 1708444800, + ClientVersion: "Lethean/go-blockchain 0.1.0", + NonPruningMode: true, + } + section := original.MarshalSection() + data, err := levin.EncodeStorage(section) + if err != nil { + t.Fatalf("encode: %v", err) + } + decoded, err := levin.DecodeStorage(data) + if err != nil { + t.Fatalf("decode: %v", err) + } + var got CoreSyncData + if err := got.UnmarshalSection(decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if got.CurrentHeight != original.CurrentHeight { + t.Errorf("height: got %d, want %d", got.CurrentHeight, original.CurrentHeight) + } + if got.TopID != original.TopID { + t.Errorf("top_id: got %x, want %x", got.TopID, original.TopID) + } + if got.ClientVersion != original.ClientVersion { + t.Errorf("version: got %q, want %q", got.ClientVersion, original.ClientVersion) + } + if got.NonPruningMode != original.NonPruningMode { + t.Errorf("pruning: got %v, want %v", got.NonPruningMode, original.NonPruningMode) + } + if got.LastCheckpointHeight != original.LastCheckpointHeight { + t.Errorf("checkpoint: got %d, want %d", got.LastCheckpointHeight, original.LastCheckpointHeight) + } + if got.CoreTime != original.CoreTime { + t.Errorf("core_time: got %d, want %d", got.CoreTime, original.CoreTime) + } +}