Skip to content

Commit 684c79c

Browse files
fix(revive): handle transaction hash conflicts during re-org (paritytech#10950)
## Summary Fixes a UNIQUE constraint violation when processing blocks after a re-org: ``` UNIQUE constraint failed: transaction_hashes.transaction_hash ``` ## Problem When a blockchain re-org occurs: 1. Block A contains transaction TX1 → stored in `transaction_hashes` 2. Server restarts (clearing the in-memory `block_number_to_hashes` map) 3. Re-org happens, Block B (different hash) now contains the same TX1 4. INSERT fails because TX1 already exists with old block_hash --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent a9c09b0 commit 684c79c

File tree

4 files changed

+76
-13
lines changed

4 files changed

+76
-13
lines changed

prdoc/pr_10950.prdoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
title: 'fix(revive): handle transaction hash conflicts during re-org'
2+
doc:
3+
- audience: Runtime Dev
4+
description: "## Summary\n\nFixes a UNIQUE constraint violation when processing\
5+
\ blocks after a re-org:\n```\nUNIQUE constraint failed: transaction_hashes.transaction_hash\n\
6+
```\n\n## Problem\n\nWhen a blockchain re-org occurs:\n1. Block A contains transaction\
7+
\ TX1 \u2192 stored in `transaction_hashes`\n2. Server restarts (clearing the\
8+
\ in-memory `block_number_to_hashes` map)\n3. Re-org happens, Block B (different\
9+
\ hash) now contains the same TX1\n4. INSERT fails because TX1 already exists\
10+
\ with old block_hash"
11+
crates:
12+
- name: pallet-revive-eth-rpc
13+
bump: patch

substrate/frame/revive/rpc/.sqlx/query-5c0ea8efbd2591e3ede3833acfcadf2d552140a20d84edbf90583da4619bcf2a.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

substrate/frame/revive/rpc/.sqlx/query-b296f5ac320c7537133dca1ede0285ccf101a8e36d68455e05a76df3b366420e.json

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

substrate/frame/revive/rpc/src/receipt_provider.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ impl<B: BlockInfoProvider> ReceiptProvider<B> {
334334

335335
query!(
336336
r#"
337-
INSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)
337+
INSERT OR REPLACE INTO transaction_hashes (transaction_hash, block_hash, transaction_index)
338338
VALUES ($1, $2, $3)
339339
"#,
340340
transaction_hash,
@@ -765,6 +765,56 @@ mod tests {
765765
return Ok(());
766766
}
767767

768+
#[sqlx::test]
769+
async fn test_reorg_same_transaction_hash(pool: SqlitePool) -> anyhow::Result<()> {
770+
let provider = setup_sqlite_provider(pool).await;
771+
772+
// Build two blocks at the same height with the same transaction hash
773+
let tx_hash = H256::from([42u8; 32]);
774+
775+
// Block A at height 1
776+
let block_a = MockBlockInfo { hash: H256::from([1u8; 32]), number: 1 };
777+
let ethereum_hash_a = H256::from([2u8; 32]);
778+
let receipts_a = vec![(
779+
TransactionSigned::default(),
780+
ReceiptInfo {
781+
transaction_hash: tx_hash,
782+
transaction_index: U256::from(0),
783+
..Default::default()
784+
},
785+
)];
786+
787+
provider.insert(&block_a, &receipts_a, &ethereum_hash_a).await?;
788+
789+
// Verify transaction points to block A
790+
let (found_hash, _) = provider.find_transaction(&tx_hash).await.unwrap();
791+
assert_eq!(found_hash, block_a.hash);
792+
793+
// Clear the in-memory map to simulate server restart
794+
provider.block_number_to_hashes.lock().await.clear();
795+
796+
// Block B at same height 1 (re-org) with SAME transaction
797+
let block_b = MockBlockInfo { hash: H256::from([3u8; 32]), number: 1 };
798+
let ethereum_hash_b = H256::from([4u8; 32]);
799+
let receipts_b = vec![(
800+
TransactionSigned::default(),
801+
ReceiptInfo {
802+
transaction_hash: tx_hash, // Same tx hash!
803+
transaction_index: U256::from(0),
804+
..Default::default()
805+
},
806+
)];
807+
808+
// This should NOT fail with UNIQUE constraint violation
809+
provider.insert(&block_b, &receipts_b, &ethereum_hash_b).await?;
810+
811+
// Transaction should now point to block B
812+
let (found_hash, _) = provider.find_transaction(&tx_hash).await.unwrap();
813+
assert_eq!(found_hash, block_b.hash);
814+
815+
Ok(())
816+
}
817+
768818
#[sqlx::test]
769819
async fn test_receipts_count_per_block(pool: SqlitePool) -> anyhow::Result<()> {
770820
let provider = setup_sqlite_provider(pool).await;

0 commit comments

Comments
 (0)