core/parallel: promote cevm from shadow-verifier to GATED APPLIER

cevm now ACTUALLY EXECUTES blocks (applies post-state) in luxfi/evm's block
executor, not just shadow-verifies the root — safely gated so it can never
diverge consensus.

Bridge (cevmbridge): ProcessBlock now surfaces the ABI-v2 post-state DELTA and
logs the C-ABI already exports — PostAccounts (address, absolute nonce/balance/
code, Deleted, CodeChanged), PostStorage (address, key, value), and Logs
(address, topics, data, tx_index) — alongside the root + per-tx results.

Executor (cevm_shadow.go): on the AGREE path (cevm root == header.Root) the
executor PROMOTES to applier under an APPLY-THEN-VERIFY-ELSE-DECLINE gate that
is consensus-safe BY CONSTRUCTION:

  - reconstruct receipts from cevm's per-tx {status, cumulative gas, gasUsed,
    logs} + txs (Type, TxHash, ContractAddress via crypto.CreateAddress for
    CREATE, per-receipt Bloom) mirroring core/state_processor.go applyTransaction;
  - replay cevm's post-state delta onto an ISOLATED statedb.Copy() and compute
    its IntermediateRoot(IsEIP158);
  - keep the writeback ONLY IF all FOUR quantities block_validator.ValidateState
    checks byte-match the header: state root, aggregate bloom, receipt-trie hash
    (DeriveSha), and cumulative gas. Then replay the identical delta onto the
    LIVE statedb and return the receipts — the Go EVM does NOT re-run the block;
  - on ANY mismatch, or an unrepresentable block (blob tx, cevm-rejected tx,
    tx-result count skew, missing preimage, too-large, disagree, finalize-gap),
    return (nil, nil) with the LIVE statedb UNTOUCHED so the Go EVM applies.

The trial runs on a deep Copy() rather than Snapshot()/RevertToSnapshot()
because IntermediateRoot -> Finalise -> clearJournalAndRefund invalidates the
journal, making a post-IntermediateRoot revert unreliable; verifying on a copy
leaves the live statedb pristine on every decline (nothing to revert). A wrong
or incomplete delta can therefore never reach the live state.

New honest counters: Applied (four-way gate passed, cevm EXECUTED the block) and
RevertedMismatch (agreed on root but the gated writeback/gate failed → declined
to Go). Invariant: Applied + RevertedMismatch == Agree.

Proven (-tags cevm, build-mpt CPU): the e2e test drives 256 real InsertChain
blocks (768 transfers) through Process -> ExecuteBlock and reports
Applied:256 RevertedMismatch:0 Disagree:0 — cevm executed every block in the
proven safe subset and InsertChain succeeded (ValidateState's four-way check
passed on the written-back state). Whole ./core suite stays GREEN: 9794 blocks
observed, all safely declining (preimages off => cannot seed cevm) and falling
through to Go, no regression. ./core/parallel suite green.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-19 21:05:12 -07:00
co-authored by Hanzo Dev
parent c435ce5aea
commit 3c323394d3
4 changed files with 414 additions and 65 deletions
+17 -9
View File
@@ -14,13 +14,16 @@ import (
)
// TestMain (cevm build) runs the full ./core suite and then prints the aggregate
// cevm shadow-verifier counters. Because core/state_processor.go routes EVERY
// block through parallel.DefaultExecutor().ExecuteBlock — which under -tags cevm
// is the cevmShadowExecutor — these counters are the honest, whole-suite tally
// of how many real test blocks cevm's independent real-MPT process_block root
// byte-matched the Go EVM's header.Root. It NEVER applies state: the shadow
// always returns (nil,nil), so the Go path applies every block and the suite
// result is unaffected by cevm.
// cevm shadow-verifier / applier counters. Because core/state_processor.go routes
// EVERY block through parallel.DefaultExecutor().ExecuteBlock — which under -tags
// cevm is the cevmShadowExecutor — these counters are the honest, whole-suite
// tally of how many real test blocks cevm's independent real-MPT process_block
// root byte-matched the Go EVM's header.Root, and among those how many cevm went
// on to APPLY (four-way byte-match against the header, post-state written into the
// Go statedb, receipts returned) versus declined back to Go. Declined blocks fall
// through to the Go EVM with the live statedb untouched, so the suite result is
// unaffected by cevm; applied blocks are byte-identical by construction (the gate
// keeps a writeback only on a full state-root/bloom/receipt-hash/gas match).
//
// The default (!cevm) build keeps the goleak TestMain in main_test.go; this
// variant drops goleak (orthogonal to the cevm reporting run) so the shadow
@@ -29,11 +32,13 @@ func TestMain(m *testing.M) {
code := m.Run()
s := parallel.CevmShadowStats()
total := s.Processed + s.DeclinedTooLarge + s.DeclinedNoPreimage + s.DeclinedTx + s.Errored
fmt.Printf("\n===== CEVM SHADOW VERIFIER — whole ./core suite tally =====\n")
fmt.Printf("\n===== CEVM SHADOW VERIFIER / APPLIER — whole ./core suite tally =====\n")
fmt.Printf("cevm shadow enabled : %v\n", parallel.CevmShadowEnabled())
fmt.Printf("blocks observed : %d (every block InsertChain/Process ran through ExecuteBlock)\n", total)
fmt.Printf(" processed (cevm ran process_block) : %d\n", s.Processed)
fmt.Printf(" agree (cevm root == Go header.Root, byte-exact) : %d\n", s.Agree)
fmt.Printf(" applied (four-way gate passed; cevm EXECUTED the block) : %d\n", s.Applied)
fmt.Printf(" reverted (gated writeback mismatch → declined to Go) : %d\n", s.RevertedMismatch)
fmt.Printf(" disagree : %d\n", s.Disagree)
fmt.Printf(" finalize-gap (withdrawals move header.Root) : %d\n", s.FinalizeGap)
fmt.Printf(" declined-too-large (pre-state over snapshot caps) : %d\n", s.DeclinedTooLarge)
@@ -43,6 +48,9 @@ func TestMain(m *testing.M) {
if s.Processed > 0 {
fmt.Printf("agreement rate over PROCESSED blocks : %d/%d\n", s.Agree, s.Processed)
}
fmt.Printf("===========================================================\n")
if s.Agree > 0 {
fmt.Printf("apply rate over AGREED blocks : %d/%d\n", s.Applied, s.Agree)
}
fmt.Printf("=====================================================================\n")
os.Exit(code)
}
+270 -50
View File
@@ -4,32 +4,50 @@
package parallel
// cevm_shadow.go wires the Lux C++ EVM (cevm) into the block executor as a
// byte-exact SHADOW VERIFIER, not an applier.
// consensus-SAFE, apply-then-verify GATED APPLIER (a strict superset of the
// original byte-exact shadow verifier).
//
// 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.
// (block_validator.go), NOT from cevm's returned root. So for cevm to be
// the APPLIER it must write its post-execution state into the Go statedb,
// after which block_validator recomputes IntermediateRoot over that
// written-back state — which must equal cevm's root (== header.Root).
//
// 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.
// to get cevm's independent keccak-MPT root + per-tx results + logs + a
// POST-STATE DELTA (ABI v2), (c) cross-checks cevm's root against header.Root
// (the post-state root the Go EVM produced when the block was built), tallying
// agree/disagree/finalize-gap/declined counters.
//
// 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.
// When the roots AGREE, it PROMOTES to applier under a four-way safety gate:
// - write cevm's post-state delta into an ISOLATED COPY of the Go statedb;
// - compute that copy's IntermediateRoot(IsEIP158) and rebuild the receipts;
// - keep the writeback ONLY IF all four consensus quantities byte-match the
// header: state root, aggregate bloom, receipt-trie hash, and cumulative
// gas. Then apply the identical delta to the LIVE statedb and return the
// receipts — cevm has now EXECUTED the block and the Go EVM will NOT re-run
// it. If ANY of the four mismatches (or a tx was rejected, or a blob tx is
// present), the live statedb is NEVER touched and it returns (nil, nil) so
// the Go EVM applies. A wrong or incomplete delta can therefore never be
// committed — the gate is consensus-safe BY CONSTRUCTION.
//
// The trial writeback runs on statedb.Copy() (not the live statedb) precisely
// because IntermediateRoot -> Finalise -> clearJournalAndRefund invalidates the
// journal, which would make a post-hoc RevertToSnapshot unreliable. Verifying on
// an isolated copy and only replaying onto the live statedb after a full match
// sidesteps that entirely: on any mismatch there is nothing to revert.
//
// The proven safe subset (pure value transfers + non-reverting calls into
// existing code) yields a byte-exact match and is APPLIED by cevm; everything
// else (top-level CREATE, REVERT/OOG, EIP-158 empty-account divergence, EIP-2930
// access lists, EIP-3529 refunds, withdrawals, or a state too large to snapshot)
// is tallied as a decline/disagree/reverted-mismatch and the Go EVM applies it
// unchallenged. Declined blocks fall through with the live state untouched, so
// the suite stays correct.
//
// This file is intentionally NOT behind //go:build cevm: it always compiles and
// always registers. When built without -tags cevm, cevmbridge.Enabled is false
@@ -42,12 +60,15 @@ import (
"github.com/holiman/uint256"
"github.com/luxfi/crypto"
"github.com/luxfi/evm/core/parallel/cevmbridge"
"github.com/luxfi/evm/core/state"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/tracing"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/core/vm"
ethparams "github.com/luxfi/geth/params"
"github.com/luxfi/geth/trie"
)
// Snapshot caps. A block whose pre-state exceeds these is declined (counted as
@@ -68,9 +89,13 @@ var (
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
cevmApplied uint64 // cevm APPLIED the block: four-way gate passed, receipts returned
cevmRevertedMismatch uint64 // agreed on root but the gated writeback/gate failed → declined to Go
)
// ShadowStats is a snapshot of the cevm shadow-verifier counters.
// ShadowStats is a snapshot of the cevm shadow-verifier / applier counters.
// Invariant: Applied + RevertedMismatch == Agree — every agreeing block is put
// through the four-way apply gate, which either applies it or declines to Go.
type ShadowStats struct {
Processed uint64
Agree uint64
@@ -80,6 +105,8 @@ type ShadowStats struct {
DeclinedNoPreimage uint64
DeclinedTx uint64
Errored uint64
Applied uint64
RevertedMismatch uint64
}
// CevmShadowStats returns the current cevm shadow-verifier counters.
@@ -93,6 +120,8 @@ func CevmShadowStats() ShadowStats {
DeclinedNoPreimage: atomic.LoadUint64(&cevmDeclinedNoPreimage),
DeclinedTx: atomic.LoadUint64(&cevmDeclinedTx),
Errored: atomic.LoadUint64(&cevmErrored),
Applied: atomic.LoadUint64(&cevmApplied),
RevertedMismatch: atomic.LoadUint64(&cevmRevertedMismatch),
}
}
@@ -101,6 +130,7 @@ func ResetCevmShadowStats() {
for _, p := range []*uint64{
&cevmProcessed, &cevmAgree, &cevmDisagree, &cevmFinalizeGap,
&cevmDeclinedTooLarge, &cevmDeclinedNoPreimage, &cevmDeclinedTx, &cevmErrored,
&cevmApplied, &cevmRevertedMismatch,
} {
atomic.StoreUint64(p, 0)
}
@@ -120,42 +150,51 @@ func init() {
}
}
// cevmShadowExecutor is a BlockExecutor that only ever verifies: it returns
// (nil, nil) unconditionally so the Go sequential path applies the block.
// cevmShadowExecutor is a BlockExecutor that verifies cevm's independent
// keccak-MPT root against the header and, when they byte-match under the
// four-way gate, APPLIES cevm's post-state into the live statedb and returns its
// receipts; on any mismatch or decline reason it returns (nil, nil) so the Go
// EVM applies the block, leaving the live statedb untouched.
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.
// ExecuteBlock runs the cevm cross-check and, on a four-way byte-match, APPLIES
// cevm's execution by writing its post-state delta into the live statedb and
// returning the reconstructed receipts. On any mismatch, decline reason, or
// internal failure it returns (nil, nil) with the live statedb untouched, so the
// Go EVM applies the block — cevm can therefore never destabilise consensus.
func (cevmShadowExecutor) ExecuteBlock(
config *ethparams.ChainConfig,
header *types.Header,
txs types.Transactions,
statedb *state.StateDB,
_ vm.Config,
) ([]*types.Receipt, error) {
) (receipts []*types.Receipt, err 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.
// A panic anywhere in the snapshot, bridge, or gate must NEVER destabilise
// consensus: swallow it, count it, and decline. All risky work (bridge cgo,
// trial writeback, root/receipt hashing) runs BEFORE the single live
// writeback, so a panic always leaves the live statedb pristine.
defer func() {
if r := recover(); r != nil {
atomic.AddUint64(&cevmErrored, 1)
receipts, err = nil, nil
}
}()
shadowVerify(config, header, txs, statedb)
return nil, nil
return shadowApply(config, header, txs, statedb), nil
}
// shadowVerify performs the read-only snapshot, cevm replay, and root
// cross-check, updating exactly one counter.
func shadowVerify(
// shadowApply performs the read-only snapshot, cevm replay, and root
// cross-check, updating exactly one verifier counter. When cevm's root
// byte-matches header.Root it PROMOTES to applier via applyAgreedBlock, returning
// cevm's receipts (block APPLIED by cevm) or nil (declined; the Go EVM applies).
func shadowApply(
config *ethparams.ChainConfig,
header *types.Header,
txs types.Transactions,
statedb *state.StateDB,
) {
) []*types.Receipt {
// (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.
@@ -164,23 +203,26 @@ func shadowVerify(
switch {
case snap.tooLarge:
atomic.AddUint64(&cevmDeclinedTooLarge, 1)
return
return nil
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
return nil
}
// (b) Build the cevm tx array from the block's transactions.
// (b) Build the cevm tx array from the block's transactions, retaining each
// recovered sender for ContractAddress reconstruction on CREATE receipts.
signer := types.MakeSigner(config, header.Number, header.Time)
ctxs := make([]cevmbridge.Tx, len(txs))
froms := make([]common.Address, len(txs))
for i, tx := range txs {
from, err := types.Sender(signer, tx)
if err != nil {
atomic.AddUint64(&cevmDeclinedTx, 1)
return
return nil
}
froms[i] = from
var t cevmbridge.Tx
copy(t.Sender[:], from[:])
if to := tx.To(); to != nil {
@@ -198,29 +240,207 @@ func shadowVerify(
ctxs[i] = t
}
// (c) cevm's independent root over the same pre-state + txs.
// (c) cevm's independent root + per-tx results + logs + post-state delta 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
return nil
}
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
if common.BytesToHash(res.StateRoot[:]) != header.Root {
// 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)
} else {
atomic.AddUint64(&cevmDisagree, 1)
}
return nil
}
// 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(&cevmAgree, 1)
// Roots agree: promote to APPLIER under the four-way safety gate. On any
// mismatch the gate declines with the live statedb untouched (Go EVM applies).
receipts := applyAgreedBlock(config, header, txs, froms, statedb, res)
if receipts == nil {
atomic.AddUint64(&cevmRevertedMismatch, 1)
return nil
}
atomic.AddUint64(&cevmApplied, 1)
return receipts
}
// applyAgreedBlock is entered ONLY when cevm's root already byte-matches
// header.Root. It promotes cevm from verifier to APPLIER under the four-way
// consensus gate — the identical quantities block_validator.ValidateState
// checks: state root, aggregate bloom, receipt-trie hash, and cumulative gas.
//
// It reconstructs the receipts, replays cevm's post-state delta onto an ISOLATED
// COPY of the statedb, and keeps the writeback ONLY IF all four byte-match the
// header. On a full match it replays the identical delta onto the LIVE statedb
// and returns the receipts (cevm has executed the block); on ANY mismatch — or an
// unrepresentable block (blob tx, cevm-rejected tx, tx-result count skew) — it
// returns nil WITHOUT touching the live statedb, so the Go EVM applies.
//
// The trial runs on statedb.Copy() rather than a Snapshot()/RevertToSnapshot()
// pair because IntermediateRoot → Finalise → clearJournalAndRefund invalidates
// the journal, making a post-IntermediateRoot revert unreliable. Verifying on a
// deep copy leaves the live statedb pristine on every decline (nothing to
// revert) and is consensus-safe by construction: a wrong or incomplete delta can
// never reach the live state.
func applyAgreedBlock(
config *ethparams.ChainConfig,
header *types.Header,
txs types.Transactions,
froms []common.Address,
statedb *state.StateDB,
res cevmbridge.Result,
) []*types.Receipt {
n := len(txs)
// Unrepresentable-block pre-checks: decline before any state work.
if len(res.TxResults) != n {
return nil
}
for i := range txs {
if txs[i].Type() == types.BlobTxType {
return nil // blob receipts carry BlobGasUsed/Price we do not reconstruct
}
if res.TxResults[i].Rejected {
return nil // block includes a tx cevm deems non-includable
}
}
receipts := buildCevmReceipts(header, txs, froms, res)
// Trial writeback + verification on an isolated deep copy — the live statedb
// is NOT touched here.
trial := statedb.Copy()
writePostState(trial, res)
root := trial.IntermediateRoot(config.IsEIP158(header.Number))
// FOUR-WAY consensus gate. Any mismatch ⇒ decline; live statedb untouched.
if root != header.Root {
return nil
}
var rbloom types.Bloom
for _, r := range receipts {
b := types.CreateBloom(r)
for k := range rbloom {
rbloom[k] |= b[k]
}
}
if rbloom != header.Bloom {
return nil
}
if types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil)) != header.ReceiptHash {
return nil
}
if receipts[n-1].CumulativeGasUsed != header.GasUsed {
return nil
}
// All four byte-match: cevm's execution reproduces the header exactly. Replay
// the identical delta onto the LIVE statedb and hand back cevm's receipts —
// state_processor will NOT re-run the Go EVM for this block.
writePostState(statedb, res)
return receipts
}
// buildCevmReceipts reconstructs the block's receipts from cevm's per-tx results
// + emitted logs, mirroring core/state_processor.go applyTransaction (post-
// Byzantium: PostState=nil, Status from the per-tx flag, ContractAddress via
// crypto.CreateAddress for CREATE, logs attached by tx index with a block-wide
// running index, per-receipt Bloom). The result is gate-checked against the
// header before any receipt is returned.
func buildCevmReceipts(
header *types.Header,
txs types.Transactions,
froms []common.Address,
res cevmbridge.Result,
) []*types.Receipt {
blockHash := header.Hash()
blockNum := header.Number
receipts := make([]*types.Receipt, len(txs))
var logIndex uint
for i, tx := range txs {
tr := res.TxResults[i]
r := &types.Receipt{
Type: tx.Type(),
Status: uint64(tr.Status),
CumulativeGasUsed: tr.CumulativeGasUsed,
TxHash: tx.Hash(),
GasUsed: tr.GasUsed,
BlockHash: blockHash,
BlockNumber: blockNum,
TransactionIndex: uint(i),
}
if tx.To() == nil {
var from crypto.Address
copy(from[:], froms[i][:])
created := crypto.CreateAddress(from, tx.Nonce())
r.ContractAddress = common.BytesToAddress(created[:])
}
var txLogs []*types.Log
for k := range res.Logs {
lg := &res.Logs[k]
if lg.TxIndex != uint32(i) {
continue
}
l := &types.Log{
Address: common.BytesToAddress(lg.Address[:]),
Topics: make([]common.Hash, len(lg.Topics)),
Data: append([]byte(nil), lg.Data...),
BlockNumber: blockNum.Uint64(),
TxHash: tx.Hash(),
TxIndex: uint(i),
BlockHash: blockHash,
BlockTimestamp: header.Time,
Index: logIndex,
}
for t := range lg.Topics {
l.Topics[t] = common.BytesToHash(lg.Topics[t][:])
}
txLogs = append(txLogs, l)
logIndex++
}
r.Logs = txLogs
r.Bloom = types.CreateBloom(r)
receipts[i] = r
}
return receipts
}
// writePostState replays cevm's post-state DELTA onto sdb using ABSOLUTE setters.
// It is the ONE writeback, applied first to an isolated Copy for verification
// and, only after the four-way gate passes, to the live statedb — so the copy
// and the live state receive byte-identical mutations. cevm emits ABSOLUTE post
// values (SetNonce/SetBalance/SetCode/SetState), a Deleted flag for
// selfdestructed accounts (Finalise drops the object and its storage under the
// promoted deletion), and omits storage rows for deleted accounts.
func writePostState(sdb *state.StateDB, res cevmbridge.Result) {
for i := range res.PostAccounts {
pa := &res.PostAccounts[i]
addr := common.BytesToAddress(pa.Address[:])
if pa.Deleted {
sdb.SelfDestruct(addr)
continue
}
sdb.SetNonce(addr, pa.Nonce, tracing.NonceChangeUnspecified)
sdb.SetBalance(addr, &uint256.Int{pa.Balance[0], pa.Balance[1], pa.Balance[2], pa.Balance[3]}, tracing.BalanceChangeUnspecified)
if pa.CodeChanged {
sdb.SetCode(addr, pa.Code, tracing.CodeChangeUnspecified)
}
}
for i := range res.PostStorage {
ps := &res.PostStorage[i]
sdb.SetState(common.BytesToAddress(ps.Address[:]), common.BytesToHash(ps.Key[:]), common.BytesToHash(ps.Value[:]))
}
atomic.AddUint64(&cevmDisagree, 1)
}
// effectiveGasPrice mirrors evm/core/state_transition.go: the price used for the
+79
View File
@@ -173,5 +173,84 @@ func ProcessBlock(accts []Account, storage []Storage, txs []Tx, ctx BlockCtx) (R
}
}
}
// --- logs (ABI v2) ---------------------------------------------------
// Marshal the emitted logs so the applier can rebuild receipt bloom +
// receipt-trie hash (statedb.GetLogs is empty when cevm — not the Go EVM —
// executed). Topics are flat n_topics*32 big-endian.
if n := int(r.n_logs); n > 0 && r.logs != nil {
clogs := unsafe.Slice((*C.cevm_pb_log)(unsafe.Pointer(r.logs)), n)
res.Logs = make([]Log, n)
for i := 0; i < n; i++ {
lg := Log{TxIndex: uint32(clogs[i].tx_index)}
for j := 0; j < 20; j++ {
lg.Address[j] = byte(clogs[i].address[j])
}
if nt := int(clogs[i].n_topics); nt > 0 && clogs[i].topics != nil {
tb := unsafe.Slice((*C.uint8_t)(unsafe.Pointer(clogs[i].topics)), nt*32)
lg.Topics = make([][32]byte, nt)
for t := 0; t < nt; t++ {
for b := 0; b < 32; b++ {
lg.Topics[t][b] = byte(tb[t*32+b])
}
}
}
if dl := int(clogs[i].data_len); dl > 0 && clogs[i].data != nil {
db := unsafe.Slice((*C.uint8_t)(unsafe.Pointer(clogs[i].data)), dl)
lg.Data = make([]byte, dl)
for b := 0; b < dl; b++ {
lg.Data[b] = byte(db[b])
}
}
res.Logs[i] = lg
}
}
// --- post-state account deltas (ABI v2) ------------------------------
if n := int(r.n_out_accts); n > 0 && r.out_accts != nil {
ca := unsafe.Slice((*C.cevm_pb_out_account)(unsafe.Pointer(r.out_accts)), n)
res.PostAccounts = make([]PostAccount, n)
for i := 0; i < n; i++ {
pa := PostAccount{
Nonce: uint64(ca[i].nonce),
Deleted: ca[i].deleted != 0,
CodeChanged: ca[i].code_changed != 0,
}
for j := 0; j < 20; j++ {
pa.Address[j] = byte(ca[i].address[j])
}
for j := 0; j < 4; j++ {
pa.Balance[j] = uint64(ca[i].balance[j])
}
for j := 0; j < 32; j++ {
pa.CodeHash[j] = byte(ca[i].code_hash[j])
}
if cl := int(ca[i].code_len); cl > 0 && ca[i].code != nil {
cb := unsafe.Slice((*C.uint8_t)(unsafe.Pointer(ca[i].code)), cl)
pa.Code = make([]byte, cl)
for b := 0; b < cl; b++ {
pa.Code[b] = byte(cb[b])
}
}
res.PostAccounts[i] = pa
}
}
// --- post-state storage deltas (ABI v2) ------------------------------
if n := int(r.n_out_storage); n > 0 && r.out_storage != nil {
cs := unsafe.Slice((*C.cevm_pb_out_storage)(unsafe.Pointer(r.out_storage)), n)
res.PostStorage = make([]PostStorage, n)
for i := 0; i < n; i++ {
var ps PostStorage
for j := 0; j < 20; j++ {
ps.Address[j] = byte(cs[i].address[j])
}
for j := 0; j < 32; j++ {
ps.Key[j] = byte(cs[i].key[j])
ps.Value[j] = byte(cs[i].value[j])
}
res.PostStorage[i] = ps
}
}
return res, nil
}
+48 -6
View File
@@ -86,13 +86,55 @@ type TxResult struct {
NLogs uint32 // logs this tx emitted (0 => within the proven safe subset)
}
// Result is the whole-block outcome.
// Log is one EVM log (LOG0..LOG4) cevm emitted. The three fields are the
// consensus fields (Address, Topics, Data); TxIndex identifies the emitting tx
// so the applier can attach the log to the right receipt. Logs arrive in tx
// order (the flat index is the block-wide log index).
type Log struct {
Address [20]byte
Topics [][32]byte // each 32-byte big-endian topic
Data []byte
TxIndex uint32
}
// PostAccount is one post-state account delta (DIFF final-vs-seed): an account
// the block CREATED or whose nonce/balance/code CHANGED, or (Deleted) a seed
// account the block REMOVED (selfdestruct). Values are ABSOLUTE post values —
// the applier writes them with SetNonce/SetBalance/SetCode. Code is present
// only when CodeChanged; otherwise the applier keeps the seed code.
type PostAccount struct {
Address [20]byte
Nonce uint64
Balance [4]uint64 // little-endian limbs, [0] = low 64 bits (absolute)
CodeHash [32]byte // final keccak256(code)
Code []byte // non-nil iff CodeChanged
Deleted bool // true => remove the account (selfdestruct)
CodeChanged bool // true => Code differs from seed; apply SetCode
}
// PostStorage is one post-state storage delta: a slot whose POST value differs
// from the seed. Value all-zero encodes a CLEARED slot (SetState(key,0)). Slots
// of a selfdestructed account are covered by its PostAccount.Deleted and are not
// listed here.
type PostStorage struct {
Address [20]byte
Key [32]byte // big-endian slot key
Value [32]byte // big-endian post value (all-zero => cleared)
}
// Result is the whole-block outcome. StateRoot + TxResults + TotalGas are what a
// shadow VERIFIER needs; Logs + PostAccounts + PostStorage are the additional
// ABI-v2 export an APPLIER needs to write cevm's post-state into the Go StateDB
// and rebuild the receipts.
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
StateRoot [32]byte // REAL keccak Merkle-Patricia-Trie root
TxResults []TxResult // length == len(txs) on success
Logs []Log // all txs' logs, in tx order (nil if none)
PostAccounts []PostAccount // post-state account deltas (nil if none)
PostStorage []PostStorage // post-state storage deltas (nil if none)
TotalGas uint64
OK bool
ABIVersion uint32
}
// evmc_revision values (mirror go_process_block.h CEVM_PB_REV_*).