xrpld
Loading...
Searching...
No Matches
tests/libxrpl/shamap/SHAMapNodeID.cpp
1#include <xrpl/shamap/SHAMapNodeID.h>
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/protocol/Serializer.h>
5#include <xrpl/shamap/SHAMap.h>
6
7#include <gtest/gtest.h>
8
9#include <stdexcept>
10
11namespace xrpl::tests {
12
13// An arbitrary 32-byte key reused across tests below that don't care about its specific value,
14// only that it is a well-formed key.
15constexpr UInt256 kTestKey("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
16
17TEST(SHAMapNodeIDTest, root_is_prefix_of_every_key)
18{
19 SHAMapNodeID const root;
20 EXPECT_EQ(root.getDepth(), 0u);
21 EXPECT_TRUE(root.isPrefixOf(UInt256{}));
22 EXPECT_TRUE(root.isPrefixOf(kTestKey));
23}
24
25TEST(SHAMapNodeIDTest, child_id_is_prefix_of_keys_in_that_branch)
26{
27 // Walking the branches spelled by the key's own nibbles must keep every
28 // intermediate ID a prefix of that key.
29 SHAMapNodeID id;
30 for (auto depth = 0u; depth < SHAMap::kLeafDepth; ++depth)
31 {
32 id = id.getChildNodeID(selectBranch(id, kTestKey));
33 EXPECT_EQ(id.getDepth(), depth + 1);
34 EXPECT_TRUE(id.isPrefixOf(kTestKey)) << "depth " << id.getDepth();
35 }
36}
37
38TEST(SHAMapNodeIDTest, wrong_branch_is_not_prefix_of_key)
39{
40 SHAMapNodeID const root;
41 auto const correct = selectBranch(root, kTestKey);
42 ASSERT_EQ(correct, 0xbu);
43
44 // An ID built from the wrong branch still has a valid depth and a self-consistent mask, so
45 // isPrefixOf(kTestKey) below is what actually distinguishes the correct branch from the rest.
46 for (auto branch = 0u; branch < SHAMap::kBranchFactor; ++branch)
47 {
48 auto const child = root.getChildNodeID(branch);
49 EXPECT_EQ(child.getDepth(), 1u);
50 EXPECT_EQ(child.isPrefixOf(kTestKey), branch == correct) << "branch " << branch;
51 }
52}
53
54TEST(SHAMapNodeIDTest, prefix_check_is_depth_sensitive)
55{
56 // kTestKey and kOther agree on the first two nibbles ("b9") and then diverge.
57 constexpr UInt256 kOther("b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
58
60 EXPECT_TRUE(id.isPrefixOf(kTestKey));
61 EXPECT_TRUE(id.isPrefixOf(kOther)) << "shared first nibble";
62
63 id = id.getChildNodeID(selectBranch(id, kTestKey));
64 EXPECT_TRUE(id.isPrefixOf(kTestKey));
65 EXPECT_TRUE(id.isPrefixOf(kOther)) << "shared second nibble";
66
67 // Third nibble differs, so the deeper ID no longer covers kOther.
68 id = id.getChildNodeID(selectBranch(id, kTestKey));
69 EXPECT_TRUE(id.isPrefixOf(kTestKey));
70 EXPECT_FALSE(id.isPrefixOf(kOther));
71}
72
73TEST(SHAMapNodeIDTest, leaf_id_from_key_is_prefix_of_that_key)
74{
76 EXPECT_TRUE(leaf.isPrefixOf(kTestKey));
77
78 // At full depth the prefix is the whole key, so nothing else matches.
79 constexpr UInt256 kOther("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca9");
80 EXPECT_FALSE(leaf.isPrefixOf(kOther));
81}
82
83TEST(SHAMapNodeIDTest, create_id_masks_key_to_depth)
84{
85 for (auto depth = 0u; depth <= SHAMap::kLeafDepth; ++depth)
86 {
87 auto const id = SHAMapNodeID::createID(depth, kTestKey);
88 EXPECT_EQ(id.getDepth(), depth);
89 EXPECT_TRUE(id.isPrefixOf(kTestKey)) << "depth " << depth;
90 }
91}
92
93// The guards below must hold with XRPL_ASSERT compiled out (NDEBUG), so each one
94// has to be a real runtime check rather than an assert.
95
96TEST(SHAMapNodeIDTest, child_of_leaf_depth_id_throws)
97{
98 auto const leafDepthID = SHAMapNodeID::createID(SHAMap::kLeafDepth, kTestKey);
99 ASSERT_EQ(leafDepthID.getDepth(), SHAMap::kLeafDepth);
100 EXPECT_THROW((void)leafDepthID.getChildNodeID(0), std::logic_error);
101}
102
103TEST(SHAMapNodeIDDeathTest, out_of_range_depth_is_clamped)
104{
105 // A depth past kLeafDepth has no mask in depthMask's 65-entry table, so both the constructor
106 // and createID clamp it. createID needs its own clamp: it picks the mask while evaluating the
107 // constructor's argument, so the constructor's clamp cannot cover that read.
108 //
109 // Both clamps are marked UNREACHABLE, which is an assert and therefore fatal wherever asserts
110 // are live. Only a build with them compiled out (or routed to Antithesis's non-fatal handler)
111 // reaches the clamp itself, so that is the only configuration that can assert on the result.
112#if defined(NDEBUG) || defined(ENABLE_VOIDSTAR)
113 for (auto const depth : {SHAMap::kLeafDepth + 1u, 100u, 255u, 256u, 320u})
114 {
115 auto const id = SHAMapNodeID::createID(depth, kTestKey);
116
117 // Clamped to a real depth, not the depth asked for, and not a byte-narrowed version of it:
118 // 256 would otherwise become 0 and name the root, 320 would become 64.
119 EXPECT_EQ(id.getDepth(), SHAMap::kLeafDepth) << "depth " << depth;
120
121 // id_ and depth_ still agree, so the object is usable rather than merely non-crashing.
122 EXPECT_TRUE(id.isPrefixOf(kTestKey)) << "depth " << depth;
123 EXPECT_EQ(id, SHAMapNodeID::createID(SHAMap::kLeafDepth, kTestKey)) << "depth " << depth;
124
125 // The clamp holds through the wire format too, which encodes the depth in one byte.
126 auto const roundTripped = deserializeSHAMapNodeID(id.getRawString());
127 ASSERT_TRUE(roundTripped.has_value()) << "depth " << depth;
128 EXPECT_EQ(roundTripped->getDepth(), SHAMap::kLeafDepth) << "depth " << depth;
129 }
130
131 // The constructor clamps on its own, for the paths that do not go through createID.
132 SHAMapNodeID const direct{SHAMap::kLeafDepth + 1u, UInt256{}};
133 EXPECT_EQ(direct.getDepth(), SHAMap::kLeafDepth);
134#else
135 EXPECT_DEATH(
136 (void)SHAMapNodeID::createID(SHAMap::kLeafDepth + 1u, kTestKey), "depth within tree");
137#endif
138}
139
140TEST(SHAMapNodeIDDeathTest, select_branch_clamps_leaf_depth)
141{
142 // selectBranch's own precondition is depth < kLeafDepth: a depth-64 ID has no nibble left
143 // to select. That makes it unlike the guards above, which have a throw/return reachable
144 // even with XRPL_ASSERT compiled out; selectBranch has no such path, so the two build
145 // configurations have to be tested differently.
146 //
147 // Under ENABLE_VOIDSTAR, XRPL_ASSERT routes to Antithesis's assert_impl, which only records
148 // the hit and returns rather than aborting, even though NDEBUG is undefined there (voidstar
149 // requires a Debug build). So the assert is live in name but never fatal, the same as the
150 // NDEBUG case below.
151 auto const leafDepthID = SHAMapNodeID::createID(SHAMap::kLeafDepth, kTestKey);
152
153#if defined(NDEBUG) || defined(ENABLE_VOIDSTAR)
154 // With the assert compiled out or routed to a non-fatal handler, the clamp is what stands
155 // between this call and reading past the end of the 32-byte key. Clamping means it reads the
156 // same byte, and returns the same branch, as the deepest ID that still has one: depth 63.
157 auto const deepestWithBranchID = SHAMapNodeID::createID(SHAMap::kLeafDepth - 1u, kTestKey);
158 auto const branch = selectBranch(leafDepthID, kTestKey);
159 EXPECT_LT(branch, SHAMap::kBranchFactor);
160 EXPECT_EQ(branch, selectBranch(deepestWithBranchID, kTestKey));
161#else
162 // In a debug build the assert is live and must reject this call outright, in a forked
163 // process so a failure here cannot take down the rest of the suite.
164 EXPECT_DEATH((void)selectBranch(leafDepthID, kTestKey), "depth below leaf depth");
165#endif
166}
167
168TEST(SHAMapNodeIDTest, deserialize_rejects_out_of_range_depth)
169{
170 // getRawString() only serializes a depth already accepted by the constructor's own
171 // assertion, so an out-of-range depth here is built by hand instead.
172 auto serializeWithRawDepth = [](unsigned int depth) {
173 Serializer s;
175 s.add8(static_cast<unsigned char>(depth));
176 return s.getString();
177 };
178
179 for (auto const depth : {65u, 100u, 255u})
180 {
181 EXPECT_FALSE(deserializeSHAMapNodeID(serializeWithRawDepth(depth)).has_value())
182 << "depth " << depth;
183 }
184
185 // A depth-64 ID is legal, since leaves live there, but it has no children.
186 auto const id =
188 ASSERT_TRUE(id.has_value());
189 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) has_value checked above
190 EXPECT_THROW((void)id->getChildNodeID(0), std::logic_error);
191}
192
193} // namespace xrpl::tests
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
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.
unsigned int getDepth() const
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)
constexpr UInt256 kTestKey("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8")
TEST(IntrusiveSharedTest, basics)
Number root(Number f, unsigned d)
BaseUInt< 256 > UInt256
Definition base_uint.h:580
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.