Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions native/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub trait ClientProvider: Send + Sync {
hash: H256,
key: &StorageKey,
) -> polkadot_sdk::sp_blockchain::Result<Option<StorageData>>;
/// Given a block's number, return the hash of that block.
fn hash(&self, number: u32) -> polkadot_sdk::sp_blockchain::Result<Option<H256>>;
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -54,6 +56,15 @@ pub trait Client {
.map_err(|error| error.to_string())
})
}

/// Given a block's number, return the hash of that block.
///
/// Wraps [`ClientProvider::hash`], which is modeled off of `FullClient::hash`.
/// Returns `None` if the [`ClientExt`] extension is not registered. Otherwise, returns `Some(FullClient::hash(number))`.
fn hash(&mut self, number: u32) -> Option<Result<Option<H256>, String>> {
ExternalitiesExt::extension(self)
.map(|ClientExt(provider)| provider.hash(number).map_err(|error| error.to_string()))
}
}

#[cfg(all(test, feature = "std"))]
Expand Down Expand Up @@ -83,6 +94,10 @@ mod tests {
.clone()
.map_err(polkadot_sdk::sp_blockchain::Error::UnknownBlock)
}

fn hash(&self, number: u32) -> polkadot_sdk::sp_blockchain::Result<Option<H256>> {
Ok(Some(H256::repeat_byte(number as u8)))
}
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions node/src/client_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ impl ClientProvider for FullClientHandle {
fn storage(&self, hash: H256, key: &StorageKey) -> sp_blockchain::Result<Option<StorageData>> {
self.0.storage(hash, key)
}

fn hash(&self, number: u32) -> polkadot_sdk::sp_blockchain::Result<Option<H256>> {
self.0.hash(number)
}
}
5 changes: 4 additions & 1 deletion pallets/prover_db_indexer/src/db_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ mod tests {
ext.register_extension(MockClientProvider::client_ext(
None,
[Err("test error".to_string())],
[],
));
ext.execute_with(|| {
let err = db_events_at::<Test>(H256::zero()).err().unwrap();
Expand All @@ -150,7 +151,7 @@ mod tests {
#[test]
fn empty_when_no_value_at_key() {
let mut ext = new_test_ext();
ext.register_extension(MockClientProvider::client_ext(None, [Ok(None)]));
ext.register_extension(MockClientProvider::client_ext(None, [Ok(None)], []));
ext.execute_with(|| {
assert!(matches!(
db_events_at::<Test>(H256::zero()),
Expand All @@ -165,6 +166,7 @@ mod tests {
ext.register_extension(MockClientProvider::client_ext(
None,
[Ok(Some(StorageData(alloc::vec![0xFF, 0xFF, 0xFF])))],
[],
));
ext.execute_with(|| {
assert!(matches!(
Expand Down Expand Up @@ -214,6 +216,7 @@ mod tests {
ext.register_extension(MockClientProvider::client_ext(
None,
[Ok(Some(StorageData(codec::Encode::encode(&records))))],
[],
));
ext.execute_with(|| {
let events: Vec<_> = db_events_at::<Test>(H256::zero()).unwrap().collect();
Expand Down
31 changes: 28 additions & 3 deletions pallets/prover_db_indexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ pub enum ConsumerError {
/// Underlying error from [`crate::db_events::db_events_at`].
source: crate::db_events::DBEventError,
},
/// `HeaderBackend::hash` succeeded but returned `None`, meaning the
/// block number's header is not in the node's chain.
#[snafu(display("block number's header is not in the node's chain"))]
HeaderNotInChain,
/// The node's client failed to look up a block's hash by number.
#[snafu(display("client failed to look up block hash: {source}"))]
ClientHash {
/// Error from the underlying `HeaderBackend::hash` call.
#[snafu(source(false))]
source: alloc::string::String,
},
}

#[polkadot_sdk::frame_support::pallet]
Expand Down Expand Up @@ -197,9 +208,7 @@ pub mod pallet {
block_num: u64,
config: &ProverDbConsumerConfig,
) -> Result<(), ConsumerError> {
let bn: BlockNumberFor<T> =
block_num.checked_into().context(BlockNumberOverflowSnafu)?;
for event in db_events_at::<T>(frame_system::Pallet::<T>::block_hash(bn))? {
for event in db_events_at::<T>(Self::block_hash(block_num)?)? {
match event {
DBEvent::TableDropped(_, _, table, _) => {
if table_matches_filters(&table, &config.include) {
Expand Down Expand Up @@ -237,5 +246,21 @@ pub mod pallet {

Ok(())
}

/// Looks up a block number's hash, falling back to the node's client if `BlockHash` doesn't have it.
pub(crate) fn block_hash(block_num: u64) -> Result<H256, ConsumerError> {
let bn: BlockNumberFor<T> =
block_num.checked_into().context(BlockNumberOverflowSnafu)?;
let hash = match frame_system::pallet::BlockHash::<T>::try_get(bn) {
Ok(hash) => hash,
Err(()) => native::client::client::hash(
block_num.checked_into().context(BlockNumberOverflowSnafu)?,
)
.context(NoRegisteredClientSnafu)?
.map_err(|source| ClientHashSnafu { source }.build())?
.context(HeaderNotInChainSnafu)?,
};
Ok(hash)
}
}
}
12 changes: 12 additions & 0 deletions pallets/prover_db_indexer/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub struct MockClientProvider {
storage_responses: std::sync::Mutex<
std::collections::VecDeque<Result<Option<sp_core::storage::StorageData>, String>>,
>,
hash_responses: std::sync::Mutex<std::collections::VecDeque<Result<Option<H256>, String>>>,
}

#[cfg(all(test, feature = "std"))]
Expand All @@ -230,10 +231,12 @@ impl MockClientProvider {
storage_responses: impl IntoIterator<
Item = Result<Option<sp_core::storage::StorageData>, String>,
>,
hash_responses: impl IntoIterator<Item = Result<Option<H256>, String>>,
) -> native::client::ClientExt {
native::client::ClientExt(std::sync::Arc::new(Self {
finalized_state,
storage_responses: std::sync::Mutex::new(storage_responses.into_iter().collect()),
hash_responses: std::sync::Mutex::new(hash_responses.into_iter().collect()),
}))
}
}
Expand All @@ -256,6 +259,15 @@ impl native::client::ClientProvider for MockClientProvider {
.unwrap_or(Ok(None))
.map_err(polkadot_sdk::sp_blockchain::Error::UnknownBlock)
}

fn hash(&self, _number: u32) -> polkadot_sdk::sp_blockchain::Result<Option<H256>> {
self.hash_responses
.lock()
.unwrap()
.pop_front()
.unwrap_or(Ok(None))
.map_err(polkadot_sdk::sp_blockchain::Error::UnknownBlock)
Comment thread
JayWhite2357 marked this conversation as resolved.
}
}

/// Builds an `EventRecord` the way `frame_system` would have deposited it,
Expand Down
94 changes: 93 additions & 1 deletion pallets/prover_db_indexer/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use native_api::Api;
use pallet_tables::{CommitmentCreationCmd, UpdateTable};
use polkadot_sdk::frame_support::traits::Hooks;
use polkadot_sdk::frame_support::BoundedVec;
use polkadot_sdk::frame_system::pallet::BlockHash;
use polkadot_sdk::frame_system::EventRecord;
use polkadot_sdk::sp_core::offchain::testing::{PendingRequest, TestOffchainExt};
use polkadot_sdk::sp_core::offchain::{OffchainDbExt, OffchainWorkerExt};
Expand Down Expand Up @@ -62,11 +63,20 @@ fn setup_with_config(
ext.register_extension(OffchainWorkerExt::new(offchain.clone()));
ext.register_extension(OffchainDbExt::new(offchain));
ext.register_extension(MockClientProvider::client_ext(
Some((H256::zero(), finalized_block_num)),
Some((
H256::repeat_byte(finalized_block_num as u8),
finalized_block_num,
)),
events
.into_iter()
.map(|e| Ok(Some(StorageData(e.encode())))),
[],
));
ext.execute_with(|| {
for bn in 1..=finalized_block_num {
BlockHash::<Test>::insert(bn as u64, H256::repeat_byte(bn as u8));
}
});
let mut config_store = std::collections::HashMap::new();
config_store.insert(PROVER_DB_CONFIG_URL_KEY.to_string(), MOCK_URL.to_string());
config_store.insert(
Expand Down Expand Up @@ -500,3 +510,85 @@ fn ocw_with_empty_include_set_forwards_nothing() {
ProverDbIndexer::offchain_worker(1);
});
}

fn advance_block(parent_hash: &mut H256, i: u64) -> Option<H256> {
System::initialize(&(i + 1), parent_hash, &Default::default());
*parent_hash = System::finalize().hash();
Some(*parent_hash)
}

#[test]
fn block_hash_falls_back_to_client_once_frame_system_prunes_the_entry() {
let mut ext = new_test_ext();
let fallback_hash = H256::repeat_byte(0xAB);
ext.register_extension(MockClientProvider::client_ext(
None,
[],
std::iter::repeat_n(Ok(Some(fallback_hash)), 6),
));

ext.execute_with(|| {
let mut h: Vec<_> = (0..6).scan(H256::zero(), advance_block).collect();
for i in 0..5 {
assert_eq!(ProverDbIndexer::block_hash(i + 1).unwrap(), h[i as usize]);
}
h.extend((6..17).scan(h[5], advance_block));
for i in 0..6 {
assert_eq!(ProverDbIndexer::block_hash(i + 1).unwrap(), fallback_hash);
}
for i in 6..16 {
assert_eq!(ProverDbIndexer::block_hash(i + 1).unwrap(), h[i as usize]);
}
});
}

#[test]
fn block_hash_returns_stored_hash_when_present() {
let mut ext = new_test_ext();
ext.execute_with(|| {
let hash = H256::repeat_byte(7);
BlockHash::<Test>::insert(5u64, hash);

assert_eq!(ProverDbIndexer::block_hash(5).unwrap(), hash);
});
}

#[test]
fn block_hash_errors_when_client_not_registered() {
let mut ext = new_test_ext();
ext.execute_with(|| {
assert!(matches!(
ProverDbIndexer::block_hash(5),
Err(crate::ConsumerError::NoRegisteredClient)
));
});
}

#[test]
fn block_hash_errors_when_client_hash_lookup_fails() {
let mut ext = new_test_ext();
ext.register_extension(MockClientProvider::client_ext(
None,
[],
[Err("simulated client hash lookup failure".to_string())],
));
ext.execute_with(|| {
assert!(matches!(
ProverDbIndexer::block_hash(5),
Err(crate::ConsumerError::ClientHash { source })
if source == "UnknownBlock: simulated client hash lookup failure"
));
});
}

#[test]
fn block_hash_errors_when_header_not_in_chain() {
let mut ext = new_test_ext();
ext.register_extension(MockClientProvider::client_ext(None, [], [Ok(None)]));
ext.execute_with(|| {
assert!(matches!(
ProverDbIndexer::block_hash(5),
Err(crate::ConsumerError::HeaderNotInChain)
));
});
}
Loading