The executor sends the header's difficulty as PREVRANDAO

It sent the MixDigest, which is zero on every Lux header. luxfi/evm's
NewEVMBlockContext answers 0x44 with the difficulty: from Shanghai on as
Random, and before it as DIFFICULTY, which is the same number. The block
context now carries the difficulty as a 32-byte word, and
TestPrevrandaoIsWhatTheGoEVMAnswers holds it to what NewEVMBlockContext
gives for the same header, before Shanghai and after.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-23 11:28:07 -07:00
co-authored by Hanzo Dev
parent 47028b9f41
commit 10f6937c1f
2 changed files with 67 additions and 6 deletions
+59 -2
View File
@@ -4,6 +4,7 @@
package parallel
import (
"context"
"errors"
"math/big"
"testing"
@@ -12,6 +13,8 @@ import (
"github.com/luxfi/chains/evm/cevm"
"github.com/luxfi/crypto/backend"
"github.com/luxfi/evm/consensus"
evmcore "github.com/luxfi/evm/core"
evmparallel "github.com/luxfi/evm/core/parallel"
"github.com/luxfi/evm/core/state"
"github.com/luxfi/geth/common"
@@ -417,6 +420,7 @@ func TestTheBlockContextIsTheHeader(t *testing.T) {
BaseFee: big.NewInt(25_000_000_000),
ExcessBlobGas: &excess,
Coinbase: common.Address{0xC0, 0xFF, 0xEE},
Difficulty: big.NewInt(0x1234),
MixDigest: common.Hash{0xAB, 0xCD},
}
cancun := uint64(0)
@@ -457,8 +461,52 @@ func TestTheBlockContextIsTheHeader(t *testing.T) {
if common.Address(got.Coinbase) != header.Coinbase {
t.Errorf("Coinbase = %x, want %x", got.Coinbase, header.Coinbase)
}
if common.Hash(got.Prevrandao) != header.MixDigest {
t.Errorf("Prevrandao = %x, want the header's MixDigest %x", got.Prevrandao, header.MixDigest)
if want := common.BigToHash(header.Difficulty); common.Hash(got.Prevrandao) != want {
t.Errorf("Prevrandao = %x, want the header's difficulty %x, not its MixDigest %x",
got.Prevrandao, want, header.MixDigest)
}
}
// PREVRANDAO (0x44) is what luxfi/evm's EVM answers for the same header: the
// block context's Random from Shanghai on, DIFFICULTY before it, and both are
// the header's difficulty. The MixDigest, which is zero on a Lux header, is
// neither: sending it would have every contract that reads 0x44 see a number
// the Go EVM does not give it.
func TestPrevrandaoIsWhatTheGoEVMAnswers(t *testing.T) {
shanghai := uint64(0)
for name, config := range map[string]*ethparams.ChainConfig{
"before Shanghai": {ChainID: big.NewInt(96369)},
"Shanghai": {ChainID: big.NewInt(96369), LondonBlock: big.NewInt(0), ShanghaiTime: &shanghai},
} {
t.Run(name, func(t *testing.T) {
header := &types.Header{
Number: big.NewInt(7),
Time: 7,
BaseFee: big.NewInt(1),
Difficulty: big.NewInt(0x1234),
MixDigest: common.Hash{0xAB, 0xCD},
}
evmCtx := evmcore.NewEVMBlockContext(header, chainOf{config}, nil)
want := common.BigToHash(evmCtx.Difficulty) // what DIFFICULTY pushes
if evmCtx.Random != nil {
want = *evmCtx.Random // what PREVRANDAO pushes
}
got, ok := blockContext(config, header)
if !ok {
t.Fatal("blockContext refused a header whose every field fits")
}
if common.Hash(got.Prevrandao) != want {
t.Errorf("Prevrandao = %x, the Go EVM answers %x", got.Prevrandao, want)
}
})
}
}
// A header without a difficulty gives a zero PREVRANDAO, not a dereference.
func TestAHeaderWithoutDifficultyHasAZeroPrevrandao(t *testing.T) {
got, ok := blockContext(chainConfig(), &types.Header{Number: big.NewInt(1)})
if !ok || got.Prevrandao != ([32]byte{}) {
t.Fatalf("blockContext = (%x, %v), want a zero Prevrandao", got.Prevrandao, ok)
}
}
@@ -905,6 +953,15 @@ func chainConfig() *ethparams.ChainConfig {
return &ethparams.ChainConfig{ChainID: big.NewInt(96369)}
}
// chainOf is the chain luxfi/evm's NewEVMBlockContext reads a config from, and
// nothing else.
type chainOf struct{ config *ethparams.ChainConfig }
func (chainOf) Engine() consensus.Engine { return nil }
func (chainOf) GetHeader(common.Hash, uint64) *types.Header { return nil }
func (c chainOf) Config() *ethparams.ChainConfig { return c.config }
func (chainOf) ConsensusContext() context.Context { return nil }
// senders is n distinct sender addresses, positionally paired with a block.
func senders(n int) []common.Address {
out := make([]common.Address, n)
+8 -4
View File
@@ -279,7 +279,8 @@ func fits(v *big.Int) (uint64, bool) {
// blockContext is the block-level execution context every transaction in the
// block sees: what TIMESTAMP, NUMBER, CHAINID, BASEFEE, COINBASE, GASLIMIT,
// PREVRANDAO and BLOBBASEFEE answer.
// PREVRANDAO and BLOBBASEFEE answer, as luxfi/evm's NewEVMBlockContext has
// them.
//
// It reports false when the chain id, the base fee or the blob base fee does
// not fit cevm's 64-bit wire. A base fee carried as anything else would let cevm pass a tx
@@ -310,9 +311,12 @@ func blockContext(config *ethparams.ChainConfig, header *types.Header) (cevm.Blo
ctx.BlobBaseFee = fee
}
copy(ctx.Coinbase[:], header.Coinbase.Bytes())
// Prevrandao = post-merge MixDigest. Pre-merge headers carry zero
// MixDigest; the cevm side treats zero as "not set".
copy(ctx.Prevrandao[:], header.MixDigest.Bytes())
// PREVRANDAO (0x44) is the header's difficulty as a 32-byte word:
// NewEVMBlockContext sets Random to it from Shanghai on, and DIFFICULTY
// before that answers the same number. A Lux header's MixDigest is zero.
if header.Difficulty != nil {
ctx.Prevrandao = common.BigToHash(header.Difficulty)
}
return ctx, true
}