mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Completes the "one and only one way" wire migration: every VM in this repo now
serializes blocks and txs through luxfi/zap struct-is-wire at fixed field
offsets — no encoding/json, no hand-rolled big-endian, no cursor codec.
- aivm: Block + AIVertex + nested CIntent native-encoded. Fixes a latent
data-loss bug — AIVertex had unexported fields, so encoding/json silently
marshalled it as {}; the native encoder round-trips every field.
- keyvm: Transaction (signing preimage = SigningBytes(), excludes Auth/Sig;
full wire = signing || sig object, so the signed bytes stay a genuine prefix
and authenticate() re-derives a byte-identical preimage) + Block.
- quantumvm: Block + BaseTransaction. Block wire is the quantum-signature
preimage (deterministic fixed offsets, ordered tx list).
- dexvm: block envelope (txs were already ZAP). Preserves UnixNano timestamps,
deterministic tx order, and content-addressed carried fills.
Hardening from the adversarial crypto review (must-holds all verified: no
keyvm signature malleability/replay, no aivm CIntent id-forgery/escrow tamper,
no dexvm ordering/fill tamper, all parsers fail closed):
- keyvm (M1): make the wire strictly canonical. zap follows the root offset
and ignores unreferenced padding inside a message's declared size, so a twin
buffer could decode to identical fields with a different hash (id-malleability).
ParseTransaction now binds the id to the re-serialized (canonical) form and
rejects any non-canonical input at the door — exactly one byte-string
authenticates per logical tx. Regression test: the padded twin is rejected.
- quantumvm (M2): derive block + tx ids as sha256(Bytes()), matching the other
three VMs. The prior ids.ToID(parent||height) over 40 bytes (and ids.ToID over
the >=32-byte tx wire) silently yielded ids.Empty for every block/tx,
collapsing the block store and tx pool to a single slot. The block id is no
longer stored in the wire, so the invariant id == sha256(Bytes()) holds
unconditionally. Regression tests: ids are non-Empty and collision-distinct.
Not in scope (tracked separately, fail-closed today): aivm RewardPerOperator is
escrowed but not bound by ComputeIntentID — a pre-existing design gap that needs
a coordinated A-side + C-side intent-id preimage bump before any non-fail-closed
CCommitVerifier ships.
Builds + tests green under the canonical CGO=0 -mod=readonly recipe; go.mod/go.sum
are the go mod tidy canonical set (indirect deps resolved forward, all within
major). Cryptographer verdict: SAFE TO PUBLISH.
Co-authored-by: Hanzo Dev <[email protected]>
736 lines
21 KiB
Go
736 lines
21 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"
|
|
|
|
"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
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
}
|