mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
core/state: charge account creation to parent
This commit is contained in:
parent
c1b1faed8b
commit
87641c7265
6 changed files with 73 additions and 43 deletions
|
|
@ -216,14 +216,23 @@ func (j *journal) length() int {
|
|||
return len(j.entries)
|
||||
}
|
||||
|
||||
// stateChangedBytes computes the state bytes created by the current (topmost)
|
||||
// call frame, walking only entries that belong directly to this frame and
|
||||
// skipping over closed child frame ranges.
|
||||
func (j *journal) stateChangedBytes(stateObjects map[common.Address]*stateObject) int64 {
|
||||
if len(j.validRevisions) == 0 {
|
||||
return 0
|
||||
// stateChangedBytes computes the state bytes created by the call frame
|
||||
// identified by revid, walking only entries that belong directly to this
|
||||
// frame and skipping over closed child frame ranges. The result is cached
|
||||
// in stateBytesCharged so that parent frames can look it up.
|
||||
//
|
||||
// When excludeSubcalls is true, cached subcall costs are not added to the
|
||||
// total. This is useful when subcalls have already been charged to their
|
||||
// own gas budgets and shouldn't bubble up to the ancestor frames.
|
||||
func (j *journal) stateChangedBytes(revid int, stateObjects map[common.Address]*stateObject, excludeSubcalls bool) int64 {
|
||||
// Find the revision by ID.
|
||||
idx := sort.Search(len(j.validRevisions), func(i int) bool {
|
||||
return j.validRevisions[i].id >= revid
|
||||
})
|
||||
if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid {
|
||||
panic(fmt.Errorf("revision id %v not found for stateChangedBytes", revid))
|
||||
}
|
||||
rev := j.validRevisions[len(j.validRevisions)-1]
|
||||
rev := j.validRevisions[idx]
|
||||
|
||||
type slotKey struct {
|
||||
addr common.Address
|
||||
|
|
@ -253,17 +262,19 @@ func (j *journal) stateChangedBytes(stateObjects map[common.Address]*stateObject
|
|||
}
|
||||
}
|
||||
}
|
||||
idx := rev.journalIndex
|
||||
pos := rev.journalIndex
|
||||
for _, child := range rev.closedChildren {
|
||||
for ; idx < child.start; idx++ {
|
||||
visit(j.entries[idx])
|
||||
for ; pos < child.start; pos++ {
|
||||
visit(j.entries[pos])
|
||||
}
|
||||
// Add the cached cost for this subcall.
|
||||
subcallBytes += j.stateBytesCharged[child.start]
|
||||
idx = child.end
|
||||
if !excludeSubcalls {
|
||||
// Add the cached cost for this subcall.
|
||||
subcallBytes += j.stateBytesCharged[child.start]
|
||||
}
|
||||
pos = child.end
|
||||
}
|
||||
for ; idx < len(j.entries); idx++ {
|
||||
visit(j.entries[idx])
|
||||
for ; pos < len(j.entries); pos++ {
|
||||
visit(j.entries[pos])
|
||||
}
|
||||
|
||||
var totalBytes int64
|
||||
|
|
|
|||
|
|
@ -772,10 +772,11 @@ const (
|
|||
CostPerSlot = 32
|
||||
)
|
||||
|
||||
// StateChangedBytes computes the state bytes created by the current (topmost)
|
||||
// call frame, excluding entries from closed child frames.
|
||||
func (s *StateDB) StateChangedBytes() int64 {
|
||||
return s.journal.stateChangedBytes(s.stateObjects)
|
||||
// StateChangedBytes computes the state bytes created by the call frame
|
||||
// identified by revid, excluding entries from closed child frames. When
|
||||
// excludeSubcalls is true, cached subcall costs are not added to the total.
|
||||
func (s *StateDB) StateChangedBytes(revid int, excludeSubcalls bool) int64 {
|
||||
return s.journal.stateChangedBytes(revid, s.stateObjects, excludeSubcalls)
|
||||
}
|
||||
|
||||
type removedAccountWithBalance struct {
|
||||
|
|
|
|||
|
|
@ -293,6 +293,6 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
|||
return s.inner.Finalise(deleteEmptyObjects)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) StateChangedBytes() int64 {
|
||||
return s.inner.StateChangedBytes()
|
||||
func (s *hookedStateDB) StateChangedBytes(revid int, excludeSubcalls bool) int64 {
|
||||
return s.inner.StateChangedBytes(revid, excludeSubcalls)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -600,7 +600,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
)
|
||||
|
||||
// Take a snapshot for gas calculation
|
||||
st.state.Snapshot()
|
||||
outerSnapshot := st.state.Snapshot()
|
||||
|
||||
var execGasUsed vm.GasUsed
|
||||
if contractCreation {
|
||||
|
|
@ -633,7 +633,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// EIP-8037: charge state gas for the outer call frame's own state changes.
|
||||
if rules.IsAmsterdam {
|
||||
if vmerr == nil {
|
||||
outerBytes := st.state.StateChangedBytes()
|
||||
outerBytes := st.state.StateChangedBytes(outerSnapshot, false)
|
||||
st.gasRemaining.Charge(vm.GasCosts{StateGas: outerBytes * int64(st.evm.Context.CostPerStateByte)})
|
||||
} else {
|
||||
if execGasUsed.StateGas > 0 {
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
snapshot1 := evm.StateDB.Snapshot()
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
|
||||
|
|
@ -268,7 +268,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
// Thus, only pay for the creation of the code hash leaf here.
|
||||
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
|
||||
if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
gas.Exhaust()
|
||||
return nil, gas, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||
// Calling a non-existing account, don't do anything.
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
evm.StateDB.CloseSnapshot(snapshot1)
|
||||
return nil, gas, nil
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
|
|
@ -288,6 +288,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
evm.Context.Transfer(evm.StateDB, caller, addr, value, &evm.chainRules)
|
||||
}
|
||||
|
||||
// Second snapshot: callee execution frame.
|
||||
snapshot2 := evm.StateDB.Snapshot()
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
||||
} else {
|
||||
|
|
@ -308,28 +311,31 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
// above we revert to the snapshot and consume any gas remaining. Additionally,
|
||||
// when we're in homestead this also counts for code storage gas errors.
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
// TODO: consider clearing up unused snapshots:
|
||||
//} else {
|
||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
||||
} else {
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Charge state costs
|
||||
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||
// Charge callee's state changes to the callee's gas.
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !gas.CanAfford(stateGasCost) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
gas.Exhaust()
|
||||
return ret, gas, ErrOutOfGas
|
||||
}
|
||||
gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot2)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Cache parents costs (excluding subcalls)
|
||||
evm.StateDB.StateChangedBytes(snapshot1, true)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot1)
|
||||
}
|
||||
return ret, gas, err
|
||||
}
|
||||
|
|
@ -382,9 +388,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
gas.Exhaust()
|
||||
}
|
||||
} else {
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !gas.CanAfford(stateGasCost) {
|
||||
gas.Exhaust()
|
||||
|
|
@ -392,6 +397,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
}
|
||||
gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
}
|
||||
return ret, gas, err
|
||||
}
|
||||
|
|
@ -437,9 +443,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
|||
gas.Exhaust()
|
||||
}
|
||||
} else {
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !gas.CanAfford(stateGasCost) {
|
||||
gas.Exhaust()
|
||||
|
|
@ -447,6 +452,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
|||
}
|
||||
gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
}
|
||||
|
||||
return ret, gas, err
|
||||
|
|
@ -567,7 +573,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
// Create a new account on the state only if the object was not present.
|
||||
// It might be possible the contract code is deployed to a pre-existent
|
||||
// account with non-zero balance.
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
snapshot1 := evm.StateDB.Snapshot()
|
||||
if !evm.StateDB.Exist(address) {
|
||||
evm.StateDB.CreateAccount(address)
|
||||
}
|
||||
|
|
@ -594,6 +600,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
||||
|
||||
// Second snapshot: initcode execution frame.
|
||||
snapshot2 := evm.StateDB.Snapshot()
|
||||
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, address, value, gas, evm.jumpDests)
|
||||
|
|
@ -605,22 +614,29 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
|
||||
ret, err = evm.initNewContract(contract, address)
|
||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
// Revert to snapshot1 to undo both account creation and initcode changes.
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
} else {
|
||||
evm.StateDB.CloseSnapshot(snapshot)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Charge initcode's state changes to the created contract's gas.
|
||||
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot2, false)
|
||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||
if !contract.Gas.CanAfford(stateGasCost) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot1)
|
||||
contract.Gas.Exhaust()
|
||||
return ret, address, contract.Gas, ErrOutOfGas
|
||||
}
|
||||
contract.Gas.Charge(stateGasCost)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot2)
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
// Cache snapshot1's state bytes (exclude subcalls)
|
||||
evm.StateDB.StateChangedBytes(snapshot1, true)
|
||||
}
|
||||
evm.StateDB.CloseSnapshot(snapshot1)
|
||||
}
|
||||
return ret, address, contract.Gas, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ type StateDB interface {
|
|||
Finalise(bool) *bal.StateAccessList
|
||||
|
||||
// StateChangedBytes returns the number of state bytes created by the
|
||||
// current call frame. Used by EIP-8037 for state gas metering.
|
||||
StateChangedBytes() int64
|
||||
// call frame identified by the given snapshot ID. When excludeSubcalls
|
||||
// is true, cached subcall costs are not added to the total. Used by
|
||||
// EIP-8037 for state gas metering.
|
||||
StateChangedBytes(revid int, excludeSubcalls bool) int64
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue