@@ -339,12 +339,26 @@ fn get_and_set_txs_and_receipts(
339
339
metadata : Metadata ,
340
340
) -> Result < Vec < OpReceipt > , Box < dyn std:: error:: Error > > {
341
341
let mut diff_receipts: Vec < OpReceipt > = vec ! [ ] ;
342
+ let mut tx_hashes: Vec < String > = vec ! [ ] ;
343
+
344
+ // Get existing tx_hashes list if any
345
+ if let Some ( existing_hashes) = cache. get :: < Vec < String > > ( & format ! ( "tx_hashes:{}" , block_number) ) {
346
+ tx_hashes = existing_hashes;
347
+ }
348
+
342
349
// Store tx transaction signed
343
350
for ( idx, transaction) in block. body . transactions . iter ( ) . enumerate ( ) {
351
+ let tx_hash = transaction. tx_hash ( ) . to_string ( ) ;
352
+
353
+ // Add transaction hash to the ordered list if not already present
354
+ if !tx_hashes. contains ( & tx_hash) {
355
+ tx_hashes. push ( tx_hash. clone ( ) ) ;
356
+ }
357
+
344
358
// check if exists, if not update
345
- let existing_tx = cache. get :: < OpTransactionSigned > ( & transaction . tx_hash ( ) . to_string ( ) ) ;
359
+ let existing_tx = cache. get :: < OpTransactionSigned > ( & tx_hash) ;
346
360
if existing_tx. is_none ( ) {
347
- if let Err ( e) = cache. set ( & transaction . tx_hash ( ) . to_string ( ) , & transaction, Some ( 10 ) ) {
361
+ if let Err ( e) = cache. set ( & tx_hash, & transaction, Some ( 10 ) ) {
348
362
error ! ( "Failed to set transaction in cache: {}" , e) ;
349
363
continue ;
350
364
}
@@ -393,15 +407,15 @@ fn get_and_set_txs_and_receipts(
393
407
// TODO: move this into the transaction check
394
408
if metadata
395
409
. receipts
396
- . contains_key ( & transaction . tx_hash ( ) . to_string ( ) )
410
+ . contains_key ( & tx_hash)
397
411
{
398
412
// find receipt in metadata and set it in cache
399
413
let receipt = metadata
400
414
. receipts
401
- . get ( & transaction . tx_hash ( ) . to_string ( ) )
415
+ . get ( & tx_hash)
402
416
. unwrap ( ) ;
403
417
if let Err ( e) = cache. set (
404
- & format ! ( "receipt:{:?}" , transaction . tx_hash( ) . to_string ( ) ) ,
418
+ & format ! ( "receipt:{:?}" , tx_hash) ,
405
419
receipt,
406
420
Some ( 10 ) ,
407
421
) {
@@ -410,7 +424,7 @@ fn get_and_set_txs_and_receipts(
410
424
}
411
425
// map receipt's block number as well
412
426
if let Err ( e) = cache. set (
413
- & format ! ( "receipt_block:{:?}" , transaction . tx_hash( ) . to_string ( ) ) ,
427
+ & format ! ( "receipt_block:{:?}" , tx_hash) ,
414
428
& block_number,
415
429
Some ( 10 ) ,
416
430
) {
@@ -421,6 +435,11 @@ fn get_and_set_txs_and_receipts(
421
435
diff_receipts. push ( receipt. clone ( ) ) ;
422
436
}
423
437
}
438
+
439
+ // Update the ordered list of transaction hashes in the cache
440
+ if let Err ( e) = cache. set ( & format ! ( "tx_hashes:{}" , block_number) , & tx_hashes, Some ( 10 ) ) {
441
+ error ! ( "Failed to update transaction hashes list in cache: {}" , e) ;
442
+ }
424
443
425
444
Ok ( diff_receipts)
426
445
}
@@ -562,7 +581,7 @@ mod tests {
562
581
payload_id : PayloadId :: new ( [ 0 ; 8 ] ) ,
563
582
base : None ,
564
583
diff : delta2,
565
- metadata : serde_json:: to_value ( metadata2) . unwrap ( ) ,
584
+ metadata : serde_json:: to_value ( metadata2. clone ( ) ) . unwrap ( ) ,
566
585
}
567
586
}
568
587
@@ -688,4 +707,156 @@ mod tests {
688
707
// Verify no block was stored, since it skips the first payload
689
708
assert ! ( cache. get:: <OpBlock >( "pending" ) . is_none( ) ) ;
690
709
}
710
+
711
+ #[ test]
712
+ fn test_tx_hash_list_storage_and_deduplication ( ) {
713
+ let cache = Arc :: new ( Cache :: default ( ) ) ;
714
+ let block_number = 1 ;
715
+
716
+ // Create first payload with one transaction
717
+ let base = ExecutionPayloadBaseV1 {
718
+ parent_hash : Default :: default ( ) ,
719
+ parent_beacon_block_root : Default :: default ( ) ,
720
+ fee_recipient : Address :: from_str ( "0x1234567890123456789012345678901234567890" ) . unwrap ( ) ,
721
+ block_number,
722
+ gas_limit : 1000000 ,
723
+ timestamp : 1234567890 ,
724
+ prev_randao : Default :: default ( ) ,
725
+ extra_data : Default :: default ( ) ,
726
+ base_fee_per_gas : U256 :: from ( 1000 ) ,
727
+ } ;
728
+
729
+ // First transaction with hash 0x3cbbc9a6811ac5b2a2e5780bdb67baffc04246a59f39e398be048f1b2d05460c
730
+ let tx1 = Bytes :: from_str ( "0x02f87483014a3482017e8459682f0084596830a98301f1d094b01866f195533de16eb929b73f87280693ca0cb480844e71d92dc001a0a658c18bdba29dd4022ee6640fdd143691230c12b3c8c86cf5c1a1f1682cc1e2a0248a28763541ebed2b87ecea63a7024b5c2b7de58539fa64c887b08f5faf29c1" ) . unwrap ( ) ;
731
+
732
+ let delta1 = ExecutionPayloadFlashblockDeltaV1 {
733
+ transactions : vec ! [ tx1. clone( ) ] ,
734
+ withdrawals : vec ! [ ] ,
735
+ state_root : Default :: default ( ) ,
736
+ receipts_root : Default :: default ( ) ,
737
+ logs_bloom : Default :: default ( ) ,
738
+ gas_used : 21000 ,
739
+ block_hash : Default :: default ( ) ,
740
+ } ;
741
+
742
+ let tx1_hash = "0x3cbbc9a6811ac5b2a2e5780bdb67baffc04246a59f39e398be048f1b2d05460c" . to_string ( ) ;
743
+
744
+ let metadata1 = Metadata {
745
+ block_number,
746
+ receipts : {
747
+ let mut receipts = HashMap :: default ( ) ;
748
+ receipts. insert (
749
+ tx1_hash. clone ( ) ,
750
+ OpReceipt :: Legacy ( Receipt {
751
+ status : true . into ( ) ,
752
+ cumulative_gas_used : 21000 ,
753
+ logs : vec ! [ ] ,
754
+ } ) ,
755
+ ) ;
756
+ receipts
757
+ } ,
758
+ new_account_balances : HashMap :: default ( ) ,
759
+ } ;
760
+
761
+ let payload1 = FlashblocksPayloadV1 {
762
+ index : 0 ,
763
+ payload_id : PayloadId :: new ( [ 0 ; 8 ] ) ,
764
+ base : Some ( base) ,
765
+ diff : delta1,
766
+ metadata : serde_json:: to_value ( metadata1) . unwrap ( ) ,
767
+ } ;
768
+
769
+ // Process first payload
770
+ process_payload ( payload1, cache. clone ( ) ) ;
771
+
772
+ // Verify transaction hash is stored in the cache
773
+ let tx_hashes1 = cache. get :: < Vec < String > > ( & format ! ( "tx_hashes:{}" , block_number) ) . unwrap ( ) ;
774
+ assert_eq ! ( tx_hashes1. len( ) , 1 ) ;
775
+ assert_eq ! ( tx_hashes1[ 0 ] , tx1_hash) ;
776
+
777
+ // Create second payload with two transactions (one repeated, one new)
778
+ let tx2 = Bytes :: from_str ( "0xf8cd82016d8316e5708302c01c94f39635f2adf40608255779ff742afe13de31f57780b8646e530e9700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000156ddc81eed2a36d68302948ba0a608703e79b22164f74523d188a11f81c25a65dd59535bab1cd1d8b30d115f3ea07f4cfbbad77a139c9209d3bded89091867ff6b548dd714109c61d1f8e7a84d14" ) . unwrap ( ) ;
779
+
780
+ let tx2_hash = "0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8" . to_string ( ) ;
781
+
782
+ let delta2 = ExecutionPayloadFlashblockDeltaV1 {
783
+ transactions : vec ! [ tx1. clone( ) , tx2. clone( ) ] , // Note tx1 is repeated
784
+ withdrawals : vec ! [ ] ,
785
+ state_root : B256 :: repeat_byte ( 0x1 ) ,
786
+ receipts_root : B256 :: repeat_byte ( 0x2 ) ,
787
+ logs_bloom : Default :: default ( ) ,
788
+ gas_used : 42000 ,
789
+ block_hash : B256 :: repeat_byte ( 0x3 ) ,
790
+ } ;
791
+
792
+ let metadata2 = Metadata {
793
+ block_number,
794
+ receipts : {
795
+ let mut receipts = HashMap :: default ( ) ;
796
+ receipts. insert (
797
+ tx1_hash. clone ( ) ,
798
+ OpReceipt :: Legacy ( Receipt {
799
+ status : true . into ( ) ,
800
+ cumulative_gas_used : 21000 ,
801
+ logs : vec ! [ ] ,
802
+ } ) ,
803
+ ) ;
804
+ receipts. insert (
805
+ tx2_hash. clone ( ) ,
806
+ OpReceipt :: Legacy ( Receipt {
807
+ status : true . into ( ) ,
808
+ cumulative_gas_used : 42000 ,
809
+ logs : vec ! [ ] ,
810
+ } ) ,
811
+ ) ;
812
+ receipts
813
+ } ,
814
+ new_account_balances : HashMap :: default ( ) ,
815
+ } ;
816
+
817
+ let payload2 = FlashblocksPayloadV1 {
818
+ index : 1 ,
819
+ payload_id : PayloadId :: new ( [ 0 ; 8 ] ) ,
820
+ base : None ,
821
+ diff : delta2,
822
+ metadata : serde_json:: to_value ( metadata2. clone ( ) ) . unwrap ( ) ,
823
+ } ;
824
+
825
+ // Process second payload
826
+ process_payload ( payload2, cache. clone ( ) ) ;
827
+
828
+ // Verify transaction hashes are stored with deduplication
829
+ let tx_hashes2 = cache. get :: < Vec < String > > ( & format ! ( "tx_hashes:{}" , block_number) ) . unwrap ( ) ;
830
+ assert_eq ! ( tx_hashes2. len( ) , 2 , "Should have 2 unique transaction hashes" ) ;
831
+ assert_eq ! ( tx_hashes2[ 0 ] , tx1_hash, "First hash should be tx1" ) ;
832
+ assert_eq ! ( tx_hashes2[ 1 ] , tx2_hash, "Second hash should be tx2" ) ;
833
+
834
+ // Process another payload with same transactions in different order
835
+ let delta3 = ExecutionPayloadFlashblockDeltaV1 {
836
+ transactions : vec ! [ tx2. clone( ) , tx1. clone( ) ] , // Different order
837
+ withdrawals : vec ! [ ] ,
838
+ state_root : B256 :: repeat_byte ( 0x1 ) ,
839
+ receipts_root : B256 :: repeat_byte ( 0x2 ) ,
840
+ logs_bloom : Default :: default ( ) ,
841
+ gas_used : 42000 ,
842
+ block_hash : B256 :: repeat_byte ( 0x3 ) ,
843
+ } ;
844
+
845
+ let payload3 = FlashblocksPayloadV1 {
846
+ index : 2 ,
847
+ payload_id : PayloadId :: new ( [ 0 ; 8 ] ) ,
848
+ base : None ,
849
+ diff : delta3,
850
+ metadata : serde_json:: to_value ( metadata2) . unwrap ( ) , // Same metadata
851
+ } ;
852
+
853
+ // Process third payload
854
+ process_payload ( payload3, cache. clone ( ) ) ;
855
+
856
+ // Verify transaction hashes are still the same (no duplicates added)
857
+ let tx_hashes3 = cache. get :: < Vec < String > > ( & format ! ( "tx_hashes:{}" , block_number) ) . unwrap ( ) ;
858
+ assert_eq ! ( tx_hashes3. len( ) , 2 , "Should still have 2 unique transaction hashes" ) ;
859
+ assert_eq ! ( tx_hashes3[ 0 ] , tx1_hash, "First hash should be tx1" ) ;
860
+ assert_eq ! ( tx_hashes3[ 1 ] , tx2_hash, "Second hash should be tx2" ) ;
861
+ }
691
862
}
0 commit comments