DCIPs/assets/eip-5027/0001-unlimit-code-size.patch

501 lines
20 KiB
Diff
Raw Normal View History

From 7b8d4d1b8e00c0515ead0abb3f556e2b5a0617a7 Mon Sep 17 00:00:00 2001
From: Qi Zhou <qzhou64@gmail.com>
Date: Thu, 21 Apr 2022 11:35:27 -0700
Subject: [PATCH] unlimit code size with cold/warm storage
---
core/rawdb/accessors_state.go | 18 +++++++
core/rawdb/schema.go | 6 +++
core/state/access_list.go | 32 +++++++++++-
core/state/database.go | 6 +++
core/state/journal.go | 11 ++++
core/state/statedb.go | 23 ++++++--
core/vm/eips.go | 20 +++++++
core/vm/evm.go | 8 +--
core/vm/interface.go | 2 +
core/vm/operations_acl.go | 98 +++++++++++++++++++++++++++++++++++
params/protocol_params.go | 10 ++--
11 files changed, 221 insertions(+), 13 deletions(-)
diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go
index 41e21b6ca..ad7fc150d 100644
--- a/core/rawdb/accessors_state.go
+++ b/core/rawdb/accessors_state.go
@@ -17,6 +17,8 @@
package rawdb
import (
+ "encoding/binary"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
@@ -48,6 +50,16 @@ func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
return data
}
+// ReadCodeSize retrieves the contract code size of the provided code hash.
+// Return 0 if not found
+func ReadCodeSize(db ethdb.KeyValueReader, hash common.Hash) int {
+ data, _ := db.Get(codeSizeKey(hash))
+ if len(data) != 4 {
+ return 0
+ }
+ return int(binary.BigEndian.Uint32(data))
+}
+
// ReadTrieNode retrieves the trie node of the provided hash.
func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
data, _ := db.Get(hash.Bytes())
@@ -96,6 +108,12 @@ 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)
+ }
}
// WriteTrieNode writes the provided trie node database.
diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go
index 08f373488..cbf1dc40f 100644
--- a/core/rawdb/schema.go
+++ b/core/rawdb/schema.go
@@ -96,6 +96,7 @@ var (
SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header
+ CodeSizePrefix = []byte("s") // CodePrefixSize
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
@@ -230,6 +231,11 @@ func codeKey(hash common.Hash) []byte {
return append(CodePrefix, hash.Bytes()...)
}
+// codeSizekey = CodeSizePreifx + 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) {
diff --git a/core/state/access_list.go b/core/state/access_list.go
index 419469134..22812a936 100644
--- a/core/state/access_list.go
+++ b/core/state/access_list.go
@@ -21,8 +21,9 @@ import (
)
type accessList struct {
- addresses map[common.Address]int
- slots []map[common.Hash]struct{}
+ addresses map[common.Address]int
+ codeInAddresses map[common.Address]bool
+ slots []map[common.Hash]struct{}
}
// ContainsAddress returns true if the address is in the access list.
@@ -31,6 +32,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.codeInAddresses[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) {
@@ -60,6 +67,9 @@ func (a *accessList) Copy() *accessList {
for k, v := range a.addresses {
cp.addresses[k] = v
}
+ for k, v := range a.codeInAddresses {
+ cp.codeInAddresses[k] = v
+ }
cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
for i, slotMap := range a.slots {
newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
@@ -81,6 +91,16 @@ func (al *accessList) AddAddress(address common.Address) bool {
return true
}
+// AddAddressCode adds the code of an address 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.codeInAddresses[address]; present {
+ return false
+ }
+ al.codeInAddresses[address] = true
+ return true
+}
+
// AddSlot adds the specified (addr, slot) combo to the access list.
// Return values are:
// - address added
@@ -134,3 +154,11 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
func (al *accessList) DeleteAddress(address common.Address) {
delete(al.addresses, address)
}
+
+// DeleteAddressCode removes the code of an address from the access list. This operation
+// needs to be performed in the same order as the addition happened.
+// This method is meant to be used by the journal, which maintains ordering of
+// operations.
+func (al *accessList) DeleteAddressCode(address common.Address) {
+ delete(al.codeInAddresses, address)
+}
diff --git a/core/state/database.go b/core/state/database.go
index bbcd2358e..7445e627f 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -194,6 +194,12 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
return cached.(int), nil
}
+
+ size := rawdb.ReadCodeSize(db.db.DiskDB(), codeHash)
+ if size != 0 {
+ return size, nil
+ }
+
code, err := db.ContractCode(addrHash, codeHash)
return len(code), err
}
diff --git a/core/state/journal.go b/core/state/journal.go
index 57a692dc7..8e2250dde 100644
--- a/core/state/journal.go
+++ b/core/state/journal.go
@@ -134,6 +134,9 @@ type (
accessListAddAccountChange struct {
address *common.Address
}
+ accessListAddAccountCodeChange struct {
+ address *common.Address
+ }
accessListAddSlotChange struct {
address *common.Address
slot *common.Hash
@@ -260,6 +263,14 @@ func (ch accessListAddAccountChange) dirtied() *common.Address {
return nil
}
+func (ch accessListAddAccountCodeChange) revert(s *StateDB) {
+ s.accessList.DeleteAddressCode(*ch.address)
+}
+
+func (ch accessListAddAccountCodeChange) dirtied() *common.Address {
+ return nil
+}
+
func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot)
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 1d31cf470..d95dd79aa 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -984,11 +984,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
}
// PrepareAccessList handles the preparatory steps for executing a state transition with
-// regards to both EIP-2929 and EIP-2930:
+// regards to both EIP-2929, EIP-2930, and EIP-5027:
//
-// - Add sender to access list (2929)
-// - Add destination to access list (2929)
-// - Add precompiles to access list (2929)
+// - Add sender to access list (2929, 5027)
+// - Add destination to access list (2929, 5027)
+// - Add precompiles to access list (2929, 5027)
// - Add the contents of the optional tx access list (2930)
//
// This method should only be called if Berlin/2929+2930 is applicable at the current number.
@@ -997,12 +997,15 @@ func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address,
s.accessList = newAccessList()
s.AddAddressToAccessList(sender)
+ s.AddAddressCodeToAccessList(sender)
if dst != nil {
s.AddAddressToAccessList(*dst)
+ s.AddAddressCodeToAccessList(*dst)
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range precompiles {
s.AddAddressToAccessList(addr)
+ s.AddAddressCodeToAccessList(addr)
}
for _, el := range list {
s.AddAddressToAccessList(el.Address)
@@ -1019,6 +1022,13 @@ func (s *StateDB) AddAddressToAccessList(addr common.Address) {
}
}
+// AddAddressCodeToAccessList adds the given address to the access list
+func (s *StateDB) AddAddressCodeToAccessList(addr common.Address) {
+ if s.accessList.AddAddressCode(addr) {
+ s.journal.append(accessListAddAccountCodeChange{&addr})
+ }
+}
+
// AddSlotToAccessList adds the given (address, slot)-tuple to the access list
func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
addrMod, slotMod := s.accessList.AddSlot(addr, slot)
@@ -1042,6 +1052,11 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddress(addr)
}
+// AddressCodeInAccessList returns true if the given address's 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)
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 4070a2db5..e9a8ee78c 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -31,6 +31,7 @@ var activators = map[int]func(*JumpTable){
2200: enable2200,
1884: enable1884,
1344: enable1344,
+ 5027: enable5027,
}
// EnableEIP enables the given EIP on the config.
@@ -147,6 +148,25 @@ func enable2929(jt *JumpTable) {
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929
}
+// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes"
+// https://eips.ethereum.org/EIPS/eip-2929
+func enable5027(jt *JumpTable) {
+ jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929
+ jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP5027
+
+ jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
+ jt[CALL].dynamicGas = gasCallEIP5027
+
+ jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
+ jt[CALLCODE].dynamicGas = gasCallCodeEIP5027
+
+ jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
+ jt[STATICCALL].dynamicGas = gasStaticCallEIP5027
+
+ jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
+ jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP5027
+}
+
// enable3529 enabled "EIP-3529: Reduction in refunds":
// - Removes refunds for selfdestructs
// - Reduces refunds for SSTORE
diff --git a/core/vm/evm.go b/core/vm/evm.go
index dd55618bf..99e57c28e 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -421,6 +421,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// the access-list change should not be rolled back
if evm.chainRules.IsBerlin {
evm.StateDB.AddAddressToAccessList(address)
+ // TODO: check shanghai
+ evm.StateDB.AddAddressCodeToAccessList(address)
}
// Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address)
@@ -453,9 +455,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
ret, err := evm.interpreter.Run(contract, nil, false)
// Check whether the max code size has been exceeded, assign err if the case.
- if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
- err = ErrMaxCodeSizeExceeded
- }
+ // if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
+ // err = ErrMaxCodeSizeExceeded
+ // }
// Reject code starting with 0xEF if EIP-3541 is enabled.
if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
diff --git a/core/vm/interface.go b/core/vm/interface.go
index ad9b05d66..12660dd08 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -59,6 +59,7 @@ type StateDB interface {
PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
AddressInAccessList(addr common.Address) bool
+ AddressCodeInAccessList(addr common.Address) bool
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
@@ -66,6 +67,7 @@ type StateDB interface {
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
AddSlotToAccessList(addr common.Address, slot common.Hash)
+ AddAddressCodeToAccessList(addr common.Address)
RevertToSnapshot(int)
Snapshot() int
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 551e1f5f1..cb76a4390 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -138,6 +138,41 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
return gas, nil
}
+// gasExtCodeCopyEIP5027 implements extcodecopy according to EIP-5027
+// EIP spec:
+// > If the target is not in accessed_addresses,
+// > charge COLD_ACCOUNT_ACCESS_COST * N_CODE_UNIT gas, and add the address to accessed_addresses and accessed_code_in_addresses.
+// > Else if the target is not in accessed_code_in_addresses,
+// > charge COLD_ACCOUNT_ACCESS_COST * (N_CODE_UNIT - 1) gas, and add the address to accessed_code_in_addresses.
+// > Otherwise, charge WARM_STORAGE_READ_COST gas.
+func gasExtCodeCopyEIP5027(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ // memory expansion first (dynamic part of pre-5027 implementation)
+ gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return 0, err
+ }
+ addr := common.Address(stack.peek().Bytes20())
+ // Check slot presence in the access list
+ if !evm.StateDB.AddressInAccessList(addr) {
+ evm.StateDB.AddAddressToAccessList(addr)
+ var overflow bool
+ // We charge (cold-warm), since 'warm' is already charged as constantGas
+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ if !evm.StateDB.AddressCodeInAccessList(addr) {
+ evm.StateDB.AddAddressCodeToAccessList(addr)
+ var overflow bool
+
+ // We charge cold for extra code
+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929*getExtraCodeUnit(evm, addr)); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ return gas, nil
+}
+
// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list.
// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it
// is also using 'warm' as constant factor.
@@ -191,6 +226,64 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
}
}
+func getExtraCodeUnit(evm *EVM, addr common.Address) uint64 {
+ codeSize := evm.StateDB.GetCodeSize(addr)
+ extraCodeUnit := uint64(0)
+ if codeSize > params.CodeSizeUnit {
+ extraCodeUnit = (uint64(codeSize - 1)) / params.CodeSizeUnit
+ }
+ return extraCodeUnit
+}
+
+func makeCallVariantGasCallEIP5027(oldCalculator gasFunc) gasFunc {
+ return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ addr := common.Address(stack.Back(1).Bytes20())
+ // Check slot presence in the access list
+ warmAccess := evm.StateDB.AddressInAccessList(addr)
+ warmCodeAccess := evm.StateDB.AddressCodeInAccessList(addr)
+ // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
+ // the cost to charge for cold access, if any, is n * Cold - Warm
+ coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
+
+ if !warmAccess {
+ evm.StateDB.AddAddressToAccessList(addr)
+ evm.StateDB.AddAddressCodeToAccessList(addr)
+
+ coldCost += getExtraCodeUnit(evm, addr) * params.ColdAccountCodeAccessCostEIP5027
+
+ // Charge the remaining difference here already, to correctly calculate available
+ // gas for call
+ if !contract.UseGas(coldCost) {
+ return 0, ErrOutOfGas
+ }
+ } else if !warmCodeAccess {
+ evm.StateDB.AddAddressCodeToAccessList(addr)
+
+ coldCost = getExtraCodeUnit(evm, addr) * params.ColdAccountCodeAccessCostEIP5027
+ // Charge the remaining difference here already, to correctly calculate available
+ // gas for call
+ if !contract.UseGas(coldCost) {
+ return 0, ErrOutOfGas
+ }
+ }
+ // Now call the old calculator, which takes into account
+ // - create new account
+ // - transfer value
+ // - memory expansion
+ // - 63/64ths rule
+ gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
+ if (warmAccess && warmCodeAccess) || err != nil {
+ return gas, err
+ }
+ // In case of a cold access, we temporarily add the cold charge back, and also
+ // add it to the returned gas. By adding it to the return, it will be charged
+ // outside of this function, as part of the dynamic gas, and that will make it
+ // also become correctly reported to tracers.
+ contract.Gas += coldCost
+ return gas + coldCost, nil
+ }
+}
+
var (
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall)
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall)
@@ -200,6 +293,11 @@ var (
// gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds)
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
+ gasCallEIP5027 = makeCallVariantGasCallEIP5027(gasCall)
+ gasDelegateCallEIP5027 = makeCallVariantGasCallEIP5027(gasDelegateCall)
+ gasStaticCallEIP5027 = makeCallVariantGasCallEIP5027(gasStaticCall)
+ gasCallCodeEIP5027 = makeCallVariantGasCallEIP5027(gasCallCode)
+
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929
//
// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys.
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 5f154597a..c3d5c66ce 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -58,9 +58,11 @@ const (
SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else
SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot
- ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST
- ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST
- WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST
+ ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST
+ ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST
+ WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST
+ ColdAccountCodeAccessCostEIP5027 = uint64(2600) // COLD_ACCOUNT_CODE_ACCESS_COST_PER_UNIT
+ WarmAccountCodeAccessCostEIP5027 = uint64(2600) // WARM_ACCOUNT_CODE_ACCESS_COST_PER_UNIT
// In EIP-2200: SstoreResetGas was 5000.
// In EIP-2929: SstoreResetGas was changed to '5000 - COLD_SLOAD_COST'.
@@ -123,7 +125,7 @@ const (
ElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks.
- MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
+ CodeSizeUnit = 24576 // Code size unit for gas metering.
// Precompiled contract gas prices
--
2.30.1 (Apple Git-130)