Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit d685fe1

Browse files
authored
Introduce Concurrent Merkle Tree Implementation (#3455)
1 parent 5d03efb commit d685fe1

File tree

14 files changed

+1368
-0
lines changed

14 files changed

+1368
-0
lines changed

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ members = [
1818
"governance/tools",
1919
"governance/chat/program",
2020
"libraries/math",
21+
"libraries/concurrent-merkle-tree",
22+
"libraries/merkle-tree-reference",
2123
"memo/program",
2224
"name-service/program",
2325
"record/program",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "spl-concurrent-merkle-tree"
3+
version = "0.1.0"
4+
description = "Solana Program Library Concurrent Merkle Tree"
5+
authors = ["Solana Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2018"
9+
10+
[features]
11+
log = []
12+
sol-log = [ "log" ]
13+
14+
[dependencies]
15+
solana-program = "1.10.33"
16+
bytemuck = "1.8.0"
17+
thiserror = "1.0.30"
18+
19+
[dev-dependencies]
20+
rand_distr = "0.4.3"
21+
rand = "0.7"
22+
spl-merkle-tree-reference = { path = "../merkle-tree-reference" }
23+
tokio = { version = "1.12", features = ["full"] }
24+
25+
[lib]
26+
crate-type = ["cdylib", "lib"]
27+
28+
[package.metadata.docs.rs]
29+
targets = ["x86_64-unknown-linux-gnu"]
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use crate::{
2+
hash::hash_to_parent,
3+
node::{Node, EMPTY},
4+
};
5+
6+
/// Stores the path of nodes changed in a tree by a Merkle tree operation
7+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
8+
#[repr(C)]
9+
pub struct ChangeLog<const MAX_DEPTH: usize> {
10+
/// Historical root value before Path was applied
11+
pub root: Node,
12+
/// Nodes of off-chain merkle tree
13+
pub path: [Node; MAX_DEPTH],
14+
/// Bitmap of node parity (used when hashing)
15+
pub index: u32,
16+
pub _padding: u32,
17+
}
18+
19+
impl<const MAX_DEPTH: usize> ChangeLog<MAX_DEPTH> {
20+
pub fn default() -> Self {
21+
Self {
22+
root: EMPTY,
23+
path: [EMPTY; MAX_DEPTH],
24+
index: 0,
25+
_padding: 0,
26+
}
27+
}
28+
29+
pub fn new(root: Node, path: [Node; MAX_DEPTH], index: u32) -> Self {
30+
Self {
31+
root,
32+
path,
33+
index,
34+
_padding: 0,
35+
}
36+
}
37+
38+
/// Returns the leaf value modified when the change log was recorded
39+
pub fn get_leaf(&self) -> Node {
40+
self.path[0]
41+
}
42+
43+
/// Sets all change log values from a leaf and valid proof
44+
pub fn replace_and_recompute_path(
45+
&mut self,
46+
index: u32,
47+
mut node: Node,
48+
proof: &[Node],
49+
) -> Node {
50+
self.index = index;
51+
for (i, sibling) in proof.iter().enumerate() {
52+
self.path[i] = node;
53+
hash_to_parent(&mut node, sibling, self.index >> i & 1 == 0);
54+
}
55+
self.root = node;
56+
node
57+
}
58+
59+
/// Fast forwards the given proof and corresponding leaf by applying an update from
60+
/// the current change log
61+
pub fn update_proof_or_leaf(
62+
&self,
63+
leaf_index: u32,
64+
proof: &mut [Node; MAX_DEPTH],
65+
leaf: &mut Node,
66+
) {
67+
let padding: usize = 32 - MAX_DEPTH;
68+
if leaf_index != self.index {
69+
// This bit math is used to identify which node in the proof
70+
// we need to swap for a corresponding node in a saved change log
71+
let common_path_len = ((leaf_index ^ self.index) << padding).leading_zeros() as usize;
72+
let critbit_index = (MAX_DEPTH - 1) - common_path_len;
73+
proof[critbit_index] = self.path[critbit_index];
74+
} else {
75+
*leaf = self.get_leaf();
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)