From 1fd9d20e14e69109dde4c2004ec90168bc5012c5 Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Thu, 24 Aug 2023 17:10:50 +0200 Subject: [PATCH] EIP-4788 v2 (no precompile) (#8038) See https://github.com/ethereum/EIPs/pull/7456 & https://github.com/ethereum/go-ethereum/pull/27849. Also set the gas limit for system calls to 30M (previously 2^64-1), which is in line with the [Gnosis spec](https://github.com/gnosischain/specs/blob/master/execution/withdrawals.md#specification), but should be doubled checked for Gnosis Chain. --- consensus/bor/bor.go | 1 - consensus/merge/merge.go | 4 +- consensus/misc/eip4788.go | 19 +++---- consensus/misc/eip4788_test.go | 94 ---------------------------------- core/blockchain.go | 9 +++- core/vm/contracts.go | 77 +++++----------------------- core/vm/contracts_test.go | 8 +-- core/vm/evm.go | 2 +- core/vm/operations_acl.go | 8 +-- params/protocol_params.go | 10 ++-- tests/exec_spec_test.go | 3 +- 11 files changed, 40 insertions(+), 195 deletions(-) delete mode 100644 consensus/misc/eip4788_test.go diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index bc1fd76fc07..ae358ba7fbf 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -66,7 +66,6 @@ var ( // diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures validatorHeaderBytesLength = length.Addr + 20 // address + power - // systemAddress = libcommon.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") ) // Various error messages to mark blocks invalid. These should be private to diff --git a/consensus/merge/merge.go b/consensus/merge/merge.go index 7aeb7c6d225..05662c2ab3b 100644 --- a/consensus/merge/merge.go +++ b/consensus/merge/merge.go @@ -272,7 +272,9 @@ func (s *Merge) Initialize(config *chain.Config, chain consensus.ChainHeaderRead s.eth1Engine.Initialize(config, chain, header, state, syscall) } if chain.Config().IsCancun(header.Time) { - misc.ApplyBeaconRootEip4788(chain, header, state) + misc.ApplyBeaconRootEip4788(header.ParentBeaconBlockRoot, func(addr libcommon.Address, data []byte) ([]byte, error) { + return syscall(addr, data, state, header, false /* constCall */) + }) } } diff --git a/consensus/misc/eip4788.go b/consensus/misc/eip4788.go index e95b1c51ece..26293004b28 100644 --- a/consensus/misc/eip4788.go +++ b/consensus/misc/eip4788.go @@ -1,23 +1,16 @@ package misc import ( - "github.com/holiman/uint256" + "github.com/ledgerwatch/log/v3" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/consensus" - "github.com/ledgerwatch/erigon/core/state" - "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/params" ) -func ApplyBeaconRootEip4788(chain consensus.ChainHeaderReader, header *types.Header, state *state.IntraBlockState) { - historyStorageAddress := libcommon.BytesToAddress(params.HistoryStorageAddress) - historicalRootsModulus := params.HistoricalRootsModulus - timestampReduced := header.Time % historicalRootsModulus - timestampExtended := timestampReduced + historicalRootsModulus - timestampIndex := libcommon.BytesToHash((uint256.NewInt(timestampReduced)).Bytes()) - rootIndex := libcommon.BytesToHash(uint256.NewInt(timestampExtended).Bytes()) - parentBeaconBlockRootInt := *uint256.NewInt(0).SetBytes(header.ParentBeaconBlockRoot.Bytes()) - state.SetState(historyStorageAddress, ×tampIndex, *uint256.NewInt(header.Time)) - state.SetState(historyStorageAddress, &rootIndex, parentBeaconBlockRootInt) +func ApplyBeaconRootEip4788(parentBeaconBlockRoot *libcommon.Hash, syscall consensus.SystemCall) { + _, err := syscall(params.BeaconRootsAddress, parentBeaconBlockRoot.Bytes()) + if err != nil { + log.Warn("Failed to call beacon roots contract", "err", err) + } } diff --git a/consensus/misc/eip4788_test.go b/consensus/misc/eip4788_test.go deleted file mode 100644 index 0b7e50272ec..00000000000 --- a/consensus/misc/eip4788_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package misc - -import ( - "crypto/sha256" - "math/big" - "testing" - - "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/chain" - libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/core/state" - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/types/accounts" - "github.com/ledgerwatch/erigon/core/vm" - "github.com/ledgerwatch/erigon/params" - "github.com/stretchr/testify/assert" -) - -type dummyChainHeaderReader struct { -} - -func (r dummyChainHeaderReader) Config() *chain.Config { - return nil -} - -func (r dummyChainHeaderReader) CurrentHeader() *types.Header { - return nil -} - -func (r dummyChainHeaderReader) GetHeader(libcommon.Hash, uint64) *types.Header { - return nil -} - -func (r dummyChainHeaderReader) GetHeaderByNumber(uint64) *types.Header { - return nil -} - -func (r dummyChainHeaderReader) GetHeaderByHash(libcommon.Hash) *types.Header { - return nil -} - -func (r dummyChainHeaderReader) GetTd(libcommon.Hash, uint64) *big.Int { - return nil -} - -func (r dummyChainHeaderReader) FrozenBlocks() uint64 { - return 0 -} - -type dummyStateReader struct { -} - -func (dsr *dummyStateReader) ReadAccountData(address libcommon.Address) (*accounts.Account, error) { - return nil, nil -} -func (dsr *dummyStateReader) ReadAccountStorage(address libcommon.Address, incarnation uint64, key *libcommon.Hash) ([]byte, error) { - return nil, nil -} -func (dsr *dummyStateReader) ReadAccountCode(address libcommon.Address, incarnation uint64, codeHash libcommon.Hash) ([]byte, error) { - return make([]byte, 0), nil -} -func (dsr *dummyStateReader) ReadAccountCodeSize(address libcommon.Address, incarnation uint64, codeHash libcommon.Hash) (int, error) { - return 0, nil -} -func (dsr *dummyStateReader) ReadAccountIncarnation(address libcommon.Address) (uint64, error) { - return 0, nil -} - -func TestApplyBeaconRoot(t *testing.T) { - var mockReader dummyChainHeaderReader - testHashBytes := sha256.Sum256([]byte("test")) - testRootHash := libcommon.BytesToHash(testHashBytes[:]) - header := types.Header{ - ParentBeaconBlockRoot: &testRootHash, - Time: 1, - } - - var state state.IntraBlockState = *state.New(&dummyStateReader{}) - - ApplyBeaconRootEip4788(mockReader, &header, &state) - pc := vm.PrecompiledContractsCancun[libcommon.BytesToAddress(params.HistoryStorageAddress)] - spc, ok := pc.(vm.StatefulPrecompiledContract) - if !ok { - t.Fatalf("Error instantiating pre-compile") - } - timestampParam := uint256.NewInt(1).Bytes32() - - res, err := spc.RunStateful(timestampParam[:], &state) - if err != nil { - t.Errorf("error %v", err) - } - assert.Equal(t, testRootHash.Bytes(), res, "Beacon root mismatch") - t.Logf("result %v", res) -} diff --git a/core/blockchain.go b/core/blockchain.go index 2181c9d7c0a..1b3d4279e11 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -49,6 +49,9 @@ type SyncMode string const ( TriesInMemory = 128 + + // See gas_limit in https://github.com/gnosischain/specs/blob/master/execution/withdrawals.md + SysCallGasLimit = uint64(30_000_000) ) type RejectedTx struct { @@ -216,7 +219,8 @@ func SysCallContract(contract libcommon.Address, data []byte, chainConfig *chain state.SystemAddress, &contract, 0, u256.Num0, - math.MaxUint64, u256.Num0, + SysCallGasLimit, + u256.Num0, nil, nil, data, nil, false, true, // isFree @@ -257,7 +261,8 @@ func SysCreate(contract libcommon.Address, data []byte, chainConfig chain.Config contract, nil, // to 0, u256.Num0, - math.MaxUint64, u256.Num0, + SysCallGasLimit, + u256.Num0, nil, nil, data, nil, false, true, // isFree diff --git a/core/vm/contracts.go b/core/vm/contracts.go index cec4ac12a8a..1fe0386030a 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -17,7 +17,6 @@ package vm import ( - "bytes" "crypto/sha256" "encoding/binary" "errors" @@ -31,7 +30,6 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/math" - "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/crypto/blake2b" "github.com/ledgerwatch/erigon/crypto/bls12381" @@ -51,12 +49,6 @@ type PrecompiledContract interface { Run(input []byte) ([]byte, error) // Run runs the precompiled contract } -type StatefulPrecompiledContract interface { - RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use - RunStateful(input []byte, state evmtypes.IntraBlockState) ([]byte, error) // Run runs the precompiled contract - Run(input []byte) ([]byte, error) // Run runs the precompiled contract -} - // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. var PrecompiledContractsHomestead = map[libcommon.Address]PrecompiledContract{ @@ -108,21 +100,16 @@ var PrecompiledContractsBerlin = map[libcommon.Address]PrecompiledContract{ } var PrecompiledContractsCancun = map[libcommon.Address]PrecompiledContract{ - libcommon.BytesToAddress([]byte{1}): &ecrecover{}, - libcommon.BytesToAddress([]byte{2}): &sha256hash{}, - libcommon.BytesToAddress([]byte{3}): &ripemd160hash{}, - libcommon.BytesToAddress([]byte{4}): &dataCopy{}, - libcommon.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, - libcommon.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - libcommon.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - libcommon.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - libcommon.BytesToAddress([]byte{9}): &blake2F{}, - libcommon.BytesToAddress([]byte{0x0a}): &pointEvaluation{}, - libcommon.BytesToAddress(params.HistoryStorageAddress): &parentBeaconBlockRoot{}, -} - -var StatefulPrecompile = map[libcommon.Address]bool{ - libcommon.BytesToAddress(params.HistoryStorageAddress): true, + libcommon.BytesToAddress([]byte{0x01}): &ecrecover{}, + libcommon.BytesToAddress([]byte{0x02}): &sha256hash{}, + libcommon.BytesToAddress([]byte{0x03}): &ripemd160hash{}, + libcommon.BytesToAddress([]byte{0x04}): &dataCopy{}, + libcommon.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, + libcommon.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, + libcommon.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, + libcommon.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, + libcommon.BytesToAddress([]byte{0x09}): &blake2F{}, + libcommon.BytesToAddress([]byte{0x0a}): &pointEvaluation{}, } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum @@ -186,19 +173,14 @@ func ActivePrecompiles(rules *chain.Rules) []libcommon.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, state evmtypes.IntraBlockState) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, +) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } suppliedGas -= gasCost - var output []byte - sp, isStateful := p.(StatefulPrecompiledContract) - if isStateful { - output, err = sp.RunStateful(input, state) - } else { - output, err = p.Run(input) - } + output, err := p.Run(input) return output, suppliedGas, err } @@ -1116,36 +1098,3 @@ func (c *pointEvaluation) RequiredGas(input []byte) uint64 { func (c *pointEvaluation) Run(input []byte) ([]byte, error) { return libkzg.PointEvaluationPrecompile(input) } - -type parentBeaconBlockRoot struct{} - -func (c *parentBeaconBlockRoot) RequiredGas(input []byte) uint64 { - return params.ParentBeaconBlockRootGas -} - -func (c *parentBeaconBlockRoot) Run(input []byte) ([]byte, error) { - return nil, nil -} - -func (c *parentBeaconBlockRoot) RunStateful(input []byte, state evmtypes.IntraBlockState) ([]byte, error) { - timestampParam := input[:32] - if len(timestampParam) < 32 { - return nil, errors.New("timestamp param too short") - } - - timestampReduced := uint256.NewInt(0).SetBytes(timestampParam).Uint64() % params.HistoricalRootsModulus - timestampIndex := libcommon.BigToHash(libcommon.Big256.SetUint64((timestampReduced))) - recordedTimestamp := uint256.NewInt(0) - root := uint256.NewInt(0) - state.GetState(libcommon.BytesToAddress(params.HistoryStorageAddress), ×tampIndex, recordedTimestamp) - - recordedTimestampBytes := recordedTimestamp.Bytes32() - if !bytes.Equal(recordedTimestampBytes[:], timestampParam) { - return make([]byte, 32), nil - } - timestampExtended := timestampReduced + params.HistoricalRootsModulus - rootIndex := libcommon.BigToHash(libcommon.Big256.SetUint64((timestampExtended))) - state.GetState(libcommon.BytesToAddress(params.HistoryStorageAddress), &rootIndex, root) - res := root.Bytes32() - return res[:], nil -} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index bb3a9eeda4f..da2f81c9e2f 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -99,7 +99,7 @@ func testPrecompiled(t *testing.T, addr string, test precompiledTest) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { + if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -121,7 +121,7 @@ func testPrecompiledOOG(t *testing.T, addr string, test precompiledTest) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -138,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -170,7 +170,7 @@ func benchmarkPrecompiled(b *testing.B, addr string, test precompiledTest) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas, nil) + res, _, err = RunPrecompiledContract(p, data, reqGas) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/evm.go b/core/vm/evm.go index ce69c8eed57..1c9d18481a6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -233,7 +233,7 @@ func (evm *EVM) call(typ OpCode, caller ContractRef, addr libcommon.Address, inp // It is allowed to call precompiles, even via delegatecall if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.intraBlockState) + ret, gas, err = RunPrecompiledContract(p, input, gas) } else if len(code) == 0 { // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 10ace257499..526b855676b 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -42,16 +42,10 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { ) evm.IntraBlockState().GetState(contract.Address(), &slot, ¤t) // Check slot presence in the access list - if addrPresent, slotPresent := evm.IntraBlockState().SlotInAccessList(contract.Address(), slot); !slotPresent { + if _, slotPresent := evm.IntraBlockState().SlotInAccessList(contract.Address(), slot); !slotPresent { cost = params.ColdSloadCostEIP2929 // If the caller cannot afford the cost, this change will be rolled back evm.IntraBlockState().AddSlotToAccessList(contract.Address(), slot) - if !addrPresent { - // Once we're done with YOLOv2 and schedule this for mainnet, might - // be good to remove this panic here, which is just really a - // canary to have during testing - panic("impossible case: address was not present in access list during sstore op") - } } var value uint256.Int value.Set(y) diff --git a/params/protocol_params.go b/params/protocol_params.go index c8b0941a73c..fe64b4c36d4 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -18,6 +18,8 @@ package params import ( "math/big" + + "github.com/ledgerwatch/erigon-lib/common" ) const ( @@ -168,14 +170,10 @@ const ( MinBlobGasPrice = 1 BlobGasPriceUpdateFraction = 3338477 PointEvaluationGas uint64 = 50000 - - //EIP-4788: Parent Beacon Root Precompile - ParentBeaconBlockRootGas uint64 = 4200 - HistoricalRootsModulus uint64 = 98304 ) -// EIP-4788: Storage address for parent beacon root -var HistoryStorageAddress = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0B} +// EIP-4788: Beacon block root in the EVM +var BeaconRootsAddress = common.HexToAddress("0x0b") // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations var Bls12381MultiExpDiscountTable = [128]uint64{1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174} diff --git a/tests/exec_spec_test.go b/tests/exec_spec_test.go index 66c5d231c45..de6dcfbe010 100644 --- a/tests/exec_spec_test.go +++ b/tests/exec_spec_test.go @@ -20,8 +20,7 @@ func TestExecutionSpec(t *testing.T) { bt.skipLoad(`^homestead/`) // TODO(yperbasis): fix me - bt.skipLoad(`^cancun/eip4844_blobs/`) - bt.skipLoad(`^cancun/eip6780_selfdestruct/`) + bt.skipLoad(`^cancun/`) // TODO(yperbasis): re-enable checkStateRoot checkStateRoot := false