diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 3c09229e1c..00c996c447 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -194,7 +194,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) - core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb, big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) } for i := 0; txIt.Next(); i++ { diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index dd273a6f59..59bbba9fd1 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -361,8 +361,10 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + // Add the balance of each withdrawal to the witness, no gas will + // be charged. if chain.Config().IsEIP4762(header.Number, header.Time) { - state.Witness().TouchFullAccount(w.Address[:], true) + state.Witness().TouchBalance(w.Address[:], true) } } // No block reward which is issued by consensus layer instead. diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 7fd836d363..efb6ce8adb 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -89,6 +89,8 @@ func (aw *AccessWitness) Copy() *AccessWitness { return naw } +// TouchFullAccount returns the gas to be charged for each of the currently cold +// member fields of an account. func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { var gas uint64 for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { @@ -97,6 +99,9 @@ func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { return gas } +// TouchAndChargeMessageCall returns the gas to be charged for each of the currently +// cold member fields of an account, that need to be touched when making a message +// call to that account. func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) @@ -104,6 +109,8 @@ func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { return gas } +// TouchAndChargeValueTransfer returns the gas to be charged for each of the currently +// cold balance member fields of the caller and the callee accounts. func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) @@ -111,8 +118,8 @@ func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []by return gas } -// TouchAndChargeContractCreateInit charges access costs to initiate -// a contract creation +// TouchAndChargeContractCreateInit returns the access gas costs for the initialization of +// a contract creation. func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) @@ -123,41 +130,30 @@ func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSen return gas } -func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { +// TouchTxOrigin adds the member fields of the sender account to the witness, +// so that cold accesses are not charged, since they are covered by the 21000 gas. +func (aw *AccessWitness) TouchTxOrigin(originAddr []byte) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) } - - // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address - // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. - // This is the reason why we return 0 instead of `gas`. - // Note that we still have to touch the addresses to make sure the witness is correct. - return 0 } -func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false) - if sendsValue { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) - } else { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false) +// TouchTxDestination adds the member fields of the sender account to the witness, +// so that cold accesses are not charged, since they are covered by the 21000 gas. +func (aw *AccessWitness) TouchTxDestination(targetAddr []byte, sendsValue bool) { + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) } - - // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address - // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. - // This is the reason why we return 0 instead of `gas`. - // Note that we still have to touch the addresses to make sure the witness is correct. - return 0 } +// TouchSlotAndChargeGas returns the amount of gas to be charged for a cold storage access. func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } +// touchAddressAndChargeGas adds any missing access event to the witness, and returns the cold +// access cost to be charged, if need be. func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite) diff --git a/core/state_transition.go b/core/state_transition.go index 1e4bab6559..14f71be5fb 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -423,17 +423,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { targetAddr := msg.To originAddr := msg.From - statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes()) - if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) - } + st.evm.Accesses.TouchTxOrigin(originAddr.Bytes()) originNonce := st.evm.StateDB.GetNonce(originAddr) if msg.To != nil { - statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0) - if !tryConsumeGas(&st.gasRemaining, statelessGasDest) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) - } + st.evm.Accesses.TouchTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) @@ -501,7 +495,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true) + st.evm.Accesses.TouchBalance(st.evm.Context.Coinbase[:], true) } } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 9ca6ee39fb..33ce4c45b8 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -272,6 +272,12 @@ const ( GasChangeCallStorageColdAccess GasChangeReason = 13 // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. GasChangeCallFailedExecution GasChangeReason = 14 + // GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step + GasChangeWitnessContractInit GasChangeReason = 15 + // GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step + GasChangeWitnessContractCreation GasChangeReason = 16 + // GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks + GasChangeWitnessCodeChunk GasChangeReason = 17 // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // it will be "manually" tracked by a direct emit of the gas change event. diff --git a/core/vm/eips.go b/core/vm/eips.go index ff01a67e54..ccf6e8f9b4 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -334,6 +334,7 @@ func enable4762(jt *JumpTable) { jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 jt[EXTCODECOPY].constantGas = 0 jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 + jt[CODECOPY].dynamicGas = gasCodeCopyEip4762 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 jt[CREATE].constantGas = params.CreateNGasEip4762 jt[CREATE2].constantGas = params.CreateNGasEip4762 diff --git a/core/vm/evm.go b/core/vm/evm.go index b8be848e68..48871e8d32 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -510,7 +510,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeUnspecified) { + if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -541,11 +541,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeUnspecified) { + if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrOutOfGas } - if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeUnspecified) { + if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrOutOfGas } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 8603366514..7246a363fd 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -364,16 +364,8 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ uint64CodeOffset = math.MaxUint64 } - contractAddr := scope.Contract.Address() - paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) - if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } - scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index a342622cf5..bb768a9e9a 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -108,6 +108,26 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return statelessGas, nil } +func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + var ( + codeOffset = stack.Back(1) + length = stack.Back(2) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) + if !contract.IsDeployment { + gas += evm.Accesses.TouchCodeChunksRangeAndChargeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + } + return gas, nil +} + func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // memory expansion first (dynamic part of pre-2929 implementation) gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)