use std::collections::{BTreeMap, BTreeSet};
use miden_protocol::block::BlockNumber;
use miden_protocol::note::{NoteId, NoteInclusionProof};
use miden_protocol::transaction::PartialBlockchain;
use super::StateView;
use crate::errors::{GetBlockInclusionProofsError, GetNoteInclusionProofsError};
impl StateView {
pub async fn get_note_inclusion_proofs(
&self,
reference_block: BlockNumber,
note_ids: BTreeSet<NoteId>,
) -> Result<BTreeMap<NoteId, NoteInclusionProof>, GetNoteInclusionProofsError> {
let latest_block_num = self.tip();
let reference_block = self.scope_block(reference_block).ok_or(
GetNoteInclusionProofsError::ReferenceBlockAfterTip {
reference_block,
latest_block_num: *latest_block_num,
},
)?;
let note_commitments = note_ids.into_iter().map(|note_id| note_id.as_word()).collect();
self.db
.select_note_inclusion_proofs(note_commitments, reference_block)
.await
.map_err(GetNoteInclusionProofsError::SelectNoteInclusionProofError)
}
pub async fn get_block_inclusion_proofs(
&self,
reference_block: BlockNumber,
block_numbers: BTreeSet<BlockNumber>,
) -> Result<PartialBlockchain, GetBlockInclusionProofsError> {
let latest_block_num = self.tip();
let reference_block = self.scope_block(reference_block).ok_or(
GetBlockInclusionProofsError::ReferenceBlockAfterTip {
reference_block,
latest_block_num: *latest_block_num,
},
)?;
if let Some(&block_num) = block_numbers.last()
&& block_num > *reference_block
{
return Err(GetBlockInclusionProofsError::BlockAfterReferenceBlock {
block_num,
reference_block: *reference_block,
});
}
let mut blocks = block_numbers;
blocks.remove(&*reference_block);
let scoped_blocks = blocks
.iter()
.map(|&block| {
self.scope_block(block)
.expect("requested blocks must not exceed the reference block")
})
.collect::<Vec<_>>();
let partial_mmr = self
.blockchain()
.partial_mmr_from_blocks(&blocks, *reference_block)
.expect("all requested blocks must exist before the reference block");
let headers = self
.db
.select_block_headers(scoped_blocks.into_iter())
.await
.map_err(GetBlockInclusionProofsError::SelectBlockHeaderError)?;
Ok(PartialBlockchain::new_unchecked(partial_mmr, headers)
.expect("partial mmr and block headers should be consistent"))
}
}
#[cfg(test)]
mod tests {
use miden_node_utils::fee::{test_fee_params, test_protocol_config};
use miden_protocol::block::ValidatorConfig;
use miden_protocol::testing::random_secret_key::random_secret_key;
use super::*;
use crate::GenesisState;
use crate::state::State;
#[tokio::test]
async fn block_inclusion_proofs_use_the_requested_block() {
let data_directory = tempfile::tempdir().expect("tempdir should be created");
bootstrap_store(data_directory.path());
let (state, _block_writer, _proof_writer) = State::for_tests(data_directory.path()).await;
let partial_blockchain = state
.view()
.get_block_inclusion_proofs(BlockNumber::GENESIS, BTreeSet::new())
.await
.expect("block inclusion proofs should be returned");
assert_eq!(partial_blockchain.chain_length(), BlockNumber::GENESIS);
let error = state
.view()
.get_block_inclusion_proofs(
BlockNumber::GENESIS.child(),
BTreeSet::from([BlockNumber::GENESIS]),
)
.await
.expect_err("a reference block after the tip should fail");
assert!(matches!(
error,
GetBlockInclusionProofsError::ReferenceBlockAfterTip {
reference_block,
latest_block_num,
} if reference_block == BlockNumber::GENESIS.child()
&& latest_block_num == BlockNumber::GENESIS
));
let error = state
.view()
.get_block_inclusion_proofs(
BlockNumber::GENESIS,
BTreeSet::from([BlockNumber::GENESIS.child()]),
)
.await
.expect_err("a requested block after the reference block should fail");
assert!(matches!(
error,
GetBlockInclusionProofsError::BlockAfterReferenceBlock {
block_num,
reference_block,
} if block_num == BlockNumber::GENESIS.child()
&& reference_block == BlockNumber::GENESIS
));
}
#[tokio::test]
async fn note_inclusion_proofs_reject_a_reference_block_after_the_tip() {
let data_directory = tempfile::tempdir().expect("tempdir should be created");
bootstrap_store(data_directory.path());
let (state, _block_writer, _proof_writer) = State::for_tests(data_directory.path()).await;
let error = state
.view()
.get_note_inclusion_proofs(BlockNumber::GENESIS.child(), BTreeSet::new())
.await
.expect_err("a reference block after the tip should fail");
assert!(matches!(
error,
GetNoteInclusionProofsError::ReferenceBlockAfterTip {
reference_block,
latest_block_num,
} if reference_block == BlockNumber::GENESIS.child()
&& latest_block_num == BlockNumber::GENESIS
));
}
fn bootstrap_store(path: &std::path::Path) {
let signer = random_secret_key();
let genesis_state = GenesisState::new(
vec![],
test_fee_params(),
1,
ValidatorConfig::new(vec![signer.public_key()], 1)
.expect("validator config should be valid"),
test_protocol_config(),
);
let genesis_block = genesis_state.into_block().expect("genesis block should be created");
State::bootstrap(genesis_block, path).expect("store should bootstrap");
}
}