mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
459 lines
12 KiB
Go
459 lines
12 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||
// See the file LICENSE for licensing terms.
|
||
|
||
package message
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"errors"
|
||
"fmt"
|
||
|
||
"github.com/luxfi/consensus/core/router"
|
||
"github.com/luxfi/math/set"
|
||
"github.com/luxfi/node/proto/p2p"
|
||
)
|
||
|
||
// Op is an opcode
|
||
type Op byte
|
||
|
||
// Types of messages that may be sent between nodes
|
||
// Note: If you add a new parseable Op below, you must add it to either
|
||
// [UnrequestedOps] or [FailedToResponseOps].
|
||
const (
|
||
// Handshake:
|
||
PingOp Op = iota
|
||
PongOp
|
||
HandshakeOp
|
||
GetPeerListOp
|
||
PeerListOp
|
||
// State sync:
|
||
GetStateSummaryFrontierOp
|
||
GetStateSummaryFrontierFailedOp
|
||
StateSummaryFrontierOp
|
||
GetAcceptedStateSummaryOp
|
||
GetAcceptedStateSummaryFailedOp
|
||
AcceptedStateSummaryOp
|
||
// Bootstrapping:
|
||
GetAcceptedFrontierOp
|
||
GetAcceptedFrontierFailedOp
|
||
AcceptedFrontierOp
|
||
GetAcceptedOp
|
||
GetAcceptedFailedOp
|
||
AcceptedOp
|
||
GetAncestorsOp
|
||
GetAncestorsFailedOp
|
||
AncestorsOp
|
||
// Consensus:
|
||
GetOp
|
||
GetFailedOp
|
||
PutOp
|
||
PushQueryOp
|
||
PullQueryOp
|
||
QueryFailedOp
|
||
QbitOp // Authenticated preference signal (formerly ChitsOp)
|
||
// Application:
|
||
RequestOp
|
||
ErrorOp
|
||
ResponseOp
|
||
GossipOp
|
||
// Internal:
|
||
ConnectedOp
|
||
DisconnectedOp
|
||
NotifyOp
|
||
GossipRequestOp
|
||
// BFT
|
||
BFTOp
|
||
)
|
||
|
||
var (
|
||
// UnrequestedOps are operations that are expected to be seen without having
|
||
// requested them. For example, a peer may receive a Get request for a block
|
||
// without having sent a message previously.
|
||
UnrequestedOps = set.Of(
|
||
GetAcceptedFrontierOp,
|
||
GetAcceptedOp,
|
||
GetAncestorsOp,
|
||
GetOp,
|
||
PushQueryOp,
|
||
PullQueryOp,
|
||
RequestOp,
|
||
GossipOp,
|
||
GetStateSummaryFrontierOp,
|
||
GetAcceptedStateSummaryOp,
|
||
BFTOp,
|
||
)
|
||
// FailedToResponseOps maps response failure messages to their successful
|
||
// counterparts.
|
||
FailedToResponseOps = map[Op]Op{
|
||
GetStateSummaryFrontierFailedOp: StateSummaryFrontierOp,
|
||
GetAcceptedStateSummaryFailedOp: AcceptedStateSummaryOp,
|
||
GetAcceptedFrontierFailedOp: AcceptedFrontierOp,
|
||
GetAcceptedFailedOp: AcceptedOp,
|
||
GetAncestorsFailedOp: AncestorsOp,
|
||
GetFailedOp: PutOp,
|
||
QueryFailedOp: QbitOp,
|
||
ErrorOp: ResponseOp,
|
||
}
|
||
|
||
errUnknownMessageType = errors.New("unknown message type")
|
||
)
|
||
|
||
func (op Op) String() string {
|
||
switch op {
|
||
// Handshake
|
||
case PingOp:
|
||
return "ping"
|
||
case PongOp:
|
||
return "pong"
|
||
case HandshakeOp:
|
||
return "handshake"
|
||
case GetPeerListOp:
|
||
return "get_peerlist"
|
||
case PeerListOp:
|
||
return "peerlist"
|
||
// State sync
|
||
case GetStateSummaryFrontierOp:
|
||
return "get_state_summary_frontier"
|
||
case GetStateSummaryFrontierFailedOp:
|
||
return "get_state_summary_frontier_failed"
|
||
case StateSummaryFrontierOp:
|
||
return "state_summary_frontier"
|
||
case GetAcceptedStateSummaryOp:
|
||
return "get_accepted_state_summary"
|
||
case GetAcceptedStateSummaryFailedOp:
|
||
return "get_accepted_state_summary_failed"
|
||
case AcceptedStateSummaryOp:
|
||
return "accepted_state_summary"
|
||
// Bootstrapping
|
||
case GetAcceptedFrontierOp:
|
||
return "get_accepted_frontier"
|
||
case GetAcceptedFrontierFailedOp:
|
||
return "get_accepted_frontier_failed"
|
||
case AcceptedFrontierOp:
|
||
return "accepted_frontier"
|
||
case GetAcceptedOp:
|
||
return "get_accepted"
|
||
case GetAcceptedFailedOp:
|
||
return "get_accepted_failed"
|
||
case AcceptedOp:
|
||
return "accepted"
|
||
case GetAncestorsOp:
|
||
return "get_ancestors"
|
||
case GetAncestorsFailedOp:
|
||
return "get_ancestors_failed"
|
||
case AncestorsOp:
|
||
return "ancestors"
|
||
// Consensus
|
||
case GetOp:
|
||
return "get"
|
||
case GetFailedOp:
|
||
return "get_failed"
|
||
case PutOp:
|
||
return "put"
|
||
case PushQueryOp:
|
||
return "push_query"
|
||
case PullQueryOp:
|
||
return "pull_query"
|
||
case QueryFailedOp:
|
||
return "query_failed"
|
||
case QbitOp:
|
||
return "qbit"
|
||
// Application
|
||
case RequestOp:
|
||
return "app_request"
|
||
case ErrorOp:
|
||
return "app_error"
|
||
case ResponseOp:
|
||
return "app_response"
|
||
case GossipOp:
|
||
return "app_gossip"
|
||
// Internal
|
||
case ConnectedOp:
|
||
return "connected"
|
||
case DisconnectedOp:
|
||
return "disconnected"
|
||
case NotifyOp:
|
||
return "notify"
|
||
case GossipRequestOp:
|
||
return "gossip_request"
|
||
// BFT
|
||
case BFTOp:
|
||
return "bft"
|
||
default:
|
||
return "unknown"
|
||
}
|
||
}
|
||
|
||
func Unwrap(m *p2p.Message) (fmt.Stringer, error) {
|
||
switch msg := m.GetMessage().(type) {
|
||
// Handshake:
|
||
case *p2p.Message_Ping:
|
||
return msg.Ping, nil
|
||
case *p2p.Message_Pong:
|
||
return msg.Pong, nil
|
||
case *p2p.Message_Handshake:
|
||
return msg.Handshake, nil
|
||
case *p2p.Message_GetPeerList:
|
||
return msg.GetPeerList, nil
|
||
case *p2p.Message_PeerList_:
|
||
return msg.PeerList_, nil
|
||
// State sync:
|
||
case *p2p.Message_GetStateSummaryFrontier:
|
||
return msg.GetStateSummaryFrontier, nil
|
||
case *p2p.Message_StateSummaryFrontier_:
|
||
return msg.StateSummaryFrontier_, nil
|
||
case *p2p.Message_GetAcceptedStateSummary:
|
||
return msg.GetAcceptedStateSummary, nil
|
||
case *p2p.Message_AcceptedStateSummary_:
|
||
return msg.AcceptedStateSummary_, nil
|
||
// Bootstrapping:
|
||
case *p2p.Message_GetAcceptedFrontier:
|
||
return msg.GetAcceptedFrontier, nil
|
||
case *p2p.Message_AcceptedFrontier_:
|
||
return msg.AcceptedFrontier_, nil
|
||
case *p2p.Message_GetAccepted:
|
||
return msg.GetAccepted, nil
|
||
case *p2p.Message_Accepted_:
|
||
return msg.Accepted_, nil
|
||
case *p2p.Message_GetAncestors:
|
||
return msg.GetAncestors, nil
|
||
case *p2p.Message_Ancestors_:
|
||
return msg.Ancestors_, nil
|
||
// Consensus:
|
||
case *p2p.Message_Get:
|
||
return msg.Get, nil
|
||
case *p2p.Message_Put:
|
||
return msg.Put, nil
|
||
case *p2p.Message_PushQuery:
|
||
return msg.PushQuery, nil
|
||
case *p2p.Message_PullQuery:
|
||
return msg.PullQuery, nil
|
||
case *p2p.Message_Chits:
|
||
return msg.Chits, nil
|
||
// Application:
|
||
case *p2p.Message_Request:
|
||
return msg.Request, nil
|
||
case *p2p.Message_Response:
|
||
return msg.Response, nil
|
||
case *p2p.Message_Error:
|
||
return msg.Error, nil
|
||
case *p2p.Message_Gossip:
|
||
return msg.Gossip, nil
|
||
// BFT
|
||
case *p2p.Message_BFT:
|
||
return extractBFT(msg), nil
|
||
default:
|
||
return nil, fmt.Errorf("%w: %T", errUnknownMessageType, msg)
|
||
}
|
||
}
|
||
|
||
// ToConsensusOp maps a wire message Op to the consensus router op the chain
|
||
// router (node/chain_router.go) dispatches on, returning false for ops that are
|
||
// not consensus-routed (the router drops those as "unhandled message op"). The
|
||
// returned values ARE the router constants, so they cannot drift in value; the
|
||
// mapping must also stay a bijection onto the router op space [0, router.NumOps)
|
||
// — proven by message/ops_test.go. A missing case silently drops that op: that
|
||
// is the α-of-K finality wedge, where GossipOp had no case and every vote
|
||
// vanished before reaching blockHandler.Gossip -> engine.HandleIncomingVote.
|
||
func ToConsensusOp(op Op) (byte, bool) {
|
||
switch op {
|
||
case GetAcceptedFrontierOp:
|
||
return byte(router.GetAcceptedFrontier), true
|
||
case AcceptedFrontierOp:
|
||
return byte(router.AcceptedFrontier), true
|
||
case GetAcceptedOp:
|
||
return byte(router.GetAccepted), true
|
||
case AcceptedOp:
|
||
return byte(router.Accepted), true
|
||
case GetOp:
|
||
return byte(router.Get), true
|
||
case PutOp:
|
||
return byte(router.Put), true
|
||
case PushQueryOp:
|
||
return byte(router.PushQuery), true
|
||
case PullQueryOp:
|
||
return byte(router.PullQuery), true
|
||
case QbitOp:
|
||
return byte(router.Vote), true // votes ride the Qbit wire op
|
||
case GetAncestorsOp:
|
||
return byte(router.GetContext), true // wire op is still GetAncestors
|
||
case AncestorsOp:
|
||
return byte(router.Context), true // wire op is still Ancestors
|
||
case GossipOp:
|
||
return byte(router.Gossip), true // α-of-K vote/cert transport
|
||
case GetStateSummaryFrontierOp:
|
||
return byte(router.GetStateSummaryFrontier), true
|
||
case StateSummaryFrontierOp:
|
||
return byte(router.StateSummaryFrontier), true
|
||
case GetAcceptedStateSummaryOp:
|
||
return byte(router.GetAcceptedStateSummary), true
|
||
case AcceptedStateSummaryOp:
|
||
return byte(router.AcceptedStateSummary), true
|
||
case RequestOp:
|
||
return byte(router.AppRequest), true // AppRequest — EVM trie-leaf/code fetch
|
||
case ResponseOp:
|
||
return byte(router.AppResponse), true // AppResponse
|
||
case ErrorOp:
|
||
return byte(router.AppError), true // AppError
|
||
default:
|
||
return 0, false
|
||
}
|
||
}
|
||
|
||
// GetContainerBytes extracts the container/body bytes from various message types
|
||
func GetContainerBytes(msg fmt.Stringer) []byte {
|
||
switch m := msg.(type) {
|
||
case *p2p.Put:
|
||
return m.Container
|
||
case *p2p.PushQuery:
|
||
return m.Container
|
||
case *p2p.Ancestors:
|
||
// Encode all containers with length prefix so handlers can decode
|
||
// multiple blocks from a single byte slice.
|
||
if len(m.Containers) == 0 {
|
||
return nil
|
||
}
|
||
totalLen := 0
|
||
for _, container := range m.Containers {
|
||
totalLen += 4 + len(container)
|
||
}
|
||
encoded := make([]byte, 0, totalLen)
|
||
for _, container := range m.Containers {
|
||
var lenBuf [4]byte
|
||
binary.BigEndian.PutUint32(lenBuf[:], uint32(len(container)))
|
||
encoded = append(encoded, lenBuf[:]...)
|
||
encoded = append(encoded, container...)
|
||
}
|
||
return encoded
|
||
case *p2p.Gossip:
|
||
return m.AppBytes
|
||
case *p2p.Request:
|
||
return m.AppBytes
|
||
case *p2p.Response:
|
||
return m.AppBytes
|
||
// State sync. The frontier request carries no body — the requester is asking
|
||
// what the peer holds, not about anything in particular — so it flattens to
|
||
// nothing and the handler answers from the chain alone.
|
||
case *p2p.GetStateSummaryFrontier:
|
||
return nil
|
||
case *p2p.StateSummaryFrontier:
|
||
return m.Summary
|
||
case *p2p.GetAcceptedStateSummary:
|
||
// The heights asked about, fixed-width and in order. Fixed-width because
|
||
// the reader recovers the count by division and cannot disagree with the
|
||
// writer about where one ends.
|
||
if len(m.Heights) == 0 {
|
||
return nil
|
||
}
|
||
out := make([]byte, 0, len(m.Heights)*8)
|
||
var h [8]byte
|
||
for _, height := range m.Heights {
|
||
binary.BigEndian.PutUint64(h[:], height)
|
||
out = append(out, h[:]...)
|
||
}
|
||
return out
|
||
case *p2p.AcceptedStateSummary:
|
||
// The summaries the peer holds, named by id. Same shape as GetAccepted
|
||
// below: fixed 32-byte ids, concatenated.
|
||
if len(m.SummaryIds) == 0 {
|
||
return nil
|
||
}
|
||
out := make([]byte, 0, len(m.SummaryIds)*32)
|
||
for _, id := range m.SummaryIds {
|
||
out = append(out, id...)
|
||
}
|
||
return out
|
||
// Request messages with container IDs for P-Chain sync
|
||
case *p2p.GetAccepted:
|
||
containerIDs := m.GetContainerIds()
|
||
if len(containerIDs) == 0 {
|
||
return nil
|
||
}
|
||
result := make([]byte, 0, len(containerIDs)*32)
|
||
for _, id := range containerIDs {
|
||
result = append(result, id...)
|
||
}
|
||
return result
|
||
case *p2p.Get:
|
||
return m.GetContainerId()
|
||
case *p2p.GetAncestors:
|
||
return m.GetContainerId()
|
||
case *p2p.PullQuery:
|
||
return m.GetContainerId()
|
||
case *p2p.Chits:
|
||
// For Qbit messages (formerly Chits), return the PreferredId (the block being voted for)
|
||
return m.GetPreferredId()
|
||
case *p2p.AcceptedFrontier:
|
||
return m.GetContainerId()
|
||
case *p2p.Accepted:
|
||
containerIDs := m.GetContainerIds()
|
||
if len(containerIDs) == 0 {
|
||
return nil
|
||
}
|
||
result := make([]byte, 0, len(containerIDs)*32)
|
||
for _, id := range containerIDs {
|
||
result = append(result, id...)
|
||
}
|
||
return result
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func ToOp(m *p2p.Message) (Op, error) {
|
||
switch msg := m.GetMessage().(type) {
|
||
case *p2p.Message_Ping:
|
||
return PingOp, nil
|
||
case *p2p.Message_Pong:
|
||
return PongOp, nil
|
||
case *p2p.Message_Handshake:
|
||
return HandshakeOp, nil
|
||
case *p2p.Message_GetPeerList:
|
||
return GetPeerListOp, nil
|
||
case *p2p.Message_PeerList_:
|
||
return PeerListOp, nil
|
||
case *p2p.Message_GetStateSummaryFrontier:
|
||
return GetStateSummaryFrontierOp, nil
|
||
case *p2p.Message_StateSummaryFrontier_:
|
||
return StateSummaryFrontierOp, nil
|
||
case *p2p.Message_GetAcceptedStateSummary:
|
||
return GetAcceptedStateSummaryOp, nil
|
||
case *p2p.Message_AcceptedStateSummary_:
|
||
return AcceptedStateSummaryOp, nil
|
||
case *p2p.Message_GetAcceptedFrontier:
|
||
return GetAcceptedFrontierOp, nil
|
||
case *p2p.Message_AcceptedFrontier_:
|
||
return AcceptedFrontierOp, nil
|
||
case *p2p.Message_GetAccepted:
|
||
return GetAcceptedOp, nil
|
||
case *p2p.Message_Accepted_:
|
||
return AcceptedOp, nil
|
||
case *p2p.Message_GetAncestors:
|
||
return GetAncestorsOp, nil
|
||
case *p2p.Message_Ancestors_:
|
||
return AncestorsOp, nil
|
||
case *p2p.Message_Get:
|
||
return GetOp, nil
|
||
case *p2p.Message_Put:
|
||
return PutOp, nil
|
||
case *p2p.Message_PushQuery:
|
||
return PushQueryOp, nil
|
||
case *p2p.Message_PullQuery:
|
||
return PullQueryOp, nil
|
||
case *p2p.Message_Chits:
|
||
return QbitOp, nil
|
||
case *p2p.Message_Request:
|
||
return RequestOp, nil
|
||
case *p2p.Message_Response:
|
||
return ResponseOp, nil
|
||
case *p2p.Message_Error:
|
||
return ErrorOp, nil
|
||
case *p2p.Message_Gossip:
|
||
return GossipOp, nil
|
||
case *p2p.Message_BFT:
|
||
return BFTOp, nil
|
||
default:
|
||
return 0, fmt.Errorf("%w: %T", errUnknownMessageType, msg)
|
||
}
|
||
}
|