Skip to content

Commit f9aef44

Browse files
committed
add pre-commit merkle tree to use in ABCI::FinalizeBlock
1 parent d18e97e commit f9aef44

File tree

13 files changed

+406
-12
lines changed

13 files changed

+406
-12
lines changed

Cargo.lock

Lines changed: 2 additions & 2 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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ arbitrary = {version = "1.4", features = ["derive"]}
109109
ark-bls12-381 = {version = "0.5"}
110110
ark-serialize = {version = "0.5"}
111111
ark-std = "0.5"
112-
arse-merkle-tree = {package = "nam-sparse-merkle-tree", version = "0.3.3-nam.0", default-features = false, features = ["std", "borsh"]}
112+
# arse-merkle-tree = {package = "nam-sparse-merkle-tree", version = "0.3.3-nam.0", default-features = false, features = ["std", "borsh"]}
113+
arse-merkle-tree = {package = "nam-sparse-merkle-tree", git = "https://github.com/namada-net/sparse-merkle-tree.git", rev = "604ef3b73d3c18c3c8047c6f164649bf94a565dd", default-features = false, features = ["std", "borsh"]}
113114
assert_cmd = "2.0"
114115
assert_matches = "1.5"
115116
async-trait = {version = "0.1"}

crates/merkle_tree/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namada_migrations = { workspace = true, optional = true }
2424

2525
arse-merkle-tree.workspace = true
2626
borsh.workspace = true
27+
derivative.workspace = true
2728
eyre.workspace = true
2829
ics23.workspace = true
2930
linkme = { workspace = true, optional = true }

crates/merkle_tree/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use arse_merkle_tree::error::Error as MtError;
2929
use arse_merkle_tree::{
3030
Hash as SmtHash, Key as TreeKey, SparseMerkleTree as ArseMerkleTree,
3131
};
32+
use derivative::Derivative;
3233
use eth_bridge_pool::{BridgePoolProof, BridgePoolTree};
3334
pub use ics23::CommitmentProof;
3435
use ics23::commitment_proof::Proof as Ics23Proof;
@@ -457,7 +458,8 @@ impl CommitDataRoot {
457458
}
458459

459460
/// Merkle tree storage
460-
#[derive(Default)]
461+
#[derive(Default, Derivative)]
462+
#[derivative(Clone(bound = ""))] // No bound on hasher
461463
pub struct MerkleTree<H: StorageHasher + Default> {
462464
base: Smt<H>,
463465
account: Smt<H>,

crates/node/src/shell/abci.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ fn process_request(
172172
events,
173173
tx_results,
174174
validator_updates,
175+
app_hash,
175176
}| {
176177
Response::FinalizeBlock(response::FinalizeBlock {
177178
events: events
@@ -181,7 +182,7 @@ fn process_request(
181182
tx_results,
182183
validator_updates,
183184
consensus_param_updates: None,
184-
app_hash: Default::default(),
185+
app_hash,
185186
})
186187
},
187188
)

crates/node/src/shell/finalize_block.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ pub struct Response {
6868
/// A list of updates to the validator set.
6969
/// These will reflect the validator set at current height + 2.
7070
pub validator_updates: Vec<validator::Update>,
71+
/// Merkle tree root hash
72+
pub app_hash: AppHash,
7173
}
7274

7375
impl<D, H> Shell<D, H>
@@ -81,6 +83,12 @@ where
8183
///
8284
/// Apply the transactions included in the block.
8385
pub fn finalize_block(&mut self, req: Request) -> ShellResult<Response> {
86+
// Copy merkle tree from canonical tree and pre-commit block to it
87+
{
88+
let block = &mut self.state.in_mem_mut().block;
89+
block.pre_commit_tree = block.tree.clone();
90+
}
91+
8492
let Request {
8593
txs,
8694
decided_last_commit,
@@ -269,10 +277,23 @@ where
269277

270278
debug_assert_eq!(txs.len(), tx_results.len());
271279

280+
self.state.pre_commit_block()?;
281+
282+
if let Some(migration) = &self.scheduled_migration {
283+
let migration = migration
284+
.load_and_validate()
285+
.expect("The scheduled migration is not valid.");
286+
migrations::pre_commit(&mut self.state, migration);
287+
}
288+
289+
let merkle_root = self.state.in_mem().block.pre_commit_tree.root();
290+
let app_hash = AppHash::try_from(merkle_root.0.to_vec())
291+
.expect("expected a valid app hash");
272292
Ok(Response {
273293
events,
274294
tx_results,
275295
validator_updates,
296+
app_hash,
276297
})
277298
}
278299

crates/node/src/shell/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,9 @@ where
830830
.expect("Must update merkle tree after migration");
831831
}
832832

833+
let pre_root = self.state.in_mem().block.pre_commit_tree.root();
833834
let merkle_root = self.state.in_mem().merkle_root();
835+
assert_eq!(pre_root, merkle_root);
834836

835837
tracing::info!(
836838
"Committed block hash: {merkle_root}, height: {height_to_commit}",

crates/sdk/src/migrations.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,153 @@ impl DbUpdateType {
481481
migrator.commit(state.db())?;
482482
Ok(status)
483483
}
484+
485+
/// Validate a change and persist it to pre-commit merkle tree if valid.
486+
pub fn pre_commit_update<D, H>(
487+
&self,
488+
state: &mut FullAccessState<D, H>,
489+
) -> eyre::Result<UpdateStatus>
490+
where
491+
D: 'static + DB + for<'iter> DBIter<'iter>,
492+
H: 'static + StorageHasher,
493+
{
494+
let migrator = D::migrator();
495+
let status = match self {
496+
Self::Add { key, cf, value, .. } => {
497+
let (deserialized, deserializer) = self.validate()?;
498+
if let (Some(prev), Some(des)) =
499+
(migrator.read(state.db(), key, cf), deserializer)
500+
{
501+
des(prev).ok_or_else(|| {
502+
eyre::eyre!(
503+
"The previous value under the key {} did not have \
504+
the same type as that provided: Input was {}",
505+
key,
506+
deserialized
507+
)
508+
})?;
509+
}
510+
let value = value.bytes();
511+
let persist_diffs = (state.diff_key_filter)(key);
512+
if let DbColFam::SUBSPACE = cf {
513+
// Update the merkle tree
514+
let merk_key = if !persist_diffs {
515+
let prefix = storage::Key::from(
516+
NO_DIFF_KEY_PREFIX.to_string().to_db_key(),
517+
);
518+
&prefix.join(key)
519+
} else {
520+
key
521+
};
522+
state
523+
.in_mem_mut()
524+
.block
525+
.pre_commit_tree
526+
.update(merk_key, value)?;
527+
}
528+
Ok(UpdateStatus::Add(vec![(key.to_string(), deserialized)]))
529+
}
530+
Self::Delete(key, cf) => {
531+
let persist_diffs = (state.diff_key_filter)(key);
532+
if let DbColFam::SUBSPACE = cf {
533+
// Update the merkle tree
534+
let merk_key = if !persist_diffs {
535+
let prefix = storage::Key::from(
536+
NO_DIFF_KEY_PREFIX.to_string().to_db_key(),
537+
);
538+
&prefix.join(key)
539+
} else {
540+
key
541+
};
542+
state
543+
.in_mem_mut()
544+
.block
545+
.pre_commit_tree
546+
.delete(merk_key)?;
547+
}
548+
Ok(UpdateStatus::Deleted(vec![key.to_string()]))
549+
}
550+
DbUpdateType::RepeatAdd {
551+
pattern, cf, value, ..
552+
} => {
553+
let pattern = Regex::new(pattern).unwrap();
554+
let mut pairs = vec![];
555+
let (deserialized, deserializer) = self.validate()?;
556+
for (key, prev) in
557+
migrator.get_pattern(state.db(), pattern.clone())
558+
{
559+
if let Some(des) = deserializer {
560+
des(prev).ok_or_else(|| {
561+
eyre::eyre!(
562+
"The previous value under the key {} did not \
563+
have the same type as that provided: Input \
564+
was {}",
565+
key,
566+
deserialized,
567+
)
568+
})?;
569+
pairs.push((key.clone(), deserialized.clone()));
570+
} else {
571+
pairs.push((key.clone(), deserialized.clone()));
572+
}
573+
let key = storage::Key::from_str(&key).unwrap();
574+
let value = value.bytes();
575+
let persist_diffs = (state.diff_key_filter)(&key);
576+
if let DbColFam::SUBSPACE = cf {
577+
// Update the merkle tree
578+
let merk_key = if !persist_diffs {
579+
let prefix = storage::Key::from(
580+
NO_DIFF_KEY_PREFIX.to_string().to_db_key(),
581+
);
582+
&prefix.join(&key)
583+
} else {
584+
&key
585+
};
586+
state
587+
.in_mem_mut()
588+
.block
589+
.pre_commit_tree
590+
.update(merk_key, value)?;
591+
}
592+
}
593+
Ok::<_, eyre::Error>(UpdateStatus::Add(pairs))
594+
}
595+
DbUpdateType::RepeatDelete(pattern, cf) => {
596+
let pattern = Regex::new(pattern).unwrap();
597+
Ok(UpdateStatus::Deleted(
598+
migrator
599+
.get_pattern(state.db(), pattern.clone())
600+
.into_iter()
601+
.map(|(raw_key, _)| {
602+
let key = storage::Key::from_str(&raw_key).unwrap();
603+
let persist_diffs = (state.diff_key_filter)(&key);
604+
if let DbColFam::SUBSPACE = cf {
605+
// Update the merkle tree
606+
let merk_key = if !persist_diffs {
607+
let prefix = storage::Key::from(
608+
NO_DIFF_KEY_PREFIX
609+
.to_string()
610+
.to_db_key(),
611+
);
612+
&prefix.join(&key)
613+
} else {
614+
&key
615+
};
616+
state
617+
.in_mem_mut()
618+
.block
619+
.pre_commit_tree
620+
.delete(merk_key)?;
621+
}
622+
623+
Ok(raw_key)
624+
})
625+
.collect::<eyre::Result<Vec<_>>>()?,
626+
))
627+
}
628+
}?;
629+
Ok(status)
630+
}
484631
}
485632

486633
/// A set of key-value changes to be applied to
@@ -655,6 +802,39 @@ pub fn commit<D, H>(
655802
}
656803
}
657804

805+
/// Check if a scheduled migration should take place at this block height.
806+
/// If so, apply it to the pre-commit merkle tree.
807+
pub fn pre_commit<D, H>(
808+
state: &mut FullAccessState<D, H>,
809+
migration: impl IntoIterator<Item = DbUpdateType>,
810+
) where
811+
D: 'static + DB + for<'iter> DBIter<'iter>,
812+
H: 'static + StorageHasher,
813+
{
814+
tracing::info!("Starting pre-commit migration...");
815+
816+
for change in migration.into_iter() {
817+
match change.pre_commit_update(state) {
818+
Ok(status) => {
819+
tracing::info!("{status}");
820+
}
821+
Err(e) => {
822+
let error = format!(
823+
"Attempt to write to key/pattern <{}> failed:\n{}.",
824+
change.pattern(),
825+
e
826+
);
827+
tracing::error!(error);
828+
panic!(
829+
"Failed to execute migration, no changes persisted. \
830+
Encountered error: {}",
831+
e
832+
);
833+
}
834+
}
835+
}
836+
}
837+
658838
derive_borshdeserializer!(Vec::<u8>);
659839
derive_borshdeserializer!(Vec::<String>);
660840
derive_borshdeserializer!(u64);

crates/state/src/in_memory.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,11 @@ pub enum ProcessProposalCachedResult {
109109
/// The block storage data
110110
#[derive(Debug)]
111111
pub struct BlockStorage<H: StorageHasher> {
112-
/// Merkle tree of all the other data in block storage
112+
/// Canonical merkle tree of all the other data in block storage.
113113
pub tree: MerkleTree<H>,
114+
/// Pre-commit merkle tree copy. This is used to determine merkle tree root
115+
/// hash on `Abci::FinalizeBlock` before commit.
116+
pub pre_commit_tree: MerkleTree<H>,
114117
/// From the start of `FinalizeBlock` until the end of `Commit`, this is
115118
/// height of the block that is going to be committed. Otherwise, it is the
116119
/// height of the most recently committed block, or `BlockHeight::sentinel`
@@ -139,6 +142,7 @@ where
139142
) -> Self {
140143
let block = BlockStorage {
141144
tree: MerkleTree::default(),
145+
pre_commit_tree: MerkleTree::default(),
142146
height: BlockHeight::default(),
143147
epoch: Epoch::default(),
144148
pred_epochs: Epochs::default(),
@@ -308,6 +312,29 @@ where
308312
Ok(())
309313
}
310314

315+
/// Update the pre-commit merkle tree with epoch data
316+
pub fn update_epoch_in_pre_commit_merkle_tree(&mut self) -> Result<()> {
317+
let key_prefix: Key =
318+
Address::Internal(InternalAddress::PoS).to_db_key().into();
319+
320+
let key = key_prefix.push(&"epoch_start_height".to_string())?;
321+
self.block
322+
.pre_commit_tree
323+
.update(&key, encode(&self.next_epoch_min_start_height))?;
324+
325+
let key = key_prefix.push(&"epoch_start_time".to_string())?;
326+
self.block
327+
.pre_commit_tree
328+
.update(&key, encode(&self.next_epoch_min_start_time))?;
329+
330+
let key = key_prefix.push(&"current_epoch".to_string())?;
331+
self.block
332+
.pre_commit_tree
333+
.update(&key, encode(&self.block.epoch))?;
334+
335+
Ok(())
336+
}
337+
311338
/// Get the height of the last committed block or 0 if no block has been
312339
/// committed yet. The first block is at height 1.
313340
pub fn get_last_block_height(&self) -> BlockHeight {

crates/state/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,9 +621,9 @@ pub mod testing {
621621
impl Default for InMemoryState {
622622
fn default() -> Self {
623623
let chain_id = ChainId::default();
624-
let tree = MerkleTree::default();
625624
let block = BlockStorage {
626-
tree,
625+
tree: MerkleTree::default(),
626+
pre_commit_tree: MerkleTree::default(),
627627
height: BlockHeight::default(),
628628
epoch: Epoch::default(),
629629
pred_epochs: Epochs::default(),

0 commit comments

Comments
 (0)