From a624a8504c722a2c561ce147f64c2a1a0bb6f4b0 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 20 Jul 2018 15:15:05 +0200 Subject: Merkle search tree --- lib/data/merklesearchtree.ex | 181 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 lib/data/merklesearchtree.ex (limited to 'lib') diff --git a/lib/data/merklesearchtree.ex b/lib/data/merklesearchtree.ex new file mode 100644 index 0000000..d23e8a7 --- /dev/null +++ b/lib/data/merklesearchtree.ex @@ -0,0 +1,181 @@ +defmodule SData.MerkleSearchTree do + @moduledoc""" + A Merkle search tree. + + A node of the tree is + { + level, + hash_of_node | nil, + [ + { k_low_bound, v, hash_of_node | nil }, + { k_low_bound, v, hash_of_node | nil }, + ... + } + } + """ + + defstruct [:root, :store] + + def new() do + %SData.MerkleSearchTree{ + root: nil, + store: %{} + } + end + + def insert(state, key, value) do + level = calc_level(key) + {hash, store} = insert_at(state.store, state.root, key, level, value) + %{ state | root: hash, store: store } + end + + def remove(_state, _key) do + # TODO + end + + defp insert_at(store, root, key, level, value) do + {new_page, store} = if root == nil do + { { level, nil, [ { key, value, nil } ] }, store } + else + { plevel, low, lst } = store[root] + [ { k0, _, _} | _ ] = lst + cond do + plevel < level -> + {low, high, store} = split(store, root, key) + { { level, low, [ { key, value, high } ] }, store } + plevel == level -> + store = Map.delete(store, root) + if key < k0 do + {low2a, low2b, store} = split(store, low, key) + { { level, low2a, [ { key, value, low2b } | lst] }, store } + else + {new_lst, store} = aux_insert_after_first(store, lst, key, value) + { { plevel, low, new_lst }, store } + end + plevel > level -> + store = Map.delete(store, root) + if key < k0 do + {new_low, store} = insert_at(store, low, key, level, value) + { { plevel, new_low, lst }, store } + else + {new_lst, store} = aux_insert_sub_after_first(store, lst, key, level, value) + { { plevel, low, new_lst }, store } + end + end + end + hash = SData.term_hash new_page + store = Map.put(store, hash, new_page) + { hash, store } + end + + defp split(store, hash, key) do + if hash == nil do + {nil, nil, store} + else + { level, low, lst } = store[hash] + store = Map.delete(store, hash) + [ { k0, _, _} | _ ] = lst + cond do + key < k0 -> + {lowlow, lowhi, store} = split(store, low, key) + newp2 = { level, lowhi, lst } + newp2h = SData.term_hash newp2 + store = Map.put(store, newp2h, newp2) + {lowlow, newp2h, store} + key > k0 -> + {lst1, p2, store} = split_aux(store, lst, key, level) + newp1 = {level, low, lst1} + newp1h = SData.term_hash newp1 + store = Map.put(store, newp1h, newp1) + {newp1h, p2, store} + end + end + end + + defp split_aux(store, lst, key, level) do + case lst do + [ {k1, _, _} | _ ] when k1 == key -> + raise "Bad logic" + [ {k1, v1, r1} ] -> + {r1l, r1h, store} = split(store, r1, key) + { [{k1, v1, r1l}], r1h, store } + [ {k1, v1, r1}, {k2, v2, r2} | rst ] when key < k2-> + {r1l, r1h, store} = split(store, r1, key) + p2 = { level, r1h, [ {k2, v2, r2} | rst ] } + p2h = SData.term_hash p2 + store = store + |> Map.put(p2h, p2) + { [{k1, v1, r1l}], p2h, store } + [ fst | rst ] -> + {rst2, hi, store} = split_aux(store, rst, key, level) + { [ fst | rst2 ], hi, store } + end + end + + defp aux_insert_after_first(store, lst, key, value) do + case lst do + [ {k1, _, r1} | rst ] when k1 == key -> + { [ {k1, value, r1} | rst ], store } + [ {k1, v1, r1} ] -> + {r1a, r1b, new_store} = split(store, r1, key) + { [ {k1, v1, r1a}, {key, value, r1b} ], new_store } + [ {k1, v1, r1}, {k2, v2, r2} | rst ] when key < k2-> + {r1a, r1b, new_store} = split(store, r1, key) + { [ {k1, v1, r1a}, {key, value, r1b}, {k2, v2, r2} | rst ], new_store } + [ fst | rst ] -> + {rst2, new_store} = aux_insert_after_first(store, rst, key, value) + { [ fst | rst2 ], new_store } + end + end + + defp aux_insert_sub_after_first(store, lst, key, level, value) do + case lst do + [ {k1, _, _} | _ ] when k1 == key -> + raise "Bad logic" + [ {k1, v1, r1} ] -> + {r1new, store_new} = insert_at(store, r1, key, level, value) + { [{k1, v1, r1new}], store_new } + [ {k1, v1, r1}, {k2, v2, r2} | rst ] when key < k2-> + {r1new, store_new} = insert_at(store, r1, key, level, value) + { [{k1, v1, r1new}, {k2, v2, r2} | rst], store_new } + [ fst | rst ] -> + {rst2, new_store} = aux_insert_sub_after_first(store, rst, key, level, value) + { [ fst | rst2 ], new_store } + end + end + + def dump(state) do + dump(state.store, state.root, "") + end + + defp dump(store, root, lvl) do + case root do + nil -> + IO.puts(lvl <> "nil") + _ -> + { level, low, lst} = store[root] + IO.puts(lvl <> "#{root|>Base.encode16} (#{level})") + dump(store, low, lvl <> " ") + for {k, v, r} <- lst do + IO.puts(lvl<>"- #{inspect k} => #{inspect v}") + dump(store, r, lvl <> " ") + end + end + end + + defp calc_level(key) do + key + |> SData.term_hash + |> Base.encode16 + |> String.to_charlist + |> count_leading_zeroes + end + + defp count_leading_zeroes('0' ++ rest) do + 1 + count_leading_zeroes(rest) + end + defp count_leading_zeroes(_) do + 0 + end + +end -- cgit v1.2.3