Files
zeekayandHanzo Dev 974d0d5422 rename: drop the upstream protocol name from the tree
The module is luxfi/bft and that is now the only name it answers to.

- simplex/ -> engine/. Upstream's module is named for the protocol, so its root
  package and its engine package shared that name; under luxfi/bft the engine
  package would have collided with the module's own root package. It is the
  consensus engine, so engine.Epoch and engine.NewEpoch read without stutter.
- Identifiers keep the qualifier where it carries meaning and only change the
  word: a block produced by this protocol as against a pre-existing chain's block
  is a real distinction, so makeNonSimplexBlock became makeNonBFTBlock,
  lastNonSimplexInnerBlockHeight became lastNonBFTInnerBlockHeight,
  newSnowToSimplexChain became newSnowToBFTChain. Dropping the qualifier outright
  would have left names that assert nothing.
- The canoto-tagged fields of StateMachineMetadata lose the prefix entirely:
  EpochInfo, ProtocolMetadata, Blacklist. The wire format is unchanged — canoto
  keys off the tag numbers, which still read 1,2,3,4,5, and the generated
  .canoto.go files were renamed in step with the source. The msm fuzz and
  encoding round-trip tests cover this.
- Prose cites the protocol paper by reference number instead of by name, so the
  origin stays findable without the name. The HyperSDK sentence still says
  Avalanche because that sentence is about Avalanche's VM and is true.

Also here, from review of the logger port:
- monitor_test's Fatal panics instead of returning. It reported a state the
  caller had declared impossible and then carried on regardless, which made it
  the one Fatal in the tree that was weaker than the original.
- TestInstanceNonValidatorBootstraps asserts the property it is named for — that
  the node was still a non-validator when the bootstrap finished — rather than
  counting how many times a debug line was emitted. The count tracks how often
  onEpochChange happened to observe the branch, so transitions can coalesce; it
  measured 2 here where it had been reliably 3, and a logger-cost explanation was
  tested and refuted. The count never checked the property either way: a node
  that wrongly became a validator mid-bootstrap would still have counted 3.
- SilenceExceptKeywords documents last-call-wins, and Trace/Verbo now reaching
  hooks is recorded where a future hook author will see it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 05:16:49 -07:00

215 lines
6.6 KiB
Go

// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package common
import (
"bytes"
"context"
"fmt"
"slices"
"github.com/luxfi/log"
)
type Logger interface {
// Log that a fatal error has occurred. The program should likely exit soon
// after this is called
Fatal(msg string, fields ...log.Field)
// Log that an error has occurred. The program should be able to recover
// from this error
Error(msg string, fields ...log.Field)
// Log that an event has occurred that may indicate a future error or
// vulnerability
Warn(msg string, fields ...log.Field)
// Log an event that may be useful for a user to see to measure the progress
// of the protocol
Info(msg string, fields ...log.Field)
// Log an event that may be useful for understanding the order of the
// execution of the protocol
Trace(msg string, fields ...log.Field)
// Log an event that may be useful for a programmer to see when debuging the
// execution of the protocol
Debug(msg string, fields ...log.Field)
// Log extremely detailed events that can be useful for inspecting every
// aspect of the program
Verbo(msg string, fields ...log.Field)
}
type BlockBuilder interface {
// BuildBlock blocks until some transactions are available to be batched into a block,
// in which case a block and true are returned.
// When the given context is cancelled by the caller, returns false.
// The given metadata and blacklist are encoded into the built block.
BuildBlock(ctx context.Context, metadata ProtocolMetadata, blacklist Blacklist) (VerifiedBlock, bool)
// WaitForPendingBlock returns when either the given context is cancelled,
// or when the application signals that a block should be built.
WaitForPendingBlock(ctx context.Context)
}
var ErrBlockNotFound = fmt.Errorf("block not found")
type Storage interface {
NumBlocks() uint64
// Retrieve returns the block and finalization at [seq].
// If [seq] the block cannot be found, returns ErrBlockNotFound.
Retrieve(seq uint64) (VerifiedBlock, Finalization, error)
Index(ctx context.Context, block VerifiedBlock, certificate Finalization) error
}
type Communication interface {
// Validators returns all nodes that participate in consensus in the epoch.
Validators() Nodes
// Send sends a message to the given destination node
Send(msg *Message, destination NodeID)
// Broadcast broadcasts the given message to all nodes.
// Does not send it to yourself.
Broadcast(msg *Message)
}
type Signer interface {
Sign(message []byte) ([]byte, error)
}
type SignatureVerifier interface {
VerifySignature(message []byte, signature []byte, publicKey []byte) error
}
type WriteAheadLog interface {
Append([]byte) error
ReadAll() ([][]byte, error)
Close() error
}
type Block interface {
// BlockHeader encodes a succinct and collision-free representation of a block.
BlockHeader() BlockHeader
Blacklist() Blacklist
// Verify verifies the block by speculatively executing it on top of its ancestor.
Verify(ctx context.Context) (VerifiedBlock, error)
// non nil only for sealing blocks & first ever bft block
SealingBlockInfo() *SealingBlockInfo
}
// SealingBlockInfo defines information that is derived from a sealing block,
// namely the validator set and the hash of the previous sealing block if applicable.
type SealingBlockInfo struct {
// ValidatorSet of the new epoch
ValidatorSet Nodes
// PrevSealingBlockHash is the hash of the previous sealing block
PrevSealingBlockHash Digest
}
func (s *SealingBlockInfo) String() string {
return fmt.Sprintf("Info: Num Validators %d, PrevHash %s", len(s.ValidatorSet), s.PrevSealingBlockHash)
}
type VerifiedBlock interface {
// BlockHeader encodes a succinct and collision-free representation of a block.
BlockHeader() BlockHeader
Blacklist() Blacklist
// Bytes returns a byte encoding of the block
Bytes() []byte
// Size returns the number of bytes in the encoding of the block
Size() int
// SealingBlockInfo returns a non-nil value for a block that is not a sealing block and that is not the first ever bft block.
SealingBlockInfo() *SealingBlockInfo
}
// BlockDeserializer deserializes blocks according to formatting
// enforced by the application.
type BlockDeserializer interface {
// DeserializeBlock deserializes the given bytes and initializes a VerifiedBlock.
// Returns an error upon failure.
DeserializeBlock(ctx context.Context, bytes []byte) (Block, error)
}
// Signature encodes a signature and the node that signed it, without the message it was signed on.
type Signature struct {
// Signer is the NodeID of the creator of the signature.
Signer NodeID
// Value is the byte representation of the signature.
Value []byte
}
// QCDeserializer deserializes QuorumCertificates according to formatting
type QCDeserializer interface {
// DeserializeQuorumCertificate parses the given bytes and initializes a QuorumCertificate.
// Returns an error upon failure.
DeserializeQuorumCertificate(bytes []byte) (QuorumCertificate, error)
}
// SignatureAggregator aggregates signatures into a QuorumCertificate
type SignatureAggregator interface {
// Aggregate aggregates several signatures into a QuorumCertificate
Aggregate([]Signature) (QuorumCertificate, error)
// AppendSignatures appends signatures to an existing signature.
// If the existing signature is empty, it just aggregates the given signatures.
AppendSignatures([]byte, ...[]byte) ([]byte, error)
// IsQuorum returns true if the given signers constitute a quorum.
// In the case of PoA, this means at least a quorum of the nodes are given.
// In the case of PoS, this means at least two thirds of the st.
IsQuorum([]NodeID) bool
}
// Nodes is a list of Node elements.
type Nodes []Node
// NodeIDs returns the NodeIDs of the nodes in the Nodes.
func (nws Nodes) NodeIDs() []NodeID {
nodes := make([]NodeID, len(nws))
for i, nw := range nws {
nodes[i] = nw.Id
}
return nodes
}
func (nws Nodes) Contains(nodeID NodeID) bool {
for _, nw := range nws {
if bytes.Equal(nw.Id, nodeID) {
return true
}
}
return false
}
// Node is a struct that pairs a node ID with its weight and public key.
type Node struct {
Id NodeID
Weight uint64
PK []byte
}
// SortNodes sorts the nodes in place by their byte representations.
func SortNodes(nodes Nodes) {
slices.SortFunc(nodes, func(a, b Node) int {
return bytes.Compare(a.Id[:], b.Id[:])
})
}
func Quorum(n int) int {
f := (n - 1) / 3
// Obtained from the equation:
// Quorum * 2 = N + F + 1
return (n+f)/2 + 1
}
func F(n int) int {
return (n - 1) / 3
}
// SignatureAggregatorCreator creates a SignatureAggregator from a list of nodes and their weights.
type SignatureAggregatorCreator func([]Node) SignatureAggregator