ethclient/lightclient: more comments and minor changes

This commit is contained in:
Zsolt Felfoldi 2024-06-14 11:10:50 +02:00
parent 37950cad34
commit e2afe317e6
3 changed files with 48 additions and 11 deletions

View file

@ -54,6 +54,7 @@ func (c *Client) closeBlocksAndHeaders() {
c.blockRequests.close() c.blockRequests.close()
} }
// getHeader returns the header with the given block hash.
func (c *Client) getHeader(ctx context.Context, hash common.Hash) (*types.Header, error) { func (c *Client) getHeader(ctx context.Context, hash common.Hash) (*types.Header, error) {
if header, ok := c.headerCache.Get(hash); ok { if header, ok := c.headerCache.Get(hash); ok {
return header, nil return header, nil
@ -84,15 +85,25 @@ func (c *Client) getHeader(ctx context.Context, hash common.Hash) (*types.Header
return header, err return header, err
} }
// getPayloadHeader returns the payload header with the given block hash.
// Note that types.Header and the CL execution payload header (btypes.ExecutionHeader)
// both contain the information relevant to the light client but they are not
// interchangeable. Payload headers are received from head/finality updates and
// are cached but they are not obtainable later on demand as they are not directly
// chained to each other by hash reference. If reverse syncing is required then
// EL headers should be used.
func (c *Client) getPayloadHeader(hash common.Hash) *btypes.ExecutionHeader { func (c *Client) getPayloadHeader(hash common.Hash) *btypes.ExecutionHeader {
pheader, _ := c.payloadHeaderCache.Get(hash) pheader, _ := c.payloadHeaderCache.Get(hash)
return pheader return pheader
} }
// addPayloadHeader caches the given payload header.
func (c *Client) addPayloadHeader(header *btypes.ExecutionHeader) { func (c *Client) addPayloadHeader(header *btypes.ExecutionHeader) {
c.payloadHeaderCache.Add(header.BlockHash(), header) c.payloadHeaderCache.Add(header.BlockHash(), header)
} }
// getBlock returns the block with the given hash. If the block has been retrieved
// from the server then the transaction inclusion positions are also cached.
func (c *Client) getBlock(ctx context.Context, hash common.Hash) (*types.Block, error) { func (c *Client) getBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
if block, ok := c.blockCache.Get(hash); ok { if block, ok := c.blockCache.Get(hash); ok {
return block, nil return block, nil
@ -149,7 +160,8 @@ func (c *Client) requestBlock(ctx context.Context, hash common.Hash) (*types.Blo
return block, nil return block, nil
} }
//TODO de-duplicate json block decoding // decodeBlock decodes a JSON encoded block.
//TODO de-duplicate
func decodeBlock(raw json.RawMessage) (*types.Block, error) { func decodeBlock(raw json.RawMessage) (*types.Block, error) {
// Decode header and transactions. // Decode header and transactions.
var head *types.Header var head *types.Header

View file

@ -31,6 +31,8 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
// recentCanonicalLength specifies the maximum number of most recent number to hash
// associations stored in the recent map
const recentCanonicalLength = 256 const recentCanonicalLength = 256
// canonicalChainFields defines Client fields related to canonical chain tracking. // canonicalChainFields defines Client fields related to canonical chain tracking.
@ -40,13 +42,13 @@ type canonicalChainFields struct {
recent map[uint64]common.Hash // nil while head == nil recent map[uint64]common.Hash // nil while head == nil
recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head
tailFetchCh, closeCh chan struct{} tailFetchCh, closeCh chan struct{}
finalized *lru.Cache[uint64, common.Hash] // finalized but not recent hashes cache *lru.Cache[uint64, common.Hash] // older than recentTail
requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached finalized requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached
} }
// initCanonicalChain initializes the structures related to canonical chain tracking. // initCanonicalChain initializes the structures related to canonical chain tracking.
func (c *Client) initCanonicalChain() { func (c *Client) initCanonicalChain() {
c.finalized = lru.NewCache[uint64, common.Hash](10000) c.cache = lru.NewCache[uint64, common.Hash](10000)
c.requests = newRequestMap[uint64, common.Hash](nil) c.requests = newRequestMap[uint64, common.Hash](nil)
c.tailFetchCh = make(chan struct{}) c.tailFetchCh = make(chan struct{})
c.closeCh = make(chan struct{}) c.closeCh = make(chan struct{})
@ -59,6 +61,8 @@ func (c *Client) closeCanonicalChain() {
close(c.closeCh) close(c.closeCh)
} }
// setHead sets a new head for the canonical chain. It also updates the recent
// hash associations and takes care of state cache invalidation if necessary.
func (c *Client) setHead(head *btypes.ExecutionHeader) bool { func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
c.chainLock.Lock() c.chainLock.Lock()
defer c.chainLock.Unlock() defer c.chainLock.Unlock()
@ -68,9 +72,15 @@ func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
return false return false
} }
if c.recent == nil || c.head == nil || c.head.BlockNumber()+1 != headNum || c.head.BlockHash() != head.ParentHash() { if c.recent == nil || c.head == nil || c.head.BlockNumber()+1 != headNum || c.head.BlockHash() != head.ParentHash() {
// initialize recent canonical hash map when first head is added or when // new head is not a descendant of the previous one; everything that was
// it is not a descendant of the previous head // not finalized should be invalidated.
if c.finality == nil || c.recentTail > c.finality.BlockNumber()+1 {
// purge cache if the previous contents were not all finalized
c.cache.Purge()
}
// state proofs are cached by number
c.clearStateCache() c.clearStateCache()
// initialize recent canonical hash map
c.recent = make(map[uint64]common.Hash) c.recent = make(map[uint64]common.Hash)
if headNum > 0 { if headNum > 0 {
c.recent[headNum-1] = head.ParentHash() c.recent[headNum-1] = head.ParentHash()
@ -82,9 +92,7 @@ func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
c.head = head c.head = head
c.recent[headNum] = headHash c.recent[headNum] = headHash
for headNum >= c.recentTail+recentCanonicalLength { for headNum >= c.recentTail+recentCanonicalLength {
if c.finality != nil && c.recentTail <= c.finality.BlockNumber() { c.cache.Add(c.recentTail, c.recent[c.recentTail])
c.finalized.Add(c.recentTail, c.recent[c.recentTail])
}
delete(c.recent, c.recentTail) delete(c.recent, c.recentTail)
c.recentTail++ c.recentTail++
} }
@ -93,6 +101,7 @@ func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
return true return true
} }
// setFinality sets a new finality slot.
func (c *Client) setFinality(finality *btypes.ExecutionHeader) { func (c *Client) setFinality(finality *btypes.ExecutionHeader) {
c.chainLock.Lock() c.chainLock.Lock()
defer c.chainLock.Unlock() defer c.chainLock.Unlock()
@ -100,11 +109,12 @@ func (c *Client) setFinality(finality *btypes.ExecutionHeader) {
c.finality = finality c.finality = finality
finalNum := finality.BlockNumber() finalNum := finality.BlockNumber()
if finalNum < c.recentTail { if finalNum < c.recentTail {
c.finalized.Add(finalNum, finality.BlockHash()) c.cache.Add(finalNum, finality.BlockHash())
} }
c.requests.tryDeliver(finalNum, finality.BlockHash()) c.requests.tryDeliver(finalNum, finality.BlockHash())
} }
// getHead returns the current local canonical chain head.
func (c *Client) getHead() *btypes.ExecutionHeader { func (c *Client) getHead() *btypes.ExecutionHeader {
c.chainLock.Lock() c.chainLock.Lock()
defer c.chainLock.Unlock() defer c.chainLock.Unlock()
@ -112,6 +122,7 @@ func (c *Client) getHead() *btypes.ExecutionHeader {
return c.head return c.head
} }
// getFinality returns the current finality header
func (c *Client) getFinality() *btypes.ExecutionHeader { func (c *Client) getFinality() *btypes.ExecutionHeader {
c.chainLock.Lock() c.chainLock.Lock()
defer c.chainLock.Unlock() defer c.chainLock.Unlock()
@ -119,6 +130,8 @@ func (c *Client) getFinality() *btypes.ExecutionHeader {
return c.finality return c.finality
} }
// addRecentTail tries to add the given header to the tail of the recent canonical
// section and returns true if successful.
func (c *Client) addRecentTail(tail *types.Header) bool { func (c *Client) addRecentTail(tail *types.Header) bool {
c.chainLock.Lock() c.chainLock.Lock()
defer c.chainLock.Unlock() defer c.chainLock.Unlock()
@ -136,6 +149,7 @@ func (c *Client) addRecentTail(tail *types.Header) bool {
return true return true
} }
// tailFetcher reverse syncs canonical hashes until all requested items are present.
func (c *Client) tailFetcher() { func (c *Client) tailFetcher() {
for { for {
c.chainLock.Lock() c.chainLock.Lock()
@ -173,6 +187,7 @@ func (c *Client) tailFetcher() {
} }
} }
// getHash returns the requested number -> hash association.
func (c *Client) getHash(ctx context.Context, number uint64) (common.Hash, error) { func (c *Client) getHash(ctx context.Context, number uint64) (common.Hash, error) {
if hash, ok := c.getCachedHash(number); ok { if hash, ok := c.getCachedHash(number); ok {
return hash, nil return hash, nil
@ -186,6 +201,8 @@ func (c *Client) getHash(ctx context.Context, number uint64) (common.Hash, error
return req.waitForResult(ctx) return req.waitForResult(ctx)
} }
// getCachedHash returns the requested number -> hash association if it is already
// in memory cache.
func (c *Client) getCachedHash(number uint64) (common.Hash, bool) { func (c *Client) getCachedHash(number uint64) (common.Hash, bool) {
c.chainLock.Lock() c.chainLock.Lock()
hash, ok := c.recent[number] hash, ok := c.recent[number]
@ -193,9 +210,11 @@ func (c *Client) getCachedHash(number uint64) (common.Hash, bool) {
if ok { if ok {
return hash, true return hash, true
} }
return c.finalized.Get(number) return c.cache.Get(number)
} }
// resolveBlockNumber resolves an RPC block number into uint64 and also returns
// the payload header in case of special (negative) RPC numbers.
func (c *Client) resolveBlockNumber(number *big.Int) (uint64, *btypes.ExecutionHeader, error) { func (c *Client) resolveBlockNumber(number *big.Int) (uint64, *btypes.ExecutionHeader, error) {
if !number.IsInt64() { if !number.IsInt64() {
return 0, nil, errors.New("Invalid block number") return 0, nil, errors.New("Invalid block number")
@ -220,6 +239,10 @@ func (c *Client) resolveBlockNumber(number *big.Int) (uint64, *btypes.ExecutionH
return uint64(num), nil, nil return uint64(num), nil, nil
} }
// blockNumberToHash returns the canonical hash belonging to the given RPC block
// number. Note that requesting older canonical hashes might trigger a reverse
// header sync process which might take a very long time depending on the age
// of the specified block.
func (c *Client) blockNumberToHash(ctx context.Context, number *big.Int) (common.Hash, error) { func (c *Client) blockNumberToHash(ctx context.Context, number *big.Int) (common.Hash, error) {
num, pheader, err := c.resolveBlockNumber(number) num, pheader, err := c.resolveBlockNumber(number)
if err != nil { if err != nil {
@ -231,6 +254,7 @@ func (c *Client) blockNumberToHash(ctx context.Context, number *big.Int) (common
return c.getHash(ctx, num) return c.getHash(ctx, num)
} }
// blockNumberOrHashToHash resolves rpc.BlockNumberOrHash into a block hash.
func (c *Client) blockNumberOrHashToHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (common.Hash, error) { func (c *Client) blockNumberOrHashToHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (common.Hash, error) {
if blockNrOrHash.BlockNumber != nil { if blockNrOrHash.BlockNumber != nil {
return c.blockNumberToHash(ctx, big.NewInt(int64(*blockNrOrHash.BlockNumber))) return c.blockNumberToHash(ctx, big.NewInt(int64(*blockNrOrHash.BlockNumber)))

View file

@ -370,6 +370,7 @@ func (c *Client) sendTransaction(ctx context.Context, tx *types.Transaction) err
return c.TrackTransaction(tx) return c.TrackTransaction(tx)
} }
// TrackTransaction adds a transaction to the set of tracked transactions.
func (c *Client) TrackTransaction(tx *types.Transaction) error { func (c *Client) TrackTransaction(tx *types.Transaction) error {
sender, err := types.Sender(c.signer, tx) sender, err := types.Sender(c.signer, tx)
if err != nil { if err != nil {