Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions internal/rpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ func (rpc *Client) GetFullBlocks(ctx context.Context, blockNumbers []*big.Int) [
blocks = rpc.fetchTransactionsForChain296(ctx, blocks)
}

if rpc.chainID != nil && rpc.chainID.Uint64() == 2020 && rpc.supportsBlockReceipts {
receipts = rpc.fallbackBlockReceiptsForChain2020(ctx, blocks, receipts)
}

return SerializeFullBlocks(rpc.chainID, blocks, logs, traces, receipts)
}

Expand Down Expand Up @@ -461,3 +465,89 @@ func (rpc *Client) fetchTransactionsInBatches(ctx context.Context, txHashes []st

return results
}

func (rpc *Client) fallbackBlockReceiptsForChain2020(ctx context.Context, blocks []RPCFetchBatchResult[*big.Int, common.RawBlock], receipts []RPCFetchBatchResult[*big.Int, common.RawReceipts]) []RPCFetchBatchResult[*big.Int, common.RawReceipts] {
blockByNumber := mapBatchResultsByBlockNumber[common.RawBlock](blocks)

for i := range receipts {
if receipts[i].Error == nil || !isChain2020BlockReceiptsError(receipts[i].Error) {
continue
}

blockNumber := receipts[i].Key.String()
block, ok := blockByNumber[blockNumber]
if !ok || block.Error != nil || block.Result == nil {
log.Warn().
Err(receipts[i].Error).
Str("block_number", blockNumber).
Msg("Skipping chain 2020 eth_getBlockReceipts fallback because block data is unavailable")
continue
}

txHashes := extractTransactionHashes(block.Result)
if len(txHashes) == 0 {
receipts[i].Result = common.RawReceipts{}
receipts[i].Error = nil
continue
Comment on lines +487 to +491
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not clear receipt errors when transaction extraction fails.

At Line 533-536, parse failure and “no transactions” are conflated (nil in both cases). Then at Line 487-491, len(txHashes)==0 clears the original receipt error and returns empty receipts. This can silently hide real receipt loss if block payload shape is unexpected.

💡 Suggested fix
-func extractTransactionHashes(block common.RawBlock) []string {
-	rawTransactions, ok := block["transactions"].([]interface{})
-	if !ok {
-		return nil
-	}
+func extractTransactionHashes(block common.RawBlock) ([]string, bool) {
+	rawTransactions, exists := block["transactions"]
+	if !exists {
+		return nil, false
+	}
+	txList, ok := rawTransactions.([]interface{})
+	if !ok {
+		return nil, false
+	}
 
-	txHashes := make([]string, 0, len(rawTransactions))
-	for _, rawTx := range rawTransactions {
+	txHashes := make([]string, 0, len(txList))
+	for _, rawTx := range txList {
 		switch tx := rawTx.(type) {
 		case string:
 			if tx != "" {
 				txHashes = append(txHashes, tx)
 			}
 		case map[string]interface{}:
 			if hash, ok := tx["hash"].(string); ok && hash != "" {
 				txHashes = append(txHashes, hash)
 			}
 		}
 	}
 
-	return txHashes
+	return txHashes, true
 }
-		txHashes := extractTransactionHashes(block.Result)
-		if len(txHashes) == 0 {
+		txHashes, parsed := extractTransactionHashes(block.Result)
+		if !parsed {
+			log.Warn().
+				Err(receipts[i].Error).
+				Str("block_number", blockNumber).
+				Msg("Skipping chain 2020 eth_getBlockReceipts fallback because transactions could not be parsed")
+			continue
+		}
+		if len(txHashes) == 0 {
 			receipts[i].Result = common.RawReceipts{}
 			receipts[i].Error = nil
 			continue
 		}

Also applies to: 533-536

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/rpc/rpc.go` around lines 487 - 491, The code clears receipt errors
when extractTransactionHashes(block.Result) returns an empty slice: change the
logic in the branch handling len(txHashes) == 0 so it does not overwrite
receipts[i].Error; only set receipts[i].Result = common.RawReceipts{} when
receipts[i].Error is already nil (preserve any existing error), and do not set
receipts[i].Error = nil unconditionally. Apply the same fix to the similar
parse-failure/no-transactions handling around the other extractTransactionHashes
usage (the block referenced at the other location) so parsing failures remain
visible and are not conflated with "no transactions".

}

txReceiptResults := RPCFetchSingleBatch[string, common.RawReceipt](rpc, ctx, txHashes, "eth_getTransactionReceipt", GetTransactionParams)
rawReceipts := make(common.RawReceipts, 0, len(txReceiptResults))
var fallbackErr error
for _, txReceipt := range txReceiptResults {
if txReceipt.Error != nil {
fallbackErr = txReceipt.Error
break
}
rawReceipts = append(rawReceipts, txReceipt.Result)
}
if fallbackErr != nil {
log.Error().
Err(fallbackErr).
Str("block_number", blockNumber).
Int("transaction_count", len(txHashes)).
Msg("Chain 2020 eth_getTransactionReceipt fallback failed")
continue
}

log.Warn().
Str("block_number", blockNumber).
Int("receipt_count", len(rawReceipts)).
Msg("Recovered chain 2020 block receipts using eth_getTransactionReceipt fallback")

receipts[i].Result = rawReceipts
receipts[i].Error = nil
}

return receipts
}

func isChain2020BlockReceiptsError(err error) bool {
if err == nil {
return false
}
return strings.Contains(err.Error(), "could not find l1 block info tx in the L2 block")
}

func extractTransactionHashes(block common.RawBlock) []string {
rawTransactions, ok := block["transactions"].([]interface{})
if !ok {
return nil
}

txHashes := make([]string, 0, len(rawTransactions))
for _, rawTx := range rawTransactions {
switch tx := rawTx.(type) {
case string:
if tx != "" {
txHashes = append(txHashes, tx)
}
case map[string]interface{}:
if hash, ok := tx["hash"].(string); ok && hash != "" {
txHashes = append(txHashes, hash)
}
}
}

return txHashes
}
Loading