diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8c7f5fd9..8d0e79c4b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ More expansive patch notes and explanations may be found in the specific [pathfi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Pathfinder exits with an error when detecting a one-block reorg if `--storage.state-tries` is set to `0`. + ## [0.13.2] - 2024-06-24 ### Fixed diff --git a/crates/pathfinder/examples/feeder_gateway.rs b/crates/pathfinder/examples/feeder_gateway.rs index 6e4cb1ed3a..0279889a57 100644 --- a/crates/pathfinder/examples/feeder_gateway.rs +++ b/crates/pathfinder/examples/feeder_gateway.rs @@ -294,6 +294,12 @@ async fn serve(cli: Cli) -> anyhow::Result<()> { } }); + let get_public_key = warp::path("get_public_key").then(|| async { + warp::reply::json(&serde_json::json!( + "0x1252b6bce1351844c677869c6327e80eae1535755b611c66b8f46e595b40eea" + )) + }); + #[derive(Debug, Deserialize)] struct ClassHashParam { #[serde(rename = "classHash")] @@ -336,7 +342,8 @@ async fn serve(cli: Cli) -> anyhow::Result<()> { .or(get_state_update) .or(get_contract_addresses) .or(get_class_by_hash) - .or(get_signature), + .or(get_signature) + .or(get_public_key), ) .with(warp::filters::trace::request()); diff --git a/crates/storage/src/connection/trie.rs b/crates/storage/src/connection/trie.rs index 80f36afb8f..7c55c451e9 100644 --- a/crates/storage/src/connection/trie.rs +++ b/crates/storage/src/connection/trie.rs @@ -101,16 +101,17 @@ impl Transaction<'_> { RootIndexUpdate::TrieEmpty => None, }; + self.inner().execute( + "INSERT OR REPLACE INTO class_roots (block_number, root_index) VALUES(?, ?)", + params![&block_number, &new_root_index], + )?; + if let TriePruneMode::Prune { num_blocks_kept } = self.trie_prune_mode { if let Some(block_number) = block_number.checked_sub(num_blocks_kept) { self.delete_class_roots(block_number)?; } } - self.inner().execute( - "INSERT OR REPLACE INTO class_roots (block_number, root_index) VALUES(?, ?)", - params![&block_number, &new_root_index], - )?; Ok(()) } @@ -143,18 +144,18 @@ impl Transaction<'_> { contract: ContractAddress, state_hash: ContractStateHash, ) -> anyhow::Result<()> { - if let TriePruneMode::Prune { num_blocks_kept } = self.trie_prune_mode { - if let Some(block_number) = block_number.checked_sub(num_blocks_kept) { - self.delete_contract_state_hashes(contract, block_number)?; - } - } - self.inner().execute( "INSERT OR REPLACE INTO contract_state_hashes(block_number, contract_address, \ state_hash) VALUES(?,?,?)", params![&block_number, &contract, &state_hash], )?; + if let TriePruneMode::Prune { num_blocks_kept } = self.trie_prune_mode { + if let Some(block_number) = block_number.checked_sub(num_blocks_kept) { + self.delete_contract_state_hashes(contract, block_number)?; + } + } + Ok(()) } @@ -211,16 +212,16 @@ impl Transaction<'_> { RootIndexUpdate::Updated(idx) => Some(idx), RootIndexUpdate::TrieEmpty => None, }; + self.inner().execute( + "INSERT OR REPLACE INTO storage_roots (block_number, root_index) VALUES(?, ?)", + params![&block_number, &new_root_index], + )?; if let TriePruneMode::Prune { num_blocks_kept } = self.trie_prune_mode { if let Some(block_number) = block_number.checked_sub(num_blocks_kept) { self.delete_storage_roots(block_number)?; } } - self.inner().execute( - "INSERT OR REPLACE INTO storage_roots (block_number, root_index) VALUES(?, ?)", - params![&block_number, &new_root_index], - )?; Ok(()) } @@ -258,6 +259,11 @@ impl Transaction<'_> { RootIndexUpdate::Updated(idx) => Some(idx), RootIndexUpdate::TrieEmpty => None, }; + self.inner().execute( + "INSERT OR REPLACE INTO contract_roots (block_number, contract_address, root_index) \ + VALUES(?, ?, ?)", + params![&block_number, &contract, &new_root_index], + )?; if let TriePruneMode::Prune { num_blocks_kept } = self.trie_prune_mode { if let Some(block_number) = block_number.checked_sub(num_blocks_kept) { @@ -265,11 +271,33 @@ impl Transaction<'_> { } } - self.inner().execute( - "INSERT OR REPLACE INTO contract_roots (block_number, contract_address, root_index) \ - VALUES(?, ?, ?)", - params![&block_number, &contract, &new_root_index], + Ok(()) + } + + fn delete_contract_roots( + &self, + contract: ContractAddress, + before_block: BlockNumber, + ) -> anyhow::Result<()> { + let mut stmt = self.inner().prepare_cached( + "SELECT block_number + FROM contract_roots + WHERE contract_address = ? AND block_number <= ? + ORDER BY block_number DESC + LIMIT 1", )?; + let last_block_with_root_index = stmt + .query_row(params![&contract, &before_block], |row| { + row.get_block_number(0) + }) + .optional()?; + + if let Some(last_block_with_root_index) = last_block_with_root_index { + let mut stmt = self.inner().prepare_cached( + "DELETE FROM contract_roots WHERE contract_address = ? AND block_number < ?", + )?; + stmt.execute(params![&contract, &last_block_with_root_index])?; + } Ok(()) } @@ -344,34 +372,6 @@ impl Transaction<'_> { self.coalesce_removed_trie_nodes(target_block, "trie_class") } - fn delete_contract_roots( - &self, - contract: ContractAddress, - before_block: BlockNumber, - ) -> anyhow::Result<()> { - let mut stmt = self.inner().prepare_cached( - "SELECT block_number - FROM contract_roots - WHERE contract_address = ? AND block_number <= ? - ORDER BY block_number DESC - LIMIT 1", - )?; - let last_block_with_root_index = stmt - .query_row(params![&contract, &before_block], |row| { - row.get_block_number(0) - }) - .optional()?; - - if let Some(last_block_with_root_index) = last_block_with_root_index { - let mut stmt = self.inner().prepare_cached( - "DELETE FROM contract_roots WHERE contract_address = ? AND block_number < ?", - )?; - stmt.execute(params![&contract, &last_block_with_root_index])?; - } - - Ok(()) - } - /// Mark the input nodes as ready for removal. fn remove_trie( &self, @@ -1435,7 +1435,7 @@ mod tests { } #[test] - fn trie_root_updates() { + fn class_trie_root_updates() { let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { num_blocks_kept: 0, }) @@ -1466,4 +1466,248 @@ mod tests { .unwrap(); assert_eq!(root_update, RootIndexUpdate::Updated(1)); } + + #[test] + fn class_root_insert_should_prune_old_roots() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 1, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + tx.insert_class_root(BlockNumber::GENESIS, RootIndexUpdate::Updated(1)) + .unwrap(); + tx.insert_class_root(BlockNumber::new_or_panic(1), RootIndexUpdate::Updated(2)) + .unwrap(); + // no root inserted for block 2 + tx.insert_class_root(BlockNumber::new_or_panic(3), RootIndexUpdate::Updated(3)) + .unwrap(); + + assert!(!tx.class_root_exists(BlockNumber::GENESIS).unwrap()); + // root at block 1 cannot be deleted because it is still required for + // reconstructing state at block 2 + assert!(tx.class_root_exists(BlockNumber::new_or_panic(1)).unwrap()); + assert!(tx.class_root_exists(BlockNumber::new_or_panic(3)).unwrap()); + } + + #[test] + fn class_root_insert_should_prune_old_roots_in_no_history_mode() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 0, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + tx.insert_class_root(BlockNumber::GENESIS, RootIndexUpdate::Updated(1)) + .unwrap(); + tx.insert_class_root(BlockNumber::new_or_panic(1), RootIndexUpdate::Updated(2)) + .unwrap(); + + assert!(!tx.class_root_exists(BlockNumber::GENESIS).unwrap()); + assert!(tx.class_root_exists(BlockNumber::new_or_panic(1)).unwrap()); + } + + #[test] + fn contract_state_hash_insert_should_prune_old_state_hashes() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 1, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + let contract = contract_address!("0xdeadbeef"); + tx.insert_contract_state_hash(BlockNumber::GENESIS, contract, contract_state_hash!("0x01")) + .unwrap(); + tx.insert_contract_state_hash( + BlockNumber::new_or_panic(1), + contract, + contract_state_hash!("0x02"), + ) + .unwrap(); + // no new state hash for block 2 + tx.insert_contract_state_hash( + BlockNumber::new_or_panic(3), + contract, + contract_state_hash!("0x03"), + ) + .unwrap(); + + assert_eq!( + tx.contract_state_hash(BlockNumber::GENESIS, contract) + .unwrap(), + None + ); + assert_eq!( + tx.contract_state_hash(BlockNumber::new_or_panic(2), contract) + .unwrap(), + Some(contract_state_hash!("0x02")) + ); + assert_eq!( + tx.contract_state_hash(BlockNumber::new_or_panic(3), contract) + .unwrap(), + Some(contract_state_hash!("0x03")) + ); + } + + #[test] + fn contract_state_hash_insert_should_prune_all_old_state_in_no_history_mode() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 0, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + let contract = contract_address!("0xdeadbeef"); + tx.insert_contract_state_hash(BlockNumber::GENESIS, contract, contract_state_hash!("0x01")) + .unwrap(); + tx.insert_contract_state_hash( + BlockNumber::new_or_panic(1), + contract, + contract_state_hash!("0x02"), + ) + .unwrap(); + + assert_eq!( + tx.contract_state_hash(BlockNumber::GENESIS, contract) + .unwrap(), + None + ); + assert_eq!( + tx.contract_state_hash(BlockNumber::new_or_panic(1), contract) + .unwrap(), + Some(contract_state_hash!("0x02")) + ); + } + + #[test] + fn storage_root_insert_should_prune_old_roots() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 1, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + tx.insert_storage_root(BlockNumber::GENESIS, RootIndexUpdate::Updated(1)) + .unwrap(); + tx.insert_storage_root(BlockNumber::new_or_panic(1), RootIndexUpdate::Updated(2)) + .unwrap(); + // no new root index for block 2 + tx.insert_storage_root(BlockNumber::new_or_panic(3), RootIndexUpdate::Updated(3)) + .unwrap(); + + assert!(!tx.storage_root_exists(BlockNumber::GENESIS).unwrap()); + assert!(tx + .storage_root_exists(BlockNumber::new_or_panic(1)) + .unwrap()); + assert!(tx + .storage_root_exists(BlockNumber::new_or_panic(3)) + .unwrap()); + } + + #[test] + fn storage_root_insert_should_prune_all_old_roots_in_no_history_mode() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 0, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + tx.insert_storage_root(BlockNumber::GENESIS, RootIndexUpdate::Updated(1)) + .unwrap(); + tx.insert_storage_root(BlockNumber::new_or_panic(1), RootIndexUpdate::Updated(2)) + .unwrap(); + + assert!(!tx.storage_root_exists(BlockNumber::GENESIS).unwrap()); + assert!(tx + .storage_root_exists(BlockNumber::new_or_panic(1)) + .unwrap()); + } + + #[test] + fn contract_root_insert_should_prune_old_state_hashes() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 1, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + let contract = contract_address!("0xdeadbeef"); + tx.insert_contract_root(BlockNumber::GENESIS, contract, RootIndexUpdate::Updated(1)) + .unwrap(); + tx.insert_contract_root( + BlockNumber::new_or_panic(1), + contract, + RootIndexUpdate::Updated(2), + ) + .unwrap(); + // no new root for block 2 + tx.insert_contract_root( + BlockNumber::new_or_panic(3), + contract, + RootIndexUpdate::Updated(3), + ) + .unwrap(); + + assert_eq!( + tx.contract_root_index(BlockNumber::GENESIS, contract) + .unwrap(), + None + ); + assert_eq!( + tx.contract_root_index(BlockNumber::new_or_panic(2), contract) + .unwrap(), + Some(2) + ); + assert_eq!( + tx.contract_root_index(BlockNumber::new_or_panic(3), contract) + .unwrap(), + Some(3) + ); + } + + #[test] + fn contract_root_insert_should_prune_all_old_roots_in_no_history_mode() { + let mut db = crate::StorageBuilder::in_memory_with_trie_pruning(TriePruneMode::Prune { + num_blocks_kept: 0, + }) + .unwrap() + .connection() + .unwrap(); + let tx = db.transaction().unwrap(); + + let contract = contract_address!("0xdeadbeef"); + tx.insert_contract_root(BlockNumber::GENESIS, contract, RootIndexUpdate::Updated(1)) + .unwrap(); + tx.insert_contract_root( + BlockNumber::new_or_panic(1), + contract, + RootIndexUpdate::Updated(2), + ) + .unwrap(); + + assert_eq!( + tx.contract_root_index(BlockNumber::GENESIS, contract) + .unwrap(), + None + ); + assert_eq!( + tx.contract_root_index(BlockNumber::new_or_panic(1), contract) + .unwrap(), + Some(2) + ); + } }