Skip to content

Commit

Permalink
core: implement eip-4399 random opcode
Browse files Browse the repository at this point in the history
  • Loading branch information
MariusVanDerWijden committed Dec 20, 2021
1 parent 99be62a commit 461995d
Show file tree
Hide file tree
Showing 16 changed files with 110 additions and 15 deletions.
2 changes: 2 additions & 0 deletions cmd/evm/internal/t8ntool/execution.go
Expand Up @@ -67,6 +67,7 @@ type ommer struct {
type stEnv struct {
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
Difficulty *big.Int `json:"currentDifficulty"`
Random *big.Int `json:"currentRandom"`
ParentDifficulty *big.Int `json:"parentDifficulty"`
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
Number uint64 `json:"currentNumber" gencodec:"required"`
Expand All @@ -81,6 +82,7 @@ type stEnv struct {
type stEnvMarshaling struct {
Coinbase common.UnprefixedAddress
Difficulty *math.HexOrDecimal256
Random *math.HexOrDecimal256
ParentDifficulty *math.HexOrDecimal256
GasLimit math.HexOrDecimal64
Number math.HexOrDecimal64
Expand Down
6 changes: 6 additions & 0 deletions cmd/evm/internal/t8ntool/gen_stenv.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions consensus/beacon/consensus.go
Expand Up @@ -43,7 +43,6 @@ var (
// error types into the consensus package.
var (
errTooManyUncles = errors.New("too many uncles")
errInvalidMixDigest = errors.New("invalid mix digest")
errInvalidNonce = errors.New("invalid nonce")
errInvalidUncleHash = errors.New("invalid uncle hash")
)
Expand Down Expand Up @@ -182,10 +181,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
if len(header.Extra) > 32 {
return fmt.Errorf("extra-data longer than 32 bytes (%d)", len(header.Extra))
}
// Verify the seal parts. Ensure the mixhash, nonce and uncle hash are the expected value.
if header.MixDigest != (common.Hash{}) {
return errInvalidMixDigest
}
// Verify the seal parts. Ensure the nonce and uncle hash are the expected value.
if header.Nonce != beaconNonce {
return errInvalidNonce
}
Expand Down
6 changes: 6 additions & 0 deletions core/blockchain_reader.go
Expand Up @@ -349,6 +349,12 @@ func (bc *BlockChain) GetVMConfig() *vm.Config {
return &bc.vmConfig
}

// SetVMConfig sets the vm config.
// Warning: might not be threadsafe with other components
func (bc *BlockChain) SetVMConfig(config vm.Config) {
bc.vmConfig = config
}

// SetTxLookupLimit is responsible for updating the txlookup limit to the
// original one stored in db if the new mismatches with the old one.
func (bc *BlockChain) SetTxLookupLimit(limit uint64) {
Expand Down
1 change: 1 addition & 0 deletions core/evm.go
Expand Up @@ -61,6 +61,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
GasLimit: header.GasLimit,
Random: header.MixDigest,
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/genesis.go
Expand Up @@ -294,7 +294,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit
}
if g.Difficulty == nil {
if g.Difficulty == nil && g.Mixhash == (common.Hash{}) {
head.Difficulty = params.GenesisDifficulty
}
if g.Config != nil && g.Config.IsLondon(common.Big0) {
Expand Down
1 change: 1 addition & 0 deletions core/vm/evm.go
Expand Up @@ -75,6 +75,7 @@ type BlockContext struct {
Time *big.Int // Provides information for TIME
Difficulty *big.Int // Provides information for DIFFICULTY
BaseFee *big.Int // Provides information for BASEFEE
Random common.Hash // Provides information for RANDOM
}

// TxContext provides the EVM with information about a transaction.
Expand Down
6 changes: 6 additions & 0 deletions core/vm/instructions.go
Expand Up @@ -477,6 +477,12 @@ func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
return nil, nil
}

func opRandom(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
v := new(uint256.Int).SetBytes((interpreter.evm.Context.Random.Bytes()))
scope.Stack.push(v)
return nil, nil
}

func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit))
return nil, nil
Expand Down
34 changes: 34 additions & 0 deletions core/vm/instructions_test.go
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -654,3 +655,36 @@ func TestCreate2Addreses(t *testing.T) {
}
}
}

func TestRandom(t *testing.T) {
type testcase struct {
name string
random common.Hash
}

for _, tt := range []testcase{
{name: "empty hash", random: common.Hash{}},
{name: "1", random: common.Hash{0}},
{name: "emptyCodeHash", random: emptyCodeHash},
{name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})},
} {
var (
env = NewEVM(BlockContext{Random: tt.random}, TxContext{}, nil, params.TestChainConfig, Config{RandomOpcode: true})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes()))
if overflow {
t.Errorf("Testcase %v: invalid overflow", tt.name)
}
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
}
}
}
3 changes: 3 additions & 0 deletions core/vm/interpreter.go
Expand Up @@ -29,6 +29,7 @@ type Config struct {
Debug bool // Enables debugging
Tracer EVMLogger // Opcode logger
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
RandomOpcode bool // Enables the random opcode
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages

JumpTable *JumpTable // EVM instruction table, automatically populated if unset
Expand Down Expand Up @@ -69,6 +70,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// If jump table was not initialised we set the default one.
if cfg.JumpTable == nil {
switch {
case cfg.RandomOpcode:
cfg.JumpTable = &mergeInstructionSet
case evm.chainRules.IsLondon:
cfg.JumpTable = &londonInstructionSet
case evm.chainRules.IsBerlin:
Expand Down
12 changes: 12 additions & 0 deletions core/vm/jump_table.go
Expand Up @@ -54,6 +54,7 @@ var (
istanbulInstructionSet = newIstanbulInstructionSet()
berlinInstructionSet = newBerlinInstructionSet()
londonInstructionSet = newLondonInstructionSet()
mergeInstructionSet = newMergeInstructionSet()
)

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

func newMergeInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet()
instructionSet[RANDOM] = &operation{
execute: opRandom,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
return validate(instructionSet)
}

// newLondonInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg, berlin and london instructions.
func newLondonInstructionSet() JumpTable {
Expand Down
3 changes: 2 additions & 1 deletion core/vm/opcodes.go
Expand Up @@ -99,6 +99,7 @@ const (
CHAINID OpCode = 0x46
SELFBALANCE OpCode = 0x47
BASEFEE OpCode = 0x48
RANDOM OpCode = 0x44 // Same as DIFFICULTY
)

// 0x50 range - 'storage' and execution.
Expand Down Expand Up @@ -275,7 +276,7 @@ var opCodeToString = map[OpCode]string{
COINBASE: "COINBASE",
TIMESTAMP: "TIMESTAMP",
NUMBER: "NUMBER",
DIFFICULTY: "DIFFICULTY",
DIFFICULTY: "DIFFICULTY", // TODO (MariusVanDerWijden) rename to RANDOM post merge
GASLIMIT: "GASLIMIT",
CHAINID: "CHAINID",
SELFBALANCE: "SELFBALANCE",
Expand Down
9 changes: 9 additions & 0 deletions eth/catalyst/api.go
Expand Up @@ -134,6 +134,7 @@ type blockExecutionEnv struct {

func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
vmconfig := *env.chain.GetVMConfig()
vmconfig.RandomOpcode = true
snap := env.state.Snapshot()
receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig)
if err != nil {
Expand Down Expand Up @@ -276,6 +277,9 @@ func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePaylo
if td.Cmp(ttd) < 0 {
return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
}
conf := api.eth.BlockChain().GetVMConfig()
conf.RandomOpcode = true
api.eth.BlockChain().SetVMConfig(*conf)
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
return api.invalid(), err
}
Expand Down Expand Up @@ -318,7 +322,11 @@ func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAt
GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype
Extra: []byte{}, // TODO (MariusVanDerWijden) properly set extra data
Time: params.Timestamp,
MixDigest: params.Random,
}
conf := api.eth.BlockChain().GetVMConfig()
conf.RandomOpcode = true
api.eth.BlockChain().SetVMConfig(*conf)
if config := api.eth.BlockChain().Config(); config.IsLondon(header.Number) {
header.BaseFee = misc.CalcBaseFee(config, parent.Header())
}
Expand Down Expand Up @@ -432,6 +440,7 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
Time: params.Timestamp,
BaseFee: params.BaseFeePerGas,
Extra: params.ExtraData,
MixDigest: params.Random,
// TODO (MariusVanDerWijden) add params.Random to header once required
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
Expand Down
1 change: 1 addition & 0 deletions params/config.go
Expand Up @@ -672,6 +672,7 @@ type Rules struct {

// Rules ensures c's ChainID is not nil.
func (c *ChainConfig) Rules(num *big.Int) Rules {
// TODO (MariusVanDerWijden) replace isMerge after the merge once we know the merge block
chainID := c.ChainID
if chainID == nil {
chainID = new(big.Int)
Expand Down
15 changes: 10 additions & 5 deletions tests/gen_stenv.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions tests/state_test_util.go
Expand Up @@ -80,7 +80,8 @@ type stPostState struct {

type stEnv struct {
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"`
Difficulty *big.Int `json:"currentDifficulty" gencodec:"optional"`
Random *big.Int `json:"currentRandom" gencodec:"optional"`
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
Number uint64 `json:"currentNumber" gencodec:"required"`
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
Expand All @@ -90,6 +91,7 @@ type stEnv struct {
type stEnvMarshaling struct {
Coinbase common.UnprefixedAddress
Difficulty *math.HexOrDecimal256
Random *math.HexOrDecimal256
GasLimit math.HexOrDecimal64
Number math.HexOrDecimal64
Timestamp math.HexOrDecimal64
Expand Down Expand Up @@ -218,8 +220,12 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
if t.json.Env.Random != nil {
vmconfig.RandomOpcode = true
context.Random = common.BigToHash(t.json.Env.Random)
context.Difficulty = nil
}
evm := vm.NewEVM(context, txContext, statedb, config, vmconfig)

// Execute the message.
snapshot := statedb.Snapshot()
gaspool := new(core.GasPool)
Expand Down Expand Up @@ -268,7 +274,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
}

func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis {
return &core.Genesis{
genesis := &core.Genesis{
Config: config,
Coinbase: t.json.Env.Coinbase,
Difficulty: t.json.Env.Difficulty,
Expand All @@ -277,6 +283,12 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis {
Timestamp: t.json.Env.Timestamp,
Alloc: t.json.Pre,
}
if t.json.Env.Random != nil {
// Post-Merge
genesis.Mixhash = common.BigToHash(t.json.Env.Random)
genesis.Difficulty = nil
}
return genesis
}

func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (core.Message, error) {
Expand Down

0 comments on commit 461995d

Please sign in to comment.