Skip to content

Commit 9dce356

Browse files
prestwichclaude
andcommitted
feat(cold): add first_log_index to ReceiptContext
Adds the index of a receipt's first log among all logs in its block, enabling callers to compute per-log logIndex for RPC responses without refetching prior receipts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a71ba60 commit 9dce356

File tree

3 files changed

+92
-41
lines changed

3 files changed

+92
-41
lines changed

crates/cold-mdbx/src/backend.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -443,23 +443,28 @@ impl MdbxColdBackend {
443443
return Ok(None);
444444
};
445445

446-
let prior_cumulative_gas = index
447-
.checked_sub(1)
448-
.map(|prev| {
449-
DualTableTraverse::<ColdReceipts, _>::exact_dual(
450-
&mut tx.new_cursor::<ColdReceipts>()?,
451-
&block,
452-
&prev,
453-
)
454-
})
455-
.transpose()?
456-
.flatten()
457-
.map(|r: Receipt| r.inner.cumulative_gas_used)
458-
.unwrap_or(0);
446+
let mut first_log_index = 0u64;
447+
let mut prior_cumulative_gas = 0u64;
448+
for i in 0..index {
449+
if let Some(r) = DualTableTraverse::<ColdReceipts, _>::exact_dual(
450+
&mut tx.new_cursor::<ColdReceipts>()?,
451+
&block,
452+
&i,
453+
)? {
454+
prior_cumulative_gas = r.inner.cumulative_gas_used;
455+
first_log_index += r.inner.logs.len() as u64;
456+
}
457+
}
459458

460459
let meta = ConfirmationMeta::new(block, header.hash_slow(), index);
461460
let confirmed_receipt = Confirmed::new(receipt, meta);
462-
Ok(Some(ReceiptContext::new(header, transaction, confirmed_receipt, prior_cumulative_gas)))
461+
Ok(Some(ReceiptContext::new(
462+
header,
463+
transaction,
464+
confirmed_receipt,
465+
prior_cumulative_gas,
466+
first_log_index,
467+
)))
463468
}
464469
}
465470

crates/cold/src/conformance.rs

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
};
1010
use alloy::{
1111
consensus::{Header, Receipt as AlloyReceipt, Signed, TxLegacy},
12-
primitives::{B256, BlockNumber, Signature, TxKind, U256},
12+
primitives::{B256, BlockNumber, Bytes, Log, Signature, TxKind, U256, address},
1313
};
1414
use signet_storage_types::{Receipt, TransactionSigned};
1515

@@ -54,6 +54,23 @@ fn make_test_receipt() -> Receipt {
5454
}
5555
}
5656

57+
/// Create a test receipt with the given number of logs.
58+
fn make_test_receipt_with_logs(log_count: usize, cumulative_gas: u64) -> Receipt {
59+
let logs = (0..log_count)
60+
.map(|_| {
61+
Log::new_unchecked(
62+
address!("0x0000000000000000000000000000000000000001"),
63+
vec![],
64+
Bytes::new(),
65+
)
66+
})
67+
.collect();
68+
Receipt {
69+
inner: AlloyReceipt { status: true.into(), cumulative_gas_used: cumulative_gas, logs },
70+
..Default::default()
71+
}
72+
}
73+
5774
/// Create test block data with transactions and receipts.
5875
fn make_test_block_with_txs(block_number: BlockNumber, tx_count: usize) -> BlockData {
5976
let header = Header { number: block_number, ..Default::default() };
@@ -244,33 +261,53 @@ pub async fn test_latest_block_tracking<B: ColdStorage>(backend: &B) -> ColdResu
244261

245262
/// Test get_receipt_with_context returns complete receipt context.
246263
pub async fn test_get_receipt_with_context<B: ColdStorage>(backend: &B) -> ColdResult<()> {
247-
let block = make_test_block_with_txs(700, 3);
248-
let expected_header = block.header.clone();
249-
let tx_hash = *block.transactions[1].tx_hash();
264+
// Block with 3 receipts having 2, 3, and 1 logs respectively.
265+
let header = Header { number: 700, ..Default::default() };
266+
let transactions: Vec<_> = (0..3).map(|i| make_test_tx(700 * 100 + i)).collect();
267+
let receipts = vec![
268+
make_test_receipt_with_logs(2, 21000),
269+
make_test_receipt_with_logs(3, 42000),
270+
make_test_receipt_with_logs(1, 63000),
271+
];
272+
let block = BlockData::new(header.clone(), transactions.clone(), receipts, vec![], None);
273+
let tx_hash = *transactions[1].tx_hash();
250274

251275
backend.append_block(block).await?;
252276

253-
// Lookup by block+index
254-
let ctx = backend
277+
// First receipt: prior_cumulative_gas=0, first_log_index=0
278+
let first = backend
279+
.get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 0 })
280+
.await?
281+
.unwrap();
282+
assert_eq!(first.header, header);
283+
assert_eq!(first.receipt.meta().block_number(), 700);
284+
assert_eq!(first.receipt.meta().transaction_index(), 0);
285+
assert_eq!(first.prior_cumulative_gas, 0);
286+
assert_eq!(first.first_log_index, 0);
287+
288+
// Second receipt: prior_cumulative_gas=21000, first_log_index=2
289+
let second = backend
255290
.get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 1 })
256291
.await?
257292
.unwrap();
258-
assert_eq!(ctx.header, expected_header);
259-
assert_eq!(ctx.receipt.meta().block_number(), 700);
260-
assert_eq!(ctx.receipt.meta().transaction_index(), 1);
293+
assert_eq!(second.receipt.meta().transaction_index(), 1);
294+
assert_eq!(second.prior_cumulative_gas, 21000);
295+
assert_eq!(second.first_log_index, 2);
261296

262-
// prior_cumulative_gas should equal receipt[0].cumulative_gas_used
263-
let first = backend
264-
.get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 0 })
297+
// Third receipt: prior_cumulative_gas=42000, first_log_index=5 (2+3)
298+
let third = backend
299+
.get_receipt_with_context(ReceiptSpecifier::BlockAndIndex { block: 700, index: 2 })
265300
.await?
266301
.unwrap();
267-
assert_eq!(first.prior_cumulative_gas, 0);
268-
assert_eq!(ctx.prior_cumulative_gas, first.receipt.inner().inner.cumulative_gas_used);
302+
assert_eq!(third.receipt.meta().transaction_index(), 2);
303+
assert_eq!(third.prior_cumulative_gas, 42000);
304+
assert_eq!(third.first_log_index, 5);
269305

270306
// Lookup by tx hash
271307
let by_hash =
272308
backend.get_receipt_with_context(ReceiptSpecifier::TxHash(tx_hash)).await?.unwrap();
273309
assert_eq!(by_hash.receipt.meta().transaction_index(), 1);
310+
assert_eq!(by_hash.first_log_index, 2);
274311

275312
// Non-existent returns None
276313
assert!(

crates/cold/src/traits.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ impl BlockData {
5151
/// All data needed to build a complete RPC receipt response.
5252
///
5353
/// Bundles a [`Confirmed`] receipt with its transaction, block header,
54-
/// and the prior cumulative gas (needed to compute per-tx `gas_used`).
54+
/// the prior cumulative gas (needed to compute per-tx `gas_used`),
55+
/// and the index of this receipt's first log among all logs in the block
56+
/// (needed for `logIndex` in RPC responses).
5557
#[derive(Debug, Clone)]
5658
pub struct ReceiptContext {
5759
/// The block header.
@@ -63,6 +65,9 @@ pub struct ReceiptContext {
6365
/// Cumulative gas used by all preceding transactions in the block.
6466
/// Zero for the first transaction.
6567
pub prior_cumulative_gas: u64,
68+
/// Index of this receipt's first log among all logs in the block.
69+
/// Equal to the sum of log counts from all preceding receipts.
70+
pub first_log_index: u64,
6671
}
6772

6873
impl ReceiptContext {
@@ -72,8 +77,9 @@ impl ReceiptContext {
7277
transaction: TransactionSigned,
7378
receipt: Confirmed<Receipt>,
7479
prior_cumulative_gas: u64,
80+
first_log_index: u64,
7581
) -> Self {
76-
Self { header, transaction, receipt, prior_cumulative_gas }
82+
Self { header, transaction, receipt, prior_cumulative_gas, first_log_index }
7783
}
7884
}
7985

@@ -197,7 +203,8 @@ pub trait ColdStorage: Send + Sync + 'static {
197203
/// Get a receipt with all context needed for RPC responses.
198204
///
199205
/// Returns the receipt, its transaction, the block header, confirmation
200-
/// metadata, and the cumulative gas used by preceding transactions.
206+
/// metadata, the cumulative gas used by preceding transactions, and the
207+
/// index of this receipt's first log among all logs in the block.
201208
/// Returns `None` if the receipt does not exist.
202209
///
203210
/// The default implementation composes existing trait methods. Backends
@@ -223,16 +230,18 @@ pub trait ColdStorage: Send + Sync + 'static {
223230
return Ok(None);
224231
};
225232

226-
let prior_cumulative_gas = if index > 0 {
227-
self.get_receipt(ReceiptSpecifier::BlockAndIndex { block, index: index - 1 })
228-
.await?
229-
.map(|r| r.into_inner().inner.cumulative_gas_used)
230-
.unwrap_or(0)
231-
} else {
232-
0
233-
};
234-
235-
Ok(Some(ReceiptContext::new(header, tx.into_inner(), receipt, prior_cumulative_gas)))
233+
let receipts = self.get_receipts_in_block(block).await?;
234+
let prior = &receipts[..index as usize];
235+
let prior_cumulative_gas = prior.last().map_or(0, |r| r.inner.cumulative_gas_used);
236+
let first_log_index = prior.iter().map(|r| r.inner.logs.len() as u64).sum();
237+
238+
Ok(Some(ReceiptContext::new(
239+
header,
240+
tx.into_inner(),
241+
receipt,
242+
prior_cumulative_gas,
243+
first_log_index,
244+
)))
236245
}
237246
}
238247

0 commit comments

Comments
 (0)