Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added wrapper for Mmr #668

Merged
merged 4 commits into from
Feb 18, 2025

Conversation

varun-doshi
Copy link
Contributor

Ref #663

Ideally should be merged after #667 is merged so that we can directly send in the Iterator instead of collecting into a Vec

Copy link

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. I left some comments inline.

@@ -96,17 +96,114 @@ pub struct TransactionInputs {
pub found_unauthenticated_notes: BTreeSet<NoteId>,
}

#[derive(Debug)]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also derive Clone.

Comment on lines 102 to 105
impl Blockchain {
pub fn get(&self) -> &Mmr {
&self.0
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All public methods should be documented.

Comment on lines 123 to 127
pub async fn get_chain_mmr(
&self,
blocks: &mut BTreeSet<BlockNumber>,
db: &Arc<Db>,
) -> Result<(ChainMmr, Vec<miden_objects::block::BlockHeader>, BlockNumber), GetBatchInputsError>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass in the block immutably, e.g. &BTreeSet<...>.
We also shouldn't pass in the database. This function should only deal with selectively copying part of the Mmr to the ChainMmr it creates.
The logic for selecting the block headers from the database should be done in get_batch_inputs by adding the returned batch reference block number to the headers we want to select.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct me if im wrong but this line requires a mutable ref no?

blocks.remove(&latest_block_num);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does if we want to do the logic as it is now, but I think we can compute the highest_block_num in get_batch_inputs, remove it from blocks and then pass the blocks and the highest block num, i.e.: get_chain_mmr(blocks: &BTreeSet<BlockNumber>, highest_block_num: BlockNumber). Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood
Will get it done

Comment on lines 180 to 185
// TODO: Unnecessary conversion. We should change the select_block_headers function to take
// an impl Iterator instead to avoid this allocation.
let mut blocks: Vec<_> = blocks.clone().into_iter().collect();
// Fetch the reference block of the batch as part of this query, so we can avoid looking it
// up in a separate DB access.
blocks.push(batch_reference_block);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you merge in next and update this function with the changes from next?

Comment on lines 191 to 192
let chain_mmr =
ChainMmr::new(partial_mmr, headers.clone()).expect("Unable to build ChainMmr");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you move this back to get_batch_inputs, please also re-add the SAFETY comment and revert to the previous expect message.

Copy link

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! I left some comments.

pub struct Blockchain(Mmr);

impl Blockchain {
/// Returns the latest block number

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can you add . at the end of all doc comments?

Comment on lines 132 to 135
/// Adds a new element to the MMR.
pub fn add(&mut self, el: RpoDigest) {
self.0.add(el);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Adds a new element to the MMR.
pub fn add(&mut self, el: RpoDigest) {
self.0.add(el);
}
/// Adds a block commitment to the MMR. The caller must ensure that this commitent is the one for the next block in the chain.
pub fn add(&mut self, block_commitment: RpoDigest) {
self.0.add(block_commitment);
}

account_tree: SimpleSmt<ACCOUNT_TREE_DEPTH>,
}

impl InnerState {
/// Returns the latest block number.
fn latest_block_num(&self) -> BlockNumber {
let block_number: u32 = (self.chain_mmr.forest() - 1)
let block_number: u32 = (self.blockchain.0.forest() - 1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of adding methods like Blockchain::chain_tip is that we can call them here, so we can avoid accessing the inner Mmr directly. So all blockchain.0.xyz() methods should be replaced by calling methods directly, e.g. blockchain.xyz() (or with blockchain-specific names, so blockchain.chain_tip() instead of blockchain.0.forest().

Here we should be able to call chain_tip, right?

let inner = RwLock::new(InnerState { nullifier_tree, chain_mmr, account_tree });
let inner = RwLock::new(InnerState {
nullifier_tree,
blockchain: Blockchain(chain_mmr),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a proper constructor for this, so Blockchain::new(chain_mmr)?

Comment on lines 604 to 662
.map_err(GetBatchInputsError::SelectBlockHeaderError)?;
.expect("partial mmr and block headers should be consistent");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still want to error here instead of panic.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant here was to revert this change and use .map_err(GetBatchInputsError::SelectBlockHeaderError)?;

Comment on lines 149 to 153
//
// NOTE: Scoped block to automatically drop the mutex guard asap.
//
// We also avoid accessing the db in the block as this would delay
// dropping the guard.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the comment is no longer relevant, so we can remove it.

}

/// Returns the latest block number and partial mmr
pub fn chain_mmr(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually only constructs a partial MMR now, so we should name it accordingly, e.g. partial_mmr_from_blocks or something.

pub fn chain_mmr(
&self,
blocks: &BTreeSet<BlockNumber>,
highest_block_number: &BlockNumber,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, this should actually be called latest_block_number.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to have been a misunderstanding.

I meant this:

 pub fn partial_mmr_from_blocks(
        &self,
        blocks: &BTreeSet<BlockNumber>,
        latest_block_number: BlockNumber,
    ) -> Result<PartialMmr, GetBatchInputsError> { ... }

Sorry for being ambiguous.

We don't need to return the block number because it's always the same as the latest_block_number we pass in.

We also can take a BlockNumber instead of a &BlockNumber since the type is Copy.

(latest_block_num, partial_mmr)
};
let (batch_reference_block, partial_mmr) =
self.inner.blocking_read().blockchain.chain_mmr(&blocks, &highest_block_num)?;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to pass latest_block_num here instead, and we should probably acquire the RwLock just once and drop it as soon as we can, so everything together:

// Scoped block to automatically drop the read lock guard as soon as we're done.
// We also avoid accessing the db in the block as this would delay dropping the guard.
let (batch_reference_block, partial_mmr) = {
    let inner_state = self.inner.blocking_read();

    let latest_block_num = inner_state.blockchain.chain_tip();

    let highest_block_num =
        *blocks.last().expect("we should have checked for empty block references");
    if highest_block_num > latest_block_num {
        return Err(GetBatchInputsError::TransactionBlockReferenceNewerThanLatestBlock {
            highest_block_num,
            latest_block_num,
        });
    }

    // Remove the latest block from the to-be-tracked blocks as it will be the reference
    // block for the batch itself and thus added to the MMR within the batch kernel, so
    // there is no need to prove its inclusion.
    blocks.remove(&latest_block_num);

    inner_state.blockchain.chain_mmr(&blocks, &latest_block_num)?
};

Copy link

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! I think we're close to finishing this. I left another round of comments.

pub fn chain_mmr(
&self,
blocks: &BTreeSet<BlockNumber>,
highest_block_number: &BlockNumber,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to have been a misunderstanding.

I meant this:

 pub fn partial_mmr_from_blocks(
        &self,
        blocks: &BTreeSet<BlockNumber>,
        latest_block_number: BlockNumber,
    ) -> Result<PartialMmr, GetBatchInputsError> { ... }

Sorry for being ambiguous.

We don't need to return the block number because it's always the same as the latest_block_number we pass in.

We also can take a BlockNumber instead of a &BlockNumber since the type is Copy.

Comment on lines 604 to 662
.map_err(GetBatchInputsError::SelectBlockHeaderError)?;
.expect("partial mmr and block headers should be consistent");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant here was to revert this change and use .map_err(GetBatchInputsError::SelectBlockHeaderError)?;

Copy link

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all the comments. Looks good to me!

@Mirko-von-Leipzig Ready for merging from my side.

Comment on lines 120 to 121
let latest = self.chain_tip();
latest + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe BlockNumber::child() exists.

Suggested change
let latest = self.chain_tip();
latest + 1
self.chain_tip().child()

Though I'm unsure if we should have both chain_tip and chain_length? I guarantee someone's going to confuse the two.


--edit-- in fact further down we have let chain_length = state.blockchain.chain_tip() as usize;

@varun-doshi
Copy link
Contributor Author

@Mirko-von-Leipzig did not notice your commit there; apologies
I assume the only change was removing from the changelog?

@Mirko-von-Leipzig
Copy link
Contributor

@Mirko-von-Leipzig did not notice your commit there; apologies I assume the only change was removing from the changelog?

That's my bad for inserting a commit without warning :) That was the only change, no harm done.

Copy link
Contributor

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for persevering @varun-doshi, I know its been a lengthy back-and-forth. Really appreciate it!

@Mirko-von-Leipzig Mirko-von-Leipzig merged commit d57c8dd into 0xPolygonMiden:next Feb 18, 2025
11 checks passed
Comment on lines +154 to +158
pub fn partial_mmr_from_blocks(
&self,
blocks: &BTreeSet<BlockNumber>,
latest_block_number: BlockNumber,
) -> Result<PartialMmr, GetBatchInputsError> {
Copy link
Contributor

@bobbinth bobbinth Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is already merged, but I would have probably simplified this a bit:

pub fn partial_mmr_from_blocks(
    &self,
    blocks: &BTreeSet<BlockNumber>,
) -> Result<PartialMmr, GetBatchInputsError>

And the the latest_block_number could be just in the blocks (and we could get it via BTreeSet::last()). This would remove a potential inconsistency if latest_block_number would not actually be the highest value for blocks.

Comment on lines 657 to +662
blocks.remove(&latest_block_num);

// Using latest block as the target forest means we take the state of the MMR one before
// the latest block. This is because the latest block will be used as the reference
// block of the batch and will be added to the MMR by the batch kernel.
let target_forest = latest_block_num.as_usize();
let peaks = state
.chain_mmr
.peaks_at(target_forest)
.expect("target_forest should be smaller than forest of the chain mmr");
let mut partial_mmr = PartialMmr::from_peaks(peaks);

for block_num in blocks.iter().map(BlockNumber::as_usize) {
// SAFETY: We have ensured block nums are less than chain length.
let leaf = state
.chain_mmr
.get(block_num)
.expect("block num less than chain length should exist in chain mmr");
let path = state
.chain_mmr
.open_at(block_num, target_forest)
.expect("block num and target forest should be valid for this mmr")
.merkle_path;
// SAFETY: We should be able to fill the partial MMR with data from the chain MMR
// without errors, otherwise it indicates the chain mmr is invalid.
partial_mmr
.track(block_num, leaf, &path)
.expect("filling partial mmr with data from mmr should succeed");
}

(latest_block_num, partial_mmr)
(
latest_block_num,
inner_state.blockchain.partial_mmr_from_blocks(&blocks, latest_block_num)?,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to the above comment, this could be:

let partial_mmr = inner_state.blockchain.partial_mmr_from_blocks(&blocks)?;
blocks.remove(&latest_block_num);
(latest_block_num, partial_mmr)

Copy link
Contributor Author

@varun-doshi varun-doshi Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can open a separate PR to get these changes in, if you'd like

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be awesome! Thank you!

Copy link

@PhilippGackstatter PhilippGackstatter Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did pretty much the simplification change from further above in #709 since I used that function there anyway and noticed we don't even need the Result on it, so no need for the PR I think, @varun-doshi, but thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants