mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
core/types/bal: faster bal reader
This commit is contained in:
parent
704f795ed2
commit
e230772f11
5 changed files with 238 additions and 102 deletions
|
|
@ -2129,13 +2129,16 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
|
|||
|
||||
useAsyncReads := bc.cfg.BALExecutionMode != bal.BALExecutionNoBatchIO
|
||||
al := block.AccessList()
|
||||
accessListReader := bal.NewAccessListReader(*al)
|
||||
prefetchReader, err := sdb.ReaderWithPrefetch(parentRoot, accessListReader.StorageKeys(useAsyncReads), bc.cfg.PrefetchWorkers, bc.cfg.BlockingPrefetch)
|
||||
// Preprocess the access list once for the whole block; the resulting
|
||||
// structure is read-only and shared by the prefetch reader, the state
|
||||
// transition and every per-transaction execution reader.
|
||||
prepared := bal.NewPreparedAccessList(*al)
|
||||
prefetchReader, err := sdb.ReaderWithPrefetch(parentRoot, prepared.StorageKeys(useAsyncReads), bc.cfg.PrefetchWorkers, bc.cfg.BlockingPrefetch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stateTransition, err := state.NewBALStateTransition(block, prefetchReader, sdb, parentRoot)
|
||||
stateTransition, err := state.NewBALStateTransition(block, prefetchReader, sdb, parentRoot, prepared)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ func NewParallelStateProcessor(chain *HeaderChain, vmConfig *vm.Config) Parallel
|
|||
// performs post-tx state transition (system contracts and withdrawals)
|
||||
// and calculates the ProcessResult, returning it to be sent on resCh
|
||||
// by resultHandler
|
||||
func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStart time.Time, preTxBal *bal.ConstructionBlockAccessList, statedb *state.StateDB, results []txExecResult) *ProcessResultWithMetrics {
|
||||
func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStart time.Time, preTxBal *bal.ConstructionBlockAccessList, prepared *bal.PreparedAccessList, statedb *state.StateDB, results []txExecResult) *ProcessResultWithMetrics {
|
||||
tExec := time.Since(tExecStart)
|
||||
tPostprocessStart := time.Now()
|
||||
header := block.Header()
|
||||
|
||||
vmContext := NewEVMBlockContext(header, p.chain, nil)
|
||||
lastBALIdx := len(block.Transactions()) + 1
|
||||
postTxState := statedb.WithReader(state.NewReaderWithBlockLevelAccessList(statedb.Reader(), *block.AccessList(), lastBALIdx))
|
||||
postTxState := statedb.WithReader(state.NewReaderWithPreparedAccessList(statedb.Reader(), prepared, lastBALIdx))
|
||||
|
||||
cfg := vm.Config{
|
||||
NoBaseFee: p.vmCfg.NoBaseFee,
|
||||
|
|
@ -148,7 +148,7 @@ type txExecResult struct {
|
|||
|
||||
// resultHandler polls until all transactions have finished executing and the
|
||||
// state root calculation is complete. The result is emitted on resCh.
|
||||
func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxBAL *bal.ConstructionBlockAccessList, statedb *state.StateDB, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) {
|
||||
func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxBAL *bal.ConstructionBlockAccessList, prepared *bal.PreparedAccessList, statedb *state.StateDB, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) {
|
||||
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
|
||||
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
|
||||
var results []txExecResult
|
||||
|
|
@ -187,7 +187,7 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxBAL *bal
|
|||
}
|
||||
}
|
||||
|
||||
execResults := p.prepareExecResult(block, tExecStart, preTxBAL, statedb, results)
|
||||
execResults := p.prepareExecResult(block, tExecStart, preTxBAL, prepared, statedb, results)
|
||||
rootCalcRes := <-stateRootCalcResCh
|
||||
|
||||
if execResults.ProcessResult.Error != nil {
|
||||
|
|
@ -292,6 +292,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *st
|
|||
)
|
||||
|
||||
startingState := statedb.Copy()
|
||||
prepared := stateTransition.PreparedAccessList()
|
||||
preTxBal, err := p.processBlockPreTx(block, statedb, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -302,7 +303,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *st
|
|||
|
||||
// execute transactions and state root calculation in parallel
|
||||
tExecStart = time.Now()
|
||||
go p.resultHandler(block, preTxBal, statedb, tExecStart, txResCh, rootCalcResultCh, resCh)
|
||||
go p.resultHandler(block, preTxBal, prepared, statedb, tExecStart, txResCh, rootCalcResultCh, resCh)
|
||||
var workers errgroup.Group
|
||||
workers.SetLimit(runtime.NumCPU())
|
||||
for i, t := range block.Transactions() {
|
||||
|
|
@ -310,7 +311,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *st
|
|||
idx := i
|
||||
sdb := startingState.Copy()
|
||||
workers.Go(func() error {
|
||||
startingState := sdb.WithReader(state.NewReaderWithBlockLevelAccessList(statedb.Reader(), *block.AccessList(), idx+1))
|
||||
startingState := sdb.WithReader(state.NewReaderWithPreparedAccessList(statedb.Reader(), prepared, idx+1))
|
||||
res := p.execTx(block, tx, idx+1, startingState, signer)
|
||||
txResCh <- *res
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
// and commit for EIP 7928 access-list-containing blocks. An instance of
|
||||
// this object is only used for a single block.
|
||||
type BALStateTransition struct {
|
||||
accessList bal.AccessListReader
|
||||
accessList *bal.PreparedAccessList
|
||||
written bal.WrittenCounts
|
||||
db Database
|
||||
reader Reader
|
||||
|
|
@ -86,14 +86,14 @@ type BALStateTransitionMetrics struct {
|
|||
TotalCommitTime time.Duration
|
||||
}
|
||||
|
||||
func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash) (*BALStateTransition, error) {
|
||||
func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash, prepared *bal.PreparedAccessList) (*BALStateTransition, error) {
|
||||
stateTrie, err := db.OpenTrie(parentRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BALStateTransition{
|
||||
accessList: bal.NewAccessListReader(*block.AccessList()),
|
||||
accessList: prepared,
|
||||
written: block.AccessList().WrittenCounts(),
|
||||
db: db,
|
||||
reader: prefetchReader,
|
||||
|
|
@ -115,6 +115,13 @@ func (s *BALStateTransition) WrittenCounts() bal.WrittenCounts {
|
|||
return s.written
|
||||
}
|
||||
|
||||
// PreparedAccessList returns the shared, read-only preprocessed access list for
|
||||
// the block. It is built once per block and reused by the parallel execution
|
||||
// readers so the preprocessing is not repeated per transaction.
|
||||
func (s *BALStateTransition) PreparedAccessList() *bal.PreparedAccessList {
|
||||
return s.accessList
|
||||
}
|
||||
|
||||
func (s *BALStateTransition) Error() error {
|
||||
return s.err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,20 +210,34 @@ func (r *prefetchStateReader) process(start, limit int) {
|
|||
// ReaderWithBlockLevelAccessList provides state access that reflects the
|
||||
// pre-transition state combined with the mutations made by transactions
|
||||
// prior to TxIndex.
|
||||
//
|
||||
// It is a cheap, per-transaction view over a shared, read-only
|
||||
// bal.PreparedAccessList: constructing one is O(1) and every lookup is an
|
||||
// allocation-free binary search.
|
||||
type ReaderWithBlockLevelAccessList struct {
|
||||
Reader
|
||||
AccessList bal.AccessListReader
|
||||
TxIndex int
|
||||
prepared *bal.PreparedAccessList
|
||||
TxIndex int
|
||||
}
|
||||
|
||||
func NewReaderWithBlockLevelAccessList(base Reader, accessList bal.BlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
|
||||
// NewReaderWithPreparedAccessList wraps a base reader with a shared, already
|
||||
// preprocessed access list. This is the cheap constructor used on the hot path:
|
||||
// the prepared list is built once per block and borrowed by every per-tx reader.
|
||||
func NewReaderWithPreparedAccessList(base Reader, prepared *bal.PreparedAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
|
||||
return &ReaderWithBlockLevelAccessList{
|
||||
Reader: base,
|
||||
AccessList: bal.NewAccessListReader(accessList),
|
||||
TxIndex: txIndex,
|
||||
Reader: base,
|
||||
prepared: prepared,
|
||||
TxIndex: txIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// NewReaderWithBlockLevelAccessList wraps a base reader with a raw access list,
|
||||
// preprocessing it on the spot. Prefer NewReaderWithPreparedAccessList when the
|
||||
// prepared list can be built once and shared across multiple readers.
|
||||
func NewReaderWithBlockLevelAccessList(base Reader, accessList bal.BlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
|
||||
return NewReaderWithPreparedAccessList(base, bal.NewPreparedAccessList(accessList), txIndex)
|
||||
}
|
||||
|
||||
// Account implements Reader, returning the account with the specific address.
|
||||
func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (acct *types.StateAccount, err error) {
|
||||
acct, err = r.Reader.Account(addr)
|
||||
|
|
@ -231,9 +245,11 @@ func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (acct *typ
|
|||
return nil, err
|
||||
}
|
||||
|
||||
mut := r.AccessList.AccountMutations(addr, r.TxIndex)
|
||||
if mut == nil {
|
||||
return
|
||||
balance := r.prepared.Balance(addr, r.TxIndex)
|
||||
code := r.prepared.Code(addr, r.TxIndex)
|
||||
nonce, hasNonce := r.prepared.Nonce(addr, r.TxIndex)
|
||||
if balance == nil && code == nil && !hasNonce {
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
if acct == nil {
|
||||
|
|
@ -244,15 +260,18 @@ func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (acct *typ
|
|||
acct = acct.Copy()
|
||||
}
|
||||
|
||||
if mut.Balance != nil {
|
||||
acct.Balance = mut.Balance
|
||||
// balance and code alias the shared access list; this is safe because the
|
||||
// EVM never mutates them in place (it replaces the pointer/slice wholesale,
|
||||
// and the journal clones before stashing).
|
||||
if balance != nil {
|
||||
acct.Balance = balance
|
||||
}
|
||||
if mut.Code != nil {
|
||||
codeHash := crypto.Keccak256Hash(mut.Code)
|
||||
if code != nil {
|
||||
codeHash := crypto.Keccak256Hash(code)
|
||||
acct.CodeHash = codeHash[:]
|
||||
}
|
||||
if mut.Nonce != nil {
|
||||
acct.Nonce = *mut.Nonce
|
||||
if hasNonce {
|
||||
acct.Nonce = nonce
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -260,9 +279,8 @@ func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (acct *typ
|
|||
// Storage implements Reader, returning the storage slot with the specific
|
||||
// address and slot key.
|
||||
func (r *ReaderWithBlockLevelAccessList) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
val := r.AccessList.Storage(addr, slot, r.TxIndex)
|
||||
if val != nil {
|
||||
return *val, nil
|
||||
if val, ok := r.prepared.StorageAt(addr, slot, r.TxIndex); ok {
|
||||
return val, nil
|
||||
}
|
||||
return r.Reader.Storage(addr, slot)
|
||||
}
|
||||
|
|
@ -270,9 +288,8 @@ func (r *ReaderWithBlockLevelAccessList) Storage(addr common.Address, slot commo
|
|||
// Has implements Reader, returning the flag indicating whether the contract
|
||||
// code with specified address and hash exists or not.
|
||||
func (r *ReaderWithBlockLevelAccessList) Has(addr common.Address, codeHash common.Hash) bool {
|
||||
mut := r.AccessList.AccountMutations(addr, r.TxIndex)
|
||||
if mut != nil && mut.Code != nil {
|
||||
return crypto.Keccak256Hash(mut.Code) == codeHash
|
||||
if code := r.prepared.Code(addr, r.TxIndex); code != nil {
|
||||
return crypto.Keccak256Hash(code) == codeHash
|
||||
}
|
||||
return r.Reader.Has(addr, codeHash)
|
||||
}
|
||||
|
|
@ -280,10 +297,8 @@ func (r *ReaderWithBlockLevelAccessList) Has(addr common.Address, codeHash commo
|
|||
// Code implements Reader, returning the contract code with specified address
|
||||
// and hash.
|
||||
func (r *ReaderWithBlockLevelAccessList) Code(addr common.Address, codeHash common.Hash) []byte {
|
||||
mut := r.AccessList.AccountMutations(addr, r.TxIndex)
|
||||
if mut != nil && mut.Code != nil && crypto.Keccak256Hash(mut.Code) == codeHash {
|
||||
// TODO: need to copy here?
|
||||
return mut.Code
|
||||
if code := r.prepared.Code(addr, r.TxIndex); code != nil && crypto.Keccak256Hash(code) == codeHash {
|
||||
return code
|
||||
}
|
||||
return r.Reader.Code(addr, codeHash)
|
||||
}
|
||||
|
|
@ -291,9 +306,8 @@ func (r *ReaderWithBlockLevelAccessList) Code(addr common.Address, codeHash comm
|
|||
// CodeSize implements Reader, returning the contract code size with specified
|
||||
// address and hash.
|
||||
func (r *ReaderWithBlockLevelAccessList) CodeSize(addr common.Address, codeHash common.Hash) int {
|
||||
mut := r.AccessList.AccountMutations(addr, r.TxIndex)
|
||||
if mut != nil && mut.Code != nil && crypto.Keccak256Hash(mut.Code) == codeHash {
|
||||
return len(mut.Code)
|
||||
if code := r.prepared.Code(addr, r.TxIndex); code != nil && crypto.Keccak256Hash(code) == codeHash {
|
||||
return len(code)
|
||||
}
|
||||
return r.Reader.CodeSize(addr, codeHash)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,176 @@
|
|||
package bal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// AccessListReader exposes utilities to read state mutations and accesses from an access list
|
||||
type AccessListReader map[common.Address]*AccountAccess
|
||||
|
||||
func NewAccessListReader(bal BlockAccessList) (reader AccessListReader) {
|
||||
reader = make(AccessListReader)
|
||||
for _, accountAccess := range bal {
|
||||
reader[accountAccess.Address] = &accountAccess
|
||||
}
|
||||
return
|
||||
// PreparedAccessList is an immutable, per-block preprocessed view of a
|
||||
// BlockAccessList optimized for repeated point-in-time reads.
|
||||
//
|
||||
// It is built once per block (NewPreparedAccessList) before parallel
|
||||
// transaction execution begins. The change slices it holds are the
|
||||
// already-sorted slices decoded from the BlockAccessList, borrowed by
|
||||
// reference (never copied, never mutated). After construction the structure
|
||||
// is read-only and therefore safe for concurrent use by all per-transaction
|
||||
// readers without any synchronization.
|
||||
//
|
||||
// Each lookup binary-searches the relevant change slice for the last mutation
|
||||
// strictly before the queried block-access index, which is O(log K) and
|
||||
// allocation-free, in contrast to the previous map-backed reader that
|
||||
// re-walked every change array from index 0 and re-allocated an aggregate
|
||||
// mutation object on every call.
|
||||
type PreparedAccessList struct {
|
||||
accounts map[common.Address]*preparedAccount
|
||||
}
|
||||
|
||||
// AccountMutations returns the aggregate mutation for an account up until (and not including) the given block access
|
||||
// list index.
|
||||
func (a AccessListReader) AccountMutations(addr common.Address, idx int) (res *AccountMutations) {
|
||||
diff, exist := a[addr]
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
type preparedAccount struct {
|
||||
// The following slices are borrowed directly from the decoded
|
||||
// AccountAccess. They are validated to be strictly sorted ascending by
|
||||
// BlockAccessIndex (see bal_encoding.go), which is exactly the key we
|
||||
// binary-search on.
|
||||
balances []encodingBalanceChange
|
||||
nonces []encodingAccountNonce
|
||||
codes []encodingCodeChange
|
||||
storage map[common.Hash]*preparedSlot
|
||||
|
||||
res = &AccountMutations{}
|
||||
// access is retained to back the once-per-block aggregate helpers
|
||||
// (StorageKeys, AllDestructions) without re-deriving anything.
|
||||
access *AccountAccess
|
||||
}
|
||||
|
||||
for i := 0; i < len(diff.BalanceChanges) && diff.BalanceChanges[i].BlockAccessIndex < uint32(idx); i++ {
|
||||
res.Balance = diff.BalanceChanges[i].PostBalance.Clone()
|
||||
}
|
||||
type preparedSlot struct {
|
||||
changes []encodingStorageWrite // borrowed, sorted asc by BlockAccessIndex
|
||||
}
|
||||
|
||||
for i := 0; i < len(diff.CodeChanges) && diff.CodeChanges[i].BlockAccessIndex < uint32(idx); i++ {
|
||||
res.Code = bytes.Clone(diff.CodeChanges[i].NewCode)
|
||||
}
|
||||
|
||||
for i := 0; i < len(diff.NonceChanges) && diff.NonceChanges[i].BlockAccessIndex < uint32(idx); i++ {
|
||||
res.Nonce = new(uint64)
|
||||
*res.Nonce = diff.NonceChanges[i].PostNonce
|
||||
}
|
||||
|
||||
if len(diff.StorageChanges) > 0 {
|
||||
res.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
for _, slotWrites := range diff.StorageChanges {
|
||||
for i := 0; i < len(slotWrites.SlotChanges) && slotWrites.SlotChanges[i].BlockAccessIndex < uint32(idx); i++ {
|
||||
res.StorageWrites[slotWrites.Slot.Bytes32()] = slotWrites.SlotChanges[i].PostValue.Bytes32()
|
||||
// NewPreparedAccessList preprocesses a BlockAccessList into a PreparedAccessList.
|
||||
// It performs a single linear pass and borrows the underlying change slices by
|
||||
// reference; the provided list must not be mutated afterwards.
|
||||
func NewPreparedAccessList(list BlockAccessList) *PreparedAccessList {
|
||||
accounts := make(map[common.Address]*preparedAccount, len(list))
|
||||
for i := range list {
|
||||
a := &list[i] // index; do not range-copy the AccountAccess
|
||||
pa := &preparedAccount{
|
||||
balances: a.BalanceChanges,
|
||||
nonces: a.NonceChanges,
|
||||
codes: a.CodeChanges,
|
||||
access: a,
|
||||
}
|
||||
if len(a.StorageChanges) > 0 {
|
||||
pa.storage = make(map[common.Hash]*preparedSlot, len(a.StorageChanges))
|
||||
for j := range a.StorageChanges {
|
||||
sc := &a.StorageChanges[j]
|
||||
pa.storage[sc.Slot.Bytes32()] = &preparedSlot{changes: sc.SlotChanges}
|
||||
}
|
||||
}
|
||||
accounts[a.Address] = pa
|
||||
}
|
||||
return &PreparedAccessList{accounts: accounts}
|
||||
}
|
||||
|
||||
// lastBefore returns the position of the last element in a slice of n elements
|
||||
// sorted ascending by BlockAccessIndex whose key is strictly less than idx, or
|
||||
// -1 if no such element exists. keyAt returns the BlockAccessIndex at position k.
|
||||
func lastBefore(n int, idx uint32, keyAt func(k int) uint32) int {
|
||||
// sort.Search returns the smallest position whose key is >= idx; everything
|
||||
// before it is strictly less than idx, so the answer is that position - 1.
|
||||
return sort.Search(n, func(k int) bool { return keyAt(k) >= idx }) - 1
|
||||
}
|
||||
|
||||
// Balance returns the post-balance in effect immediately before the given block
|
||||
// access index, or nil if the account's balance was not changed before idx.
|
||||
// The returned pointer aliases the access list and must not be mutated.
|
||||
func (p *PreparedAccessList) Balance(addr common.Address, idx int) *uint256.Int {
|
||||
a := p.accounts[addr]
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
k := lastBefore(len(a.balances), uint32(idx), func(i int) uint32 { return a.balances[i].BlockAccessIndex })
|
||||
if k < 0 {
|
||||
return nil
|
||||
}
|
||||
return a.balances[k].PostBalance
|
||||
}
|
||||
|
||||
// Nonce returns the post-nonce in effect immediately before the given block
|
||||
// access index. The boolean is false if the nonce was not changed before idx.
|
||||
func (p *PreparedAccessList) Nonce(addr common.Address, idx int) (uint64, bool) {
|
||||
a := p.accounts[addr]
|
||||
if a == nil {
|
||||
return 0, false
|
||||
}
|
||||
k := lastBefore(len(a.nonces), uint32(idx), func(i int) uint32 { return a.nonces[i].BlockAccessIndex })
|
||||
if k < 0 {
|
||||
return 0, false
|
||||
}
|
||||
return a.nonces[k].PostNonce, true
|
||||
}
|
||||
|
||||
// Code returns the contract code in effect immediately before the given block
|
||||
// access index, or nil if the code was not changed before idx. The returned
|
||||
// slice aliases the access list and must not be mutated.
|
||||
func (p *PreparedAccessList) Code(addr common.Address, idx int) []byte {
|
||||
a := p.accounts[addr]
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
k := lastBefore(len(a.codes), uint32(idx), func(i int) uint32 { return a.codes[i].BlockAccessIndex })
|
||||
if k < 0 {
|
||||
return nil
|
||||
}
|
||||
return a.codes[k].NewCode
|
||||
}
|
||||
|
||||
// StorageAt returns the post-value of a storage slot immediately before the
|
||||
// given block access index. The boolean is false if the slot was not written
|
||||
// before idx.
|
||||
func (p *PreparedAccessList) StorageAt(addr common.Address, slot common.Hash, idx int) (common.Hash, bool) {
|
||||
a := p.accounts[addr]
|
||||
if a == nil {
|
||||
return common.Hash{}, false
|
||||
}
|
||||
s := a.storage[slot]
|
||||
if s == nil {
|
||||
return common.Hash{}, false
|
||||
}
|
||||
k := lastBefore(len(s.changes), uint32(idx), func(i int) uint32 { return s.changes[i].BlockAccessIndex })
|
||||
if k < 0 {
|
||||
return common.Hash{}, false
|
||||
}
|
||||
return s.changes[k].PostValue.Bytes32(), true
|
||||
}
|
||||
|
||||
// AccountMutations returns the aggregate mutation for an account up until (and
|
||||
// not including) the given block access list index, or nil if the account was
|
||||
// not mutated before idx.
|
||||
func (p *PreparedAccessList) AccountMutations(addr common.Address, idx int) *AccountMutations {
|
||||
a := p.accounts[addr]
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
res := &AccountMutations{}
|
||||
if bal := p.Balance(addr, idx); bal != nil {
|
||||
res.Balance = bal.Clone()
|
||||
}
|
||||
if code := p.Code(addr, idx); code != nil {
|
||||
res.Code = code
|
||||
}
|
||||
if nonce, ok := p.Nonce(addr, idx); ok {
|
||||
res.Nonce = new(uint64)
|
||||
*res.Nonce = nonce
|
||||
}
|
||||
for slot, s := range a.storage {
|
||||
k := lastBefore(len(s.changes), uint32(idx), func(i int) uint32 { return s.changes[i].BlockAccessIndex })
|
||||
if k < 0 {
|
||||
continue
|
||||
}
|
||||
if res.StorageWrites == nil {
|
||||
res.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
res.StorageWrites[slot] = s.changes[k].PostValue.Bytes32()
|
||||
}
|
||||
if res.Code == nil && res.Nonce == nil && len(res.StorageWrites) == 0 && res.Balance == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -56,11 +179,12 @@ func (a AccessListReader) AccountMutations(addr common.Address, idx int) (res *A
|
|||
|
||||
type StorageKeys map[common.Address][]common.Hash
|
||||
|
||||
// StorageKeys returns the set of accounts and storage keys mutated in the access list.
|
||||
// If reads is set, the un-mutated accounts/keys are included in the result.
|
||||
func (a AccessListReader) StorageKeys(reads bool) (keys StorageKeys) {
|
||||
// StorageKeys returns the set of accounts and storage keys mutated in the access
|
||||
// list. If reads is set, the un-mutated accounts/keys are included in the result.
|
||||
func (p *PreparedAccessList) StorageKeys(reads bool) (keys StorageKeys) {
|
||||
keys = make(StorageKeys)
|
||||
for addr, acct := range a {
|
||||
for addr, a := range p.accounts {
|
||||
acct := a.access
|
||||
for _, storageChange := range acct.StorageChanges {
|
||||
keys[addr] = append(keys[addr], storageChange.Slot.Bytes32())
|
||||
}
|
||||
|
|
@ -74,36 +198,23 @@ func (a AccessListReader) StorageKeys(reads bool) (keys StorageKeys) {
|
|||
return
|
||||
}
|
||||
|
||||
// Storage returns the value of a storage key at the start of executing an index.
|
||||
// If the slot has no mutations in the access list, it returns nil.
|
||||
func (a AccessListReader) Storage(addr common.Address, key common.Hash, idx int) (val *common.Hash) {
|
||||
storageMuts := a.AccountMutations(addr, idx)
|
||||
if storageMuts != nil {
|
||||
res, ok := storageMuts.StorageWrites[key]
|
||||
if ok {
|
||||
return &res
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mutations returns the aggregate state mutations from bal indices [0, idx)
|
||||
func (a AccessListReader) Mutations(idx int) *StateMutations {
|
||||
// Mutations returns the aggregate state mutations from bal indices [0, idx).
|
||||
func (p *PreparedAccessList) Mutations(idx int) *StateMutations {
|
||||
res := make(StateMutations)
|
||||
for addr := range a {
|
||||
if mut := a.AccountMutations(addr, idx); mut != nil {
|
||||
for addr := range p.accounts {
|
||||
if mut := p.AccountMutations(addr, idx); mut != nil {
|
||||
res[addr] = *mut
|
||||
}
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
// AllDestructions returns all accounts that experienced a destruction, regardless of whether
|
||||
// they were later resurrected and exist after the block. It excludes ephemeral contracts from
|
||||
// the result.
|
||||
func (a AccessListReader) AllDestructions() (res []common.Address) {
|
||||
for addr, access := range a {
|
||||
for _, nonce := range access.NonceChanges {
|
||||
// AllDestructions returns all accounts that experienced a destruction, regardless
|
||||
// of whether they were later resurrected and exist after the block. It excludes
|
||||
// ephemeral contracts from the result.
|
||||
func (p *PreparedAccessList) AllDestructions() (res []common.Address) {
|
||||
for addr, a := range p.accounts {
|
||||
for _, nonce := range a.access.NonceChanges {
|
||||
if nonce.PostNonce == 0 {
|
||||
res = append(res, addr)
|
||||
break
|
||||
|
|
|
|||
Loading…
Reference in a new issue