Skip to content

Commit d2cf877

Browse files
committed
feat: add verification service for flashblocks with metrics tracking for block reorgs
1 parent d3553f7 commit d2cf877

File tree

8 files changed

+686
-25
lines changed

8 files changed

+686
-25
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ reth-rpc-types-compat = { git = "https://github.com/paradigmxyz/reth", tag = "v1
3333
reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.3.12" }
3434
reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.3.12" }
3535
reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.3.12" }
36+
reth-exex = { git = "https://github.com/paradigmxyz/reth", tag = "v1.3.12" }
3637

3738
# revm
3839
revm = { version = "22.0.1", default-features = false }

crates/flashblocks-rpc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ reth-rpc-types-compat.workspace = true
2626
reth-optimism-rpc.workspace = true
2727
reth-optimism-evm.workspace = true
2828
reth-optimism-chainspec.workspace = true
29+
reth-exex.workspace = true
2930

3031
# revm
3132
revm.workspace = true

crates/flashblocks-rpc/src/flashblocks.rs

Lines changed: 184 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -368,29 +368,37 @@ fn get_and_set_txs_and_receipts(
368368
metadata: Metadata,
369369
) -> Result<Vec<OpReceipt>, Box<dyn std::error::Error>> {
370370
let mut diff_receipts: Vec<OpReceipt> = vec![];
371-
// Store tx transaction signed
371+
let mut tx_hashes: Vec<String> = vec![];
372+
373+
if let Some(existing_hashes) = cache.get::<Vec<String>>(&format!("tx_hashes:{}", block_number))
374+
{
375+
tx_hashes = existing_hashes;
376+
}
377+
372378
for (idx, transaction) in block.body.transactions.iter().enumerate() {
373-
// check if exists, if not update
374-
let existing_tx = cache.get::<OpTransactionSigned>(&transaction.tx_hash().to_string());
379+
let tx_hash = transaction.tx_hash().to_string();
380+
381+
// Add transaction hash to the ordered list if not already present
382+
if !tx_hashes.contains(&tx_hash) {
383+
tx_hashes.push(tx_hash.clone());
384+
}
385+
386+
let existing_tx = cache.get::<OpTransactionSigned>(&tx_hash);
375387
if existing_tx.is_none() {
376-
if let Err(e) = cache.set(&transaction.tx_hash().to_string(), &transaction, Some(10)) {
388+
if let Err(e) = cache.set(&tx_hash, &transaction, Some(10)) {
377389
error!("Failed to set transaction in cache: {}", e);
378390
continue;
379391
}
380-
// update tx index
381392
if let Err(e) = cache.set(&format!("tx_idx:{}", transaction.tx_hash()), &idx, Some(10))
382393
{
383394
error!("Failed to set transaction index in cache: {}", e);
384395
continue;
385396
}
386397

387-
// update tx count for each from address
388398
if let Ok(from) = transaction.recover_signer() {
389-
// Get current tx count, default to 0 if not found
390399
let current_count = cache
391400
.get::<u64>(&format!("tx_count:{}:{}", from, block_number))
392401
.unwrap_or(0);
393-
// Increment tx count by 1
394402
if let Err(e) = cache.set(
395403
&format!("tx_count:{}:{}", from, block_number),
396404
&(current_count + 1),
@@ -399,7 +407,6 @@ fn get_and_set_txs_and_receipts(
399407
error!("Failed to set transaction count in cache: {}", e);
400408
}
401409

402-
// also keep track of sender of each transaction
403410
if let Err(e) = cache.set(
404411
&format!("tx_sender:{}", transaction.tx_hash()),
405412
&from,
@@ -408,7 +415,6 @@ fn get_and_set_txs_and_receipts(
408415
error!("Failed to set transaction sender in cache: {}", e);
409416
}
410417

411-
// also keep track of the block number of each transaction
412418
if let Err(e) = cache.set(
413419
&format!("tx_block_number:{}", transaction.tx_hash()),
414420
&block_number,
@@ -420,26 +426,16 @@ fn get_and_set_txs_and_receipts(
420426
}
421427

422428
// TODO: move this into the transaction check
423-
if metadata
424-
.receipts
425-
.contains_key(&transaction.tx_hash().to_string())
426-
{
429+
if metadata.receipts.contains_key(&tx_hash) {
427430
// find receipt in metadata and set it in cache
428-
let receipt = metadata
429-
.receipts
430-
.get(&transaction.tx_hash().to_string())
431-
.unwrap();
432-
if let Err(e) = cache.set(
433-
&format!("receipt:{:?}", transaction.tx_hash().to_string()),
434-
receipt,
435-
Some(10),
436-
) {
431+
let receipt = metadata.receipts.get(&tx_hash).unwrap();
432+
if let Err(e) = cache.set(&format!("receipt:{:?}", tx_hash), receipt, Some(10)) {
437433
error!("Failed to set receipt in cache: {}", e);
438434
continue;
439435
}
440436
// map receipt's block number as well
441437
if let Err(e) = cache.set(
442-
&format!("receipt_block:{:?}", transaction.tx_hash().to_string()),
438+
&format!("receipt_block:{:?}", tx_hash),
443439
&block_number,
444440
Some(10),
445441
) {
@@ -451,6 +447,10 @@ fn get_and_set_txs_and_receipts(
451447
}
452448
}
453449

450+
if let Err(e) = cache.set(&format!("tx_hashes:{}", block_number), &tx_hashes, Some(10)) {
451+
error!("Failed to update transaction hashes list in cache: {}", e);
452+
}
453+
454454
Ok(diff_receipts)
455455
}
456456

@@ -635,7 +635,7 @@ mod tests {
635635
payload_id: PayloadId::new([0; 8]),
636636
base: None,
637637
diff: delta2,
638-
metadata: serde_json::to_value(metadata2).unwrap(),
638+
metadata: serde_json::to_value(metadata2.clone()).unwrap(),
639639
}
640640
}
641641

@@ -832,5 +832,164 @@ mod tests {
832832
// Also verify metric would have been recorded (though we can't directly check the metric's value)
833833
let highest = cache.get::<u64>("highest_payload_index").unwrap();
834834
assert_eq!(highest, 0);
835+
836+
}
837+
838+
#[test]
839+
fn test_tx_hash_list_storage_and_deduplication() {
840+
let cache = Arc::new(Cache::default());
841+
let block_number = 1;
842+
843+
let base = ExecutionPayloadBaseV1 {
844+
parent_hash: Default::default(),
845+
parent_beacon_block_root: Default::default(),
846+
fee_recipient: Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
847+
block_number,
848+
gas_limit: 1000000,
849+
timestamp: 1234567890,
850+
prev_randao: Default::default(),
851+
extra_data: Default::default(),
852+
base_fee_per_gas: U256::from(1000),
853+
};
854+
855+
let tx1 = Bytes::from_str("0x02f87483014a3482017e8459682f0084596830a98301f1d094b01866f195533de16eb929b73f87280693ca0cb480844e71d92dc001a0a658c18bdba29dd4022ee6640fdd143691230c12b3c8c86cf5c1a1f1682cc1e2a0248a28763541ebed2b87ecea63a7024b5c2b7de58539fa64c887b08f5faf29c1").unwrap();
856+
857+
let delta1 = ExecutionPayloadFlashblockDeltaV1 {
858+
transactions: vec![tx1.clone()],
859+
withdrawals: vec![],
860+
state_root: Default::default(),
861+
receipts_root: Default::default(),
862+
logs_bloom: Default::default(),
863+
gas_used: 21000,
864+
block_hash: Default::default(),
865+
};
866+
867+
let tx1_hash =
868+
"0x3cbbc9a6811ac5b2a2e5780bdb67baffc04246a59f39e398be048f1b2d05460c".to_string();
869+
870+
let metadata1 = Metadata {
871+
block_number,
872+
receipts: {
873+
let mut receipts = HashMap::default();
874+
receipts.insert(
875+
tx1_hash.clone(),
876+
OpReceipt::Legacy(Receipt {
877+
status: true.into(),
878+
cumulative_gas_used: 21000,
879+
logs: vec![],
880+
}),
881+
);
882+
receipts
883+
},
884+
new_account_balances: HashMap::default(),
885+
};
886+
887+
let payload1 = FlashblocksPayloadV1 {
888+
index: 0,
889+
payload_id: PayloadId::new([0; 8]),
890+
base: Some(base),
891+
diff: delta1,
892+
metadata: serde_json::to_value(metadata1).unwrap(),
893+
};
894+
895+
process_payload(payload1, cache.clone());
896+
897+
let tx_hashes1 = cache
898+
.get::<Vec<String>>(&format!("tx_hashes:{}", block_number))
899+
.unwrap();
900+
assert_eq!(tx_hashes1.len(), 1);
901+
assert_eq!(tx_hashes1[0], tx1_hash);
902+
903+
let tx2 = Bytes::from_str("0xf8cd82016d8316e5708302c01c94f39635f2adf40608255779ff742afe13de31f57780b8646e530e9700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000156ddc81eed2a36d68302948ba0a608703e79b22164f74523d188a11f81c25a65dd59535bab1cd1d8b30d115f3ea07f4cfbbad77a139c9209d3bded89091867ff6b548dd714109c61d1f8e7a84d14").unwrap();
904+
905+
let tx2_hash =
906+
"0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8".to_string();
907+
908+
let delta2 = ExecutionPayloadFlashblockDeltaV1 {
909+
transactions: vec![tx1.clone(), tx2.clone()], // Note tx1 is repeated
910+
withdrawals: vec![],
911+
state_root: B256::repeat_byte(0x1),
912+
receipts_root: B256::repeat_byte(0x2),
913+
logs_bloom: Default::default(),
914+
gas_used: 42000,
915+
block_hash: B256::repeat_byte(0x3),
916+
};
917+
918+
let metadata2 = Metadata {
919+
block_number,
920+
receipts: {
921+
let mut receipts = HashMap::default();
922+
receipts.insert(
923+
tx1_hash.clone(),
924+
OpReceipt::Legacy(Receipt {
925+
status: true.into(),
926+
cumulative_gas_used: 21000,
927+
logs: vec![],
928+
}),
929+
);
930+
receipts.insert(
931+
tx2_hash.clone(),
932+
OpReceipt::Legacy(Receipt {
933+
status: true.into(),
934+
cumulative_gas_used: 42000,
935+
logs: vec![],
936+
}),
937+
);
938+
receipts
939+
},
940+
new_account_balances: HashMap::default(),
941+
};
942+
943+
let payload2 = FlashblocksPayloadV1 {
944+
index: 1,
945+
payload_id: PayloadId::new([0; 8]),
946+
base: None,
947+
diff: delta2,
948+
metadata: serde_json::to_value(metadata2.clone()).unwrap(),
949+
};
950+
951+
process_payload(payload2, cache.clone());
952+
953+
let tx_hashes2 = cache
954+
.get::<Vec<String>>(&format!("tx_hashes:{}", block_number))
955+
.unwrap();
956+
assert_eq!(
957+
tx_hashes2.len(),
958+
2,
959+
"Should have 2 unique transaction hashes"
960+
);
961+
assert_eq!(tx_hashes2[0], tx1_hash, "First hash should be tx1");
962+
assert_eq!(tx_hashes2[1], tx2_hash, "Second hash should be tx2");
963+
964+
let delta3 = ExecutionPayloadFlashblockDeltaV1 {
965+
transactions: vec![tx2.clone(), tx1.clone()], // Different order
966+
withdrawals: vec![],
967+
state_root: B256::repeat_byte(0x1),
968+
receipts_root: B256::repeat_byte(0x2),
969+
logs_bloom: Default::default(),
970+
gas_used: 42000,
971+
block_hash: B256::repeat_byte(0x3),
972+
};
973+
974+
let payload3 = FlashblocksPayloadV1 {
975+
index: 2,
976+
payload_id: PayloadId::new([0; 8]),
977+
base: None,
978+
diff: delta3,
979+
metadata: serde_json::to_value(metadata2).unwrap(), // Same metadata
980+
};
981+
982+
process_payload(payload3, cache.clone());
983+
984+
let tx_hashes3 = cache
985+
.get::<Vec<String>>(&format!("tx_hashes:{}", block_number))
986+
.unwrap();
987+
assert_eq!(
988+
tx_hashes3.len(),
989+
2,
990+
"Should still have 2 unique transaction hashes"
991+
);
992+
assert_eq!(tx_hashes3[0], tx1_hash, "First hash should be tx1");
993+
assert_eq!(tx_hashes3[1], tx2_hash, "Second hash should be tx2");
835994
}
836995
}

crates/flashblocks-rpc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod cache;
22
pub mod flashblocks;
33
mod metrics;
44
pub mod rpc;
5+
pub mod verification;
56

67
#[cfg(test)]
78
mod integration;

crates/flashblocks-rpc/src/metrics.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,16 @@ pub struct Metrics {
2929

3030
#[metric(describe = "Number of flashblocks in a block")]
3131
pub flashblocks_in_block: Histogram,
32+
33+
#[metric(describe = "Count of successful block verifications")]
34+
pub block_verification_success: Counter,
35+
36+
#[metric(describe = "Count of failed block verifications")]
37+
pub block_verification_failure: Counter,
38+
39+
#[metric(describe = "Count of blocks not found in cache during verification")]
40+
pub block_verification_not_found: Counter,
41+
42+
#[metric(describe = "Count of transaction count mismatches during verification")]
43+
pub block_verification_tx_count_mismatch: Counter,
3244
}

0 commit comments

Comments
 (0)