Update go.mod module line, all require/replace directives, and every .go import path from forge.lthn.ai/core/go-blockchain to dappco.re/go/core/blockchain. Add replace directives to bridge dappco.re paths to existing forge.lthn.ai registry during migration. Update CLAUDE.md, README, and docs to reflect the new module path. Co-Authored-By: Virgil <virgil@lethean.io>
259 lines
7.1 KiB
Go
259 lines
7.1 KiB
Go
// 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 "dappco.re/go/core/p2p/node/levin"
|
|
|
|
// NewBlockNotification is NOTIFY_NEW_BLOCK (2001).
|
|
type NewBlockNotification struct {
|
|
BlockBlob []byte // Serialised block
|
|
TxBlobs [][]byte // Serialised transactions
|
|
Height uint64 // Current blockchain height
|
|
}
|
|
|
|
// Encode serialises the notification.
|
|
func (n *NewBlockNotification) Encode() ([]byte, error) {
|
|
blockEntry := levin.Section{
|
|
"block": levin.StringVal(n.BlockBlob),
|
|
"txs": levin.StringArrayVal(n.TxBlobs),
|
|
}
|
|
s := levin.Section{
|
|
"b": levin.ObjectVal(blockEntry),
|
|
"current_blockchain_height": levin.Uint64Val(n.Height),
|
|
}
|
|
return levin.EncodeStorage(s)
|
|
}
|
|
|
|
// Decode parses a new block notification from a storage blob.
|
|
func (n *NewBlockNotification) Decode(data []byte) error {
|
|
s, err := levin.DecodeStorage(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := s["current_blockchain_height"]; ok {
|
|
n.Height, _ = v.AsUint64()
|
|
}
|
|
if v, ok := s["b"]; ok {
|
|
obj, _ := v.AsSection()
|
|
if blk, ok := obj["block"]; ok {
|
|
n.BlockBlob, _ = blk.AsString()
|
|
}
|
|
if txs, ok := obj["txs"]; ok {
|
|
n.TxBlobs, _ = txs.AsStringArray()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewTransactionsNotification is NOTIFY_NEW_TRANSACTIONS (2002).
|
|
type NewTransactionsNotification struct {
|
|
TxBlobs [][]byte
|
|
}
|
|
|
|
// Encode serialises the notification.
|
|
func (n *NewTransactionsNotification) Encode() ([]byte, error) {
|
|
s := levin.Section{
|
|
"txs": levin.StringArrayVal(n.TxBlobs),
|
|
}
|
|
return levin.EncodeStorage(s)
|
|
}
|
|
|
|
// Decode parses a new transactions notification.
|
|
func (n *NewTransactionsNotification) Decode(data []byte) error {
|
|
s, err := levin.DecodeStorage(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := s["txs"]; ok {
|
|
n.TxBlobs, _ = v.AsStringArray()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BlockCompleteEntry holds a block blob and its transaction blobs.
|
|
type BlockCompleteEntry struct {
|
|
Block []byte // Serialised block
|
|
Txs [][]byte // Serialised transactions
|
|
}
|
|
|
|
// RequestGetObjects is NOTIFY_REQUEST_GET_OBJECTS (2003).
|
|
type RequestGetObjects struct {
|
|
Blocks [][]byte // 32-byte block hashes
|
|
Txs [][]byte // 32-byte tx hashes (usually empty for sync)
|
|
}
|
|
|
|
// Encode serialises the request.
|
|
// The C++ daemon uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB for both blocks
|
|
// and txs, so we pack all hashes into single concatenated blobs.
|
|
func (r *RequestGetObjects) Encode() ([]byte, error) {
|
|
blocksBlob := make([]byte, 0, len(r.Blocks)*32)
|
|
for _, id := range r.Blocks {
|
|
blocksBlob = append(blocksBlob, id...)
|
|
}
|
|
s := levin.Section{
|
|
"blocks": levin.StringVal(blocksBlob),
|
|
}
|
|
if len(r.Txs) > 0 {
|
|
txsBlob := make([]byte, 0, len(r.Txs)*32)
|
|
for _, id := range r.Txs {
|
|
txsBlob = append(txsBlob, id...)
|
|
}
|
|
s["txs"] = levin.StringVal(txsBlob)
|
|
}
|
|
return levin.EncodeStorage(s)
|
|
}
|
|
|
|
// Decode parses a get-objects request from a storage blob.
|
|
func (r *RequestGetObjects) Decode(data []byte) error {
|
|
s, err := levin.DecodeStorage(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := s["blocks"]; ok {
|
|
blob, _ := v.AsString()
|
|
r.Blocks = splitHashes(blob, 32)
|
|
}
|
|
if v, ok := s["txs"]; ok {
|
|
blob, _ := v.AsString()
|
|
r.Txs = splitHashes(blob, 32)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResponseGetObjects is NOTIFY_RESPONSE_GET_OBJECTS (2004).
|
|
type ResponseGetObjects struct {
|
|
Blocks []BlockCompleteEntry
|
|
MissedIDs [][]byte
|
|
CurrentHeight uint64
|
|
}
|
|
|
|
// Encode serialises the response.
|
|
func (r *ResponseGetObjects) Encode() ([]byte, error) {
|
|
sections := make([]levin.Section, len(r.Blocks))
|
|
for i, entry := range r.Blocks {
|
|
sections[i] = levin.Section{
|
|
"block": levin.StringVal(entry.Block),
|
|
"txs": levin.StringArrayVal(entry.Txs),
|
|
}
|
|
}
|
|
s := levin.Section{
|
|
"blocks": levin.ObjectArrayVal(sections),
|
|
"current_blockchain_height": levin.Uint64Val(r.CurrentHeight),
|
|
}
|
|
if len(r.MissedIDs) > 0 {
|
|
// missed_ids uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB in C++.
|
|
blob := make([]byte, 0, len(r.MissedIDs)*32)
|
|
for _, id := range r.MissedIDs {
|
|
blob = append(blob, id...)
|
|
}
|
|
s["missed_ids"] = levin.StringVal(blob)
|
|
}
|
|
return levin.EncodeStorage(s)
|
|
}
|
|
|
|
// Decode parses a get-objects response from a storage blob.
|
|
func (r *ResponseGetObjects) Decode(data []byte) error {
|
|
s, err := levin.DecodeStorage(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := s["current_blockchain_height"]; ok {
|
|
r.CurrentHeight, _ = v.AsUint64()
|
|
}
|
|
if v, ok := s["blocks"]; ok {
|
|
sections, _ := v.AsSectionArray()
|
|
r.Blocks = make([]BlockCompleteEntry, len(sections))
|
|
for i, sec := range sections {
|
|
if blk, ok := sec["block"]; ok {
|
|
r.Blocks[i].Block, _ = blk.AsString()
|
|
}
|
|
if txs, ok := sec["txs"]; ok {
|
|
r.Blocks[i].Txs, _ = txs.AsStringArray()
|
|
}
|
|
}
|
|
}
|
|
if v, ok := s["missed_ids"]; ok {
|
|
// missed_ids uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB in C++.
|
|
blob, _ := v.AsString()
|
|
r.MissedIDs = splitHashes(blob, 32)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RequestChain is NOTIFY_REQUEST_CHAIN (2006).
|
|
type RequestChain struct {
|
|
BlockIDs [][]byte // Array of 32-byte block hashes
|
|
}
|
|
|
|
// Encode serialises the request.
|
|
// The C++ daemon uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB for block_ids,
|
|
// so we pack all hashes into a single concatenated blob.
|
|
func (r *RequestChain) Encode() ([]byte, error) {
|
|
blob := make([]byte, 0, len(r.BlockIDs)*32)
|
|
for _, id := range r.BlockIDs {
|
|
blob = append(blob, id...)
|
|
}
|
|
s := levin.Section{
|
|
"block_ids": levin.StringVal(blob),
|
|
}
|
|
return levin.EncodeStorage(s)
|
|
}
|
|
|
|
// BlockContextInfo holds a block hash and cumulative size from a chain
|
|
// entry response. Mirrors the C++ block_context_info struct.
|
|
type BlockContextInfo struct {
|
|
Hash []byte // 32-byte block hash (KV_SERIALIZE_VAL_POD_AS_BLOB)
|
|
CumulSize uint64 // Cumulative block size
|
|
}
|
|
|
|
// ResponseChainEntry is NOTIFY_RESPONSE_CHAIN_ENTRY (2007).
|
|
type ResponseChainEntry struct {
|
|
StartHeight uint64
|
|
TotalHeight uint64
|
|
BlockIDs [][]byte // Convenience: just the hashes
|
|
Blocks []BlockContextInfo // Full entries with cumulative sizes
|
|
}
|
|
|
|
// Decode parses a chain entry response.
|
|
// m_block_ids is an object array of block_context_info, each with
|
|
// "h" (hash blob) and "cumul_size" (uint64).
|
|
func (r *ResponseChainEntry) Decode(data []byte) error {
|
|
s, err := levin.DecodeStorage(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v, ok := s["start_height"]; ok {
|
|
r.StartHeight, _ = v.AsUint64()
|
|
}
|
|
if v, ok := s["total_height"]; ok {
|
|
r.TotalHeight, _ = v.AsUint64()
|
|
}
|
|
if v, ok := s["m_block_ids"]; ok {
|
|
sections, _ := v.AsSectionArray()
|
|
r.Blocks = make([]BlockContextInfo, len(sections))
|
|
r.BlockIDs = make([][]byte, len(sections))
|
|
for i, sec := range sections {
|
|
if hv, ok := sec["h"]; ok {
|
|
r.Blocks[i].Hash, _ = hv.AsString()
|
|
r.BlockIDs[i] = r.Blocks[i].Hash
|
|
}
|
|
if cv, ok := sec["cumul_size"]; ok {
|
|
r.Blocks[i].CumulSize, _ = cv.AsUint64()
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// splitHashes divides a concatenated blob into fixed-size hash slices.
|
|
func splitHashes(blob []byte, size int) [][]byte {
|
|
n := len(blob) / size
|
|
out := make([][]byte, n)
|
|
for i := range n {
|
|
out[i] = blob[i*size : (i+1)*size]
|
|
}
|
|
return out
|
|
}
|