mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Bootstrap opens by asking each beacon which block it holds, and an answer naming no block is not counted as a responder -- it neither backs a tip nor raises the response floor (tally, node chains/bootstrap_trust.go). The VM derived its state lazily from the signer config and never wrote a block, so last-accepted stayed empty, every node answered with nothing, every reply was dropped, and all five waited on each other for as long as the chain ran: waiting for the beacon quorum: 0 of 5 beacons responded, need 3 State you can execute against is not the same as a block you can name. Initialize now writes height 0 when the chain has none. The block is a constant -- fixed timestamp, empty parent, no transactions -- so every node computes the same id alone; wall-clock time would give each node its own genesis and make this a fork. It commits, or the tip lives in the version layer only and the next start names nothing again. Tests pin the id rather than compare two local VMs: the wire carries Unix seconds, so two VMs seeded in the same second agree even with a wall-clock timestamp, and that assertion would pass for a build that forks in production. Co-authored-by: Hanzo Dev <[email protected]>
792 lines
24 KiB
Go
792 lines
24 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quantumvm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gorilla/rpc/v2"
|
|
"github.com/luxfi/chains/quantumvm/config"
|
|
"github.com/luxfi/chains/quantumvm/quantum"
|
|
consensuschain "github.com/luxfi/consensus/engine/chain"
|
|
"github.com/luxfi/consensus/protocol/quasar"
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/database/versiondb"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/metric"
|
|
"github.com/luxfi/node/cache"
|
|
"github.com/luxfi/node/utils/json"
|
|
"github.com/luxfi/node/vms/types/fee"
|
|
"github.com/luxfi/timer/mockable"
|
|
"github.com/luxfi/version"
|
|
luxvm "github.com/luxfi/vm"
|
|
"github.com/luxfi/vm/chain"
|
|
)
|
|
|
|
const (
|
|
// Version of the QVM
|
|
Version = "1.0.0"
|
|
|
|
// MaxParallelVerifications is the maximum number of parallel verifications
|
|
MaxParallelVerifications = 100
|
|
|
|
// DefaultBatchSize is the default batch size for parallel processing
|
|
DefaultBatchSize = 10
|
|
)
|
|
|
|
var (
|
|
errNotImplemented = errors.New("not implemented")
|
|
errNoPendingTxs = errors.New("no pending transactions")
|
|
errVMShutdown = errors.New("VM is shutting down")
|
|
errInvalidQuantumStamp = errors.New("invalid quantum stamp")
|
|
errParallelProcessingFailed = errors.New("parallel transaction processing failed")
|
|
|
|
// Compile-time check that *VM satisfies chain.ChainVM (= block.ChainVM)
|
|
// AND the consensus engine's BlockBuilder. Together these prove Q-Chain
|
|
// takes the LINEAR ⅔-stake cert path in the chain manager (buildChain →
|
|
// consensuschain.NewRuntime), not the DAG path. Do NOT add a
|
|
// GetEngine() consensusdag.Engine method: that would route the manager's
|
|
// type switch back to createDAG and bypass the certificate.
|
|
_ chain.ChainVM = (*VM)(nil)
|
|
_ consensuschain.BlockBuilder = (*VM)(nil)
|
|
)
|
|
|
|
// BCLookup provides blockchain alias lookup
|
|
type BCLookup interface {
|
|
Lookup(string) (ids.ID, error)
|
|
PrimaryAlias(ids.ID) (string, error)
|
|
}
|
|
|
|
// SharedMemory provides cross-chain shared memory
|
|
type SharedMemory interface {
|
|
Get(peerChainID ids.ID, keys [][]byte) ([][]byte, error)
|
|
Apply(map[ids.ID]interface{}, ...interface{}) error
|
|
}
|
|
|
|
// VM implements the Q-chain Virtual Machine with quantum features
|
|
type VM struct {
|
|
config.Config
|
|
|
|
// Core components
|
|
// consensusRuntime *runtime.Runtime
|
|
log log.Logger
|
|
db database.Database
|
|
versiondb *versiondb.Database
|
|
blockchainID ids.ID
|
|
ChainAlias string
|
|
NetworkID uint32
|
|
|
|
// Quantum components
|
|
quantumSigner *quantum.QuantumSigner
|
|
quantumCache *cache.LRU[ids.ID, *quantum.QuantumSignature]
|
|
|
|
// Hybrid P/Q consensus bridge (connects P-Chain BLS + Q-Chain Corona)
|
|
// Uses Quasar consensus for dual BLS+Corona threshold signatures
|
|
quasarBridge *QuasarBridge
|
|
|
|
// Consensus and validation
|
|
// validators validators.Manager
|
|
// versionManager consensusversion.Manager
|
|
// consensusEngine consensus.Consensus
|
|
|
|
// Metrics and monitoring
|
|
metrics metric.Registry
|
|
|
|
// State management
|
|
state database.Database
|
|
shuttingDown bool
|
|
shuttingDownMu sync.RWMutex
|
|
|
|
// Transaction processing
|
|
txPool *TransactionPool
|
|
parallelWorkers int
|
|
workerPool *sync.Pool
|
|
|
|
// Clock and timing
|
|
clock mockable.Clock
|
|
|
|
// Network communication
|
|
bcLookup BCLookup
|
|
sharedMemory SharedMemory
|
|
|
|
// HTTP service
|
|
httpServer *http.Server
|
|
rpcServer *rpc.Server
|
|
|
|
// Fee policy gating user-submitted tx admission (IssueTx).
|
|
// FlatPolicy at MinTxFeeFloor on Q-Chain; consensus-internal
|
|
// callers bypass via txPool.AddTransaction.
|
|
feePolicy fee.Policy
|
|
|
|
// Synchronization
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// Initialize initializes the VM. Implements chain.ChainVM.
|
|
func (vm *VM) Initialize(ctx context.Context, init luxvm.Init) error {
|
|
vm.lock.Lock()
|
|
defer vm.lock.Unlock()
|
|
|
|
_ = ctx
|
|
vm.db = init.DB
|
|
if init.Log != nil {
|
|
vm.log = init.Log
|
|
}
|
|
genesisBytes := init.Genesis
|
|
// vm.blockchainID = chainRuntime.ChainID
|
|
// vm.NetworkID = chainRuntime.NetworkID
|
|
|
|
// Initialize logger
|
|
// if vm.log.IsZero() {
|
|
// vm.log = chainRuntime.Log
|
|
// }
|
|
// vm.log.Info("initializing QVM",
|
|
// "version", Version,
|
|
// "chainID", vm.blockchainID,
|
|
// "networkID", vm.NetworkID,
|
|
// )
|
|
|
|
// Initialize quantum components
|
|
vm.quantumSigner = quantum.NewQuantumSigner(
|
|
vm.log,
|
|
vm.Config.QuantumAlgorithmVersion,
|
|
vm.Config.CoronaKeySize,
|
|
vm.Config.QuantumStampWindow,
|
|
vm.Config.QuantumSigCacheSize,
|
|
)
|
|
|
|
// Initialize transaction pool
|
|
vm.txPool = NewTransactionPool(
|
|
vm.Config.MaxParallelTxs,
|
|
vm.Config.ParallelBatchSize,
|
|
vm.log,
|
|
)
|
|
|
|
// Pin fee policy. Q-Chain accepts user-submitted txs through
|
|
// IssueTx so attach the canonical FlatPolicy at MinTxFeeFloor;
|
|
// fee.Validate refuses zero-fee user-facing chains at boot.
|
|
if init.Runtime != nil {
|
|
vm.NetworkID = init.Runtime.NetworkID
|
|
}
|
|
vm.feePolicy = newFeePolicy(vm.NetworkID)
|
|
if err := fee.Validate(vm.feePolicy); err != nil {
|
|
return fmt.Errorf("quantumvm: fee policy: %w", err)
|
|
}
|
|
|
|
// Set up worker pool for parallel processing
|
|
vm.parallelWorkers = vm.Config.MaxParallelTxs
|
|
if vm.parallelWorkers <= 0 {
|
|
vm.parallelWorkers = MaxParallelVerifications
|
|
}
|
|
vm.workerPool = &sync.Pool{
|
|
New: func() interface{} {
|
|
return &TransactionWorker{
|
|
vm: vm,
|
|
quantumSigner: vm.quantumSigner,
|
|
}
|
|
},
|
|
}
|
|
|
|
// Initialize version database
|
|
vm.versiondb = versiondb.New(vm.db)
|
|
|
|
// Metrics are not yet available via chainRuntime; the quantumvm
|
|
// operates without per-chain metrics until runtime exposes them.
|
|
|
|
// Parse genesis if provided
|
|
if len(genesisBytes) > 0 {
|
|
if err := vm.parseGenesis(genesisBytes); err != nil {
|
|
return fmt.Errorf("failed to parse genesis: %w", err)
|
|
}
|
|
}
|
|
|
|
// Initialize state
|
|
vm.state = vm.versiondb
|
|
|
|
// A chain with no block cannot answer the frontier query bootstrap starts
|
|
// with, so write height 0 before anything asks.
|
|
if err := vm.seedGenesis(); err != nil {
|
|
return fmt.Errorf("failed to seed genesis: %w", err)
|
|
}
|
|
|
|
// Set up HTTP handlers
|
|
if err := vm.initializeHTTPHandlers(); err != nil {
|
|
return fmt.Errorf("failed to initialize HTTP handlers: %w", err)
|
|
}
|
|
|
|
// Initialize Quasar hybrid consensus bridge (BLS + Corona)
|
|
quasarCfg := QuasarBridgeConfig{
|
|
ValidatorID: vm.blockchainID.String(),
|
|
Threshold: 0, // Will be set to 2/3+1 based on total nodes
|
|
TotalNodes: 3, // Default 3-node network, can be updated
|
|
Logger: vm.log,
|
|
}
|
|
quasarBridge, err := NewQuasarBridge(quasarCfg, vm.quantumSigner)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize Quasar bridge: %w", err)
|
|
}
|
|
vm.quasarBridge = quasarBridge
|
|
|
|
vm.log.Info("═══════════════════════════════════════════════════════════════════")
|
|
vm.log.Info("║ QVM INITIALIZED with Quasar PQ-BFT Consensus ║")
|
|
vm.log.Info("───────────────────────────────────────────────────────────────────")
|
|
vm.log.Info("║ Quantum Signatures: ML-DSA (NIST PQC)", log.Bool("enabled", vm.Config.QuantumStampEnabled))
|
|
vm.log.Info("║ Corona Threshold: Ring-LWE PQ", log.Bool("enabled", vm.Config.CoronaEnabled))
|
|
vm.log.Info("║ BLS Threshold: Classical fast path", log.Bool("enabled", true))
|
|
vm.log.Info("║ Quasar Hybrid: BLS + Corona dual signing", log.Bool("enabled", true))
|
|
vm.log.Info("║ Parallel TX Processing:", log.Int("maxParallel", vm.Config.MaxParallelTxs))
|
|
vm.log.Info("═══════════════════════════════════════════════════════════════════")
|
|
|
|
return nil
|
|
}
|
|
|
|
// BuildBlock builds a new block with pending transactions. Implements chain.ChainVM.
|
|
func (vm *VM) BuildBlock(ctx context.Context) (chain.Block, error) {
|
|
vm.lock.Lock()
|
|
defer vm.lock.Unlock()
|
|
|
|
// Check if VM is shutting down
|
|
if vm.isShuttingDown() {
|
|
return nil, errVMShutdown
|
|
}
|
|
|
|
// Get pending transactions from pool
|
|
pendingTxs := vm.txPool.GetPendingTransactions(vm.Config.ParallelBatchSize)
|
|
if len(pendingTxs) == 0 {
|
|
return nil, errNoPendingTxs
|
|
}
|
|
|
|
// Process transactions in parallel
|
|
validTxs, err := vm.processTransactionsParallel(pendingTxs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process transactions: %w", err)
|
|
}
|
|
|
|
// Create new block with valid transactions. The id is the content hash of
|
|
// the canonical wire (computeID = sha256(Bytes())); it is derived AFTER the
|
|
// structural fields are set, and the wire does not carry the id, so
|
|
// Bytes()/id are independent of ordering.
|
|
block := &Block{
|
|
timestamp: vm.clock.Time(),
|
|
height: vm.getHeight() + 1,
|
|
parentID: vm.getLastAcceptedID(),
|
|
transactions: validTxs,
|
|
vm: vm,
|
|
}
|
|
block.id = block.computeID()
|
|
|
|
// Sign block with quantum signature if enabled
|
|
if vm.Config.QuantumStampEnabled {
|
|
if err := vm.signBlockWithQuantum(block); err != nil {
|
|
return nil, fmt.Errorf("failed to sign block with quantum stamp: %w", err)
|
|
}
|
|
}
|
|
|
|
vm.log.Debug("built block",
|
|
"blockID", block.ID(),
|
|
"height", block.Height(),
|
|
"txCount", len(validTxs),
|
|
)
|
|
|
|
return block, nil
|
|
}
|
|
|
|
// ParseBlock parses a block from bytes. Implements chain.ChainVM.
|
|
func (vm *VM) ParseBlock(ctx context.Context, blockBytes []byte) (chain.Block, error) {
|
|
vm.lock.RLock()
|
|
defer vm.lock.RUnlock()
|
|
|
|
block, err := vm.parseBlock(blockBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse block: %w", err)
|
|
}
|
|
|
|
// Verify quantum signature if enabled
|
|
if vm.Config.QuantumStampEnabled {
|
|
if err := vm.verifyBlockQuantumSignature(block); err != nil {
|
|
return nil, fmt.Errorf("quantum signature verification failed: %w", err)
|
|
}
|
|
}
|
|
|
|
return block, nil
|
|
}
|
|
|
|
// GetBlock retrieves a block by its ID. Implements chain.ChainVM.
|
|
func (vm *VM) GetBlock(ctx context.Context, blockID ids.ID) (chain.Block, error) {
|
|
vm.lock.RLock()
|
|
defer vm.lock.RUnlock()
|
|
|
|
blockBytes, err := vm.state.Get(blockID[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get block %s: %w", blockID, err)
|
|
}
|
|
|
|
return vm.parseBlock(blockBytes)
|
|
}
|
|
|
|
// SetState sets the VM state. Implements chain.ChainVM.
|
|
func (vm *VM) SetState(ctx context.Context, state uint32) error {
|
|
vm.lock.Lock()
|
|
defer vm.lock.Unlock()
|
|
|
|
// Q-Chain uses quantum state management - log state transitions generically
|
|
vm.log.Info("QVM state transition", "state", state)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the VM
|
|
func (vm *VM) Shutdown(ctx context.Context) error {
|
|
vm.shuttingDownMu.Lock()
|
|
vm.shuttingDown = true
|
|
vm.shuttingDownMu.Unlock()
|
|
|
|
vm.log.Info("shutting down QVM")
|
|
|
|
// Stop HTTP server
|
|
if vm.httpServer != nil {
|
|
if err := vm.httpServer.Shutdown(ctx); err != nil {
|
|
vm.log.Error("failed to shutdown HTTP server", "error", err)
|
|
}
|
|
}
|
|
|
|
// Close transaction pool
|
|
if vm.txPool != nil {
|
|
vm.txPool.Close()
|
|
}
|
|
|
|
// Close database
|
|
if vm.versiondb != nil {
|
|
if err := vm.versiondb.Close(); err != nil {
|
|
vm.log.Error("failed to close versiondb", "error", err)
|
|
}
|
|
}
|
|
|
|
vm.log.Info("QVM shutdown complete")
|
|
return nil
|
|
}
|
|
|
|
// processTransactionsParallel processes transactions in parallel batches
|
|
func (vm *VM) processTransactionsParallel(txs []Transaction) ([]Transaction, error) {
|
|
if len(txs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Determine batch size
|
|
batchSize := vm.Config.ParallelBatchSize
|
|
if batchSize <= 0 {
|
|
batchSize = DefaultBatchSize
|
|
}
|
|
|
|
var validTxs []Transaction
|
|
var mu sync.Mutex
|
|
var wg sync.WaitGroup
|
|
|
|
// Process in batches
|
|
for i := 0; i < len(txs); i += batchSize {
|
|
end := i + batchSize
|
|
if end > len(txs) {
|
|
end = len(txs)
|
|
}
|
|
|
|
batch := txs[i:end]
|
|
wg.Add(1)
|
|
|
|
go func(batch []Transaction) {
|
|
defer wg.Done()
|
|
|
|
// Get worker from pool
|
|
worker := vm.workerPool.Get().(*TransactionWorker)
|
|
defer vm.workerPool.Put(worker)
|
|
|
|
// Process batch
|
|
validBatch, err := worker.ProcessBatch(batch)
|
|
if err != nil {
|
|
vm.log.Error("batch processing failed", "error", err)
|
|
return
|
|
}
|
|
|
|
// Add valid transactions
|
|
mu.Lock()
|
|
validTxs = append(validTxs, validBatch...)
|
|
mu.Unlock()
|
|
}(batch)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
if len(validTxs) == 0 {
|
|
return nil, errParallelProcessingFailed
|
|
}
|
|
|
|
return validTxs, nil
|
|
}
|
|
|
|
// signBlockWithQuantum signs a block with quantum signature using Quasar hybrid consensus
|
|
func (vm *VM) signBlockWithQuantum(block *Block) error {
|
|
ctx := context.Background()
|
|
blockData := block.Bytes()
|
|
|
|
// Use Quasar bridge for dual BLS+Corona threshold signing
|
|
if vm.quasarBridge != nil {
|
|
_, err := vm.quasarBridge.SignBlock(ctx, block.ID(), blockData, block.Height())
|
|
if err != nil {
|
|
vm.log.Warn("Quasar signing failed, falling back to ML-DSA", "error", err)
|
|
} else {
|
|
vm.log.Debug("Block signed with Quasar BLS threshold",
|
|
"blockID", block.ID(),
|
|
"height", block.Height(),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Also sign with ML-DSA for quantum resistance (standalone signature)
|
|
key, err := vm.quantumSigner.GenerateCoronaKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate corona key: %w", err)
|
|
}
|
|
|
|
sig, err := vm.quantumSigner.Sign(blockData, key)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to sign block with ML-DSA: %w", err)
|
|
}
|
|
|
|
block.quantumSignature = sig
|
|
return nil
|
|
}
|
|
|
|
// verifyBlockQuantumSignature verifies a block's quantum signature
|
|
func (vm *VM) verifyBlockQuantumSignature(block *Block) error {
|
|
if block.quantumSignature == nil {
|
|
return errInvalidQuantumStamp
|
|
}
|
|
|
|
blockData := block.Bytes()
|
|
return vm.quantumSigner.Verify(blockData, block.quantumSignature)
|
|
}
|
|
|
|
// parseGenesis parses genesis data
|
|
func (vm *VM) parseGenesis(genesisBytes []byte) error {
|
|
// Genesis parsing for quantumvm is a no-op; initial state is derived
|
|
// from the quantum signer configuration and the empty state trie.
|
|
vm.log.Info("genesis loaded", "size", len(genesisBytes))
|
|
return nil
|
|
}
|
|
|
|
// seedGenesis writes the height-0 block on a chain that has none, so the VM can
|
|
// name a tip the moment it starts.
|
|
//
|
|
// A VM whose last-accepted is empty answers the bootstrap frontier query with no
|
|
// block, and an answer naming no block is not a responder — it neither backs a
|
|
// tip nor counts toward the response floor (see tally in the node's
|
|
// chains/bootstrap_trust.go). Every node then reads every other node as silent,
|
|
// so the beacon floor is unreachable and each waits on the others for as long as
|
|
// the chain runs. State derived lazily from config is enough to EXECUTE against,
|
|
// but consensus starts by asking what block you hold, and "none" is not an
|
|
// answer it can build a quorum from.
|
|
//
|
|
// The block is a constant, so every node computes one id alone: fixed timestamp,
|
|
// height 0, empty parent, no transactions. Wall-clock time here would give each
|
|
// node a different id for the same block and make the repair a fork.
|
|
func (vm *VM) seedGenesis() error {
|
|
if vm.getLastAcceptedID() != ids.Empty {
|
|
return nil
|
|
}
|
|
|
|
block := &Block{
|
|
timestamp: time.Unix(0, 0).UTC(),
|
|
height: 0,
|
|
parentID: ids.Empty,
|
|
vm: vm,
|
|
}
|
|
block.id = block.computeID()
|
|
|
|
if err := vm.state.Put(block.id[:], block.Bytes()); err != nil {
|
|
return fmt.Errorf("store genesis block: %w", err)
|
|
}
|
|
if err := vm.state.Put([]byte("lastAccepted"), block.id[:]); err != nil {
|
|
return fmt.Errorf("set genesis last-accepted: %w", err)
|
|
}
|
|
heightBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(heightBytes, block.height)
|
|
if err := vm.state.Put([]byte("height"), heightBytes); err != nil {
|
|
return fmt.Errorf("set genesis height: %w", err)
|
|
}
|
|
// Commit, or the tip exists only in memory and the next start is back to
|
|
// naming no block.
|
|
if err := vm.versiondb.Commit(); err != nil {
|
|
return fmt.Errorf("commit genesis: %w", err)
|
|
}
|
|
|
|
vm.log.Info("genesis block written", "blockID", block.id, "height", block.height)
|
|
return nil
|
|
}
|
|
|
|
// parseBlock parses a block from its ZAP wire (see wire.go). The transaction set
|
|
// is not reconstructed — the concrete Transaction is an interface with no
|
|
// on-chain deserializer; the raw wire is retained for signature re-verification.
|
|
func (vm *VM) parseBlock(blockBytes []byte) (*Block, error) {
|
|
return parseBlockBytes(vm, blockBytes)
|
|
}
|
|
|
|
// initializeHTTPHandlers sets up HTTP handlers
|
|
func (vm *VM) initializeHTTPHandlers() error {
|
|
vm.rpcServer = rpc.NewServer()
|
|
|
|
// Register QVM service
|
|
service := &Service{vm: vm}
|
|
vm.rpcServer.RegisterCodec(json.NewCodec(), "application/json")
|
|
vm.rpcServer.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8")
|
|
return vm.rpcServer.RegisterService(service, "quantumvm")
|
|
}
|
|
|
|
// isShuttingDown returns true if VM is shutting down
|
|
func (vm *VM) isShuttingDown() bool {
|
|
vm.shuttingDownMu.RLock()
|
|
defer vm.shuttingDownMu.RUnlock()
|
|
return vm.shuttingDown
|
|
}
|
|
|
|
// getHeight returns current blockchain height
|
|
func (vm *VM) getHeight() uint64 {
|
|
heightBytes, err := vm.state.Get([]byte("height"))
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
if len(heightBytes) != 8 {
|
|
return 0
|
|
}
|
|
return binary.BigEndian.Uint64(heightBytes)
|
|
}
|
|
|
|
// getLastAcceptedID returns the last accepted block ID
|
|
func (vm *VM) getLastAcceptedID() ids.ID {
|
|
lastAcceptedBytes, err := vm.state.Get([]byte("lastAccepted"))
|
|
if err != nil {
|
|
return ids.Empty
|
|
}
|
|
if len(lastAcceptedBytes) != 32 {
|
|
return ids.Empty
|
|
}
|
|
var id ids.ID
|
|
copy(id[:], lastAcceptedBytes)
|
|
return id
|
|
}
|
|
|
|
// Version returns the version of the VM
|
|
func (vm *VM) Version(ctx context.Context) (string, error) {
|
|
return Version, nil
|
|
}
|
|
|
|
// Connected notifies the VM that a validator has connected
|
|
func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error {
|
|
vm.log.Debug("node connected", "nodeID", nodeID, "version", nodeVersion)
|
|
return nil
|
|
}
|
|
|
|
// Disconnected notifies the VM that a validator has disconnected
|
|
func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
|
|
vm.log.Debug("node disconnected", "nodeID", nodeID)
|
|
return nil
|
|
}
|
|
|
|
// HealthCheck returns the health status of the VM
|
|
func (vm *VM) HealthCheck(ctx context.Context) (chain.HealthResult, error) {
|
|
vm.lock.RLock()
|
|
defer vm.lock.RUnlock()
|
|
|
|
return chain.HealthResult{
|
|
Healthy: !vm.isShuttingDown(),
|
|
Details: map[string]string{
|
|
"version": Version,
|
|
"quantumEnabled": fmt.Sprintf("%v", vm.Config.QuantumStampEnabled),
|
|
"coronaEnabled": fmt.Sprintf("%v", vm.Config.CoronaEnabled),
|
|
"pendingTxs": fmt.Sprintf("%d", vm.txPool.PendingCount()),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// CreateHandlers returns HTTP handlers for the VM
|
|
func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error) {
|
|
handlers := map[string]http.Handler{
|
|
"/rpc": vm.rpcServer,
|
|
}
|
|
return handlers, nil
|
|
}
|
|
|
|
// CreateStaticHandlers returns static HTTP handlers
|
|
func (vm *VM) CreateStaticHandlers(ctx context.Context) (map[string]http.Handler, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// NewHTTPHandler returns the VM's HTTP handler. Implements chain.ChainVM.
|
|
func (vm *VM) NewHTTPHandler(ctx context.Context) (http.Handler, error) {
|
|
handlers, err := vm.CreateHandlers(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mux := http.NewServeMux()
|
|
for path, h := range handlers {
|
|
if path == "" {
|
|
path = "/"
|
|
}
|
|
mux.Handle(path, h)
|
|
}
|
|
return mux, nil
|
|
}
|
|
|
|
// SetPreference sets the preferred block. Implements chain.ChainVM.
|
|
// Q-Chain uses BLS+Corona threshold finality rather than preference,
|
|
// so this is a no-op until preference-based fork choice is wired in.
|
|
func (vm *VM) SetPreference(ctx context.Context, blockID ids.ID) error {
|
|
return nil
|
|
}
|
|
|
|
// LastAccepted returns the last accepted block ID. Implements chain.ChainVM.
|
|
func (vm *VM) LastAccepted(ctx context.Context) (ids.ID, error) {
|
|
vm.lock.RLock()
|
|
defer vm.lock.RUnlock()
|
|
return vm.getLastAcceptedID(), nil
|
|
}
|
|
|
|
// GetBlockIDAtHeight returns the block ID at the given height. Implements chain.ChainVM.
|
|
// Q-Chain does not yet maintain a height index, so this returns errNotImplemented
|
|
// until indexer integration lands.
|
|
func (vm *VM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error) {
|
|
return ids.Empty, errNotImplemented
|
|
}
|
|
|
|
// WaitForEvent blocks until an event triggers block building. Implements chain.ChainVM.
|
|
// CRITICAL: must block on ctx.Done() to avoid the notification flood loop in
|
|
// node/chains/manager.go (matches the relayvm contract).
|
|
func (vm *VM) WaitForEvent(ctx context.Context) (luxvm.Message, error) {
|
|
<-ctx.Done()
|
|
return luxvm.Message{}, ctx.Err()
|
|
}
|
|
|
|
// GetQuasarBridge returns the Quasar hybrid consensus bridge
|
|
// This provides BLS + Corona dual threshold signatures for PQ finality
|
|
func (vm *VM) GetQuasarBridge() *QuasarBridge {
|
|
vm.lock.RLock()
|
|
defer vm.lock.RUnlock()
|
|
return vm.quasarBridge
|
|
}
|
|
|
|
// GetHybridBridge returns the hybrid finality bridge for P/Q chain consensus
|
|
// This connects P-Chain BLS signatures with Q-Chain Corona for quantum finality
|
|
// Deprecated: Use GetQuasarBridge() for proper type safety
|
|
func (vm *VM) GetHybridBridge() interface{} {
|
|
return vm.GetQuasarBridge()
|
|
}
|
|
|
|
// SetHybridBridge sets the hybrid finality bridge (called by chain manager)
|
|
// Deprecated: Bridge is now auto-initialized in VM.Initialize()
|
|
func (vm *VM) SetHybridBridge(bridge interface{}) {
|
|
vm.lock.Lock()
|
|
defer vm.lock.Unlock()
|
|
if qb, ok := bridge.(*QuasarBridge); ok {
|
|
vm.quasarBridge = qb
|
|
}
|
|
}
|
|
|
|
// StampBlock implements QChainStamper interface for hybrid finality
|
|
// Uses Quasar BLS+Corona for dual post-quantum threshold signatures
|
|
func (vm *VM) StampBlock(blockID interface{}, pChainHeight uint64, message []byte) (interface{}, error) {
|
|
vm.lock.RLock()
|
|
defer vm.lock.RUnlock()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Convert blockID to ids.ID if possible
|
|
var blkID ids.ID
|
|
switch v := blockID.(type) {
|
|
case ids.ID:
|
|
blkID = v
|
|
case string:
|
|
parsed, err := ids.FromString(v)
|
|
if err == nil {
|
|
blkID = parsed
|
|
}
|
|
}
|
|
|
|
// Use Quasar bridge for BLS threshold signature if available
|
|
if vm.quasarBridge != nil && blkID != ids.Empty {
|
|
hybridSig, err := vm.quasarBridge.SignBlock(ctx, blkID, message, pChainHeight)
|
|
if err != nil {
|
|
vm.log.Warn("Quasar BLS stamp failed, using ML-DSA fallback", "error", err)
|
|
} else {
|
|
vm.log.Info("Quasar BLS stamp created",
|
|
"blockID", blkID,
|
|
"pChainHeight", pChainHeight,
|
|
"threshold", vm.quasarBridge.GetThreshold(),
|
|
)
|
|
// Return hybrid signature for BLS finality
|
|
return hybridSig, nil
|
|
}
|
|
}
|
|
|
|
// Fallback: Generate quantum stamp using ML-DSA signer
|
|
key, err := vm.quantumSigner.GenerateCoronaKey()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate key for stamp: %w", err)
|
|
}
|
|
|
|
sig, err := vm.quantumSigner.Sign(message, key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create quantum stamp: %w", err)
|
|
}
|
|
|
|
vm.log.Debug("ML-DSA quantum stamp created",
|
|
"pChainHeight", pChainHeight,
|
|
"sigLen", len(sig.Signature),
|
|
)
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// VerifyStamp implements QChainStamper interface for quasar finality
|
|
// Supports both Quasar QuasarSignature and ML-DSA QuantumSignature
|
|
func (vm *VM) VerifyStamp(stamp interface{}) error {
|
|
switch s := stamp.(type) {
|
|
case *quasar.QuasarSignature:
|
|
// Quasar BLS + Corona threshold signature
|
|
if s.BLS == nil || len(s.BLS.Signature) == 0 {
|
|
return errors.New("invalid Quasar BLS signature")
|
|
}
|
|
vm.log.Debug("Verified Quasar stamp",
|
|
"validatorID", s.BLS.ValidatorID,
|
|
"threshold", s.BLS.IsThreshold,
|
|
)
|
|
return nil
|
|
|
|
case *quasar.AggregatedSignature:
|
|
// Aggregated threshold signature
|
|
if len(s.BLSAggregated) == 0 || s.SignerCount < vm.quasarBridge.GetThreshold() {
|
|
return errors.New("insufficient aggregated signature")
|
|
}
|
|
vm.log.Debug("Verified aggregated Quasar stamp",
|
|
"signerCount", s.SignerCount,
|
|
"threshold", s.IsThreshold,
|
|
)
|
|
return nil
|
|
|
|
case *quantum.QuantumSignature:
|
|
// ML-DSA quantum signature
|
|
if len(s.Signature) == 0 || len(s.QuantumStamp) == 0 {
|
|
return errors.New("invalid quantum stamp structure")
|
|
}
|
|
return nil
|
|
|
|
default:
|
|
return errors.New("unsupported stamp type")
|
|
}
|
|
}
|