1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
use alloc::{collections::BTreeMap, vec::Vec};
use crate::{
crypto::merkle::{InnerNodeInfo, MmrPeaks, PartialMmr},
BlockHeader, ChainMmrError,
};
// CHAIN MMR
// ================================================================================================
/// A struct that represents the chain Merkle Mountain Range (MMR).
///
/// The MMR allows for efficient authentication of input notes during transaction execution.
/// Authentication is achieved by providing inclusion proofs for the notes consumed in the
/// transaction against the chain MMR root associated with the latest block known at the time
/// of transaction execution.
///
/// [ChainMmr] represents a partial view into the actual MMR and contains authentication paths
/// for a limited set of blocks. The intent is to include only the blocks relevant for execution
/// of a specific transaction (i.e., the blocks corresponding to all input notes).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainMmr {
/// Partial view of the Chain MMR with authentication paths for the blocks listed below.
mmr: PartialMmr,
/// A map of block_num |-> block_header for all blocks for which the partial MMR contains
/// authentication paths.
blocks: BTreeMap<u32, BlockHeader>,
}
impl ChainMmr {
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns a new [ChainMmr] instantiated from the provided partial MMR and a list of block
/// headers.
///
/// # Errors
/// Returns an error if:
/// - block_num for any of the blocks is greater than the chain length implied by the provided
/// partial MMR.
/// - The same block appears more than once in the provided list of block headers.
/// - The partial MMR does not track authentication paths for any of the specified blocks.
pub fn new(mmr: PartialMmr, blocks: Vec<BlockHeader>) -> Result<Self, ChainMmrError> {
let chain_length = mmr.forest();
let mut block_map = BTreeMap::new();
for block in blocks.into_iter() {
if block.block_num() as usize >= chain_length {
return Err(ChainMmrError::block_num_too_big(chain_length, block.block_num()));
}
if block_map.insert(block.block_num(), block).is_some() {
return Err(ChainMmrError::duplicate_block(block.block_num()));
}
if !mmr.is_tracked(block.block_num() as usize) {
return Err(ChainMmrError::untracked_block(block.block_num()));
}
}
Ok(Self { mmr, blocks: block_map })
}
// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
/// Returns peaks of this MMR.
pub fn peaks(&self) -> MmrPeaks {
self.mmr.peaks()
}
/// Returns total number of blocks contain in the chain described by this MMR.
pub fn chain_length(&self) -> usize {
self.mmr.forest()
}
/// Returns true if the block is present in this chain MMR.
pub fn contains_block(&self, block_num: u32) -> bool {
self.blocks.contains_key(&block_num)
}
/// Returns the block header for the specified block, or None if the block is not present in
/// this chain MMR.
pub fn get_block(&self, block_num: u32) -> Option<&BlockHeader> {
self.blocks.get(&block_num)
}
// DATA MUTATORS
// --------------------------------------------------------------------------------------------
/// Appends the provided block header to this chain MMR. This method assumes that the provided
/// block header is for the next block in the chain.
///
/// If `track` parameter is set to true, the authentication path for the provided block header
/// will be added to this chain MMR.
///
/// # Panics
/// Panics if the `block_header.block_num` is not equal to the current chain length (i.e., the
/// provided block header is not the next block in the chain).
pub fn add_block(&mut self, block_header: BlockHeader, track: bool) {
assert_eq!(block_header.block_num(), self.chain_length() as u32);
self.mmr.add(block_header.hash(), track);
}
// ITERATORS
// --------------------------------------------------------------------------------------------
/// Returns an iterator over the inner nodes of authentication paths contained in this chain
/// MMR.
pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
self.mmr.inner_nodes(
self.blocks.values().map(|block| (block.block_num() as usize, block.hash())),
)
}
}
// TESTS
// ================================================================================================
#[cfg(test)]
mod tests {
use super::ChainMmr;
use crate::{
alloc::vec::Vec,
crypto::merkle::{Mmr, PartialMmr},
BlockHeader, Digest,
};
#[test]
fn test_chain_mmr_add() {
// create chain MMR with 3 blocks - i.e., 2 peaks
let mut mmr = Mmr::default();
for i in 0..3 {
let block_header = int_to_block_header(i);
mmr.add(block_header.hash());
}
let partial_mmr: PartialMmr = mmr.peaks(mmr.forest()).unwrap().into();
let mut chain_mmr = ChainMmr::new(partial_mmr, Vec::new()).unwrap();
// add a new block to the chain MMR, this reduces the number of peaks to 1
let block_num = 3;
let bock_header = int_to_block_header(block_num);
mmr.add(bock_header.hash());
chain_mmr.add_block(bock_header, true);
assert_eq!(
mmr.open(block_num as usize, mmr.forest()).unwrap(),
chain_mmr.mmr.open(block_num as usize).unwrap().unwrap()
);
// add one more block to the chain MMR, the number of peaks is again 2
let block_num = 4;
let bock_header = int_to_block_header(block_num);
mmr.add(bock_header.hash());
chain_mmr.add_block(bock_header, true);
assert_eq!(
mmr.open(block_num as usize, mmr.forest()).unwrap(),
chain_mmr.mmr.open(block_num as usize).unwrap().unwrap()
);
// add one more block to the chain MMR, the number of peaks is still 2
let block_num = 5;
let bock_header = int_to_block_header(block_num);
mmr.add(bock_header.hash());
chain_mmr.add_block(bock_header, true);
assert_eq!(
mmr.open(block_num as usize, mmr.forest()).unwrap(),
chain_mmr.mmr.open(block_num as usize).unwrap().unwrap()
);
}
fn int_to_block_header(block_num: u32) -> BlockHeader {
BlockHeader::new(
0,
Digest::default(),
block_num,
Digest::default(),
Digest::default(),
Digest::default(),
Digest::default(),
Digest::default(),
Digest::default(),
0,
)
}
}