Skip to content

Commit b1e72f7

Browse files
core/evm: RANDOM opcode (EIP-4399) (ethereum#24141)
* core: implement eip-4399 random opcode * core: make vmconfig threadsafe * core: miner: pass vmConfig by value not reference * all: enable 4399 by Rules * core: remove diff (f) * tests: set proper difficulty (f) * smaller diff (f) * eth/catalyst: nit * core: make RANDOM a pointer which is only set post-merge * cmd/evm/internal/t8ntool: fix t8n tracing of 4399 * tests: set difficulty * cmd/evm/internal/t8ntool: check that baserules are london before applying the merge chainrules
1 parent 1884f37 commit b1e72f7

File tree

21 files changed

+124
-29
lines changed

21 files changed

+124
-29
lines changed

cmd/evm/internal/t8ntool/execution.go

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type ommer struct {
6767
type stEnv struct {
6868
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
6969
Difficulty *big.Int `json:"currentDifficulty"`
70+
Random *big.Int `json:"currentRandom"`
7071
ParentDifficulty *big.Int `json:"parentDifficulty"`
7172
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
7273
Number uint64 `json:"currentNumber" gencodec:"required"`
@@ -81,6 +82,7 @@ type stEnv struct {
8182
type stEnvMarshaling struct {
8283
Coinbase common.UnprefixedAddress
8384
Difficulty *math.HexOrDecimal256
85+
Random *math.HexOrDecimal256
8486
ParentDifficulty *math.HexOrDecimal256
8587
GasLimit math.HexOrDecimal64
8688
Number math.HexOrDecimal64
@@ -139,6 +141,11 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
139141
if pre.Env.BaseFee != nil {
140142
vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee)
141143
}
144+
// If random is defined, add it to the vmContext.
145+
if pre.Env.Random != nil {
146+
rnd := common.BigToHash(pre.Env.Random)
147+
vmContext.Random = &rnd
148+
}
142149
// If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's
143150
// done in StateProcessor.Process(block, ...), right before transactions are applied.
144151
if chainConfig.DAOForkSupport &&

cmd/evm/internal/t8ntool/gen_stenv.go

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/evm/internal/t8ntool/transition.go

+4
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ func Transition(ctx *cli.Context) error {
252252
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
253253
}
254254
}
255+
// Sanity check, to not `panic` in state_transition
256+
if prestate.Env.Random != nil && !chainConfig.IsLondon(big.NewInt(int64(prestate.Env.Number))) {
257+
return NewError(ErrorConfig, errors.New("can only apply RANDOM on top of London chainrules"))
258+
}
255259
if env := prestate.Env; env.Difficulty == nil {
256260
// If difficulty was not provided by caller, we need to calculate it.
257261
switch {

consensus/beacon/consensus.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ var (
4343
// error types into the consensus package.
4444
var (
4545
errTooManyUncles = errors.New("too many uncles")
46-
errInvalidMixDigest = errors.New("invalid mix digest")
4746
errInvalidNonce = errors.New("invalid nonce")
4847
errInvalidUncleHash = errors.New("invalid uncle hash")
4948
)
@@ -182,10 +181,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
182181
if len(header.Extra) > 32 {
183182
return fmt.Errorf("extra-data longer than 32 bytes (%d)", len(header.Extra))
184183
}
185-
// Verify the seal parts. Ensure the mixhash, nonce and uncle hash are the expected value.
186-
if header.MixDigest != (common.Hash{}) {
187-
return errInvalidMixDigest
188-
}
184+
// Verify the seal parts. Ensure the nonce and uncle hash are the expected value.
189185
if header.Nonce != beaconNonce {
190186
return errInvalidNonce
191187
}

core/evm.go

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
4040
var (
4141
beneficiary common.Address
4242
baseFee *big.Int
43+
random *common.Hash
4344
)
4445

4546
// If we don't have an explicit author (i.e. not mining), extract from the header
@@ -51,6 +52,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
5152
if header.BaseFee != nil {
5253
baseFee = new(big.Int).Set(header.BaseFee)
5354
}
55+
if header.Difficulty.Cmp(common.Big0) == 0 {
56+
random = &header.MixDigest
57+
}
5458
return vm.BlockContext{
5559
CanTransfer: CanTransfer,
5660
Transfer: Transfer,
@@ -61,6 +65,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
6165
Difficulty: new(big.Int).Set(header.Difficulty),
6266
BaseFee: baseFee,
6367
GasLimit: header.GasLimit,
68+
Random: random,
6469
}
6570
}
6671

core/genesis.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
294294
if g.GasLimit == 0 {
295295
head.GasLimit = params.GenesisGasLimit
296296
}
297-
if g.Difficulty == nil {
297+
if g.Difficulty == nil && g.Mixhash == (common.Hash{}) {
298298
head.Difficulty = params.GenesisDifficulty
299299
}
300300
if g.Config != nil && g.Config.IsLondon(common.Big0) {

core/state_transition.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
310310
}
311311

312312
// Set up the initial access list.
313-
if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsBerlin {
313+
if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil); rules.IsBerlin {
314314
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
315315
}
316316
var (

core/vm/evm.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type BlockContext struct {
7575
Time *big.Int // Provides information for TIME
7676
Difficulty *big.Int // Provides information for DIFFICULTY
7777
BaseFee *big.Int // Provides information for BASEFEE
78+
Random *common.Hash // Provides information for RANDOM
7879
}
7980

8081
// TxContext provides the EVM with information about a transaction.
@@ -131,7 +132,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
131132
StateDB: statedb,
132133
Config: config,
133134
chainConfig: chainConfig,
134-
chainRules: chainConfig.Rules(blockCtx.BlockNumber),
135+
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil),
135136
}
136137
evm.interpreter = NewEVMInterpreter(evm, config)
137138
return evm

core/vm/instructions.go

+6
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,12 @@ func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
477477
return nil, nil
478478
}
479479

480+
func opRandom(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
481+
v := new(uint256.Int).SetBytes((interpreter.evm.Context.Random.Bytes()))
482+
scope.Stack.push(v)
483+
return nil, nil
484+
}
485+
480486
func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
481487
scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit))
482488
return nil, nil

core/vm/instructions_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/json"
2222
"fmt"
2323
"io/ioutil"
24+
"math/big"
2425
"testing"
2526

2627
"github.com/ethereum/go-ethereum/common"
@@ -654,3 +655,36 @@ func TestCreate2Addreses(t *testing.T) {
654655
}
655656
}
656657
}
658+
659+
func TestRandom(t *testing.T) {
660+
type testcase struct {
661+
name string
662+
random common.Hash
663+
}
664+
665+
for _, tt := range []testcase{
666+
{name: "empty hash", random: common.Hash{}},
667+
{name: "1", random: common.Hash{0}},
668+
{name: "emptyCodeHash", random: emptyCodeHash},
669+
{name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})},
670+
} {
671+
var (
672+
env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{})
673+
stack = newstack()
674+
pc = uint64(0)
675+
evmInterpreter = env.interpreter
676+
)
677+
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
678+
if len(stack.data) != 1 {
679+
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
680+
}
681+
actual := stack.pop()
682+
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
683+
if overflow {
684+
t.Errorf("Testcase %v: invalid overflow", tt.name)
685+
}
686+
if actual.Cmp(expected) != 0 {
687+
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
688+
}
689+
}
690+
}

core/vm/interpreter.go

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
6969
// If jump table was not initialised we set the default one.
7070
if cfg.JumpTable == nil {
7171
switch {
72+
case evm.chainRules.IsMerge:
73+
cfg.JumpTable = &mergeInstructionSet
7274
case evm.chainRules.IsLondon:
7375
cfg.JumpTable = &londonInstructionSet
7476
case evm.chainRules.IsBerlin:

core/vm/jump_table.go

+12
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ var (
5454
istanbulInstructionSet = newIstanbulInstructionSet()
5555
berlinInstructionSet = newBerlinInstructionSet()
5656
londonInstructionSet = newLondonInstructionSet()
57+
mergeInstructionSet = newMergeInstructionSet()
5758
)
5859

5960
// JumpTable contains the EVM opcodes supported at a given fork.
@@ -77,6 +78,17 @@ func validate(jt JumpTable) JumpTable {
7778
return jt
7879
}
7980

81+
func newMergeInstructionSet() JumpTable {
82+
instructionSet := newLondonInstructionSet()
83+
instructionSet[RANDOM] = &operation{
84+
execute: opRandom,
85+
constantGas: GasQuickStep,
86+
minStack: minStack(0, 1),
87+
maxStack: maxStack(0, 1),
88+
}
89+
return validate(instructionSet)
90+
}
91+
8092
// newLondonInstructionSet returns the frontier, homestead, byzantium,
8193
// contantinople, istanbul, petersburg, berlin and london instructions.
8294
func newLondonInstructionSet() JumpTable {

core/vm/opcodes.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const (
9595
TIMESTAMP OpCode = 0x42
9696
NUMBER OpCode = 0x43
9797
DIFFICULTY OpCode = 0x44
98+
RANDOM OpCode = 0x44 // Same as DIFFICULTY
9899
GASLIMIT OpCode = 0x45
99100
CHAINID OpCode = 0x46
100101
SELFBALANCE OpCode = 0x47
@@ -275,7 +276,7 @@ var opCodeToString = map[OpCode]string{
275276
COINBASE: "COINBASE",
276277
TIMESTAMP: "TIMESTAMP",
277278
NUMBER: "NUMBER",
278-
DIFFICULTY: "DIFFICULTY",
279+
DIFFICULTY: "DIFFICULTY", // TODO (MariusVanDerWijden) rename to RANDOM post merge
279280
GASLIMIT: "GASLIMIT",
280281
CHAINID: "CHAINID",
281282
SELFBALANCE: "SELFBALANCE",

core/vm/runtime/runtime.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
118118
vmenv = NewEnv(cfg)
119119
sender = vm.AccountRef(cfg.Origin)
120120
)
121-
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
121+
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin {
122122
cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
123123
}
124124
cfg.State.CreateAccount(address)
@@ -150,7 +150,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
150150
vmenv = NewEnv(cfg)
151151
sender = vm.AccountRef(cfg.Origin)
152152
)
153-
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
153+
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin {
154154
cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil)
155155
}
156156
// Call the code with the given configuration.
@@ -176,7 +176,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
176176
sender := cfg.State.GetOrNewStateObject(cfg.Origin)
177177
statedb := cfg.State
178178

179-
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
179+
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil); rules.IsBerlin {
180180
statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
181181
}
182182
// Call the code with the given configuration.

eth/catalyst/api.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ type blockExecutionEnv struct {
133133
}
134134

135135
func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
136-
vmconfig := *env.chain.GetVMConfig()
136+
vmConfig := *env.chain.GetVMConfig()
137137
snap := env.state.Snapshot()
138-
receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig)
138+
receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmConfig)
139139
if err != nil {
140140
env.state.RevertToSnapshot(snap)
141141
return err
@@ -318,6 +318,7 @@ func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAt
318318
GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype
319319
Extra: []byte{}, // TODO (MariusVanDerWijden) properly set extra data
320320
Time: params.Timestamp,
321+
MixDigest: params.Random,
321322
}
322323
if config := api.eth.BlockChain().Config(); config.IsLondon(header.Number) {
323324
header.BaseFee = misc.CalcBaseFee(config, parent.Header())
@@ -432,7 +433,7 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
432433
Time: params.Timestamp,
433434
BaseFee: params.BaseFeePerGas,
434435
Extra: params.ExtraData,
435-
// TODO (MariusVanDerWijden) add params.Random to header once required
436+
MixDigest: params.Random,
436437
}
437438
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
438439
if block.Hash() != params.BlockHash {

eth/tracers/js/tracer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
697697
jst.ctx["block"] = env.Context.BlockNumber.Uint64()
698698
jst.dbWrapper.db = env.StateDB
699699
// Update list of precompiles based on current block
700-
rules := env.ChainConfig().Rules(env.Context.BlockNumber)
700+
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
701701
jst.activePrecompiles = vm.ActivePrecompiles(rules)
702702

703703
// Compute intrinsic gas

eth/tracers/native/4byte.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
8383
t.env = env
8484

8585
// Update list of precompiles based on current block
86-
rules := env.ChainConfig().Rules(env.Context.BlockNumber)
86+
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
8787
t.activePrecompiles = vm.ActivePrecompiles(rules)
8888

8989
// Save the outer calldata also

internal/ethapi/api.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1432,8 +1432,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
14321432
} else {
14331433
to = crypto.CreateAddress(args.from(), uint64(*args.Nonce))
14341434
}
1435+
isPostMerge := header.Difficulty.Cmp(common.Big0) == 0
14351436
// Retrieve the precompiles since they don't need to be added to the access list
1436-
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number))
1437+
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge))
14371438

14381439
// Create an initial tracer
14391440
prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles)

params/config.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ var (
267267
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
268268

269269
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
270-
TestRules = TestChainConfig.Rules(new(big.Int))
270+
TestRules = TestChainConfig.Rules(new(big.Int), false)
271271
)
272272

273273
// TrustedCheckpoint represents a set of post-processed trie roots (CHT and
@@ -668,10 +668,11 @@ type Rules struct {
668668
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
669669
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
670670
IsBerlin, IsLondon bool
671+
IsMerge bool
671672
}
672673

673674
// Rules ensures c's ChainID is not nil.
674-
func (c *ChainConfig) Rules(num *big.Int) Rules {
675+
func (c *ChainConfig) Rules(num *big.Int, isMerge bool) Rules {
675676
chainID := c.ChainID
676677
if chainID == nil {
677678
chainID = new(big.Int)
@@ -688,5 +689,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
688689
IsIstanbul: c.IsIstanbul(num),
689690
IsBerlin: c.IsBerlin(num),
690691
IsLondon: c.IsLondon(num),
692+
IsMerge: isMerge,
691693
}
692694
}

0 commit comments

Comments
 (0)