xrpld
Loading...
Searching...
No Matches
libxrpl/shamap/SHAMapNodeID.cpp
1#include <xrpl/shamap/SHAMapNodeID.h>
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/protocol/Serializer.h>
7#include <xrpl/shamap/SHAMap.h>
8
9#include <algorithm>
10#include <cstddef>
11#include <format>
12#include <optional>
13#include <stdexcept>
14#include <string>
15
16namespace xrpl {
17
18static UInt256 const&
19depthMask(unsigned int depth)
20{
21 static constexpr auto kMaskSize = SHAMap::kLeafDepth + 1;
22
23 struct MasksT
24 {
25 UInt256 entry[kMaskSize];
26
27 MasksT()
28 {
29 UInt256 selector;
30 for (auto i = 0u; i < kMaskSize - 1; i += 2)
31 {
32 entry[i] = selector;
33 *(selector.begin() + (i / 2)) = 0xF0;
34 entry[i + 1] = selector;
35 *(selector.begin() + (i / 2)) = 0xFF;
36 }
37 entry[kMaskSize - 1] = selector;
38 }
39 };
40
41 static MasksT const kMasks;
42 return kMasks.entry[depth];
43}
44
45// The prefix of `key` at `depth`: the leading nibbles naming the subtree a node at that depth
46// identifies, with the remainder of the key masked off.
47static UInt256
48maskedToDepth(UInt256 const& key, unsigned int depth)
49{
50 return key & depthMask(depth);
51}
52
53// Whether `id` at `depth` is what `key` looks like once masked down to that depth, i.e.
54// whether an ID with this depth and id names a subtree that `key` falls under.
55static bool
56isPrefixOfAtDepth(UInt256 const& id, unsigned int depth, UInt256 const& key)
57{
58 return maskedToDepth(key, depth) == id;
59}
60
61// canonicalize the hash to a node ID for this depth
62SHAMapNodeID::SHAMapNodeID(unsigned int depth, UInt256 const& hash) : id_(hash), depth_(depth)
63{
64 // Every SHAMapNodeID's depth is stored here, so this is the one place that can stop an
65 // out-of-range one from being kept: a depth past kLeafDepth would go on to index depthMask
66 // out of bounds, and getRawString would narrow it to a byte, silently renaming the node.
67 // Clamp rather than throw, since node IDs are built from peer-supplied depths on the ledger
68 // data path, where no caller catches an exception before it reaches a thread boundary.
70 {
71 // LCOV_EXCL_START
72 UNREACHABLE("xrpl::SHAMapNodeID::SHAMapNodeID : depth within tree");
75 // LCOV_EXCL_STOP
76 }
77
78 // Reads the clamped member rather than the depth argument, so it cannot index depthMask past
79 // its last entry even once the clamp above has reported the bad input and carried on.
80 XRPL_ASSERT(
81 isPrefixOf(id_), "xrpl::SHAMapNodeID::SHAMapNodeID : hash and depth inputs do match");
82}
83
86{
87 Serializer s(33);
89 s.add8(depth_);
90 return s.getString();
91}
92
94SHAMapNodeID::getChildNodeID(unsigned int branch) const
95{
96 XRPL_ASSERT(
97 branch < SHAMap::kBranchFactor, "xrpl::SHAMapNodeID::getChildNodeID : valid branch input");
98
99 // A SHAMap has exactly 65 levels, so nodes must not exceed that
100 // depth; if they do, this breaks the invariant of never allowing
101 // the construction of a SHAMapNodeID at an invalid depth. We assert
102 // to catch this in debug builds.
103 //
104 // We throw (but never assert) if the node is at level 64, since
105 // entries at that depth are leaf nodes and have no children and even
106 // constructing a child node from them would break the above invariant.
107 XRPL_ASSERT(
108 depth_ <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::getChildNodeID : maximum leaf depth");
109
111 Throw<std::logic_error>(std::format("Request for child node ID of {}", to_string(*this)));
112
113 if (!isPrefixOf(id_))
114 Throw<std::logic_error>(std::format("Incorrect mask for {}", to_string(*this)));
115
116 SHAMapNodeID node{depth_ + 1, id_};
117 node.id_.begin()[depth_ / 2] |= ((depth_ & 1) != 0u) ? branch : (branch << 4);
118 return node;
119}
120
121bool
123{
124 return isPrefixOfAtDepth(id_, depth_, key);
125}
126
127[[nodiscard]] std::optional<SHAMapNodeID>
129{
131
132 if (size == 33)
133 {
134 unsigned int const depth = *(static_cast<unsigned char const*>(data) + 32);
135 if (depth <= SHAMap::kLeafDepth)
136 {
137 // Reject a serialized ID carrying bits below its own depth. Checked before
138 // constructing, since the constructor asserts that same property.
139 if (auto const id = UInt256::fromVoid(data); isPrefixOfAtDepth(id, depth, id))
140 ret.emplace(depth, id);
141 }
142 }
143
144 return ret;
145}
146
147[[nodiscard]] unsigned int
148selectBranch(SHAMapNodeID const& id, UInt256 const& hash)
149{
150 XRPL_ASSERT(id.getDepth() < SHAMap::kLeafDepth, "xrpl::selectBranch : depth below leaf depth");
151
152 // A depth-64 ID has no nibble left to select. Callers must not ask, but clamp anyway to keep
153 // the read below the end of the 32-byte key.
154 auto const depth = std::min(id.getDepth(), SHAMap::kLeafDepth - 1u);
155 auto branch = static_cast<unsigned int>(*(hash.begin() + (depth / 2)));
156
157 if ((depth & 1) != 0u)
158 {
159 branch &= 0xf;
160 }
161 else
162 {
163 branch >>= 4;
164 }
165
166 XRPL_ASSERT(branch < SHAMap::kBranchFactor, "xrpl::selectBranch : maximum result");
167 return branch;
168}
169
170SHAMapNodeID
171SHAMapNodeID::createID(unsigned int depth, UInt256 const& key)
172{
173 // The mask is chosen here, before the constructor runs, so the clamp there cannot cover this
174 // call: an out-of-range depth would index depthMask's table while still evaluating this
175 // argument. A public factory has to hold its own bound.
176 if (depth > SHAMap::kLeafDepth)
177 {
178 // LCOV_EXCL_START
179 UNREACHABLE("xrpl::SHAMapNodeID::createID : depth within tree");
180 depth = SHAMap::kLeafDepth;
181 // LCOV_EXCL_STOP
182 }
183
184 return SHAMapNodeID(depth, maskedToDepth(key, depth));
185}
186
187} // namespace xrpl
static BaseUInt fromVoid(void const *data)
Definition base_uint.h:339
iterator begin()
Definition base_uint.h:128
Identifies a node inside a SHAMap.
bool isPrefixOf(UInt256 const &key) const
Test whether this node ID lies on the path to the given leaf key.
SHAMapNodeID getChildNodeID(unsigned int branch) const
SHAMapNodeID()=default
unsigned int depth_
static SHAMapNodeID createID(unsigned int depth, UInt256 const &key)
Create a SHAMapNodeID of a node with the depth of the node and the key of a leaf.
static constexpr unsigned int kLeafDepth
The depth of the hash map: data is only present in the leaves.
Definition SHAMap.h:144
static constexpr unsigned int kBranchFactor
Number of children each non-leaf node has (the 'radix tree' part of the map).
Definition SHAMap.h:139
int addBitString(BaseUInt< Bits, Tag > const &v)
Definition Serializer.h:202
std::string getString() const
Definition Serializer.h:309
int add8(unsigned char byteValue)
T emplace(T... args)
T format(T... args)
T min(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static UInt256 maskedToDepth(UInt256 const &key, unsigned int depth)
static bool isPrefixOfAtDepth(UInt256 const &id, unsigned int depth, UInt256 const &key)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
BaseUInt< 256 > UInt256
Definition base_uint.h:580
static UInt256 const & depthMask(unsigned int depth)
std::optional< SHAMapNodeID > deserializeSHAMapNodeID(void const *data, std::size_t size)
Return an object representing a serialized SHAMap Node ID.
unsigned int selectBranch(SHAMapNodeID const &id, UInt256 const &hash)
Returns the branch that would contain the given hash.
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52