Skip to content

Commit

Permalink
ethclient/lightclient: more comments and minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
zsfelfoldi committed Jun 14, 2024
1 parent 37950ca commit e2afe31
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 11 deletions.
14 changes: 13 additions & 1 deletion ethclient/lightclient/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func (c *Client) closeBlocksAndHeaders() {
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) {
if header, ok := c.headerCache.Get(hash); ok {
return header, nil
Expand Down Expand Up @@ -84,15 +85,25 @@ func (c *Client) getHeader(ctx context.Context, hash common.Hash) (*types.Header
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 {
pheader, _ := c.payloadHeaderCache.Get(hash)
return pheader
}

// addPayloadHeader caches the given payload header.
func (c *Client) addPayloadHeader(header *btypes.ExecutionHeader) {
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) {
if block, ok := c.blockCache.Get(hash); ok {
return block, nil
Expand Down Expand Up @@ -149,7 +160,8 @@ func (c *Client) requestBlock(ctx context.Context, hash common.Hash) (*types.Blo
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) {
// Decode header and transactions.
var head *types.Header
Expand Down
44 changes: 34 additions & 10 deletions ethclient/lightclient/canonical.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
"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

// canonicalChainFields defines Client fields related to canonical chain tracking.
Expand All @@ -40,13 +42,13 @@ type canonicalChainFields struct {
recent map[uint64]common.Hash // nil while head == nil
recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head
tailFetchCh, closeCh chan struct{}
finalized *lru.Cache[uint64, common.Hash] // finalized but not recent hashes
requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached finalized
cache *lru.Cache[uint64, common.Hash] // older than recentTail
requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached
}

// initCanonicalChain initializes the structures related to canonical chain tracking.
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.tailFetchCh = make(chan struct{})
c.closeCh = make(chan struct{})
Expand All @@ -59,6 +61,8 @@ func (c *Client) closeCanonicalChain() {
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 {
c.chainLock.Lock()
defer c.chainLock.Unlock()
Expand All @@ -68,9 +72,15 @@ func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
return false
}
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
// it is not a descendant of the previous head
// new head is not a descendant of the previous one; everything that was
// 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()
// initialize recent canonical hash map
c.recent = make(map[uint64]common.Hash)
if headNum > 0 {
c.recent[headNum-1] = head.ParentHash()
Expand All @@ -82,9 +92,7 @@ func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
c.head = head
c.recent[headNum] = headHash
for headNum >= c.recentTail+recentCanonicalLength {
if c.finality != nil && c.recentTail <= c.finality.BlockNumber() {
c.finalized.Add(c.recentTail, c.recent[c.recentTail])
}
c.cache.Add(c.recentTail, c.recent[c.recentTail])
delete(c.recent, c.recentTail)
c.recentTail++
}
Expand All @@ -93,32 +101,37 @@ func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
return true
}

// setFinality sets a new finality slot.
func (c *Client) setFinality(finality *btypes.ExecutionHeader) {
c.chainLock.Lock()
defer c.chainLock.Unlock()

c.finality = finality
finalNum := finality.BlockNumber()
if finalNum < c.recentTail {
c.finalized.Add(finalNum, finality.BlockHash())
c.cache.Add(finalNum, finality.BlockHash())
}
c.requests.tryDeliver(finalNum, finality.BlockHash())
}

// getHead returns the current local canonical chain head.
func (c *Client) getHead() *btypes.ExecutionHeader {
c.chainLock.Lock()
defer c.chainLock.Unlock()

return c.head
}

// getFinality returns the current finality header
func (c *Client) getFinality() *btypes.ExecutionHeader {
c.chainLock.Lock()
defer c.chainLock.Unlock()

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 {
c.chainLock.Lock()
defer c.chainLock.Unlock()
Expand All @@ -136,6 +149,7 @@ func (c *Client) addRecentTail(tail *types.Header) bool {
return true
}

// tailFetcher reverse syncs canonical hashes until all requested items are present.
func (c *Client) tailFetcher() {
for {
c.chainLock.Lock()
Expand Down Expand Up @@ -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) {
if hash, ok := c.getCachedHash(number); ok {
return hash, nil
Expand All @@ -186,16 +201,20 @@ func (c *Client) getHash(ctx context.Context, number uint64) (common.Hash, error
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) {
c.chainLock.Lock()
hash, ok := c.recent[number]
c.chainLock.Unlock()
if ok {
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) {
if !number.IsInt64() {
return 0, nil, errors.New("Invalid block number")
Expand All @@ -220,6 +239,10 @@ func (c *Client) resolveBlockNumber(number *big.Int) (uint64, *btypes.ExecutionH
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) {
num, pheader, err := c.resolveBlockNumber(number)
if err != nil {
Expand All @@ -231,6 +254,7 @@ func (c *Client) blockNumberToHash(ctx context.Context, number *big.Int) (common
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) {
if blockNrOrHash.BlockNumber != nil {
return c.blockNumberToHash(ctx, big.NewInt(int64(*blockNrOrHash.BlockNumber)))
Expand Down
1 change: 1 addition & 0 deletions ethclient/lightclient/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ func (c *Client) sendTransaction(ctx context.Context, tx *types.Transaction) err
return c.TrackTransaction(tx)
}

// TrackTransaction adds a transaction to the set of tracked transactions.
func (c *Client) TrackTransaction(tx *types.Transaction) error {
sender, err := types.Sender(c.signer, tx)
if err != nil {
Expand Down

0 comments on commit e2afe31

Please sign in to comment.