mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 02:10:46 +00:00
clean up BAL state diff reader logic
This commit is contained in:
parent
e230772f11
commit
2bf974b6e6
5 changed files with 42 additions and 71 deletions
|
|
@ -2132,7 +2132,7 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
|
||||||
// Preprocess the access list once for the whole block; the resulting
|
// Preprocess the access list once for the whole block; the resulting
|
||||||
// structure is read-only and shared by the prefetch reader, the state
|
// structure is read-only and shared by the prefetch reader, the state
|
||||||
// transition and every per-transaction execution reader.
|
// transition and every per-transaction execution reader.
|
||||||
prepared := bal.NewPreparedAccessList(*al)
|
prepared := bal.NewAccessListReader(*al)
|
||||||
prefetchReader, err := sdb.ReaderWithPrefetch(parentRoot, prepared.StorageKeys(useAsyncReads), bc.cfg.PrefetchWorkers, bc.cfg.BlockingPrefetch)
|
prefetchReader, err := sdb.ReaderWithPrefetch(parentRoot, prepared.StorageKeys(useAsyncReads), bc.cfg.PrefetchWorkers, bc.cfg.BlockingPrefetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func NewParallelStateProcessor(chain *HeaderChain, vmConfig *vm.Config) Parallel
|
||||||
// performs post-tx state transition (system contracts and withdrawals)
|
// performs post-tx state transition (system contracts and withdrawals)
|
||||||
// and calculates the ProcessResult, returning it to be sent on resCh
|
// and calculates the ProcessResult, returning it to be sent on resCh
|
||||||
// by resultHandler
|
// by resultHandler
|
||||||
func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStart time.Time, preTxBal *bal.ConstructionBlockAccessList, prepared *bal.PreparedAccessList, statedb *state.StateDB, results []txExecResult) *ProcessResultWithMetrics {
|
func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStart time.Time, preTxBal *bal.ConstructionBlockAccessList, prepared *bal.AccessListReader, statedb *state.StateDB, results []txExecResult) *ProcessResultWithMetrics {
|
||||||
tExec := time.Since(tExecStart)
|
tExec := time.Since(tExecStart)
|
||||||
tPostprocessStart := time.Now()
|
tPostprocessStart := time.Now()
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
|
|
@ -148,7 +148,7 @@ type txExecResult struct {
|
||||||
|
|
||||||
// resultHandler polls until all transactions have finished executing and the
|
// resultHandler polls until all transactions have finished executing and the
|
||||||
// state root calculation is complete. The result is emitted on resCh.
|
// state root calculation is complete. The result is emitted on resCh.
|
||||||
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) {
|
func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxBAL *bal.ConstructionBlockAccessList, prepared *bal.AccessListReader, 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
|
// 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)
|
// 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
|
var results []txExecResult
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
// and commit for EIP 7928 access-list-containing blocks. An instance of
|
// and commit for EIP 7928 access-list-containing blocks. An instance of
|
||||||
// this object is only used for a single block.
|
// this object is only used for a single block.
|
||||||
type BALStateTransition struct {
|
type BALStateTransition struct {
|
||||||
accessList *bal.PreparedAccessList
|
accessList *bal.AccessListReader
|
||||||
written bal.WrittenCounts
|
written bal.WrittenCounts
|
||||||
db Database
|
db Database
|
||||||
reader Reader
|
reader Reader
|
||||||
|
|
@ -86,7 +86,7 @@ type BALStateTransitionMetrics struct {
|
||||||
TotalCommitTime time.Duration
|
TotalCommitTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash, prepared *bal.PreparedAccessList) (*BALStateTransition, error) {
|
func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash, prepared *bal.AccessListReader) (*BALStateTransition, error) {
|
||||||
stateTrie, err := db.OpenTrie(parentRoot)
|
stateTrie, err := db.OpenTrie(parentRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -118,7 +118,7 @@ func (s *BALStateTransition) WrittenCounts() bal.WrittenCounts {
|
||||||
// PreparedAccessList returns the shared, read-only preprocessed access list for
|
// PreparedAccessList returns the shared, read-only preprocessed access list for
|
||||||
// the block. It is built once per block and reused by the parallel execution
|
// the block. It is built once per block and reused by the parallel execution
|
||||||
// readers so the preprocessing is not repeated per transaction.
|
// readers so the preprocessing is not repeated per transaction.
|
||||||
func (s *BALStateTransition) PreparedAccessList() *bal.PreparedAccessList {
|
func (s *BALStateTransition) PreparedAccessList() *bal.AccessListReader {
|
||||||
return s.accessList
|
return s.accessList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,18 +212,18 @@ func (r *prefetchStateReader) process(start, limit int) {
|
||||||
// prior to TxIndex.
|
// prior to TxIndex.
|
||||||
//
|
//
|
||||||
// It is a cheap, per-transaction view over a shared, read-only
|
// It is a cheap, per-transaction view over a shared, read-only
|
||||||
// bal.PreparedAccessList: constructing one is O(1) and every lookup is an
|
// bal.AccessListReader: constructing one is O(1) and every lookup is an
|
||||||
// allocation-free binary search.
|
// allocation-free binary search.
|
||||||
type ReaderWithBlockLevelAccessList struct {
|
type ReaderWithBlockLevelAccessList struct {
|
||||||
Reader
|
Reader
|
||||||
prepared *bal.PreparedAccessList
|
prepared *bal.AccessListReader
|
||||||
TxIndex int
|
TxIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReaderWithPreparedAccessList wraps a base reader with a shared, already
|
// NewReaderWithPreparedAccessList wraps a base reader with a shared, already
|
||||||
// preprocessed access list. This is the cheap constructor used on the hot path:
|
// 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.
|
// 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 {
|
func NewReaderWithPreparedAccessList(base Reader, prepared *bal.AccessListReader, txIndex int) *ReaderWithBlockLevelAccessList {
|
||||||
return &ReaderWithBlockLevelAccessList{
|
return &ReaderWithBlockLevelAccessList{
|
||||||
Reader: base,
|
Reader: base,
|
||||||
prepared: prepared,
|
prepared: prepared,
|
||||||
|
|
@ -235,7 +235,7 @@ func NewReaderWithPreparedAccessList(base Reader, prepared *bal.PreparedAccessLi
|
||||||
// preprocessing it on the spot. Prefer NewReaderWithPreparedAccessList when the
|
// preprocessing it on the spot. Prefer NewReaderWithPreparedAccessList when the
|
||||||
// prepared list can be built once and shared across multiple readers.
|
// prepared list can be built once and shared across multiple readers.
|
||||||
func NewReaderWithBlockLevelAccessList(base Reader, accessList bal.BlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
|
func NewReaderWithBlockLevelAccessList(base Reader, accessList bal.BlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
|
||||||
return NewReaderWithPreparedAccessList(base, bal.NewPreparedAccessList(accessList), txIndex)
|
return NewReaderWithPreparedAccessList(base, bal.NewAccessListReader(accessList), txIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account implements Reader, returning the account with the specific address.
|
// Account implements Reader, returning the account with the specific address.
|
||||||
|
|
|
||||||
|
|
@ -7,67 +7,39 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PreparedAccessList is an immutable, per-block preprocessed view of a
|
// AccessListReader enables efficient state diff lookups from a block access
|
||||||
// BlockAccessList optimized for repeated point-in-time reads.
|
// list during block execution.
|
||||||
//
|
type AccessListReader struct {
|
||||||
// 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
|
accounts map[common.Address]*preparedAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
type preparedAccount struct {
|
type preparedAccount struct {
|
||||||
// The following slices are borrowed directly from the decoded
|
storage map[common.Hash]preparedSlot
|
||||||
// AccountAccess. They are validated to be strictly sorted ascending by
|
AccountAccess
|
||||||
// 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
|
|
||||||
|
|
||||||
// access is retained to back the once-per-block aggregate helpers
|
|
||||||
// (StorageKeys, AllDestructions) without re-deriving anything.
|
|
||||||
access *AccountAccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type preparedSlot struct {
|
type preparedSlot struct {
|
||||||
changes []encodingStorageWrite // borrowed, sorted asc by BlockAccessIndex
|
changes []encodingStorageWrite // borrowed, sorted asc by BlockAccessIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPreparedAccessList preprocesses a BlockAccessList into a PreparedAccessList.
|
// NewAccessListReader instantiates an access list reader.
|
||||||
// It performs a single linear pass and borrows the underlying change slices by
|
func NewAccessListReader(list BlockAccessList) *AccessListReader {
|
||||||
// reference; the provided list must not be mutated afterwards.
|
|
||||||
func NewPreparedAccessList(list BlockAccessList) *PreparedAccessList {
|
|
||||||
accounts := make(map[common.Address]*preparedAccount, len(list))
|
accounts := make(map[common.Address]*preparedAccount, len(list))
|
||||||
for i := range list {
|
for i := range list {
|
||||||
a := &list[i] // index; do not range-copy the AccountAccess
|
a := list[i] // index; do not range-copy the AccountAccess
|
||||||
pa := &preparedAccount{
|
pa := &preparedAccount{
|
||||||
balances: a.BalanceChanges,
|
AccountAccess: a,
|
||||||
nonces: a.NonceChanges,
|
|
||||||
codes: a.CodeChanges,
|
|
||||||
access: a,
|
|
||||||
}
|
}
|
||||||
if len(a.StorageChanges) > 0 {
|
if len(a.StorageChanges) > 0 {
|
||||||
pa.storage = make(map[common.Hash]*preparedSlot, len(a.StorageChanges))
|
pa.storage = make(map[common.Hash]preparedSlot, len(a.StorageChanges))
|
||||||
for j := range a.StorageChanges {
|
for j := range a.StorageChanges {
|
||||||
sc := &a.StorageChanges[j]
|
sc := &a.StorageChanges[j]
|
||||||
pa.storage[sc.Slot.Bytes32()] = &preparedSlot{changes: sc.SlotChanges}
|
pa.storage[sc.Slot.Bytes32()] = preparedSlot{changes: sc.SlotChanges}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accounts[a.Address] = pa
|
accounts[a.Address] = pa
|
||||||
}
|
}
|
||||||
return &PreparedAccessList{accounts: accounts}
|
return &AccessListReader{accounts: accounts}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastBefore returns the position of the last element in a slice of n elements
|
// lastBefore returns the position of the last element in a slice of n elements
|
||||||
|
|
@ -82,57 +54,57 @@ func lastBefore(n int, idx uint32, keyAt func(k int) uint32) int {
|
||||||
// Balance returns the post-balance in effect immediately before the given block
|
// 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.
|
// 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.
|
// The returned pointer aliases the access list and must not be mutated.
|
||||||
func (p *PreparedAccessList) Balance(addr common.Address, idx int) *uint256.Int {
|
func (p *AccessListReader) Balance(addr common.Address, idx int) *uint256.Int {
|
||||||
a := p.accounts[addr]
|
a := p.accounts[addr]
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
k := lastBefore(len(a.balances), uint32(idx), func(i int) uint32 { return a.balances[i].BlockAccessIndex })
|
k := lastBefore(len(a.BalanceChanges), uint32(idx), func(i int) uint32 { return a.BalanceChanges[i].BlockAccessIndex })
|
||||||
if k < 0 {
|
if k < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return a.balances[k].PostBalance
|
return a.BalanceChanges[k].PostBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nonce returns the post-nonce in effect immediately before the given block
|
// 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.
|
// 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) {
|
func (p *AccessListReader) Nonce(addr common.Address, idx int) (uint64, bool) {
|
||||||
a := p.accounts[addr]
|
a := p.accounts[addr]
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
k := lastBefore(len(a.nonces), uint32(idx), func(i int) uint32 { return a.nonces[i].BlockAccessIndex })
|
k := lastBefore(len(a.NonceChanges), uint32(idx), func(i int) uint32 { return a.NonceChanges[i].BlockAccessIndex })
|
||||||
if k < 0 {
|
if k < 0 {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
return a.nonces[k].PostNonce, true
|
return a.NonceChanges[k].PostNonce, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code returns the contract code in effect immediately before the given block
|
// 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
|
// access index, or nil if the code was not changed before idx. The returned
|
||||||
// slice aliases the access list and must not be mutated.
|
// slice aliases the access list and must not be mutated.
|
||||||
func (p *PreparedAccessList) Code(addr common.Address, idx int) []byte {
|
func (p *AccessListReader) Code(addr common.Address, idx int) []byte {
|
||||||
a := p.accounts[addr]
|
a := p.accounts[addr]
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
k := lastBefore(len(a.codes), uint32(idx), func(i int) uint32 { return a.codes[i].BlockAccessIndex })
|
k := lastBefore(len(a.CodeChanges), uint32(idx), func(i int) uint32 { return a.CodeChanges[i].BlockAccessIndex })
|
||||||
if k < 0 {
|
if k < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return a.codes[k].NewCode
|
return a.CodeChanges[k].NewCode
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageAt returns the post-value of a storage slot immediately before the
|
// 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
|
// given block access index. The boolean is false if the slot was not written
|
||||||
// before idx.
|
// before idx.
|
||||||
func (p *PreparedAccessList) StorageAt(addr common.Address, slot common.Hash, idx int) (common.Hash, bool) {
|
func (p *AccessListReader) StorageAt(addr common.Address, slot common.Hash, idx int) (common.Hash, bool) {
|
||||||
a := p.accounts[addr]
|
a := p.accounts[addr]
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return common.Hash{}, false
|
return common.Hash{}, false
|
||||||
}
|
}
|
||||||
s := a.storage[slot]
|
s, ok := a.storage[slot]
|
||||||
if s == nil {
|
if !ok {
|
||||||
return common.Hash{}, false
|
return common.Hash{}, false
|
||||||
}
|
}
|
||||||
k := lastBefore(len(s.changes), uint32(idx), func(i int) uint32 { return s.changes[i].BlockAccessIndex })
|
k := lastBefore(len(s.changes), uint32(idx), func(i int) uint32 { return s.changes[i].BlockAccessIndex })
|
||||||
|
|
@ -145,7 +117,7 @@ func (p *PreparedAccessList) StorageAt(addr common.Address, slot common.Hash, id
|
||||||
// AccountMutations returns the aggregate mutation for an account up until (and
|
// 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 including) the given block access list index, or nil if the account was
|
||||||
// not mutated before idx.
|
// not mutated before idx.
|
||||||
func (p *PreparedAccessList) AccountMutations(addr common.Address, idx int) *AccountMutations {
|
func (p *AccessListReader) AccountMutations(addr common.Address, idx int) *AccountMutations {
|
||||||
a := p.accounts[addr]
|
a := p.accounts[addr]
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -181,17 +153,16 @@ type StorageKeys map[common.Address][]common.Hash
|
||||||
|
|
||||||
// StorageKeys returns the set of accounts and storage keys mutated in the access
|
// 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.
|
// list. If reads is set, the un-mutated accounts/keys are included in the result.
|
||||||
func (p *PreparedAccessList) StorageKeys(reads bool) (keys StorageKeys) {
|
func (p *AccessListReader) StorageKeys(reads bool) (keys StorageKeys) {
|
||||||
keys = make(StorageKeys)
|
keys = make(StorageKeys)
|
||||||
for addr, a := range p.accounts {
|
for addr, a := range p.accounts {
|
||||||
acct := a.access
|
for _, storageChange := range a.StorageChanges {
|
||||||
for _, storageChange := range acct.StorageChanges {
|
|
||||||
keys[addr] = append(keys[addr], storageChange.Slot.Bytes32())
|
keys[addr] = append(keys[addr], storageChange.Slot.Bytes32())
|
||||||
}
|
}
|
||||||
if !(reads && len(acct.StorageReads) > 0) {
|
if !(reads && len(a.StorageReads) > 0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, storageRead := range acct.StorageReads {
|
for _, storageRead := range a.StorageReads {
|
||||||
keys[addr] = append(keys[addr], storageRead.Bytes32())
|
keys[addr] = append(keys[addr], storageRead.Bytes32())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +170,7 @@ func (p *PreparedAccessList) StorageKeys(reads bool) (keys StorageKeys) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutations returns the aggregate state mutations from bal indices [0, idx).
|
// Mutations returns the aggregate state mutations from bal indices [0, idx).
|
||||||
func (p *PreparedAccessList) Mutations(idx int) *StateMutations {
|
func (p *AccessListReader) Mutations(idx int) *StateMutations {
|
||||||
res := make(StateMutations)
|
res := make(StateMutations)
|
||||||
for addr := range p.accounts {
|
for addr := range p.accounts {
|
||||||
if mut := p.AccountMutations(addr, idx); mut != nil {
|
if mut := p.AccountMutations(addr, idx); mut != nil {
|
||||||
|
|
@ -212,9 +183,9 @@ func (p *PreparedAccessList) Mutations(idx int) *StateMutations {
|
||||||
// AllDestructions returns all accounts that experienced a destruction, regardless
|
// AllDestructions returns all accounts that experienced a destruction, regardless
|
||||||
// of whether they were later resurrected and exist after the block. It excludes
|
// of whether they were later resurrected and exist after the block. It excludes
|
||||||
// ephemeral contracts from the result.
|
// ephemeral contracts from the result.
|
||||||
func (p *PreparedAccessList) AllDestructions() (res []common.Address) {
|
func (p *AccessListReader) AllDestructions() (res []common.Address) {
|
||||||
for addr, a := range p.accounts {
|
for addr, a := range p.accounts {
|
||||||
for _, nonce := range a.access.NonceChanges {
|
for _, nonce := range a.NonceChanges {
|
||||||
if nonce.PostNonce == 0 {
|
if nonce.PostNonce == 0 {
|
||||||
res = append(res, addr)
|
res = append(res, addr)
|
||||||
break
|
break
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue