mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
core/parallel: wire cevm (C++ EVM) in as a byte-exact block shadow-verifier
Route-gated activation of the Lux C++ EVM (cevm) through the BlockExecutor
seam (SEAM 1): parallel.RegisterExecutor makes core/state_processor.go run
ExecuteBlock on every block. Because the CONSENSUS root is taken from the Go
statedb.IntermediateRoot() (block_validator.go), cevm-as-applier would need
host-callback state write-back (deferred); so this lands cevm as a pure
SHADOW VERIFIER — it recomputes the block's keccak-MPT root independently and
confirms it matches the Go EVM's header.Root, then ALWAYS returns (nil,nil)
so the Go path applies the block. Root-only checking sidesteps both known
cevm gaps (event logs, state write-back).
core/parallel/cevmbridge: cgo seam to cevm's extern "C" cevm_process_block
(luxcpp/cevm build-mpt CPU libs, real MPT commit — NOT the placeholder-root
gpu path). Two variants share one API: bridge_cevm.go (//go:build cevm) links
the verified build-mpt + Conan link line and calls the real process_block;
bridge_stub.go (//go:build !cevm) is pure Go and declines, so `go build ./...`
stays green on any machine and the C++ path is opt-in via -tags cevm (the
existing backend_cevm.go convention). Balance is LE limbs; value/gasPrice/ctx
256-bit fields are BE — matching go_process_block.h exactly.
core/parallel/cevm_shadow.go: the BlockExecutor. Snapshots the pre-block state
read-only via DumpToCollector (address<-trie-key preimage, nonce, balance,
code, storage), builds the cevm tx array (sender via block signer, effective
gas price matching evm/core/state_transition.go's full-fee-to-coinbase model),
runs cevmbridge.ProcessBlock, and cross-checks the root against header.Root.
Atomic counters tally agree / disagree / finalize-gap (withdrawals move
header.Root; cevm runs no engine.Finalize — a state no-op on Lux otherwise) /
declined-too-large / declined-no-preimage / declined-tx / errored, read via
CevmShadowStats. Panics in the snapshot/bridge are swallowed and counted: the
verifier can never destabilise consensus. Registration is gated on
cevmbridge.Enabled so default and -tags parallel builds are byte-unchanged.
parallel.go: initialize txExecutors at declaration instead of in init().
Backend files register from their own init() (backend_cevm.go under -tags
cevm), and Go orders init() funcs alphabetically by filename — backend_cevm.go
precedes parallel.go, so the old init()-time map creation raced too late and
panicked on a nil map under -tags cevm. Var initializers run before all
init() funcs, fixing the order deterministically.
Proven:
- cevmbridge golden roots through the Go marshalling byte-match luxfi/geth:
N=1 0xb9d849f9…805e9, N=100 0x2028713f…7cf0f, N=1000 0x57b72507…20099.
- e2e (TestCevmShadowVerifiesRealBlocks, -tags cevm): 3 real blocks through
core.BlockChain.InsertChain -> StateProcessor.Process -> shadow ExecuteBlock;
cevm's real-MPT root byte-matched the Go EVM header.Root on all 3
(Processed:3 Agree:3 Disagree:0, zero declines/errors).
- default (stub) core/parallel suite unchanged and green.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package parallel
|
||||
|
||||
// cevm_shadow.go wires the Lux C++ EVM (cevm) into the block executor as a
|
||||
// byte-exact SHADOW VERIFIER, not an applier.
|
||||
//
|
||||
// The map proved two facts that lock this design:
|
||||
// 1. parallel.RegisterExecutor(e) makes core/state_processor.go route EVERY
|
||||
// block through e.ExecuteBlock (no SetBackend needed).
|
||||
// 2. The CONSENSUS state root is taken from the GO statedb.IntermediateRoot()
|
||||
// (block_validator.go), NOT from cevm's returned root. So cevm as the
|
||||
// APPLIER would have to mutate the Go statedb via host callbacks (heavy,
|
||||
// deferred). As a VERIFIER it needs none of that.
|
||||
//
|
||||
// So this executor: (a) snapshots the pre-block Go state read-only, (b) replays
|
||||
// the block's transactions through cevm's real process_block over that snapshot
|
||||
// to get cevm's independent keccak-MPT root, (c) cross-checks that root against
|
||||
// header.Root (the post-state root the Go EVM produced when the block was
|
||||
// built), tallying agree/disagree/finalize-gap/declined counters, and then
|
||||
// ALWAYS returns (nil, nil) so the Go sequential path is the one that actually
|
||||
// applies the block. cevm never fabricates receipts and never touches the live
|
||||
// state — the suite stays correct while cevm's real MPT is checked on every
|
||||
// block it can represent.
|
||||
//
|
||||
// Root-only verification sidesteps BOTH known cevm gaps: it needs no logs (the
|
||||
// receipts gap) and no state write-back (the applier gap). The proven safe
|
||||
// subset (pure value transfers + non-reverting calls into existing code) yields
|
||||
// a byte-exact match; everything else (top-level CREATE, REVERT/OOG, EIP-2930
|
||||
// access lists, EIP-3529 refunds, or a state too large to snapshot) is tallied
|
||||
// as a decline/disagree and the Go EVM's root stands unchallenged.
|
||||
//
|
||||
// This file is intentionally NOT behind //go:build cevm: it always compiles and
|
||||
// always registers. When built without -tags cevm, cevmbridge.Enabled is false
|
||||
// and ExecuteBlock returns (nil, nil) immediately — a behavioural no-op
|
||||
// identical to the fallback executor, so the default build is unchanged.
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
|
||||
"github.com/luxfi/evm/core/parallel/cevmbridge"
|
||||
"github.com/luxfi/evm/core/state"
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/geth/core/types"
|
||||
"github.com/luxfi/geth/core/vm"
|
||||
ethparams "github.com/luxfi/geth/params"
|
||||
)
|
||||
|
||||
// Snapshot caps. A block whose pre-state exceeds these is declined (counted as
|
||||
// declined-too-large) rather than risk an unbounded dump on live chains — the
|
||||
// shadow verifier is a correctness harness, not a mainnet path.
|
||||
const (
|
||||
maxShadowAccounts = 200_000
|
||||
maxShadowStorage = 1_000_000
|
||||
)
|
||||
|
||||
// Package-level shadow counters (atomic). Read via CevmShadowStats.
|
||||
var (
|
||||
cevmProcessed uint64 // blocks cevm actually ran and returned a root for
|
||||
cevmAgree uint64 // cevm root == header.Root (byte-exact)
|
||||
cevmDisagree uint64 // cevm root != header.Root, no known-alignment excuse
|
||||
cevmFinalizeGap uint64 // mismatch attributable to withdrawals (cevm runs no engine.Finalize)
|
||||
cevmDeclinedTooLarge uint64 // pre-state exceeded the snapshot caps
|
||||
cevmDeclinedNoPreimage uint64 // an account address was unrecoverable (preimages off) — cannot seed cevm
|
||||
cevmDeclinedTx uint64 // a tx sender could not be recovered
|
||||
cevmErrored uint64 // cevm process_block failed / panicked
|
||||
)
|
||||
|
||||
// ShadowStats is a snapshot of the cevm shadow-verifier counters.
|
||||
type ShadowStats struct {
|
||||
Processed uint64
|
||||
Agree uint64
|
||||
Disagree uint64
|
||||
FinalizeGap uint64
|
||||
DeclinedTooLarge uint64
|
||||
DeclinedNoPreimage uint64
|
||||
DeclinedTx uint64
|
||||
Errored uint64
|
||||
}
|
||||
|
||||
// CevmShadowStats returns the current cevm shadow-verifier counters.
|
||||
func CevmShadowStats() ShadowStats {
|
||||
return ShadowStats{
|
||||
Processed: atomic.LoadUint64(&cevmProcessed),
|
||||
Agree: atomic.LoadUint64(&cevmAgree),
|
||||
Disagree: atomic.LoadUint64(&cevmDisagree),
|
||||
FinalizeGap: atomic.LoadUint64(&cevmFinalizeGap),
|
||||
DeclinedTooLarge: atomic.LoadUint64(&cevmDeclinedTooLarge),
|
||||
DeclinedNoPreimage: atomic.LoadUint64(&cevmDeclinedNoPreimage),
|
||||
DeclinedTx: atomic.LoadUint64(&cevmDeclinedTx),
|
||||
Errored: atomic.LoadUint64(&cevmErrored),
|
||||
}
|
||||
}
|
||||
|
||||
// ResetCevmShadowStats zeroes the counters (test helper).
|
||||
func ResetCevmShadowStats() {
|
||||
for _, p := range []*uint64{
|
||||
&cevmProcessed, &cevmAgree, &cevmDisagree, &cevmFinalizeGap,
|
||||
&cevmDeclinedTooLarge, &cevmDeclinedNoPreimage, &cevmDeclinedTx, &cevmErrored,
|
||||
} {
|
||||
atomic.StoreUint64(p, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// CevmShadowEnabled reports whether the real cevm bridge is linked (-tags cevm).
|
||||
func CevmShadowEnabled() bool { return cevmbridge.Enabled }
|
||||
|
||||
func init() {
|
||||
// Register only when the real C++ EVM is linked (-tags cevm). In the default
|
||||
// and -tags parallel builds the bridge is a no-op stub, so registering would
|
||||
// only risk clobbering another package's real BlockExecutor (e.g. a Block-STM
|
||||
// engine) with a verifier that always declines. Gating on Enabled keeps those
|
||||
// builds byte-for-byte unchanged while activating the shadow under -tags cevm.
|
||||
if cevmbridge.Enabled {
|
||||
RegisterExecutor(cevmShadowExecutor{})
|
||||
}
|
||||
}
|
||||
|
||||
// cevmShadowExecutor is a BlockExecutor that only ever verifies: it returns
|
||||
// (nil, nil) unconditionally so the Go sequential path applies the block.
|
||||
type cevmShadowExecutor struct{}
|
||||
|
||||
// ExecuteBlock runs the cevm shadow cross-check and ALWAYS declines. It never
|
||||
// returns receipts or an error, so it can never break block processing — a
|
||||
// mismatch or an internal failure only moves a counter.
|
||||
func (cevmShadowExecutor) ExecuteBlock(
|
||||
config *ethparams.ChainConfig,
|
||||
header *types.Header,
|
||||
txs types.Transactions,
|
||||
statedb *state.StateDB,
|
||||
_ vm.Config,
|
||||
) ([]*types.Receipt, error) {
|
||||
if !cevmbridge.Enabled || config == nil || header == nil || statedb == nil || len(txs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
// The verifier must NEVER destabilise consensus: any panic in the snapshot
|
||||
// or bridge is swallowed and counted, and we still decline.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
atomic.AddUint64(&cevmErrored, 1)
|
||||
}
|
||||
}()
|
||||
shadowVerify(config, header, txs, statedb)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// shadowVerify performs the read-only snapshot, cevm replay, and root
|
||||
// cross-check, updating exactly one counter.
|
||||
func shadowVerify(
|
||||
config *ethparams.ChainConfig,
|
||||
header *types.Header,
|
||||
txs types.Transactions,
|
||||
statedb *state.StateDB,
|
||||
) {
|
||||
// (a) Snapshot the pre-block state read-only. DumpToCollector iterates the
|
||||
// trie at statedb's original (parent) root, which is exactly the pre-block
|
||||
// state the block was applied to.
|
||||
snap := &shadowSnapshot{maxAccts: maxShadowAccounts, maxStorage: maxShadowStorage}
|
||||
statedb.DumpToCollector(snap, &state.DumpConfig{})
|
||||
switch {
|
||||
case snap.tooLarge:
|
||||
atomic.AddUint64(&cevmDeclinedTooLarge, 1)
|
||||
return
|
||||
case snap.missingAddr || snap.badBalance:
|
||||
// Addresses come from trie-key preimages; without them cevm cannot be
|
||||
// seeded (its leaf key is keccak256(addr)). Decline honestly.
|
||||
atomic.AddUint64(&cevmDeclinedNoPreimage, 1)
|
||||
return
|
||||
}
|
||||
|
||||
// (b) Build the cevm tx array from the block's transactions.
|
||||
signer := types.MakeSigner(config, header.Number, header.Time)
|
||||
ctxs := make([]cevmbridge.Tx, len(txs))
|
||||
for i, tx := range txs {
|
||||
from, err := types.Sender(signer, tx)
|
||||
if err != nil {
|
||||
atomic.AddUint64(&cevmDeclinedTx, 1)
|
||||
return
|
||||
}
|
||||
var t cevmbridge.Tx
|
||||
copy(t.Sender[:], from[:])
|
||||
if to := tx.To(); to != nil {
|
||||
copy(t.Recipient[:], to[:])
|
||||
} else {
|
||||
t.IsCreate = true
|
||||
}
|
||||
fillBE32(&t.Value, tx.Value())
|
||||
fillBE32(&t.GasPrice, effectiveGasPrice(tx, header.BaseFee))
|
||||
t.GasLimit = tx.Gas()
|
||||
t.Nonce = tx.Nonce()
|
||||
if d := tx.Data(); len(d) > 0 {
|
||||
t.Data = d
|
||||
}
|
||||
ctxs[i] = t
|
||||
}
|
||||
|
||||
// (c) cevm's independent root over the same pre-state + txs.
|
||||
res, err := cevmbridge.ProcessBlock(snap.accts, snap.storage, ctxs, blockCtx(config, header))
|
||||
if err != nil || !res.OK {
|
||||
atomic.AddUint64(&cevmErrored, 1)
|
||||
return
|
||||
}
|
||||
atomic.AddUint64(&cevmProcessed, 1)
|
||||
|
||||
// (d) Cross-check against the root the Go EVM committed for this block.
|
||||
if common.BytesToHash(res.StateRoot[:]) == header.Root {
|
||||
atomic.AddUint64(&cevmAgree, 1)
|
||||
return
|
||||
}
|
||||
// Known-alignment gap: cevm credits the coinbase per-tx (gas*gasPrice, which
|
||||
// matches evm's full-fee-to-coinbase state_transition) but does NOT run
|
||||
// engine.Finalize. On Lux that Finalize is a state no-op EXCEPT for
|
||||
// EIP-4895 withdrawals, which move header.Root. Attribute such mismatches to
|
||||
// the finalize gap rather than blaming cevm.
|
||||
if header.WithdrawalsHash != nil && *header.WithdrawalsHash != types.EmptyWithdrawalsHash {
|
||||
atomic.AddUint64(&cevmFinalizeGap, 1)
|
||||
return
|
||||
}
|
||||
atomic.AddUint64(&cevmDisagree, 1)
|
||||
}
|
||||
|
||||
// effectiveGasPrice mirrors evm/core/state_transition.go: the price used for the
|
||||
// sender debit and the full coinbase fee credit. For a legacy tx this is the
|
||||
// gas price; for a dynamic-fee tx, min(gasFeeCap, gasTipCap+baseFee).
|
||||
func effectiveGasPrice(tx *types.Transaction, baseFee *big.Int) *big.Int {
|
||||
if baseFee == nil {
|
||||
return tx.GasPrice()
|
||||
}
|
||||
sum := new(big.Int).Add(tx.GasTipCap(), baseFee)
|
||||
if sum.Cmp(tx.GasFeeCap()) > 0 {
|
||||
return new(big.Int).Set(tx.GasFeeCap())
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// blockCtx builds the cevm block context from the header. The 256-bit fields are
|
||||
// big-endian; PrevRandao is the post-merge MixDigest. Revision is derived from
|
||||
// the chain config (immaterial to the root for the proven transfer subset).
|
||||
func blockCtx(config *ethparams.ChainConfig, header *types.Header) cevmbridge.BlockCtx {
|
||||
var c cevmbridge.BlockCtx
|
||||
copy(c.Coinbase[:], header.Coinbase[:])
|
||||
if header.Number != nil {
|
||||
c.BlockNumber = header.Number.Int64()
|
||||
}
|
||||
c.BlockTime = int64(header.Time)
|
||||
c.BlockGasLimit = int64(header.GasLimit)
|
||||
if config.ChainID != nil {
|
||||
fillBE32(&c.ChainID, config.ChainID)
|
||||
}
|
||||
if header.BaseFee != nil {
|
||||
fillBE32(&c.BaseFee, header.BaseFee)
|
||||
}
|
||||
copy(c.PrevRandao[:], header.MixDigest[:])
|
||||
c.Revision = revisionOf(config, header)
|
||||
return c
|
||||
}
|
||||
|
||||
func revisionOf(config *ethparams.ChainConfig, header *types.Header) uint8 {
|
||||
num, t := header.Number, header.Time
|
||||
switch {
|
||||
case config.IsCancun(num, t):
|
||||
return cevmbridge.RevCancun
|
||||
case config.IsShanghai(num, t):
|
||||
return cevmbridge.RevShanghai
|
||||
case config.IsLondon(num):
|
||||
return cevmbridge.RevLondon
|
||||
case config.IsBerlin(num):
|
||||
return cevmbridge.RevBerlin
|
||||
case config.IsIstanbul(num):
|
||||
return cevmbridge.RevIstanbul
|
||||
default:
|
||||
return cevmbridge.RevIstanbul
|
||||
}
|
||||
}
|
||||
|
||||
// fillBE32 writes v as a 32-byte big-endian value (right-aligned, zero-padded).
|
||||
func fillBE32(dst *[32]byte, v *big.Int) {
|
||||
*dst = [32]byte{}
|
||||
if v == nil || v.Sign() == 0 {
|
||||
return
|
||||
}
|
||||
b := v.Bytes() // big-endian, minimal
|
||||
if len(b) >= 32 {
|
||||
copy(dst[:], b[len(b)-32:])
|
||||
return
|
||||
}
|
||||
copy(dst[32-len(b):], b)
|
||||
}
|
||||
|
||||
// shadowSnapshot is a DumpCollector that materializes the pre-block accounts +
|
||||
// storage into cevmbridge structs, bounded by the snapshot caps.
|
||||
type shadowSnapshot struct {
|
||||
maxAccts, maxStorage int
|
||||
accts []cevmbridge.Account
|
||||
storage []cevmbridge.Storage
|
||||
missingAddr bool
|
||||
badBalance bool
|
||||
tooLarge bool
|
||||
}
|
||||
|
||||
func (s *shadowSnapshot) OnRoot(common.Hash) {}
|
||||
|
||||
func (s *shadowSnapshot) OnAccount(addr *common.Address, acc state.DumpAccount) {
|
||||
if s.tooLarge || s.missingAddr || s.badBalance {
|
||||
return
|
||||
}
|
||||
if addr == nil {
|
||||
// No preimage for this trie key: cannot recover the 20-byte address.
|
||||
s.missingAddr = true
|
||||
return
|
||||
}
|
||||
if len(s.accts) >= s.maxAccts {
|
||||
s.tooLarge = true
|
||||
return
|
||||
}
|
||||
var a cevmbridge.Account
|
||||
copy(a.Address[:], addr[:])
|
||||
a.Nonce = acc.Nonce
|
||||
bal, err := uint256.FromDecimal(acc.Balance)
|
||||
if err != nil {
|
||||
s.badBalance = true
|
||||
return
|
||||
}
|
||||
a.Balance = [4]uint64{bal[0], bal[1], bal[2], bal[3]}
|
||||
if len(acc.Code) > 0 {
|
||||
a.Code = append([]byte(nil), acc.Code...)
|
||||
}
|
||||
s.accts = append(s.accts, a)
|
||||
|
||||
for k, vhex := range acc.Storage {
|
||||
if len(s.storage) >= s.maxStorage {
|
||||
s.tooLarge = true
|
||||
return
|
||||
}
|
||||
var st cevmbridge.Storage
|
||||
copy(st.Address[:], addr[:])
|
||||
copy(st.Key[:], k[:])
|
||||
vb := common.Hex2Bytes(vhex) // dump value is hex without 0x, big-endian minimal
|
||||
if len(vb) >= 32 {
|
||||
copy(st.Value[:], vb[len(vb)-32:])
|
||||
} else {
|
||||
copy(st.Value[32-len(vb):], vb)
|
||||
}
|
||||
s.storage = append(s.storage, st)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cevm
|
||||
|
||||
// End-to-end proof that the cevm shadow verifier runs over REAL Go-produced
|
||||
// blocks: it drives core.BlockChain.InsertChain (which calls
|
||||
// StateProcessor.Process -> parallel.DefaultExecutor().ExecuteBlock on every
|
||||
// block), and asserts that cevm's independent keccak-MPT root byte-matches the
|
||||
// header.Root the Go EVM committed. This is the honest deliverable: cevm's real
|
||||
// process_block is exercised against genuine blocks, not a synthetic fixture.
|
||||
//
|
||||
// Built only under -tags cevm (needs the linked C++ EVM). Lives in the external
|
||||
// test package so it may import core without an import cycle.
|
||||
package parallel_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto"
|
||||
"github.com/luxfi/evm/consensus/dummy"
|
||||
"github.com/luxfi/evm/core"
|
||||
"github.com/luxfi/evm/core/parallel"
|
||||
"github.com/luxfi/evm/params"
|
||||
"github.com/luxfi/evm/plugin/evm/upgrade/legacy"
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/geth/core/rawdb"
|
||||
"github.com/luxfi/geth/core/types"
|
||||
"github.com/luxfi/geth/core/vm"
|
||||
)
|
||||
|
||||
func TestCevmShadowVerifiesRealBlocks(t *testing.T) {
|
||||
if !parallel.CevmShadowEnabled() {
|
||||
t.Skip("cevm bridge not linked (build with -tags cevm)")
|
||||
}
|
||||
|
||||
key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
key2, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
cryptoFrom := crypto.PubkeyToAddress(key1.PublicKey)
|
||||
cryptoTo := crypto.PubkeyToAddress(key2.PublicKey)
|
||||
from := common.BytesToAddress(cryptoFrom[:])
|
||||
to := common.BytesToAddress(cryptoTo[:])
|
||||
coinbase := common.HexToAddress("0xCafE00000000000000000000000000000000C0DE")
|
||||
funds := new(big.Int).Mul(big.NewInt(1000), big.NewInt(params.Ether))
|
||||
|
||||
gspec := &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{from: {Balance: funds}, to: {Balance: funds}},
|
||||
BaseFee: big.NewInt(legacy.BaseFee),
|
||||
}
|
||||
signer := types.LatestSigner(gspec.Config)
|
||||
engine := dummy.NewCoinbaseFaker()
|
||||
|
||||
// Commit genesis WITH preimages so the shadow verifier can recover the
|
||||
// 20-byte addresses from the pre-block trie keys (its leaf key is
|
||||
// keccak256(addr)). Preimages off => the verifier declines honestly.
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
cacheConfig := *core.DefaultCacheConfig
|
||||
cacheConfig.Preimages = true
|
||||
cacheConfig.SnapshotLimit = 0
|
||||
|
||||
chain, err := core.NewBlockChain(db, &cacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBlockChain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// Generate real blocks of pure value transfers (the proven safe subset).
|
||||
// gasLimit == 21000 so gasUsed == gasLimit (no refund ambiguity); gasPrice
|
||||
// == baseFee so the effective price is exactly baseFee; full fee -> coinbase
|
||||
// matches cevm's per-tx coinbase credit.
|
||||
const nBlocks = 3
|
||||
blocks, _, err := core.GenerateChain(gspec.Config, chain.Genesis(), engine, db, nBlocks, 10, func(i int, b *core.BlockGen) {
|
||||
b.SetCoinbase(coinbase)
|
||||
tx := types.NewTransaction(uint64(i), to, big.NewInt(int64(1000+i)), 21000, big.NewInt(legacy.BaseFee), nil)
|
||||
signed, serr := types.SignTx(tx, signer, key1)
|
||||
if serr != nil {
|
||||
t.Fatalf("SignTx: %v", serr)
|
||||
}
|
||||
b.AddTx(signed)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateChain: %v", err)
|
||||
}
|
||||
|
||||
parallel.ResetCevmShadowStats()
|
||||
if _, err := chain.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("InsertChain: %v", err)
|
||||
}
|
||||
s := parallel.CevmShadowStats()
|
||||
|
||||
t.Logf("cevm shadow stats over %d real blocks: %+v", nBlocks, s)
|
||||
|
||||
// Every block carried exactly one pure transfer over a fully-snapshottable
|
||||
// pre-state with preimages on, so cevm must have PROCESSED every block and
|
||||
// AGREED byte-exactly with the Go EVM root on each.
|
||||
if s.DeclinedNoPreimage > 0 {
|
||||
t.Fatalf("verifier declined %d blocks for missing preimages — genesis/state preimages not available", s.DeclinedNoPreimage)
|
||||
}
|
||||
if s.Processed != nBlocks {
|
||||
t.Fatalf("cevm processed %d blocks, want %d (declinedTooLarge=%d declinedTx=%d errored=%d)",
|
||||
s.Processed, nBlocks, s.DeclinedTooLarge, s.DeclinedTx, s.Errored)
|
||||
}
|
||||
if s.Agree != nBlocks {
|
||||
t.Fatalf("cevm agreed on %d/%d blocks (disagree=%d finalizeGap=%d) — root mismatch vs Go EVM",
|
||||
s.Agree, nBlocks, s.Disagree, s.FinalizeGap)
|
||||
}
|
||||
if s.Disagree != 0 {
|
||||
t.Fatalf("cevm disagreed on %d blocks", s.Disagree)
|
||||
}
|
||||
t.Logf("PASS: cevm real-MPT root byte-matched the Go EVM header.Root on all %d blocks", s.Agree)
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cevm
|
||||
|
||||
package cevmbridge
|
||||
|
||||
// Real cgo bridge into the Lux C++ EVM (cevm) block-batch consensus entry
|
||||
// point. Links the FRESH build-mpt CPU libs that contain the real keccak
|
||||
// Merkle-Patricia-Trie commit — NOT the stale /opt/homebrew pre-MPT libs and
|
||||
// NOT the gpu execute_block placeholder-root path.
|
||||
//
|
||||
// The cgo link line below is byte-for-byte the one proven to link and return
|
||||
// the byte-exact geth empty-MPT root through this exact entry point (see the
|
||||
// map:linking verification). cgo takes LITERAL absolute paths — no env/${SRCDIR}
|
||||
// expansion — so the paths are spelled out. The Conan hash dirs
|
||||
// (lux-c4c40b1a25281d = CPU precompile deps, bls3f47da987ce4d = blst) are the
|
||||
// authoritative set read from the gate's own link.txt. -Wl,-rpath is MANDATORY:
|
||||
// libevm.0.19.0.dylib has install name @rpath/libevm.0.19.dylib.
|
||||
//
|
||||
// The preamble only includes the C ABI header (pure C, extern "C"); the C++
|
||||
// symbols it pulls resolve from the linked archives, so -lc++ is required and
|
||||
// not duplicated (this is a C cgo package — cgo does not auto-add libc++ here).
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/Users/z/work/luxcpp/cevm/lib/evm
|
||||
#cgo LDFLAGS: /Users/z/work/luxcpp/cevm/build-mpt/build/Release/lib/evm/libevm-state.a /Users/z/work/luxcpp/cevm/build-mpt/build/Release/lib/libevm.0.19.0.dylib /Users/z/work/luxcpp/cevm/build-mpt/build/Release/lib/cevm_precompiles/libcevm_precompiles.a /Users/z/work/luxcpp/cevm/build-mpt/build/Release/lib/cevm_precompiles/libcevm_bls_kzg_canonical_cpu.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libbls_evm.a /Users/z/.conan2/p/b/bls3f47da987ce4d/p/lib/libblst.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libbn254.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libbn254_cpu.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libmodexp.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libmodexp_cpu.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libsha256.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libsha256_cpu.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libripemd160.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libripemd160_cpu.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libblake2b.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libblake2b_cpu.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libsecp256r1.a /Users/z/.conan2/p/b/lux-c4c40b1a25281d/p/lib/libsecp256r1_cpu.a -Wl,-rpath,/Users/z/work/luxcpp/cevm/build-mpt/build/Release/lib -lc++
|
||||
#include <stdlib.h>
|
||||
#include "state/go_process_block.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Enabled reports that the real C++ EVM bridge is linked.
|
||||
const Enabled = true
|
||||
|
||||
// ABIVersion returns the ABI version compiled into the linked cevm library.
|
||||
func ABIVersion() uint32 { return uint32(C.cevm_pb_abi_version()) }
|
||||
|
||||
// ProcessBlock seeds a cevm StateDB from accts+storage, replays txs through a
|
||||
// real evmc_create_cevm() VM, and returns the real MPT state_root + per-tx
|
||||
// status/gas. It is a pure function: it does not touch any Go state.
|
||||
func ProcessBlock(accts []Account, storage []Storage, txs []Tx, ctx BlockCtx) (Result, error) {
|
||||
// C buffers allocated for code/data payloads; freed on return.
|
||||
var frees []unsafe.Pointer
|
||||
defer func() {
|
||||
for _, p := range frees {
|
||||
C.free(p)
|
||||
}
|
||||
}()
|
||||
|
||||
// --- accounts --------------------------------------------------------
|
||||
cAccts := make([]C.cevm_pb_acct, len(accts))
|
||||
for i := range accts {
|
||||
a := &accts[i]
|
||||
for j := 0; j < 20; j++ {
|
||||
cAccts[i].address[j] = C.uint8_t(a.Address[j])
|
||||
}
|
||||
cAccts[i].nonce = C.uint64_t(a.Nonce)
|
||||
for j := 0; j < 4; j++ {
|
||||
cAccts[i].balance[j] = C.uint64_t(a.Balance[j])
|
||||
}
|
||||
if len(a.Code) > 0 {
|
||||
p := C.CBytes(a.Code)
|
||||
frees = append(frees, p)
|
||||
cAccts[i].code = (*C.uint8_t)(p)
|
||||
cAccts[i].code_len = C.uint32_t(len(a.Code))
|
||||
}
|
||||
}
|
||||
|
||||
// --- storage ---------------------------------------------------------
|
||||
cStor := make([]C.cevm_pb_storage, len(storage))
|
||||
for i := range storage {
|
||||
s := &storage[i]
|
||||
for j := 0; j < 20; j++ {
|
||||
cStor[i].address[j] = C.uint8_t(s.Address[j])
|
||||
}
|
||||
for j := 0; j < 32; j++ {
|
||||
cStor[i].key[j] = C.uint8_t(s.Key[j])
|
||||
cStor[i].value[j] = C.uint8_t(s.Value[j])
|
||||
}
|
||||
}
|
||||
|
||||
// --- transactions ----------------------------------------------------
|
||||
cTxs := make([]C.cevm_pb_tx, len(txs))
|
||||
for i := range txs {
|
||||
t := &txs[i]
|
||||
for j := 0; j < 20; j++ {
|
||||
cTxs[i].sender[j] = C.uint8_t(t.Sender[j])
|
||||
cTxs[i].recipient[j] = C.uint8_t(t.Recipient[j])
|
||||
}
|
||||
for j := 0; j < 32; j++ {
|
||||
cTxs[i].value[j] = C.uint8_t(t.Value[j])
|
||||
cTxs[i].gas_price[j] = C.uint8_t(t.GasPrice[j])
|
||||
}
|
||||
cTxs[i].gas_limit = C.uint64_t(t.GasLimit)
|
||||
cTxs[i].nonce = C.uint64_t(t.Nonce)
|
||||
if len(t.Data) > 0 {
|
||||
p := C.CBytes(t.Data)
|
||||
frees = append(frees, p)
|
||||
cTxs[i].data = (*C.uint8_t)(p)
|
||||
cTxs[i].data_len = C.uint32_t(len(t.Data))
|
||||
}
|
||||
if t.IsCreate {
|
||||
cTxs[i].is_create = 1
|
||||
}
|
||||
}
|
||||
|
||||
// --- context ---------------------------------------------------------
|
||||
var cCtx C.cevm_pb_ctx
|
||||
for j := 0; j < 20; j++ {
|
||||
cCtx.coinbase[j] = C.uint8_t(ctx.Coinbase[j])
|
||||
}
|
||||
cCtx.block_number = C.int64_t(ctx.BlockNumber)
|
||||
cCtx.block_timestamp = C.int64_t(ctx.BlockTime)
|
||||
cCtx.block_gas_limit = C.int64_t(ctx.BlockGasLimit)
|
||||
for j := 0; j < 32; j++ {
|
||||
cCtx.chain_id[j] = C.uint8_t(ctx.ChainID[j])
|
||||
cCtx.block_base_fee[j] = C.uint8_t(ctx.BaseFee[j])
|
||||
cCtx.prev_randao[j] = C.uint8_t(ctx.PrevRandao[j])
|
||||
cCtx.blob_base_fee[j] = C.uint8_t(ctx.BlobBaseFee[j])
|
||||
}
|
||||
cCtx.revision = C.uint8_t(ctx.Revision)
|
||||
|
||||
var (
|
||||
txPtr *C.cevm_pb_tx
|
||||
acctPtr *C.cevm_pb_acct
|
||||
storPtr *C.cevm_pb_storage
|
||||
)
|
||||
if len(cTxs) > 0 {
|
||||
txPtr = &cTxs[0]
|
||||
}
|
||||
if len(cAccts) > 0 {
|
||||
acctPtr = &cAccts[0]
|
||||
}
|
||||
if len(cStor) > 0 {
|
||||
storPtr = &cStor[0]
|
||||
}
|
||||
|
||||
r := C.cevm_process_block(
|
||||
txPtr, C.uint32_t(len(cTxs)),
|
||||
acctPtr, C.uint32_t(len(cAccts)),
|
||||
storPtr, C.uint32_t(len(cStor)),
|
||||
&cCtx,
|
||||
)
|
||||
defer C.cevm_pb_free(&r)
|
||||
|
||||
var res Result
|
||||
res.ABIVersion = uint32(r.abi_version)
|
||||
res.OK = r.ok != 0
|
||||
res.TotalGas = uint64(r.total_gas)
|
||||
for j := 0; j < 32; j++ {
|
||||
res.StateRoot[j] = byte(r.state_root[j])
|
||||
}
|
||||
if r.ok == 0 {
|
||||
return res, fmt.Errorf("cevmbridge: cevm_process_block returned ok=0")
|
||||
}
|
||||
if n := int(r.n_tx_results); n > 0 && r.tx_results != nil {
|
||||
cres := unsafe.Slice((*C.cevm_pb_txresult)(unsafe.Pointer(r.tx_results)), n)
|
||||
res.TxResults = make([]TxResult, n)
|
||||
for i := 0; i < n; i++ {
|
||||
res.TxResults[i] = TxResult{
|
||||
EVMCStatus: int32(cres[i].evmc_status),
|
||||
Status: uint8(cres[i].status),
|
||||
Rejected: cres[i].rejected != 0,
|
||||
GasUsed: uint64(cres[i].gas_used),
|
||||
CumulativeGasUsed: uint64(cres[i].cumulative_gas_used),
|
||||
NLogs: uint32(cres[i].n_logs),
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cevm
|
||||
|
||||
package cevmbridge
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// sndr mirrors test/cabi_block_parity.c: bytes[19]=i, [18]=i>>8, [17]=i>>16.
|
||||
func sndr(i uint32) [20]byte {
|
||||
var a [20]byte
|
||||
a[19] = byte(i)
|
||||
a[18] = byte(i >> 8)
|
||||
a[17] = byte(i >> 16)
|
||||
return a
|
||||
}
|
||||
|
||||
// rcpt(i) = sndr(i) with bytes[16]=0x01.
|
||||
func rcpt(i uint32) [20]byte {
|
||||
a := sndr(i)
|
||||
a[16] = 0x01
|
||||
return a
|
||||
}
|
||||
|
||||
// TestProcessBlockGoldenRoots drives the exact golden N-transfer blocks the C
|
||||
// driver (cevm-cabi-block-parity) proves byte-identical to luxfi/geth, but
|
||||
// through the GO marshalling in this package. A match here proves the Go structs
|
||||
// serialize into the C ABI identically to the reference C driver.
|
||||
func TestProcessBlockGoldenRoots(t *testing.T) {
|
||||
cases := []struct {
|
||||
n uint32
|
||||
golden string
|
||||
}{
|
||||
{1, "b9d849f90993abb25f57172eb086e4b6ac15369954b0c556ae2280a1559805e9"},
|
||||
{100, "2028713fd7a12c1c73fb90f7b9de08be87c1d23b76acb9d2a4cfe3788577cf0f"},
|
||||
{1000, "57b7250733ad7f7a1be82d7f1e8d385496b00d2bb93a553b6730dd83dd020099"},
|
||||
}
|
||||
|
||||
if ABIVersion() != 1 {
|
||||
t.Fatalf("unexpected ABI version %d (want 1)", ABIVersion())
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
accts := make([]Account, c.n)
|
||||
txs := make([]Tx, c.n)
|
||||
for i := uint32(0); i < c.n; i++ {
|
||||
accts[i] = Account{Address: sndr(i), Nonce: 0, Balance: [4]uint64{1000000, 0, 0, 0}}
|
||||
var value [32]byte
|
||||
value[31] = 1 // big-endian value = 1
|
||||
txs[i] = Tx{
|
||||
Sender: sndr(i),
|
||||
Recipient: rcpt(i),
|
||||
Value: value,
|
||||
// GasPrice all-zero
|
||||
GasLimit: 21000,
|
||||
Nonce: 0,
|
||||
}
|
||||
}
|
||||
|
||||
var ctx BlockCtx
|
||||
ctx.Coinbase[19] = 0xFF
|
||||
ctx.BlockNumber = 1
|
||||
ctx.BlockTime = 1700000000
|
||||
ctx.BlockGasLimit = 30000000
|
||||
ctx.ChainID[31] = 1
|
||||
ctx.Revision = RevShanghai
|
||||
|
||||
res, err := ProcessBlock(accts, nil, txs, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("N=%d: ProcessBlock error: %v", c.n, err)
|
||||
}
|
||||
if !res.OK {
|
||||
t.Fatalf("N=%d: ProcessBlock ok=false", c.n)
|
||||
}
|
||||
got := hex.EncodeToString(res.StateRoot[:])
|
||||
if got != c.golden {
|
||||
t.Fatalf("N=%d: root mismatch\n got=0x%s\nwant=0x%s", c.n, got, c.golden)
|
||||
}
|
||||
t.Logf("N=%-4d cevm=0x%s == geth PASS", c.n, got)
|
||||
|
||||
if len(res.TxResults) != int(c.n) {
|
||||
t.Fatalf("N=%d: got %d tx results, want %d", c.n, len(res.TxResults), c.n)
|
||||
}
|
||||
for i, tr := range res.TxResults {
|
||||
if tr.Status != 1 || tr.GasUsed != 21000 || tr.NLogs != 0 || tr.Rejected {
|
||||
t.Fatalf("N=%d tx[%d] unexpected: status=%d gas=%d nlogs=%d rejected=%v",
|
||||
c.n, i, tr.Status, tr.GasUsed, tr.NLogs, tr.Rejected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build !cevm
|
||||
|
||||
package cevmbridge
|
||||
|
||||
// Enabled reports whether the real C++ EVM bridge is linked. It is false in the
|
||||
// default build; build with -tags cevm to link the cevm libs and get the real
|
||||
// process_block path.
|
||||
const Enabled = false
|
||||
|
||||
// ABIVersion returns 0 when the bridge is not linked.
|
||||
func ABIVersion() uint32 { return 0 }
|
||||
|
||||
// ProcessBlock is a no-op in the non-cevm build: it always declines with
|
||||
// ErrDisabled so callers fall through to the Go EVM.
|
||||
func ProcessBlock(_ []Account, _ []Storage, _ []Tx, _ BlockCtx) (Result, error) {
|
||||
return Result{}, ErrDisabled
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package cevmbridge is the cgo seam from Go into the Lux C++ EVM (cevm)
|
||||
// block-batch consensus entry point evm::state::process_block, exposed through
|
||||
// the extern "C" ABI declared in luxcpp/cevm/lib/evm/state/go_process_block.h.
|
||||
//
|
||||
// ProcessBlock seeds a fresh C++ StateDB from a caller-supplied read-only
|
||||
// snapshot of the pre-block accounts + storage, replays the block's
|
||||
// transactions through a real evmc_create_cevm() VM, and returns the REAL
|
||||
// keccak Merkle-Patricia-Trie state root (db.commit()) plus per-tx status and
|
||||
// gas. It NEVER mutates the caller's Go state — it is a pure function of its
|
||||
// inputs, which is exactly what a byte-exact shadow verifier needs.
|
||||
//
|
||||
// Two build variants share this one API:
|
||||
//
|
||||
// - //go:build cevm : bridge_cevm.go links the fresh build-mpt CPU libs and
|
||||
// calls the real cevm_process_block. Enabled == true.
|
||||
// - //go:build !cevm : bridge_stub.go is pure Go, ProcessBlock returns
|
||||
// ErrDisabled. Enabled == false. This keeps `go build ./...` green on any
|
||||
// machine (no hardcoded C++ lib paths, no cgo link) while the real path is
|
||||
// opt-in via -tags cevm, mirroring the existing backend_cevm.go convention.
|
||||
//
|
||||
// All multi-byte scalar encodings match go_process_block.h exactly:
|
||||
// - Account.Balance : 4 x uint64 little-endian limbs, [0] = low 64 bits.
|
||||
// - Tx.Value / Tx.GasPrice, Storage.Key / Storage.Value, and the BlockCtx
|
||||
// 256-bit fields : 32-byte BIG-endian.
|
||||
package cevmbridge
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrDisabled is returned by ProcessBlock when the package was built without
|
||||
// the `cevm` tag (the C++ EVM is not linked).
|
||||
var ErrDisabled = errors.New("cevmbridge: built without -tags cevm (C++ EVM not linked)")
|
||||
|
||||
// Account is a read-only snapshot of one pre-block account, materialized into
|
||||
// the cevm StateDB before the block runs.
|
||||
type Account struct {
|
||||
Address [20]byte
|
||||
Nonce uint64
|
||||
Balance [4]uint64 // little-endian limbs, [0] = low 64 bits
|
||||
Code []byte // nil/empty => EOA
|
||||
}
|
||||
|
||||
// Storage is one pre-existing contract storage slot.
|
||||
type Storage struct {
|
||||
Address [20]byte // owning account
|
||||
Key [32]byte // big-endian slot key
|
||||
Value [32]byte // big-endian slot value
|
||||
}
|
||||
|
||||
// Tx is one transaction to replay. Value and GasPrice are 32-byte big-endian.
|
||||
type Tx struct {
|
||||
Sender [20]byte
|
||||
Recipient [20]byte // ignored when IsCreate
|
||||
Value [32]byte // big-endian
|
||||
GasPrice [32]byte // big-endian
|
||||
GasLimit uint64
|
||||
Nonce uint64
|
||||
Data []byte // calldata or init code
|
||||
IsCreate bool
|
||||
}
|
||||
|
||||
// BlockCtx is the block / transaction context. The 256-bit fields are 32-byte
|
||||
// big-endian; Revision is an evmc_revision value (see the CEVM_PB_REV_* consts
|
||||
// mirrored below).
|
||||
type BlockCtx struct {
|
||||
Coinbase [20]byte
|
||||
BlockNumber int64
|
||||
BlockTime int64
|
||||
BlockGasLimit int64
|
||||
ChainID [32]byte // big-endian
|
||||
BaseFee [32]byte // big-endian
|
||||
PrevRandao [32]byte // big-endian
|
||||
BlobBaseFee [32]byte // big-endian
|
||||
Revision uint8
|
||||
}
|
||||
|
||||
// TxResult is the per-tx outcome cevm surfaced (index-aligned to the input txs).
|
||||
type TxResult struct {
|
||||
EVMCStatus int32 // raw evmc_status_code (SUCCESS=0, FAILURE=1, REVERT=2, ...)
|
||||
Status uint8 // Go receipt status: 1 iff EVMC_SUCCESS
|
||||
Rejected bool // true iff EVMC_REJECTED (nonce/intrinsic/balance) — not block-includable
|
||||
GasUsed uint64
|
||||
CumulativeGasUsed uint64
|
||||
NLogs uint32 // logs this tx emitted (0 => within the proven safe subset)
|
||||
}
|
||||
|
||||
// Result is the whole-block outcome.
|
||||
type Result struct {
|
||||
StateRoot [32]byte // REAL keccak Merkle-Patricia-Trie root
|
||||
TxResults []TxResult // length == len(txs) on success
|
||||
TotalGas uint64
|
||||
OK bool
|
||||
ABIVersion uint32
|
||||
}
|
||||
|
||||
// evmc_revision values (mirror go_process_block.h CEVM_PB_REV_*).
|
||||
const (
|
||||
RevFrontier uint8 = 0
|
||||
RevHomestead uint8 = 1
|
||||
RevTangerine uint8 = 2
|
||||
RevSpurious uint8 = 3
|
||||
RevByzantium uint8 = 4
|
||||
RevConstantinople uint8 = 5
|
||||
RevPetersburg uint8 = 6
|
||||
RevIstanbul uint8 = 7
|
||||
RevBerlin uint8 = 8
|
||||
RevLondon uint8 = 9
|
||||
RevParis uint8 = 10
|
||||
RevShanghai uint8 = 11
|
||||
RevCancun uint8 = 12
|
||||
)
|
||||
@@ -27,13 +27,14 @@ var (
|
||||
mu sync.RWMutex
|
||||
executor BlockExecutor
|
||||
accelerator GPUAccelerator
|
||||
txExecutors map[EVMBackend]TransactionExecutor
|
||||
activeBack EVMBackend = GoEVM
|
||||
)
|
||||
|
||||
func init() {
|
||||
// txExecutors is initialized at declaration (not in init()) so it exists
|
||||
// before ANY package init() runs. Backend files register from their own
|
||||
// init() (e.g. backend_cevm.go under -tags cevm), and Go orders init()
|
||||
// funcs alphabetically by filename — backend_cevm.go precedes parallel.go,
|
||||
// so an init()-time map creation would be too late and panic on a nil map.
|
||||
txExecutors = make(map[EVMBackend]TransactionExecutor)
|
||||
}
|
||||
activeBack = GoEVM
|
||||
)
|
||||
|
||||
// RegisterExecutor sets the parallel block executor.
|
||||
func RegisterExecutor(e BlockExecutor) {
|
||||
|
||||
Reference in New Issue
Block a user