diff --git a/crates/cold-mdbx/src/backend.rs b/crates/cold-mdbx/src/backend.rs index 329300f..08b2b1f 100644 --- a/crates/cold-mdbx/src/backend.rs +++ b/crates/cold-mdbx/src/backend.rs @@ -480,23 +480,28 @@ impl MdbxColdBackend { return Ok(None); }; - let prior_cumulative_gas = index - .checked_sub(1) - .map(|prev| { - DualTableTraverse::::exact_dual( - &mut tx.new_cursor::()?, - &block, - &prev, - ) - }) - .transpose()? - .flatten() - .map(|r: Receipt| r.inner.cumulative_gas_used) - .unwrap_or(0); + let mut first_log_index = 0u64; + let mut prior_cumulative_gas = 0u64; + for i in 0..index { + if let Some(r) = DualTableTraverse::::exact_dual( + &mut tx.new_cursor::()?, + &block, + &i, + )? { + prior_cumulative_gas = r.inner.cumulative_gas_used; + first_log_index += r.inner.logs.len() as u64; + } + } let meta = ConfirmationMeta::new(block, header.hash_slow(), index); let confirmed_receipt = Confirmed::new(receipt, meta); - Ok(Some(ReceiptContext::new(header, transaction, confirmed_receipt, prior_cumulative_gas))) + Ok(Some(ReceiptContext::new( + header, + transaction, + confirmed_receipt, + prior_cumulative_gas, + first_log_index, + ))) } } diff --git a/crates/cold/src/conformance.rs b/crates/cold/src/conformance.rs index ff239aa..063356a 100644 --- a/crates/cold/src/conformance.rs +++ b/crates/cold/src/conformance.rs @@ -9,7 +9,7 @@ use crate::{ }; use alloy::{ consensus::{Header, Receipt as AlloyReceipt, Signed, TxLegacy}, - primitives::{B256, BlockNumber, Signature, TxKind, U256}, + primitives::{B256, BlockNumber, Bytes, Log, Signature, TxKind, U256, address}, }; use signet_storage_types::{Receipt, TransactionSigned}; @@ -54,6 +54,23 @@ fn make_test_receipt() -> Receipt { } } +/// Create a test receipt with the given number of logs. +fn make_test_receipt_with_logs(log_count: usize, cumulative_gas: u64) -> Receipt { + let logs = (0..log_count) + .map(|_| { + Log::new_unchecked( + address!("0x0000000000000000000000000000000000000001"), + vec![], + Bytes::new(), + ) + }) + .collect(); + Receipt { + inner: AlloyReceipt { status: true.into(), cumulative_gas_used: cumulative_gas, logs }, + ..Default::default() + } +} + /// Create test block data with transactions and receipts. fn make_test_block_with_txs(block_number: BlockNumber, tx_count: usize) -> BlockData { let header = Header { number: block_number, ..Default::default() }; @@ -244,33 +261,53 @@ pub async fn test_latest_block_tracking(backend: &B) -> ColdResu /// Test get_receipt_with_context returns complete receipt context. pub async fn test_get_receipt_with_context(backend: &B) -> ColdResult<()> { - let block = make_test_block_with_txs(700, 3); - let expected_header = block.header.clone(); - let tx_hash = *block.transactions[1].tx_hash(); + // Block with 3 receipts having 2, 3, and 1 logs respectively. + let header = Header { number: 700, ..Default::default() }; + let transactions: Vec<_> = (0..3).map(|i| make_test_tx(700 * 100 + i)).collect(); + let receipts = vec![ + make_test_receipt_with_logs(2, 21000), + make_test_receipt_with_logs(3, 42000), + make_test_receipt_with_logs(1, 63000), + ]; + let block = BlockData::new(header.clone(), transactions.clone(), receipts, vec![], None); + let tx_hash = *transactions[1].tx_hash(); backend.append_block(block).await?; - // Lookup by block+index - let ctx = backend + // First receipt: prior_cumulative_gas=0, first_log_index=0 + let first = backend + .get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 0 }) + .await? + .unwrap(); + assert_eq!(first.header, header); + assert_eq!(first.receipt.meta().block_number(), 700); + assert_eq!(first.receipt.meta().transaction_index(), 0); + assert_eq!(first.prior_cumulative_gas, 0); + assert_eq!(first.first_log_index, 0); + + // Second receipt: prior_cumulative_gas=21000, first_log_index=2 + let second = backend .get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 1 }) .await? .unwrap(); - assert_eq!(ctx.header, expected_header); - assert_eq!(ctx.receipt.meta().block_number(), 700); - assert_eq!(ctx.receipt.meta().transaction_index(), 1); + assert_eq!(second.receipt.meta().transaction_index(), 1); + assert_eq!(second.prior_cumulative_gas, 21000); + assert_eq!(second.first_log_index, 2); - // prior_cumulative_gas should equal receipt[0].cumulative_gas_used - let first = backend - .get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 0 }) + // Third receipt: prior_cumulative_gas=42000, first_log_index=5 (2+3) + let third = backend + .get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 2 }) .await? .unwrap(); - assert_eq!(first.prior_cumulative_gas, 0); - assert_eq!(ctx.prior_cumulative_gas, first.receipt.inner().inner.cumulative_gas_used); + assert_eq!(third.receipt.meta().transaction_index(), 2); + assert_eq!(third.prior_cumulative_gas, 42000); + assert_eq!(third.first_log_index, 5); // Lookup by tx hash let by_hash = backend.get_receipt_with_context(ReceiptSpecifier::TxHash(tx_hash)).await?.unwrap(); assert_eq!(by_hash.receipt.meta().transaction_index(), 1); + assert_eq!(by_hash.first_log_index, 2); // Non-existent returns None assert!( diff --git a/crates/cold/src/traits.rs b/crates/cold/src/traits.rs index 52d4486..912e524 100644 --- a/crates/cold/src/traits.rs +++ b/crates/cold/src/traits.rs @@ -51,7 +51,9 @@ impl BlockData { /// All data needed to build a complete RPC receipt response. /// /// Bundles a [`Confirmed`] receipt with its transaction, block header, -/// and the prior cumulative gas (needed to compute per-tx `gas_used`). +/// the prior cumulative gas (needed to compute per-tx `gas_used`), +/// and the index of this receipt's first log among all logs in the block +/// (needed for `logIndex` in RPC responses). #[derive(Debug, Clone)] pub struct ReceiptContext { /// The block header. @@ -63,6 +65,9 @@ pub struct ReceiptContext { /// Cumulative gas used by all preceding transactions in the block. /// Zero for the first transaction. pub prior_cumulative_gas: u64, + /// Index of this receipt's first log among all logs in the block. + /// Equal to the sum of log counts from all preceding receipts. + pub first_log_index: u64, } impl ReceiptContext { @@ -72,8 +77,9 @@ impl ReceiptContext { transaction: TransactionSigned, receipt: Confirmed, prior_cumulative_gas: u64, + first_log_index: u64, ) -> Self { - Self { header, transaction, receipt, prior_cumulative_gas } + Self { header, transaction, receipt, prior_cumulative_gas, first_log_index } } } @@ -197,7 +203,8 @@ pub trait ColdStorage: Send + Sync + 'static { /// Get a receipt with all context needed for RPC responses. /// /// Returns the receipt, its transaction, the block header, confirmation - /// metadata, and the cumulative gas used by preceding transactions. + /// metadata, the cumulative gas used by preceding transactions, and the + /// index of this receipt's first log among all logs in the block. /// Returns `None` if the receipt does not exist. /// /// The default implementation composes existing trait methods. Backends @@ -223,16 +230,18 @@ pub trait ColdStorage: Send + Sync + 'static { return Ok(None); }; - let prior_cumulative_gas = if index > 0 { - self.get_receipt(ReceiptSpecifier::BlockAndIndex { block, index: index - 1 }) - .await? - .map(|r| r.into_inner().inner.cumulative_gas_used) - .unwrap_or(0) - } else { - 0 - }; - - Ok(Some(ReceiptContext::new(header, tx.into_inner(), receipt, prior_cumulative_gas))) + let receipts = self.get_receipts_in_block(block).await?; + let prior = &receipts[..index as usize]; + let prior_cumulative_gas = prior.last().map_or(0, |r| r.inner.cumulative_gas_used); + let first_log_index = prior.iter().map(|r| r.inner.logs.len() as u64).sum(); + + Ok(Some(ReceiptContext::new( + header, + tx.into_inner(), + receipt, + prior_cumulative_gas, + first_log_index, + ))) } }