diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go index ef1f977bf09f..683bc9fea004 100644 --- a/cmd/puppeth/genesis.go +++ b/cmd/puppeth/genesis.go @@ -417,7 +417,7 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin spec.Params.GasLimitBoundDivisor = (math2.HexOrDecimal64)(params.GasLimitBoundDivisor) spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainID.Uint64()) spec.Params.ChainID = (hexutil.Uint64)(genesis.Config.ChainID.Uint64()) - spec.Params.MaxCodeSize = params.MaxCodeSize + spec.Params.MaxCodeSize = params.MaxCodeSizeHard // geth has it set from zero spec.Params.MaxCodeSizeTransition = 0 diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 8f478d2597ca..730cb001563f 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" @@ -61,6 +63,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)) +} + // HasCodeWithPrefix checks if the contract code corresponding to the // provided code hash is present in the db. This function will only check // presence using the prefix-scheme. @@ -74,6 +86,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) + } } // DeleteCode deletes the specified contract code from the database. diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index b35fcba45f79..f4e378ead501 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -92,6 +92,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") // CodePrefixSize PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -220,6 +221,10 @@ func codeKey(hash common.Hash) []byte { return append(CodePrefix, hash.Bytes()...) } +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/database.go b/core/state/database.go index bbcd2358e5b8..7445e627f90e 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/tx_pool.go b/core/tx_pool.go index 3329d736a37f..9294416ef02a 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -50,7 +50,7 @@ const ( // non-trivial consequences: larger transactions are significantly harder and // more expensive to propagate; larger transactions also take more resources // to validate whether they fit into the pool or not. - txMaxSize = 4 * txSlotSize // 128KB + txMaxSize = 16 * txSlotSize // 512KB (tentative) ) var ( diff --git a/core/vm/errors.go b/core/vm/errors.go index 004f8ef1c83c..16b03383adfe 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -36,6 +36,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrCodeInsufficientStake = errors.New("insufficient staking for code") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/evm.go b/core/vm/evm.go index dd55618bf812..8af230160af5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -227,6 +227,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas + + if err == nil { + err = evm.checkContractStaking(contract, uint64(len(code))) + } } } // When an error was returned by the EVM or when setting the creation code @@ -284,6 +288,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas + + if err == nil { + err = evm.checkContractStaking(contract, 0) + } } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -324,6 +332,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas + + if err == nil { + err = evm.checkContractStaking(contract, 0) + } } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) @@ -390,6 +402,22 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return ret, gas, err } +func (evm *EVM) checkContractStaking(contract *Contract, codeSize uint64) error { + if codeSize == 0 { + codeSize = uint64(evm.StateDB.GetCodeSize(contract.Address())) + } + // Check if the remaining balance of the contract can cover the staking requirement + if !evm.StateDB.HasSuicided(contract.Address()) && codeSize > params.MaxCodeSizeSoft { + staking := big.NewInt(int64((codeSize - 1) / params.ExtcodeCopyChunkSize)) + staking.Mul(staking, big.NewInt(int64(params.CodeStakingPerChunk))) + + if !evm.Context.CanTransfer(evm.StateDB, contract.Address(), staking) { + return ErrCodeInsufficientStake + } + } + return nil +} + type codeAndHash struct { code []byte hash common.Hash @@ -453,7 +481,7 @@ 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 { + if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSizeHard { err = ErrMaxCodeSizeExceeded } @@ -467,12 +495,24 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // be stored due to not enough gas set an error and let it be handled // by the error checking condition below. if err == nil { - createDataGas := uint64(len(ret)) * params.CreateDataGas + // Be compatible with Ethereum gas metering when code size < 24 * 1024 + baseDataGas := params.CreateDataGas + dataLen := uint64(len(ret)) + createDataGas := dataLen * baseDataGas + // cap the data len and require staking for > 24KB data + if dataLen > params.MaxCodeSizeSoft { + createDataGas = params.MaxCodeSizeSoft * baseDataGas + } + if contract.UseGas(createDataGas) { evm.StateDB.SetCode(address, ret) } else { err = ErrCodeStoreOutOfGas } + + if err == nil { + err = evm.checkContractStaking(contract, dataLen) + } } // When an error was returned by the EVM or when setting the creation code diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 4c2cb3e5cf79..8192b0a909c8 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -325,6 +325,16 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor return gas, nil } +func addGasExtraCodeSize(evm *EVM, address common.Address, gas uint64) (uint64, bool) { + codeSize := evm.StateDB.GetCodeSize(address) + if codeSize <= params.MaxCodeSizeSoft { + return gas, false + } + + extraGas := (uint64(codeSize) - 1) / params.ExtcodeCopyChunkSize * params.CallGasEIP150 + return math.SafeAdd(gas, extraGas) +} + func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 @@ -357,6 +367,10 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + + if gas, overflow = addGasExtraCodeSize(evm, address, gas); overflow { + return 0, ErrGasUintOverflow + } return gas, nil } @@ -368,6 +382,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory var ( gas uint64 overflow bool + address = common.Address(stack.Back(1).Bytes20()) ) if stack.Back(2).Sign() != 0 { gas += params.CallValueTransferGas @@ -382,10 +397,14 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = addGasExtraCodeSize(evm, address, gas); overflow { + return 0, ErrGasUintOverflow + } return gas, nil } func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := common.Address(stack.Back(1).Bytes20()) gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err @@ -398,10 +417,14 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = addGasExtraCodeSize(evm, address, gas); overflow { + return 0, ErrGasUintOverflow + } return gas, nil } func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := common.Address(stack.Back(1).Bytes20()) gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err @@ -414,6 +437,9 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if gas, overflow = addGasExtraCodeSize(evm, address, gas); overflow { + return 0, ErrGasUintOverflow + } return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index db507c481100..196d230819f7 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -383,6 +383,13 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = 0xffffffffffffffff } addr := common.Address(a.Bytes20()) + codeSize := interpreter.evm.StateDB.GetCodeSize(addr) + if codeSize > params.MaxCodeSizeSoft { + extraGas := (uint64(codeSize - 1)) / params.ExtcodeCopyChunkSize * params.ExtcodeCopyBasePerChunk + if !scope.Contract.UseGas(extraGas) { + return nil, ErrOutOfGas + } + } codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) diff --git a/params/protocol_params.go b/params/protocol_params.go index 5f154597a7fa..29cc73b84cf7 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -113,6 +113,10 @@ const ( // static portion of the gas. It was changed during EIP 150 (Tangerine) ExtcodeCopyBaseFrontier uint64 = 20 ExtcodeCopyBaseEIP150 uint64 = 700 + ExtcodeCopyBasePerChunk uint64 = 700 + ExtcodeCopyChunkSize uint64 = MaxCodeSizeSoft + + CodeStakingPerChunk uint64 = 1000000000000000000 // 1 token per 24k (reclaimable upon suicide) // CreateBySelfdestructGas is used when the refunded account is one that does // not exist. This logic is similar to call. @@ -123,7 +127,8 @@ 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 + MaxCodeSizeSoft = 24576 // Maximum bytecode to permit for a contract without staking + MaxCodeSizeHard = 512 * 1024 // Maximum bytecode to permit for a contract with staking (hardlimit) // Precompiled contract gas prices