Files
zeekay 7cf87b230b node-unified is the node
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.
2026-09-07 16:15:25 -07:00

169 lines
4.9 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"context"
"fmt"
"github.com/luxfi/accel"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/math/set"
"github.com/luxfi/node/vms/platformvm/block"
"github.com/luxfi/node/vms/platformvm/txs"
txexecutor "github.com/luxfi/node/vms/platformvm/txs/executor"
"github.com/luxfi/node/vms/platformvm/warp"
validators "github.com/luxfi/validators"
)
// VerifyWarpMessages verifies every warp message the block carries. If any of
// them is invalid, an error is returned.
//
// Every transaction means every transaction: the decision txs a block charges
// for, and — on a proposal block — the tx the chain emitted about itself. A
// warp message is an assertion about another chain no matter who put it in the
// block, so which of the two sets it arrived in cannot decide whether its
// signature is checked.
//
// When GPU acceleration is available and the block contains multiple BLS
// warp signatures, verification is batched through the GPU for throughput.
// Otherwise falls back to sequential CPU verification.
func VerifyWarpMessages(
ctx context.Context,
networkID uint32,
validatorState validators.State,
pChainHeight uint64,
b block.Block,
) error {
blockTxs := b.DecisionTxs()
if proposal, ok := b.(*block.ProposalBlock); ok {
blockTxs = append(blockTxs, proposal.Tx())
}
// Collect warp messages that need BLS verification.
type blsJob struct {
aggPubKey *bls.PublicKey
aggSig *bls.Signature
msgBytes []byte
txIdx int
}
var blsJobs []blsJob
for txIdx, tx := range blockTxs {
rawMsg, hasWarp := extractWarpMessageBytes(tx.Unsigned)
if !hasWarp {
continue
}
msg, err := warp.ParseMessage(rawMsg)
if err != nil {
// Return raw error to match sequential path behavior.
return err
}
// Only BitSetSignature uses BLS that can be batched.
// Other signature types (Corona, Hybrid) fall through to sequential.
bss, ok := msg.Signature.(*warp.BitSetSignature)
if !ok || !accel.Available() {
// Sequential path for non-BLS signatures or when GPU unavailable.
if err := txexecutor.VerifyWarpMessages(ctx, networkID, validatorState, pChainHeight, tx.Unsigned); err != nil {
return err
}
continue
}
// Verify non-crypto parts (quorum weight) and extract the aggregate key/sig.
if msg.NetworkID != networkID {
return fmt.Errorf("tx %d: %w", txIdx, warp.ErrWrongNetworkID)
}
cvs, err := warp.GetCanonicalValidatorSetFromChainID(ctx, validatorState, pChainHeight, msg.SourceChainID)
if err != nil {
return fmt.Errorf("tx %d: failed to get validators: %w", txIdx, err)
}
signerIndices := set.BitsFromBytes(bss.Signers)
if len(signerIndices.Bytes()) != len(bss.Signers) {
return fmt.Errorf("tx %d: %w", txIdx, warp.ErrInvalidBitSet)
}
signers, err := warp.FilterValidators(signerIndices, cvs.Validators)
if err != nil {
return fmt.Errorf("tx %d: %w", txIdx, err)
}
sigWeight, _ := warp.SumWeight(signers)
if err := warp.VerifyWeight(sigWeight, cvs.TotalWeight, txexecutor.WarpQuorumNumerator, txexecutor.WarpQuorumDenominator); err != nil {
return fmt.Errorf("tx %d: %w", txIdx, err)
}
aggSig, err := bls.SignatureFromBytes(bss.Signature[:])
if err != nil {
return fmt.Errorf("tx %d: %w", txIdx, err)
}
aggPubKey, err := warp.AggregatePublicKeys(signers)
if err != nil {
return fmt.Errorf("tx %d: %w", txIdx, err)
}
blsJobs = append(blsJobs, blsJob{
aggPubKey: aggPubKey,
aggSig: aggSig,
msgBytes: msg.UnsignedMessage.Bytes(),
txIdx: txIdx,
})
}
if len(blsJobs) == 0 {
return nil
}
// Single BLS signature -- no batch benefit, verify directly.
if len(blsJobs) == 1 {
j := blsJobs[0]
if !bls.Verify(j.aggPubKey, j.aggSig, j.msgBytes) {
return fmt.Errorf("tx %d: %w", j.txIdx, warp.ErrInvalidSignature)
}
return nil
}
// Batch BLS verification via GPU.
pks := make([]*bls.PublicKey, len(blsJobs))
msgs := make([][]byte, len(blsJobs))
sigs := make([]*bls.Signature, len(blsJobs))
for i, j := range blsJobs {
pks[i] = j.aggPubKey
msgs[i] = j.msgBytes
sigs[i] = j.aggSig
}
results, err := warp.BatchVerifyBLSSignatures(pks, msgs, sigs)
if err != nil {
return fmt.Errorf("batch BLS verify: %w", err)
}
for i, valid := range results {
if !valid {
return fmt.Errorf("tx %d: %w", blsJobs[i].txIdx, warp.ErrInvalidSignature)
}
}
return nil
}
// extractWarpMessageBytes returns the raw warp message bytes from a tx and
// whether the tx carries a warp message. A true second return value means
// the tx type requires warp verification (even if the message bytes are nil/empty).
func extractWarpMessageBytes(unsigned txs.UnsignedTx) ([]byte, bool) {
switch tx := unsigned.(type) {
case *txs.RegisterL1ValidatorTx:
return tx.Message(), true
case *txs.SetL1ValidatorWeightTx:
return tx.Message(), true
default:
return nil, false
}
}