Files
consensus/engine/dag/engine.go
T
zeekay 79eb047505 feat: unify Python SDK with MCP mesh consensus
Major changes:
- Merge pkg/wire Python types into pkg/python/lux_consensus
- Add MCP mesh consensus module (from hanzo-consensus)
- Add types.py with Candidate, Vote, Certificate for wire protocol
- Bridge functions for AI consensus -> blockchain interop
- Keep pkg/wire as Go-only wire protocol types

Python SDK now provides:
- lux_consensus: Core chain/block/vote types
- lux_consensus.types: Wire protocol (Candidate, WireVote, WireCertificate)
- lux_consensus.mcp: MCP mesh for agent-to-agent consensus

Also includes engine improvements:
- Chain syncer with persistence
- PQ crypto enhancements
- External sequencer finality example
- Protocol field/horizon/prism updates

All tests passing.
2026-01-13 09:49:03 -08:00

261 lines
6.1 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dag
import (
"context"
"encoding/binary"
"fmt"
"sync"
"github.com/luxfi/consensus/config"
"github.com/luxfi/ids"
)
// Transaction represents a DAG transaction
type Transaction interface {
ID() ids.ID
Parent() ids.ID
Height() uint64
Bytes() []byte
Verify(context.Context) error
Accept(context.Context) error
Reject(context.Context) error
}
// Engine defines the DAG consensus engine interface
type Engine interface {
// GetVtx gets a vertex by ID
GetVtx(context.Context, ids.ID) (Transaction, error)
// BuildVtx builds a new vertex
BuildVtx(context.Context) (Transaction, error)
// ParseVtx parses a vertex from bytes
ParseVtx(context.Context, []byte) (Transaction, error)
// Start starts the engine
Start(context.Context, uint32) error
// Shutdown shuts down the engine
Shutdown(context.Context) error
}
// dagEngine implements real DAG consensus using Lux protocols (Photon → Wave → Prism)
type dagEngine struct {
mu sync.RWMutex
consensus *DAGConsensus
params config.Parameters
bootstrapped bool
ctx context.Context
cancel context.CancelFunc
// Vertex builder
pendingData [][]byte
}
// New creates a new DAG engine with real Lux consensus
func New() Engine {
return NewWithParams(config.DefaultParams())
}
// NewWithParams creates an engine with specific parameters
func NewWithParams(params config.Parameters) Engine {
return &dagEngine{
consensus: NewDAGConsensus(params.K, params.AlphaPreference, int(params.Beta)),
params: params,
bootstrapped: false,
pendingData: make([][]byte, 0),
}
}
// GetVtx gets a vertex by ID
func (e *dagEngine) GetVtx(ctx context.Context, id ids.ID) (Transaction, error) {
e.mu.RLock()
defer e.mu.RUnlock()
vertex, exists := e.consensus.GetVertex(id)
if !exists {
return nil, nil
}
return vertex, nil
}
// BuildVtx builds a new vertex from pending data
func (e *dagEngine) BuildVtx(ctx context.Context) (Transaction, error) {
e.mu.Lock()
defer e.mu.Unlock()
if len(e.pendingData) == 0 {
return nil, nil
}
// Get frontier vertices as parents
frontier := e.consensus.Frontier()
if len(frontier) == 0 {
frontier = []ids.ID{ids.Empty}
}
// Create new vertex ID
vertexID := ids.GenerateTestID()
// Build vertex with first pending data
data := e.pendingData[0]
e.pendingData = e.pendingData[1:]
vertex := NewVertex(
vertexID,
frontier,
0, // Height calculation would be based on parents
0, // Timestamp
data,
)
// Add to consensus
if err := e.consensus.AddVertex(ctx, vertex); err != nil {
return nil, fmt.Errorf("failed to add vertex: %w", err)
}
return vertex, nil
}
// ParseVtx parses a vertex from bytes.
// Format: [32-byte ID][parent count][parents...][8-byte height][8-byte timestamp][data...]
// Returns nil if the data is not a valid vertex encoding.
func (e *dagEngine) ParseVtx(ctx context.Context, b []byte) (Transaction, error) {
if len(b) < 42 { // 32 + 1 + 8 + 1 minimum
return nil, nil
}
// Parse ID - must not be all zeros
var id ids.ID
copy(id[:], b[:32])
if id == ids.Empty {
return nil, nil // Invalid: zero ID indicates invalid data
}
// Parse parent count - sanity check
parentCount := int(b[32])
if parentCount > 128 { // Unreasonable number of parents
return nil, nil
}
offset := 33
// Calculate expected minimum size with parents
expectedMinSize := 33 + (parentCount * 32) + 16
if len(b) < expectedMinSize {
return nil, nil // Not enough data for declared parent count
}
// Parse parents
parents := make([]ids.ID, parentCount)
for i := 0; i < parentCount; i++ {
if offset+32 > len(b) {
return nil, nil
}
copy(parents[i][:], b[offset:offset+32])
offset += 32
}
// Parse height and timestamp
if offset+16 > len(b) {
return nil, nil
}
height := binary.BigEndian.Uint64(b[offset : offset+8])
timestamp := binary.BigEndian.Uint64(b[offset+8 : offset+16])
offset += 16
// Remaining bytes are data
var data []byte
if offset < len(b) {
data = b[offset:]
}
return NewVertex(id, parents, height, int64(timestamp), data), nil
}
// Start starts the engine
func (e *dagEngine) Start(ctx context.Context, requestID uint32) error {
e.mu.Lock()
defer e.mu.Unlock()
e.ctx, e.cancel = context.WithCancel(ctx)
e.bootstrapped = true
return nil
}
// Shutdown shuts down the engine
func (e *dagEngine) Shutdown(ctx context.Context) error {
e.mu.Lock()
defer e.mu.Unlock()
if e.cancel != nil {
e.cancel()
}
e.bootstrapped = false
return nil
}
// Stop stops the engine (alias for Shutdown for interface compatibility)
func (e *dagEngine) Stop(ctx context.Context) error {
return e.Shutdown(ctx)
}
// HealthCheck performs a health check
func (e *dagEngine) HealthCheck(ctx context.Context) (interface{}, error) {
e.mu.RLock()
defer e.mu.RUnlock()
stats := e.consensus.Stats()
stats["bootstrapped"] = e.bootstrapped
stats["k"] = e.params.K
stats["alpha"] = e.params.AlphaPreference
stats["beta"] = e.params.Beta
return stats, nil
}
// IsBootstrapped returns whether the engine is bootstrapped
func (e *dagEngine) IsBootstrapped() bool {
e.mu.RLock()
defer e.mu.RUnlock()
return e.bootstrapped
}
// AddVertex adds a vertex to consensus
func (e *dagEngine) AddVertex(ctx context.Context, vertex *Vertex) error {
return e.consensus.AddVertex(ctx, vertex)
}
// ProcessVote processes a vote for a vertex
func (e *dagEngine) ProcessVote(ctx context.Context, vertexID ids.ID, accept bool) error {
return e.consensus.ProcessVote(ctx, vertexID, accept)
}
// Poll conducts a consensus poll
func (e *dagEngine) Poll(ctx context.Context, responses map[ids.ID]int) error {
return e.consensus.Poll(ctx, responses)
}
// IsAccepted checks if a vertex is accepted
func (e *dagEngine) IsAccepted(vertexID ids.ID) bool {
return e.consensus.IsAccepted(vertexID)
}
// Preference returns the current preferred vertex
func (e *dagEngine) Preference() ids.ID {
return e.consensus.Preference()
}
// QueueData queues data for the next vertex
func (e *dagEngine) QueueData(data []byte) {
e.mu.Lock()
defer e.mu.Unlock()
e.pendingData = append(e.pendingData, data)
}