Skip to content

Commit

Permalink
feat(mpt): Copy-on-hash (#1001)
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby authored Feb 5, 2025
1 parent ee9caca commit 8934111
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 88 deletions.
18 changes: 7 additions & 11 deletions crates/executor/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,15 @@ where
self.update_accounts(bundle)?;

// Recompute the root hash of the trie.
self.root_node.blind();
let root = self.root_node.blind();

debug!(
target: "client_executor",
"Recomputed state root: {commitment:?}",
commitment = self.root_node.blinded_commitment()
"Recomputed state root: {root}",
);

// Extract the new state root from the root node.
self.root_node.blinded_commitment().ok_or(TrieDBError::RootNotBlinded)
Ok(root)
}

/// Fetches the [TrieAccount] of an account from the trie DB.
Expand Down Expand Up @@ -239,11 +238,8 @@ where
})?;

// Recompute the account storage root.
acc_storage_root.blind();

let commitment =
acc_storage_root.blinded_commitment().ok_or(TrieDBError::RootNotBlinded)?;
trie_account.storage_root = commitment;
let root = acc_storage_root.blind();
trie_account.storage_root = root;

// RLP encode the trie account for insertion.
let mut account_buf = Vec::with_capacity(trie_account.length());
Expand Down Expand Up @@ -407,14 +403,14 @@ mod tests {
fn test_trie_db_take_root_node() {
let db = new_test_db();
let root_node = db.take_root_node();
assert_eq!(root_node.blinded_commitment(), Some(B256::default()));
assert_eq!(root_node.blind(), B256::default());
}

#[test]
fn test_trie_db_root_node_ref() {
let db = new_test_db();
let root_node = db.root();
assert_eq!(root_node.blinded_commitment(), Some(B256::default()));
assert_eq!(root_node.blind(), B256::default());
}

#[test]
Expand Down
4 changes: 1 addition & 3 deletions crates/executor/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ where
block_number: u64,
) -> Result<B256, TrieDBError> {
match db.storage_roots().get(&L2_TO_L1_BRIDGE) {
Some(storage_root) => {
storage_root.blinded_commitment().ok_or(TrieDBError::RootNotBlinded)
}
Some(storage_root) => Ok(storage_root.blind()),
None => Ok(db
.get_trie_account(&L2_TO_L1_BRIDGE, block_number)?
.ok_or(TrieDBError::MissingAccountInfo)?
Expand Down
104 changes: 30 additions & 74 deletions crates/mpt/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,17 @@ impl TrieNode {
Self::Blinded { commitment }
}

/// Returns the commitment of a [TrieNode::Blinded] node, if `self` is of the
/// [TrieNode::Blinded] or [TrieNode::Empty] variants.
///
/// ## Returns
/// - `Some(B256)` - The commitment of the blinded node
/// - `None` - `self` is not a [TrieNode::Blinded] node
pub const fn blinded_commitment(&self) -> Option<B256> {
/// Blinds the [TrieNode].. Alternatively, if the [TrieNode] is a [TrieNode::Blinded] node
/// already, its commitment is returned directly.
pub fn blind(&self) -> B256 {
match self {
Self::Blinded { commitment } => Some(*commitment),
Self::Empty => Some(EMPTY_ROOT_HASH),
_ => None,
}
}

/// Blinds the [TrieNode] if its encoded length is longer than an encoded [B256] string in
/// length. Alternatively, if the [TrieNode] is a [TrieNode::Blinded] node already, it
/// is left as-is.
pub fn blind(&mut self) {
if self.length() >= B256::ZERO.len() && !matches!(self, Self::Blinded { .. }) {
let mut rlp_buf = Vec::with_capacity(self.length());
self.encode_in_place(&mut rlp_buf);
*self = Self::Blinded { commitment: keccak256(rlp_buf) }
Self::Blinded { commitment } => *commitment,
Self::Empty => EMPTY_ROOT_HASH,
_ => {
let mut rlp_buf = Vec::with_capacity(self.length());
self.encode(&mut rlp_buf);
keccak256(rlp_buf)
}
}
}

Expand Down Expand Up @@ -360,44 +349,6 @@ impl TrieNode {
}
}

/// Alternative function to the [Encodable::encode] implementation for this type, that blinds
/// children nodes throughout the encoding process. This function is useful in the case where
/// the trie node cache is no longer required (i.e., during [Self::blind]).
///
/// ## Takes
/// - `self` - The root trie node
/// - `out` - The buffer to write the encoded trie node to
pub fn encode_in_place(&mut self, out: &mut dyn alloy_rlp::BufMut) {
let payload_length = self.payload_length();
match self {
Self::Empty => out.put_u8(EMPTY_STRING_CODE),
Self::Blinded { commitment } => commitment.encode(out),
Self::Leaf { prefix, value } => {
// Encode the leaf node's header and key-value pair.
Header { list: true, payload_length }.encode(out);
alloy_trie::nodes::encode_path_leaf(prefix, true).as_slice().encode(out);
value.encode(out);
}
Self::Extension { prefix, node } => {
// Encode the extension node's header, prefix, and pointer node.
Header { list: true, payload_length }.encode(out);
alloy_trie::nodes::encode_path_leaf(prefix, false).as_slice().encode(out);
node.blind();
node.encode_in_place(out);
}
Self::Branch { stack } => {
// In branch nodes, if an element is longer than 32 bytes in length, it is blinded.
// Assuming we have an open trie node, we must re-hash the elements
// that are longer than 32 bytes in length.
Header { list: true, payload_length }.encode(out);
stack.iter_mut().for_each(|node| {
node.blind();
node.encode_in_place(out);
});
}
}
}

/// If applicable, collapses `self` into a more compact form.
///
/// ## Takes
Expand Down Expand Up @@ -562,7 +513,7 @@ impl TrieNode {
/// than a [B256].
fn blinded_length(&self) -> usize {
let encoded_len = self.length();
if encoded_len >= B256::ZERO.len() && !matches!(self, Self::Blinded { .. }) {
if encoded_len >= B256::ZERO.len() {
B256::ZERO.length()
} else {
encoded_len
Expand All @@ -572,32 +523,39 @@ impl TrieNode {

impl Encodable for TrieNode {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
let payload_length = self.payload_length();
match self {
Self::Empty => out.put_u8(EMPTY_STRING_CODE),
Self::Blinded { commitment } => commitment.encode(out),
Self::Leaf { prefix, value } => {
// Encode the leaf node's header and key-value pair.
Header { list: true, payload_length: self.payload_length() }.encode(out);
Header { list: true, payload_length }.encode(out);
alloy_trie::nodes::encode_path_leaf(prefix, true).as_slice().encode(out);
value.encode(out);
}
Self::Extension { prefix, node } => {
// Encode the extension node's header, prefix, and pointer node.
Header { list: true, payload_length: self.payload_length() }.encode(out);
Header { list: true, payload_length }.encode(out);
alloy_trie::nodes::encode_path_leaf(prefix, false).as_slice().encode(out);
let mut blinded = node.clone();
blinded.blind();
blinded.encode(out);
if node.length() >= B256::ZERO.len() {
let hash = node.blind();
hash.encode(out);
} else {
node.encode(out);
}
}
Self::Branch { stack } => {
// In branch nodes, if an element is longer than 32 bytes in length, it is blinded.
// Assuming we have an open trie node, we must re-hash the elements
// that are longer than 32 bytes in length.
Header { list: true, payload_length: self.payload_length() }.encode(out);
Header { list: true, payload_length }.encode(out);
stack.iter().for_each(|node| {
let mut blinded = node.clone();
blinded.blind();
blinded.encode(out);
if node.length() >= B256::ZERO.len() {
let hash = node.blind();
hash.encode(out);
} else {
node.encode(out);
}
});
}
}
Expand Down Expand Up @@ -679,7 +637,7 @@ mod test {
#[test]
fn test_empty_blinded() {
let trie_node = TrieNode::Empty;
assert_eq!(trie_node.blinded_commitment().unwrap(), EMPTY_ROOT_HASH);
assert_eq!(trie_node.blind(), EMPTY_ROOT_HASH);
}

#[test]
Expand Down Expand Up @@ -801,8 +759,7 @@ mod test {
assert_eq!(v, encoded_node.as_slice());
}

root_node.blind();
let commitment = root_node.blinded_commitment().unwrap();
let commitment = root_node.blind();
assert_eq!(commitment, root);
}

Expand Down Expand Up @@ -856,8 +813,7 @@ mod test {
node.insert(&Nibbles::unpack(key), key.into(), &NoopTrieProvider).unwrap();
}

node.blind();
assert_eq!(node.blinded_commitment().unwrap(), hb.root());
assert_eq!(node.blind(), hb.root());
}

/// Differential test for deleting an arbitrary number of keys from a `TrieNode` / `HashBuilder`.
Expand Down

0 comments on commit 8934111

Please sign in to comment.