Files
evm/core/parallel/gpu_bridge.go
T
zeekay 458178730d evm: derive plugin VM ID from luxfi/constants.EVMID; bump luxfi/gpu v1.0.1
- cmd/vmid prints constants.EVMID — Go source of truth for the C-Chain VM
  ID. Build scripts and Makefile now call this instead of hardcoding the
  base58 string. Drops a stale subnet-evm ID (ag3GReYP…) from the Makefile.
- scripts/build.sh: default output is build/evm; INSTALL=1 installs under
  the derived VMID at \$LUX_PLUGIN_DIR. Codesigns on darwin in-line so a
  fresh binary doesn't get SIGKILLed by gatekeeper post-cp.
- core/parallel/gpu_bridge.go: build tag flipped from \`!nogpu\` (opt-out)
  to \`gpu\` (opt-in) so default builds don't drag in luxfi/gpu cgo.
- go.mod: luxfi/gpu v0.30.0 → v1.0.1. v0.30.0 referenced removed MLX
  symbols (mlx_from_slice_int64 etc.); v1.0.1 migrated to lux/gpu.h and
  builds cleanly with -tags gpu and -tags \"cevm gpu\".
- LLM.md: build commands updated; \"no canonical / no aliases\" reflects
  reality (single VMID, one filename, derived from Go).
2026-05-11 22:36:46 -07:00

153 lines
3.9 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build cgo && darwin && gpu
// Bridge that connects the EVM parallel interface to the lux/gpu package.
// Build with: CGO_ENABLED=1 go build -tags parallel,gpu
//
// This eliminates the signature recovery bottleneck:
// CPU ecrecover: 1613ms for 47K sigs (87% of block time)
// GPU ecrecover: ~50ms for 47K sigs (32x speedup)
package parallel
import (
"math/big"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/crypto/secp256k1"
luxgpu "github.com/luxfi/gpu"
)
// secp256k1halfN is the half-order of secp256k1 for EIP-2 low-S enforcement.
var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().Params().N, 1)
type gpuBridge struct{}
func init() {
RegisterGPU(&gpuBridge{})
}
func (g *gpuBridge) Available() bool {
return luxgpu.DefaultContext != nil && luxgpu.GetBackend() != luxgpu.CPU
}
func (g *gpuBridge) BatchEcrecover(txs []*types.Transaction) (map[common.Hash]common.Address, error) {
if len(txs) == 0 {
return nil, nil
}
n := len(txs)
sigs := make([]luxgpu.Signature, n)
// Track which tx indices have valid GPU input (per-tx signer)
valid := make([]bool, n)
for i, tx := range txs {
v, r, s := tx.RawSignatureValues()
if r == nil || s == nil {
continue
}
// EIP-2: reject high-S signatures (malleability protection).
// The CPU path enforces this in ValidateSignatureValues.
// We must enforce it here too, or GPU returns a different address.
if s.Cmp(secp256k1halfN) > 0 {
continue // skip; CPU fallback will reject this tx
}
// Reject oversized R or S (> 32 bytes = > 2^256)
if len(r.Bytes()) > 32 || len(s.Bytes()) > 32 {
continue
}
// Per-tx signer — different chain IDs get different signers.
// CRITICAL: using a single signer for the whole batch would produce
// wrong signing hashes for txs with different chain IDs (e.g., legacy
// unprotected txs mixed with EIP-155 txs), leading to wrong sender recovery.
signer := types.LatestSignerForChainID(tx.ChainId())
hash := signer.Hash(tx)
// Pack r (32 bytes big-endian, left-padded)
copy(sigs[i].R[:], padTo32(r))
// Pack s
copy(sigs[i].S[:], padTo32(s))
// Recovery ID — guard against overflow for large chain IDs
vid, ok := recoverV(v, tx.ChainId())
if !ok {
continue
}
sigs[i].V = vid
// Message hash
copy(sigs[i].MsgHash[:], hash[:])
valid[i] = true
}
results, err := luxgpu.BatchEcrecover(sigs)
if err != nil {
return nil, err
}
addrs := make(map[common.Hash]common.Address, n)
for i, tx := range txs {
if !valid[i] {
continue // was skipped due to validation; CPU fallback handles it
}
if i >= len(results) || !results[i].Valid {
continue
}
var addr common.Address
copy(addr[:], results[i].Address[:])
// Reject zero address — GPU kernel bug or edge case
if addr == (common.Address{}) {
continue
}
addrs[tx.Hash()] = addr
}
return addrs, nil
}
func (g *gpuBridge) BatchKeccak(_ [][]byte) ([]common.Hash, error) {
return nil, nil // not yet available; CPU path used
}
// padTo32 returns a 32-byte big-endian representation of a big.Int.
func padTo32(n *big.Int) []byte {
b := n.Bytes()
if len(b) > 32 {
return nil // caller must check; should not happen after validation
}
if len(b) == 32 {
return b
}
padded := make([]byte, 32)
copy(padded[32-len(b):], b)
return padded
}
// recoverV extracts the recovery ID (0 or 1) from the transaction's V value.
// Returns (id, false) if v or chainID overflows uint64.
func recoverV(v *big.Int, chainID *big.Int) (uint8, bool) {
if v.BitLen() > 63 {
return 0, false
}
vVal := v.Uint64()
if chainID != nil && chainID.Sign() > 0 {
if chainID.BitLen() > 63 {
return 0, false
}
cid := chainID.Uint64()
if vVal >= 35 {
return uint8((vVal - 35 - cid*2) & 1), true
}
}
if vVal != 27 && vVal != 28 {
return 0, false // invalid V for legacy tx
}
return uint8(vVal - 27), true
}