@@ -10,3 +10,98 @@ pub mod eth_rpc;
1010pub ( crate ) fn tx_hash_try_from_cid ( cid : Cid ) -> anyhow:: Result < TxHash > {
1111 Ok ( TxHash :: from_str ( & hex:: encode ( cid. hash ( ) . digest ( ) ) ) ?)
1212}
13+
14+ #[ cfg( test) ]
15+ mod tests {
16+ use super :: * ;
17+ use multihash_codetable:: { Code , MultihashDigest } ;
18+
19+ /// Ethereum transaction codec for IPLD (from multicodec table)
20+ /// This matches the constant in anchor-evm/src/proof_builder.rs
21+ const ETH_TX_CODEC : u64 = 0x93 ;
22+
23+ /// Simulate what ProofBuilder::tx_hash_to_cid does
24+ fn simulate_proof_builder_tx_hash_to_cid ( tx_hash : & str ) -> Cid {
25+ let hex_str = tx_hash. strip_prefix ( "0x" ) . unwrap_or ( tx_hash) ;
26+ let tx_bytes = hex:: decode ( hex_str) . unwrap ( ) ;
27+ let multihash = Code :: Keccak256 . wrap ( & tx_bytes) . unwrap ( ) ;
28+ Cid :: new_v1 ( ETH_TX_CODEC , multihash)
29+ }
30+
31+ #[ test]
32+ fn test_tx_hash_roundtrip_self_anchor_to_lookup ( ) {
33+ // critical invariant: what self-anchoring stores must match what lookup queries
34+ let original_tx_hash = "0xa1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" ;
35+
36+ // Step 1: ProofBuilder creates a CID from the tx_hash (simulated here)
37+ let tx_cid = simulate_proof_builder_tx_hash_to_cid ( original_tx_hash) ;
38+
39+ // Step 2: Validation converts CID back to tx_hash for lookup
40+ let lookup_tx_hash = tx_hash_try_from_cid ( tx_cid) . unwrap ( ) . to_string ( ) ;
41+
42+ // Step 3: Verify they match (both should be 0x-prefixed lowercase hex)
43+ assert_eq ! (
44+ original_tx_hash. to_lowercase( ) ,
45+ lookup_tx_hash. to_lowercase( ) ,
46+ "Self-anchored tx_hash must match lookup tx_hash for chain proof discovery to work"
47+ ) ;
48+ }
49+
50+ #[ test]
51+ fn test_tx_hash_roundtrip_without_0x_prefix ( ) {
52+ // Test that round-trip works even without 0x prefix in original
53+ let original_tx_hash = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" ;
54+
55+ let tx_cid = simulate_proof_builder_tx_hash_to_cid ( original_tx_hash) ;
56+ let lookup_tx_hash = tx_hash_try_from_cid ( tx_cid) . unwrap ( ) . to_string ( ) ;
57+
58+ // TxHash::to_string() always adds 0x prefix
59+ assert_eq ! (
60+ format!( "0x{}" , original_tx_hash. to_lowercase( ) ) ,
61+ lookup_tx_hash. to_lowercase( )
62+ ) ;
63+ }
64+
65+ #[ test]
66+ fn test_tx_hash_format_matches_alloy_txhash ( ) {
67+ // Verify that our round-trip produces the same format as alloy's TxHash::to_string()
68+ let tx_hash_hex = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" ;
69+ let tx_cid = simulate_proof_builder_tx_hash_to_cid ( tx_hash_hex) ;
70+
71+ let recovered = tx_hash_try_from_cid ( tx_cid) . unwrap ( ) ;
72+
73+ // Create TxHash directly from the same hex
74+ let direct = TxHash :: from_str ( tx_hash_hex) . unwrap ( ) ;
75+
76+ assert_eq ! ( recovered, direct) ;
77+ assert_eq ! ( recovered. to_string( ) , direct. to_string( ) ) ;
78+ }
79+
80+ #[ test]
81+ fn test_tx_hash_roundtrip_mixed_case ( ) {
82+ // Test that mixed-case hex input produces correct round-trip.
83+ // Ethereum tx hashes are case-insensitive, but our storage uses lowercase.
84+ // This verifies the normalization works correctly.
85+ let mixed_case_tx_hash =
86+ "0xAbCdEf1234567890AbCdEf1234567890AbCdEf1234567890AbCdEf1234567890" ;
87+
88+ // Step 1: ProofBuilder creates a CID (hex decoding is case-insensitive)
89+ let tx_cid = simulate_proof_builder_tx_hash_to_cid ( mixed_case_tx_hash) ;
90+
91+ // Step 2: Validation converts CID back to tx_hash for lookup
92+ let lookup_tx_hash = tx_hash_try_from_cid ( tx_cid) . unwrap ( ) . to_string ( ) ;
93+
94+ // Step 3: The lookup should produce lowercase (TxHash::to_string() uses lowercase)
95+ assert_eq ! (
96+ lookup_tx_hash, "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" ,
97+ "Mixed-case input should normalize to lowercase for lookup"
98+ ) ;
99+
100+ // Step 4: Verify case-insensitive equality with original
101+ assert_eq ! (
102+ mixed_case_tx_hash. to_lowercase( ) ,
103+ lookup_tx_hash,
104+ "Normalized tx_hash must match original (case-insensitive)"
105+ ) ;
106+ }
107+ }
0 commit comments