feature: Add non-best chain data to ReadStateService
Motivation
Now that Zaino is serving more than just lightwallets, access to block and chain tip data for "side chains" is required. Currently Block fetch is limited in block ReadStateService to the best chain. While this was adequate for Lightwallet use, newer users of Zaino (Zallet, Crosslink) will require access to all chains.
Specifications
Two indexes are required by zaino to serve this data:
-
Non-best chain block data:
- Currently block are limited to the best chain [https://github.com/ZcashFoundation/zebra/blob/a77de50caef234e13ccf74452a6c010b65f48e82/zebra-state/src/service.rs#L1299].
- Either the current Block request could be updated to include block data from all chains or a separate
AnyChainBlockrequest could be added, wrapping the similar call in the NonFinalizedState [https://github.com/ZcashFoundation/zebra/blob/a77de50caef234e13ccf74452a6c010b65f48e82/zebra-state/src/service/non_finalized_state.rs#L598].
-
ChainTips data:
- Currently only the best chain tip is available through the ReadStateService, Zaino will require the ability to index all chains. This could be done by adding a ChainTips request to the ReadStateService that wraps a new
all_chain_tipsNonFinalizedState method. - Possible implementation:
- Currently only the best chain tip is available through the ReadStateService, Zaino will require the ability to index all chains. This could be done by adding a ChainTips request to the ReadStateService that wraps a new
pub fn all_chain_tips(&self) -> Vec<(Height, block::Hash)> {
self.chain_iter()
.map(|chain| chain.non_finalized_tip())
.collect()
}
Complex Code or Requirements
No response
Testing
No response
Related Work
No response
How urgent is this? I can take a look after we merge #9494.
Possible Design
- ~Add an
indexer-rpcsfeature tozebra-state~ (Update: We're planning to remove that feature)
Either:
- Add a
subscribe_non_finalized_state()method toReadStateServicebehind the new feature - Call the new method in Zebrad's
start()command and pass the non-finalized state receiver to the indexer gRPC server
Or:
- Add a read state request that streams blocks in the non-finalized state to clients when called and whenever there's a change in the non-finalized state until the receiver is dropped
Example code in either case
fn non_finalized_state_change(
&self,
) -> tokio::sync::mpsc::Receiver<(zebra_chain::block::Hash, Arc<zebra_chain::block::Block>)>
{
let network = self.network.clone();
let mut non_finalized_state_receiver = self.non_finalized_state_receiver.clone();
let (response_sender, response_receiver) = tokio::sync::mpsc::channel(100);
tokio::spawn(async move {
// Start with an empty non-finalized state with the expectation that the caller doesn't yet have
// any blocks from the non-finalized state.
let mut prev_non_finalized_state = NonFinalizedState::new(&network);
loop {
let latest_finalized_state = non_finalized_state_receiver.cloned_watch_data();
let new_blocks = latest_finalized_state
.chain_iter()
.flat_map(|chain| chain.blocks.values())
.filter(|cv_block| !prev_non_finalized_state.any_chain_contains(&cv_block.hash))
.map(|cv_block| (cv_block.hash, cv_block.block.clone()));
for new_block_with_hash in new_blocks {
if response_sender.send(new_block_with_hash).await.is_err() {
tracing::debug!("non-finalized state change receiver closed, ending task");
return;
}
}
prev_non_finalized_state = latest_finalized_state;
// Wait for the next update to the non-finalized state
non_finalized_state_receiver
.changed()
.await
.expect("non-finalized state receiver should not close");
}
});
response_receiver
}
Then:
- Add a
non_finalized_state_change()method to the indexer gRPC server streams blocks to clients when called and whenever there's a change in the non-finalized state. - Update
TrustedChainSyncto use the new gRPC method to track the entire non-finalized state in Zebrad (while still using thechain_tip_change()method to track/await finalized/best chain tip changes)
The last step, updating TrustedChainSync, is likely the trickiest.
I think this will soon become a high priority, both for Zallet and Crosslink. We have been adding the functionality to be able to properly handle this data in Zaino and are now getting close to being ready for this update.
I am also happy to take a look at this either next week or the week after, I know everyone is busy at the moment!
I mainly wanted to open this so everyone is aware this functionality will be required.
I think this will soon become a high priority, both for Zallet and Crosslink. We have been adding the functionality to be able to properly handle this data in Zaino and are now getting close to being ready for this update.
I am also happy to take a look at this either next week or the week after, I know everyone is busy at the moment!
No worries, it's scheduled for this sprint but if we don't get to it then we will definitely be able to start it next sprint (starting next Monday 9th June)
@arya2 we should work on this next!