From b31680fb1998e14c826481381664fec4d99c096d Mon Sep 17 00:00:00 2001 From: Zsolt Felfoldi Date: Wed, 29 May 2024 16:51:22 +0200 Subject: [PATCH] ethclient/lightclient: TransactionReader implemented --- ethclient/lightclient/chain.go | 2 + ethclient/lightclient/lightclient.go | 15 ++++++ ethclient/lightclient/transactions.go | 67 +++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/ethclient/lightclient/chain.go b/ethclient/lightclient/chain.go index 8b93a7d04c..f9a2a40a2d 100644 --- a/ethclient/lightclient/chain.go +++ b/ethclient/lightclient/chain.go @@ -265,6 +265,7 @@ func (c *canonicalChain) blockNumberOrHashToHash(ctx context.Context, blockNrOrH type blocksAndHeaders struct { client *rpc.Client + txAndReceipts *txAndReceipts headerCache *lru.Cache[common.Hash, *types.Header] headerRequests *requestMap[common.Hash, *types.Header] payloadHeaderCache *lru.Cache[common.Hash, *btypes.ExecutionHeader] @@ -358,6 +359,7 @@ func (b *blocksAndHeaders) getBlock(ctx context.Context, hash common.Hash) (*typ b.headerCache.Add(hash, header) b.headerRequests.tryDeliver(hash, header) b.blockCache.Add(hash, block) + b.txAndReceipts.cacheBlockTxPositions(block) } req.release() return block, err diff --git a/ethclient/lightclient/lightclient.go b/ethclient/lightclient/lightclient.go index 39c1dd0b40..f11db0abfe 100644 --- a/ethclient/lightclient/lightclient.go +++ b/ethclient/lightclient/lightclient.go @@ -69,6 +69,7 @@ func NewClient(clConfig config.LightClientConfig, elConfig *params.ChainConfig, blocksAndHeaders := newBlocksAndHeaders(rpcClient) canonicalChain := newCanonicalChain(headTracker, blocksAndHeaders, client.newHead) txAndReceipts := newTxAndReceipts(rpcClient, canonicalChain, blocksAndHeaders, elConfig) + blocksAndHeaders.txAndReceipts = txAndReceipts client.blocksAndHeaders = blocksAndHeaders client.txAndReceipts = txAndReceipts client.canonicalChain = canonicalChain @@ -100,6 +101,8 @@ func (c *Client) Stop() { c.scheduler.Stop() } +// ChainReader interface + func (c *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { return c.blocksAndHeaders.getBlock(ctx, hash) } @@ -204,6 +207,18 @@ func (h *headSub) Err() <-chan error { return h.errCh } +// TransactionReader interface + +func (c *Client) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) { + return c.txAndReceipts.getTxByHash(ctx, txHash) +} + +func (c *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + return c.txAndReceipts.getReceiptByTxHash(ctx, txHash) +} + +// ChainStateReader interface { + 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) if err != nil { diff --git a/ethclient/lightclient/transactions.go b/ethclient/lightclient/transactions.go index 32194c146e..c542ba032f 100644 --- a/ethclient/lightclient/transactions.go +++ b/ethclient/lightclient/transactions.go @@ -70,7 +70,22 @@ func (t *txAndReceipts) getTxByHash(ctx context.Context, txHash common.Hash) (tx } } } - return t.requestTxByHash(ctx, txHash) + tx, isPending, err = t.requestTxByHash(ctx, txHash) + if err == nil && !isPending { + receipt, err := t.requestReceiptByTxHash(ctx, txHash) + if err == ethereum.NotFound { + return tx, false, nil + } + if err != nil { + return nil, false, err + } + if head := t.canonicalChain.getHead(); head == nil || + !receipt.BlockNumber.IsUint64() || head.BlockNumber() < receipt.BlockNumber.Uint64() { + // consider it pending if it's reported to be included higher than the light chain head + isPending = true + } + } + return } func (t *txAndReceipts) getReceiptByTxHash(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { @@ -81,8 +96,52 @@ func (t *txAndReceipts) getReceiptByTxHash(ctx context.Context, txHash common.Ha } } } - return t.requestReceiptByTxHash(ctx, txHash) - //TODO validate position + receipt, err := t.requestReceiptByTxHash(ctx, txHash) + if err != nil { + return nil, err + } + // check if it indeed belongs to the requested transaction + if receipt.TxHash != txHash { + return nil, errors.New("receipt references another transaction") + } + // check if its inclusion position is canonical + if !receipt.BlockNumber.IsUint64() { + return nil, errors.New("receipt references non-canonical block") + } + blockNumber := receipt.BlockNumber.Uint64() + if head := t.canonicalChain.getHead(); head == nil || head.BlockNumber() < blockNumber { + // consider it pending if it's reported to be included higher than the light chain head + return nil, ethereum.NotFound + } + canonicalHash, err := t.canonicalChain.getHash(ctx, blockNumber) + if err != nil { + return nil, err + } + if receipt.BlockHash != canonicalHash { + return nil, errors.New("receipt references non-canonical block") + } + // check if it is the actual canonical receipt at the given position + receipts, err := t.getBlockReceipts(ctx, receipt.BlockHash) + if err != nil { + return nil, err + } + if receipt.TransactionIndex >= uint(len(receipts)) { + return nil, errors.New("receipt references out-of-range transaction index") + } + // compare the JSON encoding of received and canonical versions + jsonReceived, err := json.Marshal(receipt) + if err != nil { + return nil, err + } + jsonCanonical, err := json.Marshal(receipts[receipt.TransactionIndex]) + if err != nil { + return nil, err + } + if !bytes.Equal(jsonReceived, jsonCanonical) { + 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}) + return receipt, err } func (t *txAndReceipts) getBlockReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { @@ -186,7 +245,7 @@ func (t *txAndReceipts) requestBlockReceipts(ctx context.Context, blockHash comm return types.Receipts(r), err } -func (t *txAndReceipts) addBlockTxs(block *types.Block) { +func (t *txAndReceipts) cacheBlockTxPositions(block *types.Block) { blockNumber, blockHash := block.NumberU64(), block.Hash() for i, tx := range block.Transactions() { t.txPosCache.Add(tx.Hash(), txInBlock{blockNumber: blockNumber, blockHash: blockHash, index: uint(i)})