From 7658c467df4480c00874b69a9f7b1fa710a6a62b Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Thu, 6 Jun 2024 12:14:41 -0400 Subject: [PATCH 1/2] Add an update method The update method can save two membership checks over a delete followed by an insert, because we don't need to bother calculating / verifying the intermediate root --- .../lib/aiken/merkle-patricia-forestry.ak | 24 +++++++++++++++++++ .../aiken/merkle-patricia-forestry.tests.ak | 23 ++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/on-chain/lib/aiken/merkle-patricia-forestry.ak b/on-chain/lib/aiken/merkle-patricia-forestry.ak index 92a8ad7..c2a1126 100644 --- a/on-chain/lib/aiken/merkle-patricia-forestry.ak +++ b/on-chain/lib/aiken/merkle-patricia-forestry.ak @@ -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)`? + 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 // ----------------------------------------------------------------------------- diff --git a/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak b/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak index 4bd23cd..b1b59c3 100644 --- a/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak +++ b/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak @@ -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() { @@ -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]" From 159cc7deae103bdb477ac0cb6d3547299c6563bb Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Thu, 6 Jun 2024 12:28:45 -0400 Subject: [PATCH 2/2] Remove TODO, formatting --- on-chain/lib/aiken/merkle-patricia-forestry.ak | 1 - on-chain/lib/aiken/merkle-patricia-forestry.tests.ak | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/on-chain/lib/aiken/merkle-patricia-forestry.ak b/on-chain/lib/aiken/merkle-patricia-forestry.ak index c2a1126..03de319 100644 --- a/on-chain/lib/aiken/merkle-patricia-forestry.ak +++ b/on-chain/lib/aiken/merkle-patricia-forestry.ak @@ -154,7 +154,6 @@ pub fn update( 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)`? 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) } diff --git a/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak b/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak index b1b59c3..dd86528 100644 --- a/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak +++ b/on-chain/lib/aiken/merkle-patricia-forestry.tests.ak @@ -489,7 +489,7 @@ fn without_banana() { // The root hash we get with `banana` mapped to 🍆 instead fn updated_banana() { mpf.from_root( - #"9057d02799a012a9d47fab6f9f5c43b4b2bf94584b339e3b4d3969fd95d55972" + #"9057d02799a012a9d47fab6f9f5c43b4b2bf94584b339e3b4d3969fd95d55972", ) }