mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-10 17:01:35 +00:00
core/state: use journaling approach
This commit is contained in:
parent
46be549b00
commit
c1b1faed8b
6 changed files with 47 additions and 60 deletions
|
|
@ -216,33 +216,15 @@ func (j *journal) length() int {
|
||||||
return len(j.entries)
|
return len(j.entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateChangedBytes computes the state bytes created by the call frame
|
// stateChangedBytes computes the state bytes created by the current (topmost)
|
||||||
// identified by snapshotId. Since subcalls always compute their results
|
// call frame, walking only entries that belong directly to this frame and
|
||||||
// before the parent (innermost-first), this only scans journal entries
|
// skipping over closed child frame ranges.
|
||||||
// between this snapshot and the next one — the frame's own entries.
|
func (j *journal) stateChangedBytes(stateObjects map[common.Address]*stateObject) int64 {
|
||||||
// Subcall results are summed from the cache and subtracted.
|
if len(j.validRevisions) == 0 {
|
||||||
//
|
return 0
|
||||||
// The result is cached in stateBytesCharged[snapshotId] so the parent
|
|
||||||
// frame can look it up instead of re-scanning.
|
|
||||||
func (j *journal) stateChangedBytes(snapshotId int, stateObjects map[common.Address]*stateObject) int64 {
|
|
||||||
// TODO (MariusVanDerWijden): this is a bit slop-py needs to be cleaned up
|
|
||||||
// Resolve snapshot index.
|
|
||||||
idx := sort.Search(len(j.validRevisions), func(i int) bool {
|
|
||||||
return j.validRevisions[i].id >= snapshotId
|
|
||||||
})
|
|
||||||
if idx == len(j.validRevisions) || j.validRevisions[idx].id != snapshotId {
|
|
||||||
panic(fmt.Errorf("snapshot id %v not found for stateChangedBytes", snapshotId))
|
|
||||||
}
|
}
|
||||||
start := j.validRevisions[idx].journalIndex
|
rev := j.validRevisions[len(j.validRevisions)-1]
|
||||||
|
|
||||||
// Our range is [start, end) where end is the next revision's start,
|
|
||||||
// or the end of the journal if we're the last revision.
|
|
||||||
end := len(j.entries)
|
|
||||||
if idx+1 < len(j.validRevisions) {
|
|
||||||
end = j.validRevisions[idx+1].journalIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk only our own entries.
|
|
||||||
type slotKey struct {
|
type slotKey struct {
|
||||||
addr common.Address
|
addr common.Address
|
||||||
key common.Hash
|
key common.Hash
|
||||||
|
|
@ -255,8 +237,11 @@ func (j *journal) stateChangedBytes(snapshotId int, stateObjects map[common.Addr
|
||||||
created := make(map[common.Address]bool)
|
created := make(map[common.Address]bool)
|
||||||
codeChanged := make(map[common.Address]bool)
|
codeChanged := make(map[common.Address]bool)
|
||||||
|
|
||||||
for i := start; i < end; i++ {
|
// Walk only this frame's own entries, skipping closed child ranges.
|
||||||
switch e := j.entries[i].(type) {
|
// Add cached subcall costs from closedChildren.
|
||||||
|
var subcallBytes int64
|
||||||
|
visit := func(e journalEntry) {
|
||||||
|
switch e := e.(type) {
|
||||||
case createContractChange:
|
case createContractChange:
|
||||||
created[e.account] = true
|
created[e.account] = true
|
||||||
case codeChange:
|
case codeChange:
|
||||||
|
|
@ -268,6 +253,18 @@ func (j *journal) stateChangedBytes(snapshotId int, stateObjects map[common.Addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
idx := rev.journalIndex
|
||||||
|
for _, child := range rev.closedChildren {
|
||||||
|
for ; idx < child.start; idx++ {
|
||||||
|
visit(j.entries[idx])
|
||||||
|
}
|
||||||
|
// Add the cached cost for this subcall.
|
||||||
|
subcallBytes += j.stateBytesCharged[child.start]
|
||||||
|
idx = child.end
|
||||||
|
}
|
||||||
|
for ; idx < len(j.entries); idx++ {
|
||||||
|
visit(j.entries[idx])
|
||||||
|
}
|
||||||
|
|
||||||
var totalBytes int64
|
var totalBytes int64
|
||||||
for range created {
|
for range created {
|
||||||
|
|
@ -296,7 +293,7 @@ func (j *journal) stateChangedBytes(snapshotId int, stateObjects map[common.Addr
|
||||||
// cleared in earlier frame, re-set here — no charge.
|
// cleared in earlier frame, re-set here — no charge.
|
||||||
// - X → Y (non-zero to non-zero): no charge.
|
// - X → Y (non-zero to non-zero): no charge.
|
||||||
// - zero → zero: no change.
|
// - zero → zero: no change.
|
||||||
// - !prevZero && curZero && !origZero: pre-exising slot was
|
// - !prevZero && curZero && !origZero: pre-existing slot was
|
||||||
// cleared now, don't refund to not enable gas tokens.
|
// cleared now, don't refund to not enable gas tokens.
|
||||||
}
|
}
|
||||||
for addr := range codeChanged {
|
for addr := range codeChanged {
|
||||||
|
|
@ -306,8 +303,11 @@ func (j *journal) stateChangedBytes(snapshotId int, stateObjects map[common.Addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache our result so the parent can look it up.
|
// Add subcall costs to get the total for this frame (own + children).
|
||||||
j.stateBytesCharged[snapshotId] = totalBytes
|
totalBytes += subcallBytes
|
||||||
|
|
||||||
|
// Cache so the parent can look up this frame's total cost.
|
||||||
|
j.stateBytesCharged[rev.journalIndex] = totalBytes
|
||||||
return totalBytes
|
return totalBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -772,11 +772,10 @@ const (
|
||||||
CostPerSlot = 32
|
CostPerSlot = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateChangedBytes computes the state bytes created since the given snapshot,
|
// StateChangedBytes computes the state bytes created by the current (topmost)
|
||||||
// excluding bytes already charged by subcalls. See journal.stateChangedBytes
|
// call frame, excluding entries from closed child frames.
|
||||||
// for the detailed accounting.
|
func (s *StateDB) StateChangedBytes() int64 {
|
||||||
func (s *StateDB) StateChangedBytes(snapshotId int) int64 {
|
return s.journal.stateChangedBytes(s.stateObjects)
|
||||||
return s.journal.stateChangedBytes(snapshotId, s.stateObjects)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type removedAccountWithBalance struct {
|
type removedAccountWithBalance struct {
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,6 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
||||||
return s.inner.Finalise(deleteEmptyObjects)
|
return s.inner.Finalise(deleteEmptyObjects)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) StateChangedBytes(snapshotId int) int64 {
|
func (s *hookedStateDB) StateChangedBytes() int64 {
|
||||||
return s.inner.StateChangedBytes(snapshotId)
|
return s.inner.StateChangedBytes()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -598,10 +598,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
ret []byte
|
ret []byte
|
||||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||||
)
|
)
|
||||||
// EIP-8037: Take a snapshot for the outer call frame so we can compute
|
|
||||||
// state gas for state changes made at the transaction level (nonce,
|
// Take a snapshot for gas calculation
|
||||||
// value transfer, authorizations, and contract creation overhead).
|
st.state.Snapshot()
|
||||||
outerSnapshot := st.state.Snapshot()
|
|
||||||
|
|
||||||
var execGasUsed vm.GasUsed
|
var execGasUsed vm.GasUsed
|
||||||
if contractCreation {
|
if contractCreation {
|
||||||
|
|
@ -634,7 +633,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// EIP-8037: charge state gas for the outer call frame's own state changes.
|
// EIP-8037: charge state gas for the outer call frame's own state changes.
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
if vmerr == nil {
|
if vmerr == nil {
|
||||||
outerBytes := st.state.StateChangedBytes(outerSnapshot)
|
outerBytes := st.state.StateChangedBytes()
|
||||||
st.gasRemaining.Charge(vm.GasCosts{StateGas: outerBytes * int64(st.evm.Context.CostPerStateByte)})
|
st.gasRemaining.Charge(vm.GasCosts{StateGas: outerBytes * int64(st.evm.Context.CostPerStateByte)})
|
||||||
} else {
|
} else {
|
||||||
if execGasUsed.StateGas > 0 {
|
if execGasUsed.StateGas > 0 {
|
||||||
|
|
|
||||||
|
|
@ -281,11 +281,6 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
}
|
}
|
||||||
evm.StateDB.CreateAccount(addr)
|
evm.StateDB.CreateAccount(addr)
|
||||||
}
|
}
|
||||||
if evm.chainRules.IsAmsterdam {
|
|
||||||
// Compute state changed bytes for account creation.
|
|
||||||
evm.StateDB.StateChangedBytes(snapshot)
|
|
||||||
}
|
|
||||||
innerSnapshot := evm.StateDB.Snapshot()
|
|
||||||
// Perform the value transfer only in non-syscall mode.
|
// Perform the value transfer only in non-syscall mode.
|
||||||
// Calling this is required even for zero-value transfers,
|
// Calling this is required even for zero-value transfers,
|
||||||
// to ensure the state clearing mechanism is applied.
|
// to ensure the state clearing mechanism is applied.
|
||||||
|
|
@ -327,7 +322,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
evm.StateDB.CloseSnapshot(snapshot)
|
evm.StateDB.CloseSnapshot(snapshot)
|
||||||
if evm.chainRules.IsAmsterdam {
|
if evm.chainRules.IsAmsterdam {
|
||||||
// Charge state costs
|
// Charge state costs
|
||||||
bytesCharged := evm.StateDB.StateChangedBytes(innerSnapshot)
|
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||||
if !gas.CanAfford(stateGasCost) {
|
if !gas.CanAfford(stateGasCost) {
|
||||||
gas.Exhaust()
|
gas.Exhaust()
|
||||||
|
|
@ -389,7 +384,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
||||||
} else {
|
} else {
|
||||||
evm.StateDB.CloseSnapshot(snapshot)
|
evm.StateDB.CloseSnapshot(snapshot)
|
||||||
if evm.chainRules.IsAmsterdam {
|
if evm.chainRules.IsAmsterdam {
|
||||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot)
|
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||||
if !gas.CanAfford(stateGasCost) {
|
if !gas.CanAfford(stateGasCost) {
|
||||||
gas.Exhaust()
|
gas.Exhaust()
|
||||||
|
|
@ -444,7 +439,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
||||||
} else {
|
} else {
|
||||||
evm.StateDB.CloseSnapshot(snapshot)
|
evm.StateDB.CloseSnapshot(snapshot)
|
||||||
if evm.chainRules.IsAmsterdam {
|
if evm.chainRules.IsAmsterdam {
|
||||||
bytesCharged := evm.StateDB.StateChangedBytes(snapshot)
|
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||||
if !gas.CanAfford(stateGasCost) {
|
if !gas.CanAfford(stateGasCost) {
|
||||||
gas.Exhaust()
|
gas.Exhaust()
|
||||||
|
|
@ -599,12 +594,6 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
}
|
}
|
||||||
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
||||||
|
|
||||||
if evm.chainRules.IsAmsterdam {
|
|
||||||
// Compute the state changed for the contract init.
|
|
||||||
evm.StateDB.StateChangedBytes(snapshot)
|
|
||||||
}
|
|
||||||
initSnapshot := evm.StateDB.Snapshot()
|
|
||||||
|
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, address, value, gas, evm.jumpDests)
|
contract := NewContract(caller, address, value, gas, evm.jumpDests)
|
||||||
|
|
@ -624,7 +613,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
evm.StateDB.CloseSnapshot(snapshot)
|
evm.StateDB.CloseSnapshot(snapshot)
|
||||||
if evm.chainRules.IsAmsterdam {
|
if evm.chainRules.IsAmsterdam {
|
||||||
// Charge initcode's state changes to the created contract's gas.
|
// Charge initcode's state changes to the created contract's gas.
|
||||||
bytesCharged := evm.StateDB.StateChangedBytes(initSnapshot)
|
bytesCharged := evm.StateDB.StateChangedBytes()
|
||||||
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
stateGasCost := GasCosts{StateGas: bytesCharged * int64(evm.Context.CostPerStateByte)}
|
||||||
if !contract.Gas.CanAfford(stateGasCost) {
|
if !contract.Gas.CanAfford(stateGasCost) {
|
||||||
contract.Gas.Exhaust()
|
contract.Gas.Exhaust()
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ type StateDB interface {
|
||||||
// Finalise must be invoked at the end of a transaction
|
// Finalise must be invoked at the end of a transaction
|
||||||
Finalise(bool) *bal.StateAccessList
|
Finalise(bool) *bal.StateAccessList
|
||||||
|
|
||||||
// StateChangedBytes returns the number of state bytes created since the
|
// StateChangedBytes returns the number of state bytes created by the
|
||||||
// given snapshot. Used by EIP-8037 for state gas metering.
|
// current call frame. Used by EIP-8037 for state gas metering.
|
||||||
StateChangedBytes(snapshotId int) int64
|
StateChangedBytes() int64
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue