Skip to content

WIP: EIP-7907 Meter Contract Code Size And Increase Limit #32003

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

Open
wants to merge 7 commits into
base: fusaka-devnet-2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func Transaction(ctx *cli.Context) error {
r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits")
}
// Check whether the init code size has been exceeded.
if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSizeEIP3860 {
r.Error = errors.New("max initcode size exceeded")
}
results = append(results, r)
Expand Down
74 changes: 74 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/pebble"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -4407,3 +4408,76 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
}
}
}

func TestEIP7907(t *testing.T) {
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
glogger.Verbosity(3)
log.SetDefault(log.NewLogger(glogger))

junk := make([]byte, 1024*250) // 250kb
for i := range junk {
junk[i] = byte(i)
}
code := program.New().Op(vm.ADDRESS).Op(vm.POP).ReturnViaCodeCopy(junk).Bytes()
var (
config = *params.MergedTestChainConfig
signer = types.LatestSigner(&config)
engine = beacon.New(ethash.NewFaker())
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
)
gspec := &Genesis{
Config: &config,
GasLimit: 70000000,
Alloc: types.GenesisAlloc{
addr1: {Balance: funds},
addr2: {Balance: funds},
aa: { // The address 0xAAAA calls into addr2
Code: code,
Nonce: 0,
Balance: big.NewInt(0),
},
bb: { // The address 0xBBBB copies and deploys the contract.
Code: program.New().ExtcodeCopy(aa, 0, 0, len(code)).Push0().Push(len(code)).Push0().Push0().Op(vm.CREATE).Op(vm.EXTCODESIZE).Bytes(),
Nonce: 0,
Balance: big.NewInt(0),
},
},
}

_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
b.SetCoinbase(aa)
txdata := &types.DynamicFeeTx{
ChainID: gspec.Config.ChainID,
Nonce: 0,
To: &bb,
Gas: 70000000,
GasFeeCap: newGwei(5),
GasTipCap: big.NewInt(2),
}
tx := types.MustSignNewTx(key1, signer, txdata)
b.AddTx(tx)
})
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
defer chain.Stop()
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}

// Verify delegation designations were deployed.
created := crypto.CreateAddress(bb, 0)
fmt.Println(created.Hex())
state, _ := chain.State()
code, want := state.GetCode(created), junk
if !bytes.Equal(code, want) {
t.Fatalf("created code incorrect: got %d, want %d", len(code), len(want))
}
}
17 changes: 17 additions & 0 deletions core/rawdb/accessors_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
return data
}

// ReadCodeWithPrefix retrieves the contract code of the provided code hash.
// Return -1 if not found for legacy db.
func ReadCodeSizeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) int {
data, _ := db.Get(codeSizeKey(hash))
if len(data) != 4 {
return -1
}
return int(binary.BigEndian.Uint32(data))
}

// HasCode checks if the contract code corresponding to the
// provided code hash is present in the db.
func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool {
Expand All @@ -90,12 +100,19 @@ func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
if err := db.Put(codeKey(hash), code); err != nil {
log.Crit("Failed to store contract code", "err", err)
}
var sizeData [4]byte
binary.BigEndian.PutUint32(sizeData[:], uint32(len(code)))
if err := db.Put(codeSizeKey(hash), sizeData[:]); err != nil {
log.Crit("Failed to store contract code size", "err", err)
}
}

// DeleteCode deletes the specified contract code from the database.
func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(codeKey(hash)); err != nil {
log.Crit("Failed to delete contract code", "err", err)
// Ignore error since the legacy db may not contain the size.
db.Delete(codeSizeKey(hash))
}
}

Expand Down
6 changes: 6 additions & 0 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ var (
SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
CodeSizePrefix = []byte("s") /// CodeSizePrefx + code hash -> code size
skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header

// Path-based storage scheme of merkle patricia trie.
Expand Down Expand Up @@ -233,6 +234,11 @@ func codeKey(hash common.Hash) []byte {
return append(CodePrefix, hash.Bytes()...)
}

// codeSizeKey = CodeSizePrefix + hash
func codeSizeKey(hash common.Hash) []byte {
return append(CodeSizePrefix, hash.Bytes()...)
}

// IsCodeKey reports whether the given byte slice is the key of contract code,
// if so return the raw code hash as well.
func IsCodeKey(key []byte) (bool, []byte) {
Expand Down
33 changes: 30 additions & 3 deletions core/state/access_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import (
)

type accessList struct {
addresses map[common.Address]int
slots []map[common.Hash]struct{}
addresses map[common.Address]int
slots []map[common.Hash]struct{}
addressCodes map[common.Address]struct{}
}

// ContainsAddress returns true if the address is in the access list.
Expand All @@ -36,6 +37,12 @@ func (al *accessList) ContainsAddress(address common.Address) bool {
return ok
}

// ContainsAddress returns true if the address is in the access list.
func (al *accessList) ContainsAddressCode(address common.Address) bool {
_, ok := al.addressCodes[address]
return ok
}

// Contains checks if a slot within an account is present in the access list, returning
// separate flags for the presence of the account and the slot respectively.
func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
Expand All @@ -55,7 +62,8 @@ func (al *accessList) Contains(address common.Address, slot common.Hash) (addres
// newAccessList creates a new accessList.
func newAccessList() *accessList {
return &accessList{
addresses: make(map[common.Address]int),
addresses: make(map[common.Address]int),
addressCodes: make(map[common.Address]struct{}),
}
}

Expand All @@ -67,6 +75,10 @@ func (al *accessList) Copy() *accessList {
for i, slotMap := range al.slots {
cp.slots[i] = maps.Clone(slotMap)
}
cp.addressCodes = make(map[common.Address]struct{}, len(al.addressCodes))
for addr := range al.addressCodes {
cp.addressCodes[addr] = struct{}{}
}
return cp
}

Expand All @@ -80,6 +92,16 @@ func (al *accessList) AddAddress(address common.Address) bool {
return true
}

// AddAddressCode adds an address code to the access list, and returns 'true' if
// the operation caused a change (addr was not previously in the list).
func (al *accessList) AddAddressCode(address common.Address) bool {
if _, present := al.addressCodes[address]; present {
return false
}
al.addressCodes[address] = struct{}{}
return true
}

// AddSlot adds the specified (addr, slot) combo to the access list.
// Return values are:
// - address added
Expand Down Expand Up @@ -142,6 +164,11 @@ func (al *accessList) Equal(other *accessList) bool {
return slices.EqualFunc(al.slots, other.slots, maps.Equal)
}

// DeleteAddressCode removes an address code from the access list.
func (al *accessList) DeleteAddressCode(address common.Address) {
delete(al.addressCodes, address)
}

// PrettyPrint prints the contents of the access list in a human-readable form
func (al *accessList) PrettyPrint() string {
out := new(strings.Builder)
Expand Down
21 changes: 21 additions & 0 deletions core/state/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ func (j *journal) accessListAddAccount(addr common.Address) {
j.append(accessListAddAccountChange{addr})
}

func (j *journal) accessListAddAccountCode(addr common.Address) {
j.append(accessListAddAccountCodeChange{addr})
}

func (j *journal) accessListAddSlot(addr common.Address, slot common.Hash) {
j.append(accessListAddSlotChange{
address: addr,
Expand Down Expand Up @@ -282,6 +286,9 @@ type (
address common.Address
slot common.Hash
}
accessListAddAccountCodeChange struct {
address common.Address
}

// Changes to transient storage
transientStorageChange struct {
Expand Down Expand Up @@ -485,6 +492,20 @@ func (ch accessListAddAccountChange) copy() journalEntry {
}
}

func (ch accessListAddAccountCodeChange) revert(s *StateDB) {
s.accessList.DeleteAddressCode(ch.address)
}

func (ch accessListAddAccountCodeChange) dirtied() *common.Address {
return nil
}

func (ch accessListAddAccountCodeChange) copy() journalEntry {
return accessListAddAccountCodeChange{
address: ch.address,
}
}

func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(ch.address, ch.slot)
}
Expand Down
5 changes: 5 additions & 0 deletions core/state/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
return cached, nil
}
codeSize := rawdb.ReadCodeSizeWithPrefix(r.db, codeHash)
if codeSize != -1 {
r.codeSizeCache.Add(codeHash, codeSize)
return codeSize, nil
}
code, err := r.Code(addr, codeHash)
if err != nil {
return 0, err
Expand Down
16 changes: 16 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
al.AddAddress(sender)
if dst != nil {
al.AddAddress(*dst)
// TODO: add for devnet-3
// if rules.IsOsaka {
// al.AddAddressCode(*dst)
// }
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range precompiles {
Expand Down Expand Up @@ -1402,11 +1406,23 @@ func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
}
}

// AddAddressCodeToAccessList adds the given address to the access list
func (s *StateDB) AddAddressCodeToAccessList(addr common.Address) {
if s.accessList.AddAddressCode(addr) {
s.journal.accessListAddAccountCode(addr)
}
}

// AddressInAccessList returns true if the given address is in the access list.
func (s *StateDB) AddressInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddress(addr)
}

// AddressCodeInAccessList returns true if the given address code is in the access list.
func (s *StateDB) AddressCodeInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddressCode(addr)
}

// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot)
Expand Down
8 changes: 8 additions & 0 deletions core/state/statedb_hooked.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,18 @@ func (s *hookedStateDB) SlotInAccessList(addr common.Address, slot common.Hash)
return s.inner.SlotInAccessList(addr, slot)
}

func (s *hookedStateDB) AddressCodeInAccessList(addr common.Address) bool {
return s.inner.AddressCodeInAccessList(addr)
}

func (s *hookedStateDB) AddAddressToAccessList(addr common.Address) {
s.inner.AddAddressToAccessList(addr)
}

func (s *hookedStateDB) AddAddressCodeToAccessList(addr common.Address) {
s.inner.AddAddressCodeToAccessList(addr)
}

func (s *hookedStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
s.inner.AddSlotToAccessList(addr, slot)
}
Expand Down
2 changes: 1 addition & 1 deletion core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestStateProcessorErrors(t *testing.T) {
},
}
blockchain, _ = NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
tooBigInitCode = [params.MaxInitCodeSizeEIP3860 + 1]byte{}
)

defer blockchain.Stop()
Expand Down
7 changes: 5 additions & 2 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}

// Check whether the init code size has been exceeded.
if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
if rules.IsOsaka && contractCreation && len(msg.Data) > params.MaxInitCodeSizeEIP7907 {
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSizeEIP7907)
}
if !rules.IsOsaka && rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSizeEIP3860 {
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSizeEIP3860)
}

// Execute the preparatory steps for state transition which includes:
Expand Down
4 changes: 2 additions & 2 deletions core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type())
}
// Check whether the init code size has been exceeded
if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSizeEIP7907 {
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSizeEIP7907)
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur for transactions created using the RPC.
Expand Down
12 changes: 12 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var activators = map[int]func(*JumpTable){
1153: enable1153,
4762: enable4762,
7702: enable7702,
7907: enable7907,
}

// EnableEIP enables the given EIP on the config.
Expand Down Expand Up @@ -538,3 +539,14 @@ func enable7702(jt *JumpTable) {
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
}

// enable7907 the EIP-7907 changes to support large contracts.
func enable7907(jt *JumpTable) {
jt[CALL].dynamicGas = gasCallEIP7907
jt[CALLCODE].dynamicGas = gasCallCodeEIP7907
jt[STATICCALL].dynamicGas = gasStaticCallEIP7907
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7907
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP7907
jt[CREATE].dynamicGas = gasCreateEip7907
jt[CREATE2].dynamicGas = gasCreate2Eip7907
}
Loading