ethclient/lightclient: refactored all functions into Client

This commit is contained in:
Zsolt Felfoldi 2024-06-08 12:53:03 +02:00
parent 5b05bb625d
commit 88a3179a8d
4 changed files with 251 additions and 298 deletions

View file

@ -25,7 +25,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/beacon/light"
"github.com/ethereum/go-ethereum/beacon/light/request" "github.com/ethereum/go-ethereum/beacon/light/request"
btypes "github.com/ethereum/go-ethereum/beacon/types" btypes "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -37,12 +36,8 @@ import (
const recentCanonicalLength = 256 const recentCanonicalLength = 256
type canonicalChain struct { type canonicalChainFields struct {
lock sync.Mutex chainLock sync.Mutex
headTracker *light.HeadTracker
blocksAndHeaders *blocksAndHeaders
newHeadCb func(uint64, common.Hash)
head, finality *btypes.ExecutionHeader head, finality *btypes.ExecutionHeader
recent map[uint64]common.Hash // nil until initialized recent map[uint64]common.Hash // nil until initialized
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
@ -51,38 +46,32 @@ type canonicalChain struct {
requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached finalized requests *requestMap[uint64, common.Hash] // requested; neither recent nor cached finalized
} }
func newCanonicalChain(headTracker *light.HeadTracker, blocksAndHeaders *blocksAndHeaders, newHeadCb func(uint64, common.Hash)) *canonicalChain { func (c *Client) initCanonicalChain() {
c := &canonicalChain{ c.finalized = lru.NewCache[uint64, common.Hash](10000)
headTracker: headTracker, c.requests = newRequestMap[uint64, common.Hash](nil)
blocksAndHeaders: blocksAndHeaders, c.tailFetchCh = make(chan struct{})
newHeadCb: newHeadCb,
finalized: lru.NewCache[uint64, common.Hash](10000),
requests: newRequestMap[uint64, common.Hash](nil),
tailFetchCh: make(chan struct{}),
}
go c.tailFetcher() go c.tailFetcher()
return c
} }
// Process implements request.Module in order to get notified about new heads. // Process implements request.Module in order to get notified about new heads.
func (c *canonicalChain) Process(requester request.Requester, events []request.Event) { func (c *Client) Process(requester request.Requester, events []request.Event) {
if finality, ok := c.headTracker.ValidatedFinality(); ok { if finality, ok := c.headTracker.ValidatedFinality(); ok {
finalized := finality.Finalized.PayloadHeader finalized := finality.Finalized.PayloadHeader
c.setFinality(finalized) c.setFinality(finalized)
c.blocksAndHeaders.addPayloadHeader(finalized) c.addPayloadHeader(finalized)
} }
if optimistic, ok := c.headTracker.ValidatedOptimistic(); ok { if optimistic, ok := c.headTracker.ValidatedOptimistic(); ok {
head := optimistic.Attested.PayloadHeader head := optimistic.Attested.PayloadHeader
c.blocksAndHeaders.addPayloadHeader(head) c.addPayloadHeader(head)
if c.setHead(head) { if c.setHead(head) {
c.newHeadCb(head.BlockNumber(), head.BlockHash()) // should not block go c.processNewHead(head.BlockNumber(), head.BlockHash())
} }
} }
} }
func (c *canonicalChain) tailFetcher() { //TODO stop func (c *Client) tailFetcher() { //TODO stop
for { for {
c.lock.Lock() c.chainLock.Lock()
var ( var (
tailNum uint64 tailNum uint64
tailHash common.Hash tailHash common.Hash
@ -96,12 +85,12 @@ func (c *canonicalChain) tailFetcher() { //TODO stop
needTail = reqNum needTail = reqNum
} }
} }
c.lock.Unlock() c.chainLock.Unlock()
if needTail < tailNum { //TODO check recentCanonicalLength if needTail < tailNum { //TODO check recentCanonicalLength
log.Debug("Fetching tail headers", "have", tailNum, "need", needTail) log.Debug("Fetching tail headers", "have", tailNum, "need", needTail)
ctx, _ := context.WithTimeout(context.Background(), time.Second*10) ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
//TODO parallel fetch by number //TODO parallel fetch by number
if header, err := c.blocksAndHeaders.getHeader(ctx, tailHash); err == nil { if header, err := c.getHeader(ctx, tailHash); err == nil {
c.addRecentTail(header) c.addRecentTail(header)
} }
} else { } else {
@ -110,7 +99,7 @@ func (c *canonicalChain) tailFetcher() { //TODO stop
} }
} }
func (c *canonicalChain) 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
} }
@ -123,19 +112,19 @@ func (c *canonicalChain) getHash(ctx context.Context, number uint64) (common.Has
return req.getResult(ctx) return req.getResult(ctx)
} }
func (c *canonicalChain) getCachedHash(number uint64) (common.Hash, bool) { func (c *Client) getCachedHash(number uint64) (common.Hash, bool) {
c.lock.Lock() c.chainLock.Lock()
hash, ok := c.recent[number] hash, ok := c.recent[number]
c.lock.Unlock() c.chainLock.Unlock()
if ok { if ok {
return hash, true return hash, true
} }
return c.finalized.Get(number) return c.finalized.Get(number)
} }
func (c *canonicalChain) setHead(head *btypes.ExecutionHeader) bool { func (c *Client) setHead(head *btypes.ExecutionHeader) bool {
c.lock.Lock() c.chainLock.Lock()
defer c.lock.Unlock() defer c.chainLock.Unlock()
headNum, headHash := head.BlockNumber(), head.BlockHash() headNum, headHash := head.BlockNumber(), head.BlockHash()
if c.head != nil && c.head.BlockHash() == headHash { if c.head != nil && c.head.BlockHash() == headHash {
@ -166,9 +155,9 @@ func (c *canonicalChain) setHead(head *btypes.ExecutionHeader) bool {
return true return true
} }
func (c *canonicalChain) setFinality(finality *btypes.ExecutionHeader) { func (c *Client) setFinality(finality *btypes.ExecutionHeader) {
c.lock.Lock() c.chainLock.Lock()
defer c.lock.Unlock() defer c.chainLock.Unlock()
c.finality = finality c.finality = finality
finalNum := finality.BlockNumber() finalNum := finality.BlockNumber()
@ -178,9 +167,9 @@ func (c *canonicalChain) setFinality(finality *btypes.ExecutionHeader) {
c.requests.tryDeliver(finalNum, finality.BlockHash()) c.requests.tryDeliver(finalNum, finality.BlockHash())
} }
func (c *canonicalChain) addRecentTail(tail *types.Header) bool { func (c *Client) addRecentTail(tail *types.Header) bool {
c.lock.Lock() c.chainLock.Lock()
defer c.lock.Unlock() defer c.chainLock.Unlock()
if c.recent == nil || tail.Number.Uint64() != c.recentTail || c.recent[c.recentTail] != tail.Hash() { if c.recent == nil || tail.Number.Uint64() != c.recentTail || c.recent[c.recentTail] != tail.Hash() {
return false return false
@ -193,21 +182,21 @@ func (c *canonicalChain) addRecentTail(tail *types.Header) bool {
return true return true
} }
func (c *canonicalChain) getHead() *btypes.ExecutionHeader { func (c *Client) getHead() *btypes.ExecutionHeader {
c.lock.Lock() c.chainLock.Lock()
defer c.lock.Unlock() defer c.chainLock.Unlock()
return c.head return c.head
} }
func (c *canonicalChain) getFinality() *btypes.ExecutionHeader { func (c *Client) getFinality() *btypes.ExecutionHeader {
c.lock.Lock() c.chainLock.Lock()
defer c.lock.Unlock() defer c.chainLock.Unlock()
return c.finality return c.finality
} }
func (c *canonicalChain) 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")
} }
@ -231,7 +220,7 @@ func (c *canonicalChain) resolveBlockNumber(number *big.Int) (uint64, *btypes.Ex
return uint64(num), nil, nil return uint64(num), nil, nil
} }
func (c *canonicalChain) 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 {
return common.Hash{}, err return common.Hash{}, err
@ -242,13 +231,13 @@ func (c *canonicalChain) blockNumberToHash(ctx context.Context, number *big.Int)
return c.getHash(ctx, num) return c.getHash(ctx, num)
} }
func (c *canonicalChain) 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)))
} }
hash := *blockNrOrHash.BlockHash hash := *blockNrOrHash.BlockHash
if blockNrOrHash.RequireCanonical { if blockNrOrHash.RequireCanonical {
header, err := c.blocksAndHeaders.getHeader(ctx, hash) header, err := c.getHeader(ctx, hash)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -263,9 +252,7 @@ func (c *canonicalChain) blockNumberOrHashToHash(ctx context.Context, blockNrOrH
return hash, nil return hash, nil
} }
type blocksAndHeaders struct { type blocksAndHeadersFields struct {
client *rpc.Client
txAndReceipts *txAndReceipts
headerCache *lru.Cache[common.Hash, *types.Header] headerCache *lru.Cache[common.Hash, *types.Header]
headerRequests *requestMap[common.Hash, *types.Header] headerRequests *requestMap[common.Hash, *types.Header]
payloadHeaderCache *lru.Cache[common.Hash, *btypes.ExecutionHeader] payloadHeaderCache *lru.Cache[common.Hash, *btypes.ExecutionHeader]
@ -273,22 +260,18 @@ type blocksAndHeaders struct {
blockRequests *requestMap[common.Hash, *types.Block] blockRequests *requestMap[common.Hash, *types.Block]
} }
func newBlocksAndHeaders(client *rpc.Client) *blocksAndHeaders { func (c *Client) initBlocksAndHeaders() {
b := &blocksAndHeaders{ c.headerCache = lru.NewCache[common.Hash, *types.Header](1000)
client: client, c.payloadHeaderCache = lru.NewCache[common.Hash, *btypes.ExecutionHeader](1000)
headerCache: lru.NewCache[common.Hash, *types.Header](1000), c.blockCache = lru.NewCache[common.Hash, *types.Block](10)
payloadHeaderCache: lru.NewCache[common.Hash, *btypes.ExecutionHeader](1000), c.headerRequests = newRequestMap[common.Hash, *types.Header](c.requestHeader)
blockCache: lru.NewCache[common.Hash, *types.Block](10), c.blockRequests = newRequestMap[common.Hash, *types.Block](c.requestBlock)
}
b.headerRequests = newRequestMap[common.Hash, *types.Header](b.requestHeader)
b.blockRequests = newRequestMap[common.Hash, *types.Block](b.requestBlock)
return b
} }
func (b *blocksAndHeaders) requestHeader(ctx context.Context, hash common.Hash) (*types.Header, error) { func (c *Client) requestHeader(ctx context.Context, hash common.Hash) (*types.Header, error) {
var header *types.Header var header *types.Header
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false) log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false)
err := b.client.CallContext(ctx, &header, "eth_getBlockByHash", hash, false) err := c.client.CallContext(ctx, &header, "eth_getBlockByHash", hash, false)
if err == nil && header.Hash() != hash { if err == nil && header.Hash() != hash {
header, err = nil, errors.New("header hash does not match") header, err = nil, errors.New("header hash does not match")
} }
@ -296,13 +279,13 @@ func (b *blocksAndHeaders) requestHeader(ctx context.Context, hash common.Hash)
return header, err return header, err
} }
func (b *blocksAndHeaders) requestBlock(ctx context.Context, hash common.Hash) (*types.Block, error) { func (c *Client) requestBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
var ( var (
raw json.RawMessage raw json.RawMessage
block *types.Block block *types.Block
) )
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true) log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true)
err := b.client.CallContext(ctx, &raw, "eth_getBlockByHash", hash, true) err := c.client.CallContext(ctx, &raw, "eth_getBlockByHash", hash, true)
log.Debug("Finished RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true, "error", err) log.Debug("Finished RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", true, "error", err)
if err == nil { if err == nil {
block, err = decodeBlock(raw) block, err = decodeBlock(raw)
@ -313,20 +296,20 @@ func (b *blocksAndHeaders) requestBlock(ctx context.Context, hash common.Hash) (
return block, err return block, err
} }
func (b *blocksAndHeaders) 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 := b.headerCache.Get(hash); ok { if header, ok := c.headerCache.Get(hash); ok {
return header, nil return header, nil
} }
if block, ok := b.blockCache.Get(hash); ok { if block, ok := c.blockCache.Get(hash); ok {
return block.Header(), nil return block.Header(), nil
} }
if b.blockRequests.has(hash) && !b.headerRequests.has(hash) { if c.blockRequests.has(hash) && !c.headerRequests.has(hash) {
req := b.blockRequests.request(hash) req := c.blockRequests.request(hash)
block, err := req.getResult(ctx) block, err := req.getResult(ctx)
if err == nil { if err == nil {
header := block.Header() header := block.Header()
b.headerCache.Add(hash, header) c.headerCache.Add(hash, header)
b.blockCache.Add(hash, block) c.blockCache.Add(hash, block)
req.release() req.release()
return header, nil return header, nil
} else { } else {
@ -334,32 +317,32 @@ func (b *blocksAndHeaders) getHeader(ctx context.Context, hash common.Hash) (*ty
return nil, err return nil, err
} }
} }
req := b.headerRequests.request(hash) req := c.headerRequests.request(hash)
header, err := req.getResult(ctx) header, err := req.getResult(ctx)
if err == nil { if err == nil {
b.headerCache.Add(hash, header) c.headerCache.Add(hash, header)
} }
req.release() req.release()
return header, err return header, err
} }
func (b *blocksAndHeaders) getPayloadHeader(hash common.Hash) *btypes.ExecutionHeader { func (c *Client) getPayloadHeader(hash common.Hash) *btypes.ExecutionHeader {
pheader, _ := b.payloadHeaderCache.Get(hash) pheader, _ := c.payloadHeaderCache.Get(hash)
return pheader return pheader
} }
func (b *blocksAndHeaders) 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 := b.blockCache.Get(hash); ok { if block, ok := c.blockCache.Get(hash); ok {
return block, nil return block, nil
} }
req := b.blockRequests.request(hash) req := c.blockRequests.request(hash)
block, err := req.getResult(ctx) block, err := req.getResult(ctx)
if err == nil { if err == nil {
header := block.Header() header := block.Header()
b.headerCache.Add(hash, header) c.headerCache.Add(hash, header)
b.headerRequests.tryDeliver(hash, header) c.headerRequests.tryDeliver(hash, header)
b.blockCache.Add(hash, block) c.blockCache.Add(hash, block)
b.txAndReceipts.cacheBlockTxPositions(block) c.cacheBlockTxPositions(block)
} }
req.release() req.release()
return block, err return block, err
@ -467,6 +450,6 @@ func decodeBlock(raw json.RawMessage) (*types.Block, error) {
return types.NewBlockWithHeader(head).WithBody(types.Body{Transactions: txs, Withdrawals: body.Withdrawals}), nil return types.NewBlockWithHeader(head).WithBody(types.Body{Transactions: txs, Withdrawals: body.Withdrawals}), nil
} }
func (b *blocksAndHeaders) addPayloadHeader(header *btypes.ExecutionHeader) { func (c *Client) addPayloadHeader(header *btypes.ExecutionHeader) {
b.payloadHeaderCache.Add(header.BlockHash(), header) c.payloadHeaderCache.Add(header.BlockHash(), header)
} }

View file

@ -38,13 +38,18 @@ import (
) )
type Client struct { type Client struct {
canonicalChainFields
blocksAndHeadersFields
lightStateFields
txAndReceiptsFields
clConfig config.LightClientConfig clConfig config.LightClientConfig
elConfig *params.ChainConfig elConfig *params.ChainConfig
client *rpc.Client
scheduler *request.Scheduler scheduler *request.Scheduler
canonicalChain *canonicalChain headTracker *light.HeadTracker
blocksAndHeaders *blocksAndHeaders
txAndReceipts *txAndReceipts
state *lightState
headSubLock ssync.Mutex headSubLock ssync.Mutex
headSubs map[*headSub]struct{} headSubs map[*headSub]struct{}
cancelHeadFetch func() cancelHeadFetch func()
@ -53,38 +58,29 @@ type Client struct {
func NewClient(clConfig config.LightClientConfig, elConfig *params.ChainConfig, db ethdb.KeyValueStore, rpcClient *rpc.Client) *Client { func NewClient(clConfig config.LightClientConfig, elConfig *params.ChainConfig, db ethdb.KeyValueStore, rpcClient *rpc.Client) *Client {
// create data structures // create data structures
var ( committeeChain := light.NewCommitteeChain(db, clConfig.ChainConfig, clConfig.SignerThreshold, clConfig.EnforceTime)
committeeChain = light.NewCommitteeChain(db, clConfig.ChainConfig, clConfig.SignerThreshold, clConfig.EnforceTime)
headTracker = light.NewHeadTracker(committeeChain, clConfig.SignerThreshold)
)
// set up scheduler and sync modules
//chainHeadFeed := new(event.Feed)
scheduler := request.NewScheduler()
client := &Client{ client := &Client{
client: rpcClient,
clConfig: clConfig, clConfig: clConfig,
elConfig: elConfig, elConfig: elConfig,
scheduler: scheduler, scheduler: request.NewScheduler(),
headTracker: light.NewHeadTracker(committeeChain, clConfig.SignerThreshold),
headSubs: make(map[*headSub]struct{}), headSubs: make(map[*headSub]struct{}),
} }
blocksAndHeaders := newBlocksAndHeaders(rpcClient) client.initBlocksAndHeaders()
canonicalChain := newCanonicalChain(headTracker, blocksAndHeaders, client.newHead) client.initCanonicalChain()
txAndReceipts := newTxAndReceipts(rpcClient, canonicalChain, blocksAndHeaders, elConfig) client.initTxAndReceipts()
blocksAndHeaders.txAndReceipts = txAndReceipts client.initLightState()
client.blocksAndHeaders = blocksAndHeaders
client.txAndReceipts = txAndReceipts
client.canonicalChain = canonicalChain
client.state = newLightState(rpcClient, canonicalChain, blocksAndHeaders)
txAndReceipts.lightState = client.state //TODO init these refs in a nicer way???
checkpointInit := sync.NewCheckpointInit(committeeChain, clConfig.Checkpoint) checkpointInit := sync.NewCheckpointInit(committeeChain, clConfig.Checkpoint)
forwardSync := sync.NewForwardUpdateSync(committeeChain) forwardSync := sync.NewForwardUpdateSync(committeeChain)
headSync := sync.NewHeadSync(headTracker, committeeChain) headSync := sync.NewHeadSync(client.headTracker, committeeChain)
scheduler.RegisterTarget(headTracker) client.scheduler.RegisterTarget(client.headTracker)
scheduler.RegisterTarget(committeeChain) client.scheduler.RegisterTarget(committeeChain)
scheduler.RegisterModule(checkpointInit, "checkpointInit") client.scheduler.RegisterModule(checkpointInit, "checkpointInit")
scheduler.RegisterModule(forwardSync, "forwardSync") client.scheduler.RegisterModule(forwardSync, "forwardSync")
scheduler.RegisterModule(headSync, "headSync") client.scheduler.RegisterModule(headSync, "headSync")
scheduler.RegisterModule(client.canonicalChain, "canonicalChain") client.scheduler.RegisterModule(client, "canonicalChain")
return client return client
} }
@ -105,11 +101,11 @@ func (c *Client) Stop() {
// ChainReader interface // ChainReader interface
func (c *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { func (c *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return c.blocksAndHeaders.getBlock(ctx, hash) return c.getBlock(ctx, hash)
} }
func (c *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { func (c *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
hash, err := c.canonicalChain.blockNumberToHash(ctx, number) hash, err := c.blockNumberToHash(ctx, number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,11 +113,11 @@ func (c *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Blo
} }
func (c *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { func (c *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return c.blocksAndHeaders.getHeader(ctx, hash) return c.getHeader(ctx, hash)
} }
func (c *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { func (c *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
hash, err := c.canonicalChain.blockNumberToHash(ctx, number) hash, err := c.blockNumberToHash(ctx, number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -160,10 +156,9 @@ func (c *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
return sub, nil return sub, nil
} }
func (c *Client) newHead(number uint64, hash common.Hash) { func (c *Client) processNewHead(number uint64, hash common.Hash) {
go func() {
log.Trace("New execution payload header received", "hash", hash) log.Trace("New execution payload header received", "hash", hash)
c.txAndReceipts.newHead(number, hash) c.txAndReceiptsNewHead(number, hash)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c.headSubLock.Lock() c.headSubLock.Lock()
if len(c.headSubs) == 0 { if len(c.headSubs) == 0 {
@ -178,7 +173,7 @@ func (c *Client) newHead(number uint64, hash common.Hash) {
hfc := c.headFetchCounter hfc := c.headFetchCounter
c.headSubLock.Unlock() c.headSubLock.Unlock()
head, err := c.blocksAndHeaders.getHeader(ctx, hash) head, err := c.getHeader(ctx, hash)
c.headSubLock.Lock() c.headSubLock.Lock()
if c.headFetchCounter == hfc { if c.headFetchCounter == hfc {
c.cancelHeadFetch = nil c.cancelHeadFetch = nil
@ -189,7 +184,6 @@ func (c *Client) newHead(number uint64, hash common.Hash) {
} }
} }
c.headSubLock.Unlock() c.headSubLock.Unlock()
}()
} }
func (c *Client) unsubscribeNewHead(sub *headSub) { func (c *Client) unsubscribeNewHead(sub *headSub) {
@ -216,17 +210,17 @@ func (h *headSub) Err() <-chan error {
// TransactionReader interface // TransactionReader interface
func (c *Client) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) { func (c *Client) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) {
return c.txAndReceipts.getTxByHash(ctx, txHash) return c.getTxByHash(ctx, txHash)
} }
func (c *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { func (c *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
return c.txAndReceipts.getReceiptByTxHash(ctx, txHash) return c.getReceiptByTxHash(ctx, txHash)
} }
// ChainStateReader interface // ChainStateReader interface
func (c *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { func (c *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
proof, _, err := c.state.getProof(ctx, blockNumber, account, nil, false) proof, _, err := c.getProof(ctx, blockNumber, account, nil, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -234,7 +228,7 @@ func (c *Client) BalanceAt(ctx context.Context, account common.Address, blockNum
} }
func (c *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { func (c *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
proof, _, err := c.state.getProof(ctx, blockNumber, account, []string{key.Hex()}, false) proof, _, err := c.getProof(ctx, blockNumber, account, []string{key.Hex()}, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -242,7 +236,7 @@ func (c *Client) StorageAt(ctx context.Context, account common.Address, key comm
} }
func (c *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { func (c *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
_, code, err := c.state.getProof(ctx, blockNumber, account, nil, true) _, code, err := c.getProof(ctx, blockNumber, account, nil, true)
return code, err return code, err
} }
@ -250,7 +244,7 @@ func (c *Client) NonceAt(ctx context.Context, account common.Address, blockNumbe
if blockNumber.IsInt64() && rpc.BlockNumber(blockNumber.Int64()) == rpc.PendingBlockNumber { if blockNumber.IsInt64() && rpc.BlockNumber(blockNumber.Int64()) == rpc.PendingBlockNumber {
return c.PendingNonceAt(ctx, account) return c.PendingNonceAt(ctx, account)
} }
proof, _, err := c.state.getProof(ctx, blockNumber, account, nil, false) proof, _, err := c.getProof(ctx, blockNumber, account, nil, false)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -258,17 +252,17 @@ func (c *Client) NonceAt(ctx context.Context, account common.Address, blockNumbe
} }
func (c *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, error) { func (c *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, error) {
hash, err := c.canonicalChain.blockNumberOrHashToHash(ctx, blockNrOrHash) hash, err := c.blockNumberOrHashToHash(ctx, blockNrOrHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.txAndReceipts.getBlockReceipts(ctx, hash) return c.getBlockReceipts(ctx, hash)
} }
// TransactionSender interface // TransactionSender interface
func (c *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { func (c *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
return c.txAndReceipts.sendTransaction(ctx, tx) return c.sendTransaction(ctx, tx)
} }
// PendingStateReader interface // PendingStateReader interface
@ -287,11 +281,11 @@ func (c *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]b
} }
func (c *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { func (c *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
head := c.canonicalChain.getHead() head := c.getHead()
if head == nil { if head == nil {
return 0, errors.New("chain head unknown") return 0, errors.New("chain head unknown")
} }
headNonce, pendingTxs, err := c.txAndReceipts.nonceAndPendingTxs(ctx, head, account) headNonce, pendingTxs, err := c.nonceAndPendingTxs(ctx, head, account)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -302,16 +296,16 @@ func (c *Client) PendingNonceAt(ctx context.Context, account common.Address) (ui
} }
func (c *Client) PendingTransactionCount(ctx context.Context) (uint, error) { func (c *Client) PendingTransactionCount(ctx context.Context) (uint, error) {
head := c.canonicalChain.getHead() head := c.getHead()
if head == nil { if head == nil {
return 0, errors.New("chain head unknown") return 0, errors.New("chain head unknown")
} }
allSenders := c.txAndReceipts.allSenders() allSenders := c.allSenders()
countCh := make(chan uint, len(allSenders)) countCh := make(chan uint, len(allSenders))
errCh := make(chan error, len(allSenders)) errCh := make(chan error, len(allSenders))
for _, sender := range allSenders { for _, sender := range allSenders {
go func() { go func() {
_, pendingTxs, err := c.txAndReceipts.nonceAndPendingTxs(ctx, head, sender) _, pendingTxs, err := c.nonceAndPendingTxs(ctx, head, sender)
errCh <- err errCh <- err
countCh <- uint(len(pendingTxs)) countCh <- uint(len(pendingTxs))
}() }()
@ -329,7 +323,7 @@ func (c *Client) PendingTransactionCount(ctx context.Context) (uint, error) {
// BlockNumberReader interface // BlockNumberReader interface
func (c *Client) BlockNumber(ctx context.Context) (uint64, error) { func (c *Client) BlockNumber(ctx context.Context) (uint64, error) {
if head := c.canonicalChain.getHead(); head != nil { if head := c.getHead(); head != nil {
return head.BlockNumber(), nil return head.BlockNumber(), nil
} }
return 0, errors.New("chain head unknown") return 0, errors.New("chain head unknown")

View file

@ -31,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -47,43 +46,34 @@ type codeRequest struct {
address common.Address address common.Address
} }
type lightState struct { type lightStateFields struct {
client *rpc.Client
canonicalChain *canonicalChain
blocksAndHeaders *blocksAndHeaders
proofCache *lru.Cache[proofRequest, *gethclient.AccountResult] proofCache *lru.Cache[proofRequest, *gethclient.AccountResult]
proofRequests *requestMap[proofRequest, *gethclient.AccountResult] proofRequests *requestMap[proofRequest, *gethclient.AccountResult]
codeCache *lru.Cache[codeRequest, []byte] codeCache *lru.Cache[codeRequest, []byte]
codeRequests *requestMap[codeRequest, []byte] codeRequests *requestMap[codeRequest, []byte]
} }
func newLightState(client *rpc.Client, canonicalChain *canonicalChain, blocksAndHeaders *blocksAndHeaders) *lightState { func (c *Client) initLightState() {
s := &lightState{ c.proofCache = lru.NewCache[proofRequest, *gethclient.AccountResult](100)
client: client, c.codeCache = lru.NewCache[codeRequest, []byte](10)
canonicalChain: canonicalChain, c.proofRequests = newRequestMap[proofRequest, *gethclient.AccountResult](c.requestProof)
blocksAndHeaders: blocksAndHeaders, c.codeRequests = newRequestMap[codeRequest, []byte](c.requestCode)
proofCache: lru.NewCache[proofRequest, *gethclient.AccountResult](100),
codeCache: lru.NewCache[codeRequest, []byte](10),
}
s.proofRequests = newRequestMap[proofRequest, *gethclient.AccountResult](s.requestProof)
s.codeRequests = newRequestMap[codeRequest, []byte](s.requestCode)
return s
} }
func (s *lightState) fetchProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) { func (c *Client) fetchProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
if proof, ok := s.proofCache.Get(req); ok { if proof, ok := c.proofCache.Get(req); ok {
return proof, nil return proof, nil
} }
request := s.proofRequests.request(req) request := c.proofRequests.request(req)
proof, err := request.getResult(ctx) proof, err := request.getResult(ctx)
if err == nil { if err == nil {
s.proofCache.Add(req, proof) //TODO cached before validation; remove and retry if invalid c.proofCache.Add(req, proof) //TODO cached before validation; remove and retry if invalid
} }
request.release() request.release()
return proof, err return proof, err
} }
func (s *lightState) requestProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) { func (c *Client) requestProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
type storageResult struct { type storageResult struct {
Key string `json:"key"` Key string `json:"key"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
@ -106,7 +96,7 @@ func (s *lightState) requestProof(ctx context.Context, req proofRequest) (*gethc
} }
log.Debug("Starting RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys)) log.Debug("Starting RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys))
var res accountResult var res accountResult
err := s.client.CallContext(ctx, &res, "eth_getProof", req.address, storageKeys, hexutil.EncodeUint64(req.blockNumber)) err := c.client.CallContext(ctx, &res, "eth_getProof", req.address, storageKeys, hexutil.EncodeUint64(req.blockNumber))
log.Debug("Finished RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys), "error", err) log.Debug("Finished RPC request", "type", "eth_getProof", "blockNumber", req.blockNumber, "address", req.address, "storageKeys", len(storageKeys), "error", err)
var proof *gethclient.AccountResult var proof *gethclient.AccountResult
if err == nil { //TODO de-duplicate if err == nil { //TODO de-duplicate
@ -132,23 +122,23 @@ func (s *lightState) requestProof(ctx context.Context, req proofRequest) (*gethc
return proof, err return proof, err
} }
func (s *lightState) fetchCode(ctx context.Context, req codeRequest) ([]byte, error) { func (c *Client) fetchCode(ctx context.Context, req codeRequest) ([]byte, error) {
if code, ok := s.codeCache.Get(req); ok { if code, ok := c.codeCache.Get(req); ok {
return code, nil return code, nil
} }
request := s.codeRequests.request(req) request := c.codeRequests.request(req)
code, err := request.getResult(ctx) code, err := request.getResult(ctx)
if err == nil { if err == nil {
s.codeCache.Add(req, code) //TODO cached before validation; remove and retry if invalid c.codeCache.Add(req, code) //TODO cached before validation; remove and retry if invalid
} }
request.release() request.release()
return code, err return code, err
} }
func (s *lightState) requestCode(ctx context.Context, req codeRequest) ([]byte, error) { func (c *Client) requestCode(ctx context.Context, req codeRequest) ([]byte, error) {
var code hexutil.Bytes var code hexutil.Bytes
log.Debug("Starting RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address) log.Debug("Starting RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address)
err := s.client.CallContext(ctx, &code, "eth_getCode", req.address, hexutil.EncodeUint64(req.blockNumber)) err := c.client.CallContext(ctx, &code, "eth_getCode", req.address, hexutil.EncodeUint64(req.blockNumber))
log.Debug("Finished RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address, "error", err) log.Debug("Finished RPC request", "type", "eth_getCode", "blockNumber", req.blockNumber, "address", req.address, "error", err)
return code, err return code, err
} }
@ -199,8 +189,8 @@ func stValueBytes(value *big.Int) ([]byte, error) {
} }
} }
func (s *lightState) getProof(ctx context.Context, blockNumber *big.Int, account common.Address, storageKeys []string, getCode bool) (*gethclient.AccountResult, []byte, error) { func (c *Client) getProof(ctx context.Context, blockNumber *big.Int, account common.Address, storageKeys []string, getCode bool) (*gethclient.AccountResult, []byte, error) {
num, pheader, err := s.canonicalChain.resolveBlockNumber(blockNumber) num, pheader, err := c.resolveBlockNumber(blockNumber)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -216,16 +206,16 @@ func (s *lightState) getProof(ctx context.Context, blockNumber *big.Int, account
go func() { go func() {
defer close(stateRootCh) defer close(stateRootCh)
blockHash, err := s.canonicalChain.getHash(ctx, num) blockHash, err := c.getHash(ctx, num)
if err != nil { if err != nil {
stateRootErr = err stateRootErr = err
return return
} }
if pheader := s.blocksAndHeaders.getPayloadHeader(blockHash); pheader != nil { if pheader := c.getPayloadHeader(blockHash); pheader != nil {
stateRoot = pheader.StateRoot() stateRoot = pheader.StateRoot()
return return
} }
header, err := s.blocksAndHeaders.getHeader(ctx, blockHash) header, err := c.getHeader(ctx, blockHash)
if err != nil { if err != nil {
stateRootErr = err stateRootErr = err
return return
@ -240,11 +230,11 @@ func (s *lightState) getProof(ctx context.Context, blockNumber *big.Int, account
) )
if getCode { if getCode {
go func() { go func() {
code, codeErr = s.fetchCode(ctx, codeRequest{blockNumber: num, address: account}) code, codeErr = c.fetchCode(ctx, codeRequest{blockNumber: num, address: account})
close(codeCh) close(codeCh)
}() }()
} }
proof, proofErr := s.fetchProof(ctx, proofRequest{blockNumber: num, address: account, storageKeys: strings.Join(storageKeys, ",")}) proof, proofErr := c.fetchProof(ctx, proofRequest{blockNumber: num, address: account, storageKeys: strings.Join(storageKeys, ",")})
if proofErr != nil { if proofErr != nil {
return nil, nil, proofErr return nil, nil, proofErr
} }
@ -252,7 +242,7 @@ func (s *lightState) getProof(ctx context.Context, blockNumber *big.Int, account
if stateRootErr != nil { if stateRootErr != nil {
return nil, nil, stateRootErr return nil, nil, stateRootErr
} }
if err := s.validateProof(proof, stateRoot, account, storageKeys); err != nil { if err := c.validateProof(proof, stateRoot, account, storageKeys); err != nil {
return nil, nil, err return nil, nil, err
} }
if getCode { if getCode {
@ -267,7 +257,7 @@ func (s *lightState) getProof(ctx context.Context, blockNumber *big.Int, account
return proof, code, nil return proof, code, nil
} }
func (s *lightState) validateProof(proof *gethclient.AccountResult, stateRoot common.Hash, account common.Address, storageKeys []string) error { func (c *Client) validateProof(proof *gethclient.AccountResult, stateRoot common.Hash, account common.Address, storageKeys []string) error {
// validate account proof // validate account proof
proofReader, err := makeProofReader(proof.AccountProof) proofReader, err := makeProofReader(proof.AccountProof)
if err != nil { if err != nil {

View file

@ -32,19 +32,12 @@ import (
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
const maxTxAge = 300 // sent transactions are typically remembered for an hour after last seen pending const maxTxAge = 300 // sent transactions are typically remembered for an hour after last seen pending
type txAndReceipts struct { type txAndReceiptsFields struct {
client *rpc.Client
canonicalChain *canonicalChain
blocksAndHeaders *blocksAndHeaders
lightState *lightState
elConfig *params.ChainConfig
signer types.Signer signer types.Signer
receiptsCache *lru.Cache[common.Hash, types.Receipts] receiptsCache *lru.Cache[common.Hash, types.Receipts]
@ -69,40 +62,33 @@ type sentTx struct {
type senderTxs map[common.Hash]sentTx type senderTxs map[common.Hash]sentTx
func newTxAndReceipts(client *rpc.Client, canonicalChain *canonicalChain, blocksAndHeaders *blocksAndHeaders, elConfig *params.ChainConfig) *txAndReceipts { func (c *Client) initTxAndReceipts() {
t := &txAndReceipts{ c.receiptsCache = lru.NewCache[common.Hash, types.Receipts](10)
client: client, c.txPosCache = lru.NewCache[common.Hash, txInBlock](10000)
canonicalChain: canonicalChain, c.sentTxs = make(map[common.Address]senderTxs)
blocksAndHeaders: blocksAndHeaders, c.signer = types.LatestSigner(c.elConfig)
elConfig: elConfig, c.receiptsRequests = newRequestMap[common.Hash, types.Receipts](c.requestBlockReceipts)
receiptsCache: lru.NewCache[common.Hash, types.Receipts](10),
txPosCache: lru.NewCache[common.Hash, txInBlock](10000),
sentTxs: make(map[common.Address]senderTxs),
signer: types.LatestSigner(elConfig),
}
t.receiptsRequests = newRequestMap[common.Hash, types.Receipts](t.requestBlockReceipts)
return t
} }
func (t *txAndReceipts) getTxByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) { func (c *Client) getTxByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) {
if pos, ok := t.txPosCache.Get(txHash); ok { if pos, ok := c.txPosCache.Get(txHash); ok {
if hash, ok := t.canonicalChain.getCachedHash(pos.blockNumber); ok && hash == pos.blockHash { if hash, ok := c.getCachedHash(pos.blockNumber); ok && hash == pos.blockHash {
if block, ok := t.blocksAndHeaders.blockCache.Get(pos.blockHash); ok { if block, ok := c.blockCache.Get(pos.blockHash); ok {
return block.Transactions()[pos.index], false, nil //TODO index range check return block.Transactions()[pos.index], false, nil //TODO index range check
} }
} }
} }
var headBlockNumber uint64 var headBlockNumber uint64
if head := t.canonicalChain.getHead(); head != nil { if head := c.getHead(); head != nil {
headBlockNumber = head.BlockNumber() headBlockNumber = head.BlockNumber()
} }
return t.getUncachedTxByHash(ctx, txHash, headBlockNumber) return c.getUncachedTxByHash(ctx, txHash, headBlockNumber)
} }
func (t *txAndReceipts) getUncachedTxByHash(ctx context.Context, txHash common.Hash, headBlockNumber uint64) (tx *types.Transaction, isPending bool, err error) { func (c *Client) getUncachedTxByHash(ctx context.Context, txHash common.Hash, headBlockNumber uint64) (tx *types.Transaction, isPending bool, err error) {
tx, isPending, err = t.requestTxByHash(ctx, txHash) tx, isPending, err = c.requestTxByHash(ctx, txHash)
if err == nil && !isPending { if err == nil && !isPending {
receipt, err := t.requestReceiptByTxHash(ctx, txHash) receipt, err := c.requestReceiptByTxHash(ctx, txHash)
if err == ethereum.NotFound { if err == ethereum.NotFound {
return tx, false, nil return tx, false, nil
} }
@ -117,15 +103,15 @@ func (t *txAndReceipts) getUncachedTxByHash(ctx context.Context, txHash common.H
return return
} }
func (t *txAndReceipts) getReceiptByTxHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { func (c *Client) getReceiptByTxHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
if pos, ok := t.txPosCache.Get(txHash); ok { if pos, ok := c.txPosCache.Get(txHash); ok {
if hash, ok := t.canonicalChain.getCachedHash(pos.blockNumber); ok && hash == pos.blockHash { if hash, ok := c.getCachedHash(pos.blockNumber); ok && hash == pos.blockHash {
if receipts, ok := t.receiptsCache.Get(pos.blockHash); ok { if receipts, ok := c.receiptsCache.Get(pos.blockHash); ok {
return receipts[pos.index], nil //TODO index range check return receipts[pos.index], nil //TODO index range check
} }
} }
} }
receipt, err := t.requestReceiptByTxHash(ctx, txHash) receipt, err := c.requestReceiptByTxHash(ctx, txHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -138,11 +124,11 @@ func (t *txAndReceipts) getReceiptByTxHash(ctx context.Context, txHash common.Ha
return nil, errors.New("receipt references non-canonical block") return nil, errors.New("receipt references non-canonical block")
} }
blockNumber := receipt.BlockNumber.Uint64() blockNumber := receipt.BlockNumber.Uint64()
if head := t.canonicalChain.getHead(); head == nil || head.BlockNumber() < blockNumber { if head := c.getHead(); head == nil || head.BlockNumber() < blockNumber {
// consider it pending if it's reported to be included higher than the light chain head // consider it pending if it's reported to be included higher than the light chain head
return nil, ethereum.NotFound return nil, ethereum.NotFound
} }
canonicalHash, err := t.canonicalChain.getHash(ctx, blockNumber) canonicalHash, err := c.getHash(ctx, blockNumber)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -150,7 +136,7 @@ func (t *txAndReceipts) getReceiptByTxHash(ctx context.Context, txHash common.Ha
return nil, errors.New("receipt references non-canonical block") return nil, errors.New("receipt references non-canonical block")
} }
// check if it is the actual canonical receipt at the given position // check if it is the actual canonical receipt at the given position
receipts, err := t.getBlockReceipts(ctx, receipt.BlockHash) receipts, err := c.getBlockReceipts(ctx, receipt.BlockHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -169,31 +155,31 @@ func (t *txAndReceipts) getReceiptByTxHash(ctx context.Context, txHash common.Ha
if !bytes.Equal(jsonReceived, jsonCanonical) { if !bytes.Equal(jsonReceived, jsonCanonical) {
return nil, errors.New("received and derived receipts do not match") return nil, errors.New("received and derived receipts do not match")
} }
t.txPosCache.Add(receipt.TxHash, txInBlock{blockNumber: blockNumber, blockHash: receipt.BlockHash, index: receipt.TransactionIndex}) c.txPosCache.Add(receipt.TxHash, txInBlock{blockNumber: blockNumber, blockHash: receipt.BlockHash, index: receipt.TransactionIndex})
return receipt, err return receipt, err
} }
func (t *txAndReceipts) getBlockReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { func (c *Client) getBlockReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
if receipts, ok := t.receiptsCache.Get(blockHash); ok { if receipts, ok := c.receiptsCache.Get(blockHash); ok {
return receipts, nil return receipts, nil
} }
request := t.receiptsRequests.request(blockHash) request := c.receiptsRequests.request(blockHash)
block, err := t.blocksAndHeaders.getBlock(ctx, blockHash) block, err := c.getBlock(ctx, blockHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
receipts, err := request.getResult(ctx) receipts, err := request.getResult(ctx)
if err == nil { if err == nil {
receipts, err = t.validateBlockReceipts(block, receipts) receipts, err = c.validateBlockReceipts(block, receipts)
} }
if err == nil { if err == nil {
t.receiptsCache.Add(blockHash, receipts) c.receiptsCache.Add(blockHash, receipts)
} }
request.release() request.release()
return receipts, err return receipts, err
} }
func (t *txAndReceipts) validateBlockReceipts(block *types.Block, receipts types.Receipts) (types.Receipts, error) { func (c *Client) validateBlockReceipts(block *types.Block, receipts types.Receipts) (types.Receipts, error) {
// verify consensus fields agains receipts hash in block // verify consensus fields agains receipts hash in block
var hash common.Hash var hash common.Hash
if len(receipts) == 0 { if len(receipts) == 0 {
@ -222,7 +208,7 @@ func (t *txAndReceipts) validateBlockReceipts(block *types.Block, receipts types
if block.ExcessBlobGas() != nil { if block.ExcessBlobGas() != nil {
blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas()) blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas())
} }
if err := newReceipts.DeriveFields(t.elConfig, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, block.Transactions()); err != nil { if err := newReceipts.DeriveFields(c.elConfig, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, block.Transactions()); err != nil {
return nil, err return nil, err
} }
// compare the JSON encoding of received and derived versions // compare the JSON encoding of received and derived versions
@ -240,9 +226,9 @@ func (t *txAndReceipts) validateBlockReceipts(block *types.Block, receipts types
return newReceipts, nil return newReceipts, nil
} }
func (t *txAndReceipts) requestTxByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { func (c *Client) requestTxByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
var json *rpcTransaction var json *rpcTransaction
err = t.client.CallContext(ctx, &json, "eth_getTransactionByHash", hash) err = c.client.CallContext(ctx, &json, "eth_getTransactionByHash", hash)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} else if json == nil { } else if json == nil {
@ -258,33 +244,33 @@ func (t *txAndReceipts) requestTxByHash(ctx context.Context, hash common.Hash) (
return json.tx, json.BlockNumber == nil, nil return json.tx, json.BlockNumber == nil, nil
} }
func (t *txAndReceipts) requestReceiptByTxHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { func (c *Client) requestReceiptByTxHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
var r *types.Receipt var r *types.Receipt
err := t.client.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) err := c.client.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash)
if err == nil && r == nil { if err == nil && r == nil {
return nil, ethereum.NotFound return nil, ethereum.NotFound
} }
return r, err return r, err
} }
func (t *txAndReceipts) requestBlockReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { func (c *Client) requestBlockReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
var r []*types.Receipt var r []*types.Receipt
err := t.client.CallContext(ctx, &r, "eth_getBlockReceipts", blockHash) err := c.client.CallContext(ctx, &r, "eth_getBlockReceipts", blockHash)
if err == nil && r == nil { if err == nil && r == nil {
return nil, ethereum.NotFound return nil, ethereum.NotFound
} }
return types.Receipts(r), err return types.Receipts(r), err
} }
func (t *txAndReceipts) cacheBlockTxPositions(block *types.Block) { func (c *Client) cacheBlockTxPositions(block *types.Block) {
blockNumber, blockHash := block.NumberU64(), block.Hash() blockNumber, blockHash := block.NumberU64(), block.Hash()
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
t.txPosCache.Add(tx.Hash(), txInBlock{blockNumber: blockNumber, blockHash: blockHash, index: uint(i)}) c.txPosCache.Add(tx.Hash(), txInBlock{blockNumber: blockNumber, blockHash: blockHash, index: uint(i)})
} }
} }
func (t *txAndReceipts) sendTransaction(ctx context.Context, tx *types.Transaction) error { func (c *Client) sendTransaction(ctx context.Context, tx *types.Transaction) error {
sender, err := types.Sender(t.signer, tx) sender, err := types.Sender(c.signer, tx)
if err != nil { if err != nil {
return nil return nil
} }
@ -292,69 +278,69 @@ func (t *txAndReceipts) sendTransaction(ctx context.Context, tx *types.Transacti
if err != nil { if err != nil {
return err return err
} }
if err := t.client.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)); err != nil { if err := c.client.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)); err != nil {
return err return err
} }
t.sentTxLock.Lock() c.sentTxLock.Lock()
t.markTxAsSeen(sender, tx) c.markTxAsSeen(sender, tx)
t.sentTxLock.Unlock() c.sentTxLock.Unlock()
return nil return nil
} }
func (t *txAndReceipts) markTxAsSeen(sender common.Address, tx *types.Transaction) { func (c *Client) markTxAsSeen(sender common.Address, tx *types.Transaction) {
senderTxs := t.sentTxs[sender] senderTxs := c.sentTxs[sender]
if senderTxs == nil { if senderTxs == nil {
senderTxs = make(map[common.Hash]sentTx) senderTxs = make(map[common.Hash]sentTx)
t.sentTxs[sender] = senderTxs c.sentTxs[sender] = senderTxs
} }
senderTxs[tx.Hash()] = sentTx{nonce: tx.Nonce(), lastSeen: t.headCounter} senderTxs[tx.Hash()] = sentTx{nonce: tx.Nonce(), lastSeen: c.headCounter}
} }
func (t *txAndReceipts) newHead(number uint64, hash common.Hash) { func (c *Client) txAndReceiptsNewHead(number uint64, hash common.Hash) {
t.sentTxLock.Lock() c.sentTxLock.Lock()
if number > t.lastHeadNumber { if number > c.lastHeadNumber {
t.lastHeadNumber = number c.lastHeadNumber = number
t.headCounter++ c.headCounter++
t.discardOldTxs() c.discardOldTxs()
} }
t.sentTxLock.Unlock() c.sentTxLock.Unlock()
} }
func (t *txAndReceipts) discardOldTxs() { func (c *Client) discardOldTxs() {
for sender, senderTxs := range t.sentTxs { for sender, senderTxs := range c.sentTxs {
for txHash, sentTx := range senderTxs { for txHash, sentTx := range senderTxs {
if sentTx.lastSeen+maxTxAge < t.headCounter { if sentTx.lastSeen+maxTxAge < c.headCounter {
delete(senderTxs, txHash) delete(senderTxs, txHash)
} }
} }
if len(senderTxs) == 0 { if len(senderTxs) == 0 {
delete(t.sentTxs, sender) delete(c.sentTxs, sender)
} }
} }
} }
func (t *txAndReceipts) allSenders() []common.Address { func (c *Client) allSenders() []common.Address {
t.sentTxLock.Lock() c.sentTxLock.Lock()
allSenders := make([]common.Address, 0, len(t.sentTxs)) allSenders := make([]common.Address, 0, len(c.sentTxs))
for sender := range t.sentTxs { for sender := range c.sentTxs {
allSenders = append(allSenders, sender) allSenders = append(allSenders, sender)
} }
t.sentTxLock.Unlock() c.sentTxLock.Unlock()
return allSenders return allSenders
} }
func (t *txAndReceipts) nonceAndPendingTxs(ctx context.Context, head *btypes.ExecutionHeader, sender common.Address) (uint64, types.Transactions, error) { func (c *Client) nonceAndPendingTxs(ctx context.Context, head *btypes.ExecutionHeader, sender common.Address) (uint64, types.Transactions, error) {
proof, err := t.lightState.fetchProof(ctx, proofRequest{blockNumber: head.BlockNumber(), address: sender, storageKeys: ""}) proof, err := c.fetchProof(ctx, proofRequest{blockNumber: head.BlockNumber(), address: sender, storageKeys: ""})
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if err := t.lightState.validateProof(proof, head.StateRoot(), sender, nil); err != nil { if err := c.validateProof(proof, head.StateRoot(), sender, nil); err != nil {
return 0, nil, err return 0, nil, err
} }
t.sentTxLock.Lock() c.sentTxLock.Lock()
senderTxs := t.sentTxs[sender] senderTxs := c.sentTxs[sender]
t.sentTxLock.Unlock() c.sentTxLock.Unlock()
type pendingResult struct { type pendingResult struct {
pendingTx *types.Transaction pendingTx *types.Transaction
@ -369,7 +355,7 @@ func (t *txAndReceipts) nonceAndPendingTxs(ctx context.Context, head *btypes.Exe
} }
reqCount++ reqCount++
go func() { go func() {
tx, isPending, err := t.getUncachedTxByHash(ctx, txHash, head.BlockNumber()) tx, isPending, err := c.getUncachedTxByHash(ctx, txHash, head.BlockNumber())
if err == nil && isPending { if err == nil && isPending {
resultCh <- pendingResult{pendingTx: tx} resultCh <- pendingResult{pendingTx: tx}
} else { } else {
@ -388,10 +374,10 @@ func (t *txAndReceipts) nonceAndPendingTxs(ctx context.Context, head *btypes.Exe
} }
} }
sort.Sort(types.TxByNonce(pendingList)) sort.Sort(types.TxByNonce(pendingList))
t.sentTxLock.Lock() c.sentTxLock.Lock()
for _, tx := range pendingList { for _, tx := range pendingList {
t.markTxAsSeen(sender, tx) c.markTxAsSeen(sender, tx)
} }
t.sentTxLock.Unlock() c.sentTxLock.Unlock()
return proof.Nonce, pendingList, nil return proof.Nonce, pendingList, nil
} }