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

Add an update method #1

Merged
merged 2 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions on-chain/lib/aiken/merkle-patricia-forestry.ak
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,30 @@ pub fn delete(
MerklePatriciaForestry { root: excluding(key, proof) }
}

/// **fails** when | the [Proof](#Proof) is invalid
/// --- | ---
/// **fails** when | there's no element in the trie at the given key
/// --- | ---
///
/// Update an element in the trie with a a new value. This requires a [Proof](#Proof)
/// of the old element, to ensure its in the list, and a [Proof](#Proof) of the new
/// element, to re-add it.
///
/// Can be thought of as a delete, followed by an insert, but is able to do it with one fewer
/// membership checks
pub fn update(
self: MerklePatriciaForestry,
key: ByteArray,
proof: Proof,
old_value: ByteArray,
new_value: ByteArray,
) {
// TODO: could we do this with a single traversal? `something(key, old_value, new_value, proof) -> (old_root, new_root)`?
Copy link
Member

Choose a reason for hiding this comment

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

I've done -- at some point -- something similar for including / excluding since only the last step in the traversal really is different. But in the end, there's an extra cost that comes from having more arguments to keep track of and you still need the same number of hashes and prefix manipulation so there's almost no visible benefits on the execution costs in doing so. And, it obfuscates the code a lot more, so I gave up on the idea eventually.

expect including(key, old_value, proof) == self.root
// If we were doing a delete followed by an insert, we'd end up checking the `excluding` again here
MerklePatriciaForestry { root: including(key, new_value, proof) }
}

// -----------------------------------------------------------------------------
// ----------------------------------------------------------------------- Proof
// -----------------------------------------------------------------------------
Expand Down
23 changes: 23 additions & 0 deletions on-chain/lib/aiken/merkle-patricia-forestry.tests.ak
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,22 @@ test example_delete() {
}
}

test example_update() {
// https://media3.giphy.com/media/Bj5ILhCPm8EQ8/giphy.gif
mpf.update(trie(), banana, proof_banana(), "🍌", "🍆") == updated_banana()
}

test example_idempotent_update() {
and {
mpf.update(trie(), banana, proof_banana(), "🍌", "🍌") == trie(),
mpf.update(updated_banana(), banana, proof_banana(), "🍆", "🍆") == updated_banana(),
}
}

test example_fake_update() fail {
mpf.update(without_banana(), banana, proof_banana(), "🍌", "🍆") == updated_banana()
}

// -------------------- Some notable cases

test example_insert_whatever() {
Expand Down Expand Up @@ -470,6 +486,13 @@ fn without_banana() {
)
}

// The root hash we get with `banana` mapped to 🍆 instead
fn updated_banana() {
mpf.from_root(
#"9057d02799a012a9d47fab6f9f5c43b4b2bf94584b339e3b4d3969fd95d55972"
)
}

// ---------- blueberry

const blueberry = "blueberry[uid: 0]"
Expand Down
Loading