Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/evm: Random opcode (EIP-4399) #24068

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
9 changes: 7 additions & 2 deletions core/blockchain.go
Expand Up @@ -209,6 +209,7 @@ type BlockChain struct {
processor Processor // Block transaction processor interface
forker *ForkChoice
vmConfig vm.Config
vmConfigMu *sync.RWMutex
}

// NewBlockChain returns a fully initialised block chain using information
Expand Down Expand Up @@ -245,6 +246,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
futureBlocks: futureBlocks,
engine: engine,
vmConfig: vmConfig,
vmConfigMu: &sync.RWMutex{},
}
bc.forker = NewForkChoice(bc, shouldPreserve)
bc.validator = NewBlockValidator(chainConfig, bc, engine)
Expand Down Expand Up @@ -1572,6 +1574,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
statedb.StartPrefetcher("chain")
activeState = statedb

bc.vmConfigMu.RLock()
vmConfig := bc.vmConfig
bc.vmConfigMu.RUnlock()
// If we have a followup block, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
var followupInterrupt uint32
Expand All @@ -1580,7 +1585,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps)

go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) {
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt)
bc.prefetcher.Prefetch(followup, throwaway, vmConfig, &followupInterrupt)

blockPrefetchExecuteTimer.Update(time.Since(start))
if atomic.LoadUint32(interrupt) == 1 {
Expand All @@ -1592,7 +1597,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)

// Process block using the parent state as reference point
substart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, vmConfig)
if err != nil {
bc.reportBlock(block, receipts, err)
atomic.StoreUint32(&followupInterrupt, 1)
Expand Down
13 changes: 11 additions & 2 deletions core/blockchain_reader.go
Expand Up @@ -345,8 +345,17 @@ func (bc *BlockChain) Genesis() *types.Block {
}

// GetVMConfig returns the block chain VM config.
func (bc *BlockChain) GetVMConfig() *vm.Config {
return &bc.vmConfig
func (bc *BlockChain) GetVMConfig() vm.Config {
bc.vmConfigMu.RLock()
defer bc.vmConfigMu.RUnlock()
return bc.vmConfig
}

// SetVMConfig sets the vm config.
func (bc *BlockChain) SetVMConfig(config vm.Config) {
bc.vmConfigMu.Lock()
defer bc.vmConfigMu.Unlock()
bc.vmConfig = config
}

// SetTxLookupLimit is responsible for updating the txlookup limit to the
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
3 changes: 2 additions & 1 deletion eth/api_backend.go
Expand Up @@ -204,7 +204,8 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
vmError := func() error { return nil }
if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig()
conf := b.eth.blockchain.GetVMConfig()
vmConfig = &conf
}
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
Expand Down
12 changes: 10 additions & 2 deletions eth/catalyst/api.go
Expand Up @@ -133,7 +133,8 @@ type blockExecutionEnv struct {
}

func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
vmconfig := *env.chain.GetVMConfig()
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,7 +440,7 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
Time: params.Timestamp,
BaseFee: params.BaseFeePerGas,
Extra: params.ExtraData,
// TODO (MariusVanDerWijden) add params.Random to header once required
MixDigest: params.Random,
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
if block.Hash() != params.BlockHash {
Expand Down
2 changes: 1 addition & 1 deletion miner/worker.go
Expand Up @@ -773,7 +773,7 @@ func (w *worker) updateSnapshot() {
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
snap := w.current.state.Snapshot()

receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig())
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, w.chain.GetVMConfig())
if err != nil {
w.current.state.RevertToSnapshot(snap)
return nil, err
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.