ethclient/lightclient: TransactionReader implemented

This commit is contained in:
Zsolt Felfoldi 2024-05-29 16:51:22 +02:00
parent ef80de057a
commit b31680fb19
3 changed files with 80 additions and 4 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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)})