aboutsummaryrefslogtreecommitdiffstats
path: root/src/Merkle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Merkle.cpp')
-rw-r--r--src/Merkle.cpp1018
1 files changed, 480 insertions, 538 deletions
diff --git a/src/Merkle.cpp b/src/Merkle.cpp
index 760cfce..09329f9 100644
--- a/src/Merkle.cpp
+++ b/src/Merkle.cpp
@@ -7,604 +7,546 @@
namespace nt {
-// ── Wire format constants ────────────────────────────────────────────────────
-
-static constexpr uint8_t TAG_LEAF = 0x4C; // 'L'
-static constexpr uint8_t TAG_INTERNAL = 0x49; // 'I'
-
-// ── Byte-order helpers ───────────────────────────────────────────────────────
-
-static void write_u32(std::vector<uint8_t>& buf, uint32_t v)
-{
- buf.push_back(static_cast<uint8_t>(v >> 24));
- buf.push_back(static_cast<uint8_t>(v >> 16));
- buf.push_back(static_cast<uint8_t>(v >> 8));
- buf.push_back(static_cast<uint8_t>(v ));
-}
-
-static void write_u64(std::vector<uint8_t>& buf, uint64_t v)
-{
- buf.push_back(static_cast<uint8_t>(v >> 56));
- buf.push_back(static_cast<uint8_t>(v >> 48));
- buf.push_back(static_cast<uint8_t>(v >> 40));
- buf.push_back(static_cast<uint8_t>(v >> 32));
- buf.push_back(static_cast<uint8_t>(v >> 24));
- buf.push_back(static_cast<uint8_t>(v >> 16));
- buf.push_back(static_cast<uint8_t>(v >> 8));
- buf.push_back(static_cast<uint8_t>(v ));
-}
-
-static uint32_t read_u32(const uint8_t* p)
-{
- return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16)
- | (uint32_t(p[2]) << 8) | uint32_t(p[3]);
-}
-
-static uint64_t read_u64(const uint8_t* p)
-{
- return (uint64_t(p[0]) << 56) | (uint64_t(p[1]) << 48)
- | (uint64_t(p[2]) << 40) | (uint64_t(p[3]) << 32)
- | (uint64_t(p[4]) << 24) | (uint64_t(p[5]) << 16)
- | (uint64_t(p[6]) << 8) | uint64_t(p[7]);
-}
-
-// ── Hex / binary conversion (free, key-agnostic) ─────────────────────────────
-
-Hash32 hex_to_bin(const std::string& hex)
-{
- if (hex.size() != 64)
- throw std::invalid_argument(
- "Merkle: hash must be exactly 64 hex characters, got " +
- std::to_string(hex.size()));
-
- Hash32 out{};
- auto nibble = [](char c) -> uint8_t {
- if (c >= '0' && c <= '9') return static_cast<uint8_t>(c - '0');
- if (c >= 'a' && c <= 'f') return static_cast<uint8_t>(c - 'a' + 10);
- if (c >= 'A' && c <= 'F') return static_cast<uint8_t>(c - 'A' + 10);
- throw std::invalid_argument(std::string("Merkle: invalid hex character '") + c + "'");
- };
- for (size_t i = 0; i < 32; ++i)
- out[i] = static_cast<uint8_t>((nibble(hex[2*i]) << 4) | nibble(hex[2*i+1]));
- return out;
-}
-
-std::string bin_to_hex(const Hash32& bin)
-{
- static const char HEX[] = "0123456789abcdef";
- std::string out(64, '0');
- for (size_t i = 0; i < 32; ++i)
- {
- out[2*i] = HEX[(bin[i] >> 4) & 0xF];
- out[2*i+1] = HEX[bin[i] & 0xF];
+ // ── Wire format constants ────────────────────────────────────────────────────
+
+ static constexpr uint8_t TAG_LEAF = 0x4C; // 'L'
+ static constexpr uint8_t TAG_INTERNAL = 0x49; // 'I'
+
+ // ── Byte-order helpers ───────────────────────────────────────────────────────
+
+ static void write_u32(std::vector<uint8_t>& buf, uint32_t v) {
+ buf.push_back(static_cast<uint8_t>(v >> 24));
+ buf.push_back(static_cast<uint8_t>(v >> 16));
+ buf.push_back(static_cast<uint8_t>(v >> 8));
+ buf.push_back(static_cast<uint8_t>(v));
}
- return out;
-}
-// ── Key codec: per-Key serialisation primitives ──────────────────────────────
+ static void write_u64(std::vector<uint8_t>& buf, uint64_t v) {
+ buf.push_back(static_cast<uint8_t>(v >> 56));
+ buf.push_back(static_cast<uint8_t>(v >> 48));
+ buf.push_back(static_cast<uint8_t>(v >> 40));
+ buf.push_back(static_cast<uint8_t>(v >> 32));
+ buf.push_back(static_cast<uint8_t>(v >> 24));
+ buf.push_back(static_cast<uint8_t>(v >> 16));
+ buf.push_back(static_cast<uint8_t>(v >> 8));
+ buf.push_back(static_cast<uint8_t>(v));
+ }
-namespace {
+ static uint32_t read_u32(const uint8_t* p) {
+ return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) | (uint32_t(p[2]) << 8) |
+ uint32_t(p[3]);
+ }
-template<typename Key>
-struct KeyCodec; // primary template intentionally undefined
+ static uint64_t read_u64(const uint8_t* p) {
+ return (uint64_t(p[0]) << 56) | (uint64_t(p[1]) << 48) | (uint64_t(p[2]) << 40) |
+ (uint64_t(p[3]) << 32) | (uint64_t(p[4]) << 24) | (uint64_t(p[5]) << 16) |
+ (uint64_t(p[6]) << 8) | uint64_t(p[7]);
+ }
-template<>
-struct KeyCodec<Hash32> {
- static void write(std::vector<uint8_t>& buf, const Hash32& k) {
- buf.insert(buf.end(), k.begin(), k.end());
+ // ── Hex / binary conversion (free, key-agnostic) ─────────────────────────────
+
+ Hash32 hex_to_bin(const std::string& hex) {
+ if (hex.size() != 64)
+ throw std::invalid_argument("Merkle: hash must be exactly 64 hex characters, got " +
+ std::to_string(hex.size()));
+
+ Hash32 out{};
+ auto nibble = [](char c) -> uint8_t {
+ if (c >= '0' && c <= '9')
+ return static_cast<uint8_t>(c - '0');
+ if (c >= 'a' && c <= 'f')
+ return static_cast<uint8_t>(c - 'a' + 10);
+ if (c >= 'A' && c <= 'F')
+ return static_cast<uint8_t>(c - 'A' + 10);
+ throw std::invalid_argument(std::string("Merkle: invalid hex character '") + c + "'");
+ };
+ for (size_t i = 0; i < 32; ++i)
+ out[i] = static_cast<uint8_t>((nibble(hex[2 * i]) << 4) | nibble(hex[2 * i + 1]));
+ return out;
}
- // Returns key and advances *p_inout by the number of consumed bytes.
- static Hash32 read(const uint8_t*& p) {
- Hash32 k;
- std::memcpy(k.data(), p, 32);
- p += 32;
- return k;
+
+ std::string bin_to_hex(const Hash32& bin) {
+ static const char HEX[] = "0123456789abcdef";
+ std::string out(64, '0');
+ for (size_t i = 0; i < 32; ++i) {
+ out[2 * i] = HEX[(bin[i] >> 4) & 0xF];
+ out[2 * i + 1] = HEX[bin[i] & 0xF];
+ }
+ return out;
+ }
+
+ // ── Key codec: per-Key serialisation primitives ──────────────────────────────
+
+ namespace {
+
+ template <typename Key> struct KeyCodec; // primary template intentionally undefined
+
+ template <> struct KeyCodec<Hash32> {
+ static void write(std::vector<uint8_t>& buf, const Hash32& k) {
+ buf.insert(buf.end(), k.begin(), k.end());
+ }
+ // Returns key and advances *p_inout by the number of consumed bytes.
+ static Hash32 read(const uint8_t*& p) {
+ Hash32 k;
+ std::memcpy(k.data(), p, 32);
+ p += 32;
+ return k;
+ }
+ static size_t encoded_size(const Hash32&) {
+ return 32;
+ }
+ };
+
+ template <> struct KeyCodec<std::string> {
+ static void write(std::vector<uint8_t>& buf, const std::string& k) {
+ write_u32(buf, static_cast<uint32_t>(k.size()));
+ buf.insert(buf.end(), k.begin(), k.end());
+ }
+ static std::string read(const uint8_t*& p) {
+ uint32_t len = read_u32(p);
+ p += 4;
+ std::string k(reinterpret_cast<const char*>(p), len);
+ p += len;
+ return k;
+ }
+ static size_t encoded_size(const std::string& k) {
+ return 4 + k.size();
+ }
+ };
+
+ } // anonymous namespace
+
+ // ── Node serialisation ───────────────────────────────────────────────────────
+
+ template <typename Key> std::vector<uint8_t> Merkle<Key>::encode_leaf(const LeafNode& n) {
+ std::vector<uint8_t> buf;
+ size_t payload_size = 0;
+ for (const auto& e : n.entries)
+ payload_size += KeyCodec<Key>::encoded_size(e.key) + 32;
+ buf.reserve(1 + 4 + payload_size);
+
+ buf.push_back(TAG_LEAF);
+ write_u32(buf, static_cast<uint32_t>(n.entries.size()));
+ for (const auto& e : n.entries) {
+ KeyCodec<Key>::write(buf, e.key);
+ buf.insert(buf.end(), e.payload.begin(), e.payload.end());
+ }
+ return buf;
}
- static size_t encoded_size(const Hash32&) { return 32; }
-};
-
-template<>
-struct KeyCodec<std::string> {
- static void write(std::vector<uint8_t>& buf, const std::string& k) {
- write_u32(buf, static_cast<uint32_t>(k.size()));
- buf.insert(buf.end(), k.begin(), k.end());
+
+ template <typename Key>
+ std::vector<uint8_t> Merkle<Key>::encode_internal(const InternalNode& n) {
+ std::vector<uint8_t> buf;
+ size_t payload_size = 0;
+ for (const auto& e : n.entries)
+ payload_size += 8 + KeyCodec<Key>::encoded_size(e.min_key) + 32;
+ buf.reserve(1 + 4 + payload_size);
+
+ buf.push_back(TAG_INTERNAL);
+ write_u32(buf, static_cast<uint32_t>(n.entries.size()));
+ for (const auto& e : n.entries) {
+ write_u64(buf, e.leaf_count);
+ KeyCodec<Key>::write(buf, e.min_key);
+ buf.insert(buf.end(), e.child_hash.begin(), e.child_hash.end());
+ }
+ return buf;
}
- static std::string read(const uint8_t*& p) {
- uint32_t len = read_u32(p);
- p += 4;
- std::string k(reinterpret_cast<const char*>(p), len);
- p += len;
- return k;
+
+ template <typename Key>
+ typename Merkle<Key>::LeafNode Merkle<Key>::decode_leaf(const std::vector<uint8_t>& bytes) {
+ uint32_t count = read_u32(bytes.data() + 1);
+ LeafNode n;
+ n.entries.reserve(count);
+ const uint8_t* p = bytes.data() + 5;
+ for (uint32_t i = 0; i < count; ++i) {
+ LeafEntry e;
+ e.key = KeyCodec<Key>::read(p);
+ std::memcpy(e.payload.data(), p, 32);
+ p += 32;
+ n.entries.push_back(std::move(e));
+ }
+ return n;
}
- static size_t encoded_size(const std::string& k) { return 4 + k.size(); }
-};
-
-} // anonymous namespace
-
-// ── Node serialisation ───────────────────────────────────────────────────────
-
-template<typename Key>
-std::vector<uint8_t> Merkle<Key>::encode_leaf(const LeafNode& n)
-{
- std::vector<uint8_t> buf;
- size_t payload_size = 0;
- for (const auto& e : n.entries)
- payload_size += KeyCodec<Key>::encoded_size(e.key) + 32;
- buf.reserve(1 + 4 + payload_size);
-
- buf.push_back(TAG_LEAF);
- write_u32(buf, static_cast<uint32_t>(n.entries.size()));
- for (const auto& e : n.entries)
- {
- KeyCodec<Key>::write(buf, e.key);
- buf.insert(buf.end(), e.payload.begin(), e.payload.end());
+
+ template <typename Key>
+ typename Merkle<Key>::InternalNode
+ Merkle<Key>::decode_internal(const std::vector<uint8_t>& bytes) {
+ uint32_t count = read_u32(bytes.data() + 1);
+ InternalNode n;
+ n.entries.reserve(count);
+ const uint8_t* p = bytes.data() + 5;
+ for (uint32_t i = 0; i < count; ++i) {
+ ChildEntry e;
+ e.leaf_count = read_u64(p);
+ p += 8;
+ e.min_key = KeyCodec<Key>::read(p);
+ std::memcpy(e.child_hash.data(), p, 32);
+ p += 32;
+ n.entries.push_back(std::move(e));
+ }
+ return n;
}
- return buf;
-}
-
-template<typename Key>
-std::vector<uint8_t> Merkle<Key>::encode_internal(const InternalNode& n)
-{
- std::vector<uint8_t> buf;
- size_t payload_size = 0;
- for (const auto& e : n.entries)
- payload_size += 8 + KeyCodec<Key>::encoded_size(e.min_key) + 32;
- buf.reserve(1 + 4 + payload_size);
-
- buf.push_back(TAG_INTERNAL);
- write_u32(buf, static_cast<uint32_t>(n.entries.size()));
- for (const auto& e : n.entries)
- {
- write_u64(buf, e.leaf_count);
- KeyCodec<Key>::write(buf, e.min_key);
- buf.insert(buf.end(), e.child_hash.begin(), e.child_hash.end());
+
+ template <typename Key> bool Merkle<Key>::is_leaf_bytes(const std::vector<uint8_t>& bytes) {
+ return !bytes.empty() && bytes[0] == TAG_LEAF;
}
- return buf;
-}
-
-template<typename Key>
-typename Merkle<Key>::LeafNode Merkle<Key>::decode_leaf(const std::vector<uint8_t>& bytes)
-{
- uint32_t count = read_u32(bytes.data() + 1);
- LeafNode n;
- n.entries.reserve(count);
- const uint8_t* p = bytes.data() + 5;
- for (uint32_t i = 0; i < count; ++i)
- {
- LeafEntry e;
- e.key = KeyCodec<Key>::read(p);
- std::memcpy(e.payload.data(), p, 32);
- p += 32;
- n.entries.push_back(std::move(e));
+
+ // ── Storage helpers ──────────────────────────────────────────────────────────
+
+ template <typename Key>
+ std::vector<uint8_t> Merkle<Key>::load_node(IStorageBackend& store,
+ const std::string& hash_hex) {
+ auto opt = store.Get(hash_hex);
+ if (!opt)
+ throw std::runtime_error("Merkle: node not found: " + hash_hex);
+ return std::move(*opt);
}
- return n;
-}
-
-template<typename Key>
-typename Merkle<Key>::InternalNode Merkle<Key>::decode_internal(const std::vector<uint8_t>& bytes)
-{
- uint32_t count = read_u32(bytes.data() + 1);
- InternalNode n;
- n.entries.reserve(count);
- const uint8_t* p = bytes.data() + 5;
- for (uint32_t i = 0; i < count; ++i)
- {
- ChildEntry e;
- e.leaf_count = read_u64(p);
- p += 8;
- e.min_key = KeyCodec<Key>::read(p);
- std::memcpy(e.child_hash.data(), p, 32);
- p += 32;
- n.entries.push_back(std::move(e));
+
+ template <typename Key>
+ std::string Merkle<Key>::store_leaf(IStorageBackend& store, const LeafNode& n) {
+ return store.Put(encode_leaf(n));
}
- return n;
-}
-
-template<typename Key>
-bool Merkle<Key>::is_leaf_bytes(const std::vector<uint8_t>& bytes)
-{
- return !bytes.empty() && bytes[0] == TAG_LEAF;
-}
-
-// ── Storage helpers ──────────────────────────────────────────────────────────
-
-template<typename Key>
-std::vector<uint8_t> Merkle<Key>::load_node(IStorageBackend& store,
- const std::string& hash_hex)
-{
- auto opt = store.Get(hash_hex);
- if (!opt)
- throw std::runtime_error("Merkle: node not found: " + hash_hex);
- return std::move(*opt);
-}
-
-template<typename Key>
-std::string Merkle<Key>::store_leaf(IStorageBackend& store, const LeafNode& n)
-{
- return store.Put(encode_leaf(n));
-}
-
-template<typename Key>
-std::string Merkle<Key>::store_internal(IStorageBackend& store, const InternalNode& n)
-{
- return store.Put(encode_internal(n));
-}
-
-template<typename Key>
-Key Merkle<Key>::subtree_min_key(IStorageBackend& store, const std::string& node_hex)
-{
- auto bytes = load_node(store, node_hex);
- if (is_leaf_bytes(bytes))
- {
- auto leaf = decode_leaf(bytes);
- assert(!leaf.entries.empty() && "Merkle invariant: leaf node must never be empty");
- return leaf.entries.front().key;
+
+ template <typename Key>
+ std::string Merkle<Key>::store_internal(IStorageBackend& store, const InternalNode& n) {
+ return store.Put(encode_internal(n));
}
- auto node = decode_internal(bytes);
- assert(!node.entries.empty() && "Merkle invariant: internal node must never be empty");
- return node.entries.front().min_key;
-}
-
-// ── Routing helper ───────────────────────────────────────────────────────────
-
-template<typename Key>
-size_t Merkle<Key>::route(const InternalNode& node, const Key& target)
-{
- assert(!node.entries.empty() && "Merkle invariant: internal node must never be empty");
- assert(std::is_sorted(node.entries.begin(), node.entries.end(),
- [](const ChildEntry& a, const ChildEntry& b) {
- return a.min_key < b.min_key;
- }) && "Merkle invariant: internal node entries must be sorted by min_key");
-
- // Rightmost child whose min_key ≤ target.
- // Defaults to 0 (leftmost) when target < all min_keys.
- size_t idx = 0;
- for (size_t i = 1; i < node.entries.size(); ++i)
- {
- if (!(target < node.entries[i].min_key))
- idx = i;
- else
- break;
+
+ template <typename Key>
+ Key Merkle<Key>::subtree_min_key(IStorageBackend& store, const std::string& node_hex) {
+ auto bytes = load_node(store, node_hex);
+ if (is_leaf_bytes(bytes)) {
+ auto leaf = decode_leaf(bytes);
+ assert(!leaf.entries.empty() && "Merkle invariant: leaf node must never be empty");
+ return leaf.entries.front().key;
+ }
+ auto node = decode_internal(bytes);
+ assert(!node.entries.empty() && "Merkle invariant: internal node must never be empty");
+ return node.entries.front().min_key;
}
- return idx;
-}
-
-// ── Insert ───────────────────────────────────────────────────────────────────
-
-template<typename Key>
-std::string Merkle<Key>::Insert(IStorageBackend& store,
- const std::string& root_hex,
- const Key& key,
- const Hash32& payload)
-{
- if (root_hex.empty())
- {
- LeafNode leaf;
- leaf.entries.push_back({ key, payload });
- return store_leaf(store, leaf);
+
+ // ── Routing helper ───────────────────────────────────────────────────────────
+
+ template <typename Key> size_t Merkle<Key>::route(const InternalNode& node, const Key& target) {
+ assert(!node.entries.empty() && "Merkle invariant: internal node must never be empty");
+ assert(std::is_sorted(node.entries.begin(), node.entries.end(),
+ [](const ChildEntry& a, const ChildEntry& b) {
+ return a.min_key < b.min_key;
+ }) &&
+ "Merkle invariant: internal node entries must be sorted by min_key");
+
+ // Rightmost child whose min_key ≤ target.
+ // Defaults to 0 (leftmost) when target < all min_keys.
+ size_t idx = 0;
+ for (size_t i = 1; i < node.entries.size(); ++i) {
+ if (!(target < node.entries[i].min_key))
+ idx = i;
+ else
+ break;
+ }
+ return idx;
}
- auto result = insert_into(store, root_hex, key, payload);
- if (!result.did_split)
- return result.new_hash;
+ // ── Insert ───────────────────────────────────────────────────────────────────
+
+ template <typename Key>
+ std::string Merkle<Key>::Insert(IStorageBackend& store, const std::string& root_hex,
+ const Key& key, const Hash32& payload) {
+ if (root_hex.empty()) {
+ LeafNode leaf;
+ leaf.entries.push_back({key, payload});
+ return store_leaf(store, leaf);
+ }
+
+ auto result = insert_into(store, root_hex, key, payload);
+ if (!result.did_split)
+ return result.new_hash;
+
+ // Root split: wrap both halves in a new internal root.
+ Key left_min = subtree_min_key(store, result.new_hash);
+
+ InternalNode new_root;
+
+ ChildEntry left;
+ left.leaf_count = result.leaf_count;
+ left.min_key = left_min;
+ left.child_hash = hex_to_bin(result.new_hash);
+ new_root.entries.push_back(std::move(left));
+
+ ChildEntry right;
+ right.leaf_count = result.split_leaf_count;
+ right.min_key = result.split_min_key;
+ right.child_hash = hex_to_bin(result.split_hash);
+ new_root.entries.push_back(std::move(right));
+
+ return store_internal(store, new_root);
+ }
+
+ template <typename Key>
+ typename Merkle<Key>::InsertResult
+ Merkle<Key>::insert_into(IStorageBackend& store, const std::string& node_hex, const Key& key,
+ const Hash32& payload) {
+ auto bytes = load_node(store, node_hex);
+
+ // ── Leaf node ───────────────────────────────────────────────────────────
+ if (is_leaf_bytes(bytes)) {
+ auto leaf = decode_leaf(bytes);
+
+ auto pos = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key,
+ [](const LeafEntry& e, const Key& k) { return e.key < k; });
+ if (pos != leaf.entries.end() && !(key < pos->key)) {
+ // Key already present — update payload if it changed, else no-op.
+ if (pos->payload == payload) {
+ InsertResult r;
+ r.new_hash = node_hex;
+ r.leaf_count = leaf.entries.size();
+ return r;
+ }
+ pos->payload = payload;
+ InsertResult r;
+ r.new_hash = store_leaf(store, leaf);
+ r.leaf_count = leaf.entries.size();
+ return r;
+ }
+
+ leaf.entries.insert(pos, LeafEntry{key, payload});
- // Root split: wrap both halves in a new internal root.
- Key left_min = subtree_min_key(store, result.new_hash);
-
- InternalNode new_root;
-
- ChildEntry left;
- left.leaf_count = result.leaf_count;
- left.min_key = left_min;
- left.child_hash = hex_to_bin(result.new_hash);
- new_root.entries.push_back(std::move(left));
-
- ChildEntry right;
- right.leaf_count = result.split_leaf_count;
- right.min_key = result.split_min_key;
- right.child_hash = hex_to_bin(result.split_hash);
- new_root.entries.push_back(std::move(right));
-
- return store_internal(store, new_root);
-}
-
-template<typename Key>
-typename Merkle<Key>::InsertResult Merkle<Key>::insert_into(IStorageBackend& store,
- const std::string& node_hex,
- const Key& key,
- const Hash32& payload)
-{
- auto bytes = load_node(store, node_hex);
-
- // ── Leaf node ───────────────────────────────────────────────────────────
- if (is_leaf_bytes(bytes))
- {
- auto leaf = decode_leaf(bytes);
-
- auto pos = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key,
- [](const LeafEntry& e, const Key& k) {
- return e.key < k;
- });
- if (pos != leaf.entries.end() && !(key < pos->key))
- {
- // Key already present — update payload if it changed, else no-op.
- if (pos->payload == payload)
- {
+ if (leaf.entries.size() <= B) {
InsertResult r;
- r.new_hash = node_hex;
+ r.new_hash = store_leaf(store, leaf);
r.leaf_count = leaf.entries.size();
return r;
}
- pos->payload = payload;
+
+ // Split leaf at midpoint.
+ size_t mid = leaf.entries.size() / 2;
+ LeafNode left_leaf, right_leaf;
+ left_leaf.entries = {leaf.entries.begin(),
+ leaf.entries.begin() + static_cast<ptrdiff_t>(mid)};
+ right_leaf.entries = {leaf.entries.begin() + static_cast<ptrdiff_t>(mid),
+ leaf.entries.end()};
+
InsertResult r;
- r.new_hash = store_leaf(store, leaf);
- r.leaf_count = leaf.entries.size();
+ r.new_hash = store_leaf(store, left_leaf);
+ r.leaf_count = left_leaf.entries.size();
+ r.did_split = true;
+ r.split_leaf_count = right_leaf.entries.size();
+ r.split_min_key = right_leaf.entries.front().key;
+ r.split_hash = store_leaf(store, right_leaf);
return r;
}
- leaf.entries.insert(pos, LeafEntry{ key, payload });
+ // ── Internal node ───────────────────────────────────────────────────────
+ auto node = decode_internal(bytes);
+
+ size_t child_idx = route(node, key);
+ auto child_result =
+ insert_into(store, bin_to_hex(node.entries[child_idx].child_hash), key, payload);
- if (leaf.entries.size() <= B)
- {
+ node.entries[child_idx].leaf_count = child_result.leaf_count;
+ node.entries[child_idx].child_hash = hex_to_bin(child_result.new_hash);
+
+ // Update min_key for the modified child — only child 0 can have its min
+ // decrease (routing guarantees min_key[i>0] ≤ inserted key, so inserting
+ // below min_key[i>0] is impossible).
+ if (child_idx == 0)
+ node.entries[0].min_key = subtree_min_key(store, child_result.new_hash);
+
+ uint64_t total = 0;
+ for (const auto& e : node.entries)
+ total += e.leaf_count;
+
+ if (!child_result.did_split) {
+ InsertResult r;
+ r.new_hash = store_internal(store, node);
+ r.leaf_count = total;
+ return r;
+ }
+
+ // Insert the right sibling immediately after child_idx.
+ ChildEntry sibling;
+ sibling.leaf_count = child_result.split_leaf_count;
+ sibling.min_key = child_result.split_min_key;
+ sibling.child_hash = hex_to_bin(child_result.split_hash);
+ node.entries.insert(node.entries.begin() + static_cast<ptrdiff_t>(child_idx) + 1,
+ std::move(sibling));
+ total += child_result.split_leaf_count;
+
+ if (node.entries.size() <= B) {
InsertResult r;
- r.new_hash = store_leaf(store, leaf);
- r.leaf_count = leaf.entries.size();
+ r.new_hash = store_internal(store, node);
+ r.leaf_count = total;
return r;
}
- // Split leaf at midpoint.
- size_t mid = leaf.entries.size() / 2;
- LeafNode left_leaf, right_leaf;
- left_leaf.entries = { leaf.entries.begin(),
- leaf.entries.begin() + static_cast<ptrdiff_t>(mid) };
- right_leaf.entries = { leaf.entries.begin() + static_cast<ptrdiff_t>(mid),
- leaf.entries.end() };
+ // Split this internal node.
+ size_t mid = node.entries.size() / 2;
+ InternalNode left_node, right_node;
+ left_node.entries = {node.entries.begin(),
+ node.entries.begin() + static_cast<ptrdiff_t>(mid)};
+ right_node.entries = {node.entries.begin() + static_cast<ptrdiff_t>(mid),
+ node.entries.end()};
+
+ uint64_t left_count = 0, right_count = 0;
+ for (const auto& e : left_node.entries)
+ left_count += e.leaf_count;
+ for (const auto& e : right_node.entries)
+ right_count += e.leaf_count;
InsertResult r;
- r.new_hash = store_leaf(store, left_leaf);
- r.leaf_count = left_leaf.entries.size();
- r.did_split = true;
- r.split_leaf_count = right_leaf.entries.size();
- r.split_min_key = right_leaf.entries.front().key;
- r.split_hash = store_leaf(store, right_leaf);
+ r.new_hash = store_internal(store, left_node);
+ r.leaf_count = left_count;
+ r.did_split = true;
+ r.split_leaf_count = right_count;
+ r.split_min_key = right_node.entries.front().min_key;
+ r.split_hash = store_internal(store, right_node);
return r;
}
- // ── Internal node ───────────────────────────────────────────────────────
- auto node = decode_internal(bytes);
-
- size_t child_idx = route(node, key);
- auto child_result = insert_into(store,
- bin_to_hex(node.entries[child_idx].child_hash),
- key,
- payload);
+ // ── Remove ───────────────────────────────────────────────────────────────────
+
+ template <typename Key>
+ std::string Merkle<Key>::Remove(IStorageBackend& store, const std::string& root_hex,
+ const Key& key) {
+ if (root_hex.empty())
+ return "";
+ auto result = remove_from(store, root_hex, key);
+ if (result.new_hash.empty())
+ return "";
+
+ // Collapse: if the root is an internal node with a single child, skip it.
+ auto bytes = load_node(store, result.new_hash);
+ if (!is_leaf_bytes(bytes)) {
+ auto node = decode_internal(bytes);
+ if (node.entries.size() == 1)
+ return bin_to_hex(node.entries[0].child_hash);
+ }
+ return result.new_hash;
+ }
- node.entries[child_idx].leaf_count = child_result.leaf_count;
- node.entries[child_idx].child_hash = hex_to_bin(child_result.new_hash);
+ template <typename Key>
+ typename Merkle<Key>::RemoveResult
+ Merkle<Key>::remove_from(IStorageBackend& store, const std::string& node_hex, const Key& key) {
+ auto bytes = load_node(store, node_hex);
+
+ // ── Leaf node ───────────────────────────────────────────────────────────
+ if (is_leaf_bytes(bytes)) {
+ auto leaf = decode_leaf(bytes);
+ auto it = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key,
+ [](const LeafEntry& e, const Key& k) { return e.key < k; });
+ if (it == leaf.entries.end() || key < it->key)
+ return {node_hex, leaf.entries.size()}; // not found — no change
+
+ leaf.entries.erase(it);
+ if (leaf.entries.empty())
+ return {"", 0};
+ return {store_leaf(store, leaf), leaf.entries.size()};
+ }
- // Update min_key for the modified child — only child 0 can have its min
- // decrease (routing guarantees min_key[i>0] ≤ inserted key, so inserting
- // below min_key[i>0] is impossible).
- if (child_idx == 0)
- node.entries[0].min_key = subtree_min_key(store, child_result.new_hash);
+ // ── Internal node ───────────────────────────────────────────────────────
+ auto node = decode_internal(bytes);
- uint64_t total = 0;
- for (const auto& e : node.entries) total += e.leaf_count;
+ size_t child_idx = route(node, key);
+ auto child_result = remove_from(store, bin_to_hex(node.entries[child_idx].child_hash), key);
+
+ if (child_result.new_hash.empty()) {
+ node.entries.erase(node.entries.begin() + static_cast<ptrdiff_t>(child_idx));
+ if (node.entries.empty())
+ return {"", 0};
+ } else {
+ node.entries[child_idx].leaf_count = child_result.leaf_count;
+ node.entries[child_idx].child_hash = hex_to_bin(child_result.new_hash);
+ // The min of any child might have changed if we removed its minimum entry.
+ node.entries[child_idx].min_key = subtree_min_key(store, child_result.new_hash);
+ }
- if (!child_result.did_split)
- {
- InsertResult r;
- r.new_hash = store_internal(store, node);
- r.leaf_count = total;
- return r;
+ uint64_t total = 0;
+ for (const auto& e : node.entries)
+ total += e.leaf_count;
+ return {store_internal(store, node), total};
}
- // Insert the right sibling immediately after child_idx.
- ChildEntry sibling;
- sibling.leaf_count = child_result.split_leaf_count;
- sibling.min_key = child_result.split_min_key;
- sibling.child_hash = hex_to_bin(child_result.split_hash);
- node.entries.insert(node.entries.begin() + static_cast<ptrdiff_t>(child_idx) + 1,
- std::move(sibling));
- total += child_result.split_leaf_count;
-
- if (node.entries.size() <= B)
- {
- InsertResult r;
- r.new_hash = store_internal(store, node);
- r.leaf_count = total;
- return r;
+ // ── Get ──────────────────────────────────────────────────────────────────────
+
+ template <typename Key>
+ std::optional<Hash32> Merkle<Key>::Get(IStorageBackend& store, const std::string& root_hex,
+ const Key& key) {
+ if (root_hex.empty())
+ return std::nullopt;
+ return get_from(store, root_hex, key);
}
- // Split this internal node.
- size_t mid = node.entries.size() / 2;
- InternalNode left_node, right_node;
- left_node.entries = { node.entries.begin(),
- node.entries.begin() + static_cast<ptrdiff_t>(mid) };
- right_node.entries = { node.entries.begin() + static_cast<ptrdiff_t>(mid),
- node.entries.end() };
-
- uint64_t left_count = 0, right_count = 0;
- for (const auto& e : left_node.entries) left_count += e.leaf_count;
- for (const auto& e : right_node.entries) right_count += e.leaf_count;
-
- InsertResult r;
- r.new_hash = store_internal(store, left_node);
- r.leaf_count = left_count;
- r.did_split = true;
- r.split_leaf_count = right_count;
- r.split_min_key = right_node.entries.front().min_key;
- r.split_hash = store_internal(store, right_node);
- return r;
-}
-
-// ── Remove ───────────────────────────────────────────────────────────────────
-
-template<typename Key>
-std::string Merkle<Key>::Remove(IStorageBackend& store,
- const std::string& root_hex,
- const Key& key)
-{
- if (root_hex.empty()) return "";
- auto result = remove_from(store, root_hex, key);
- if (result.new_hash.empty()) return "";
-
- // Collapse: if the root is an internal node with a single child, skip it.
- auto bytes = load_node(store, result.new_hash);
- if (!is_leaf_bytes(bytes))
- {
+ template <typename Key>
+ std::optional<Hash32> Merkle<Key>::get_from(IStorageBackend& store, const std::string& node_hex,
+ const Key& key) {
+ auto bytes = load_node(store, node_hex);
+ if (is_leaf_bytes(bytes)) {
+ auto leaf = decode_leaf(bytes);
+ auto it = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key,
+ [](const LeafEntry& e, const Key& k) { return e.key < k; });
+ if (it == leaf.entries.end() || key < it->key)
+ return std::nullopt;
+ return it->payload;
+ }
auto node = decode_internal(bytes);
- if (node.entries.size() == 1)
- return bin_to_hex(node.entries[0].child_hash);
+ size_t child_idx = route(node, key);
+ return get_from(store, bin_to_hex(node.entries[child_idx].child_hash), key);
}
- return result.new_hash;
-}
-
-template<typename Key>
-typename Merkle<Key>::RemoveResult Merkle<Key>::remove_from(IStorageBackend& store,
- const std::string& node_hex,
- const Key& key)
-{
- auto bytes = load_node(store, node_hex);
-
- // ── Leaf node ───────────────────────────────────────────────────────────
- if (is_leaf_bytes(bytes))
- {
- auto leaf = decode_leaf(bytes);
- auto it = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key,
- [](const LeafEntry& e, const Key& k) {
- return e.key < k;
- });
- if (it == leaf.entries.end() || key < it->key)
- return { node_hex, leaf.entries.size() }; // not found — no change
-
- leaf.entries.erase(it);
- if (leaf.entries.empty())
- return { "", 0 };
- return { store_leaf(store, leaf), leaf.entries.size() };
+
+ // ── Page ─────────────────────────────────────────────────────────────────────
+
+ template <typename Key>
+ std::vector<typename Merkle<Key>::Entry> Merkle<Key>::Page(IStorageBackend& store,
+ const std::string& root_hex,
+ size_t offset, size_t limit) {
+ if (root_hex.empty() || limit == 0)
+ return {};
+ std::vector<Entry> out;
+ out.reserve(limit);
+ size_t skip = offset;
+ page_from(store, root_hex, skip, limit, out);
+ return out;
}
- // ── Internal node ───────────────────────────────────────────────────────
- auto node = decode_internal(bytes);
+ template <typename Key>
+ void Merkle<Key>::page_from(IStorageBackend& store, const std::string& node_hex, size_t& offset,
+ size_t limit, std::vector<Entry>& out) {
+ auto bytes = load_node(store, node_hex);
- size_t child_idx = route(node, key);
- auto child_result = remove_from(store,
- bin_to_hex(node.entries[child_idx].child_hash),
- key);
+ if (is_leaf_bytes(bytes)) {
+ auto leaf = decode_leaf(bytes);
+ const size_t available = leaf.entries.size();
- if (child_result.new_hash.empty())
- {
- node.entries.erase(node.entries.begin() + static_cast<ptrdiff_t>(child_idx));
- if (node.entries.empty()) return { "", 0 };
- }
- else
- {
- node.entries[child_idx].leaf_count = child_result.leaf_count;
- node.entries[child_idx].child_hash = hex_to_bin(child_result.new_hash);
- // The min of any child might have changed if we removed its minimum entry.
- node.entries[child_idx].min_key = subtree_min_key(store, child_result.new_hash);
- }
+ if (offset >= available) {
+ offset -= available;
+ return;
+ }
- uint64_t total = 0;
- for (const auto& e : node.entries) total += e.leaf_count;
- return { store_internal(store, node), total };
-}
-
-// ── Get ──────────────────────────────────────────────────────────────────────
-
-template<typename Key>
-std::optional<Hash32> Merkle<Key>::Get(IStorageBackend& store,
- const std::string& root_hex,
- const Key& key)
-{
- if (root_hex.empty()) return std::nullopt;
- return get_from(store, root_hex, key);
-}
-
-template<typename Key>
-std::optional<Hash32> Merkle<Key>::get_from(IStorageBackend& store,
- const std::string& node_hex,
- const Key& key)
-{
- auto bytes = load_node(store, node_hex);
- if (is_leaf_bytes(bytes))
- {
- auto leaf = decode_leaf(bytes);
- auto it = std::lower_bound(leaf.entries.begin(), leaf.entries.end(), key,
- [](const LeafEntry& e, const Key& k) {
- return e.key < k;
- });
- if (it == leaf.entries.end() || key < it->key)
- return std::nullopt;
- return it->payload;
- }
- auto node = decode_internal(bytes);
- size_t child_idx = route(node, key);
- return get_from(store, bin_to_hex(node.entries[child_idx].child_hash), key);
-}
-
-// ── Page ─────────────────────────────────────────────────────────────────────
-
-template<typename Key>
-std::vector<typename Merkle<Key>::Entry> Merkle<Key>::Page(IStorageBackend& store,
- const std::string& root_hex,
- size_t offset,
- size_t limit)
-{
- if (root_hex.empty() || limit == 0) return {};
- std::vector<Entry> out;
- out.reserve(limit);
- size_t skip = offset;
- page_from(store, root_hex, skip, limit, out);
- return out;
-}
-
-template<typename Key>
-void Merkle<Key>::page_from(IStorageBackend& store,
- const std::string& node_hex,
- size_t& offset,
- size_t limit,
- std::vector<Entry>& out)
-{
- auto bytes = load_node(store, node_hex);
-
- if (is_leaf_bytes(bytes))
- {
- auto leaf = decode_leaf(bytes);
- const size_t available = leaf.entries.size();
-
- if (offset >= available)
- {
- offset -= available;
+ for (size_t i = offset; i < available && out.size() < limit; ++i)
+ out.push_back(Entry{leaf.entries[i].key, leaf.entries[i].payload});
+
+ offset = 0;
return;
}
- for (size_t i = offset; i < available && out.size() < limit; ++i)
- out.push_back(Entry{ leaf.entries[i].key, leaf.entries[i].payload });
-
- offset = 0;
- return;
- }
+ auto node = decode_internal(bytes);
- auto node = decode_internal(bytes);
+ for (const auto& entry : node.entries) {
+ if (out.size() >= limit)
+ break;
- for (const auto& entry : node.entries)
- {
- if (out.size() >= limit) break;
+ if (offset >= entry.leaf_count) {
+ offset -= entry.leaf_count;
+ continue;
+ }
- if (offset >= entry.leaf_count)
- {
- offset -= entry.leaf_count;
- continue;
+ page_from(store, bin_to_hex(entry.child_hash), offset, limit, out);
}
-
- page_from(store, bin_to_hex(entry.child_hash), offset, limit, out);
}
-}
-// ── Explicit instantiations ──────────────────────────────────────────────────
+ // ── Explicit instantiations ──────────────────────────────────────────────────
-template class Merkle<Hash32>;
-template class Merkle<std::string>;
+ template class Merkle<Hash32>;
+ template class Merkle<std::string>;
} // namespace nt