xrpld
Toggle main menu visibility
Loading...
Searching...
No Matches
tests
libxrpl
shamap
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
11
namespace
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.
15
constexpr
UInt256
kTestKey
(
"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"
);
16
17
TEST
(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
25
TEST
(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
38
TEST
(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
54
TEST
(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
59
auto
id
=
SHAMapNodeID
{}.
getChildNodeID
(
selectBranch
(
SHAMapNodeID
{},
kTestKey
));
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
73
TEST
(SHAMapNodeIDTest, leaf_id_from_key_is_prefix_of_that_key)
74
{
75
SHAMapNodeID
const
leaf{
SHAMap::kLeafDepth
,
kTestKey
};
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
83
TEST
(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
96
TEST
(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
103
TEST
(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
140
TEST
(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
168
TEST
(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;
174
s.
addBitString
(
UInt256
{});
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
=
187
deserializeSHAMapNodeID
(
SHAMapNodeID
{
SHAMap::kLeafDepth
,
UInt256
{}}.
getRawString
());
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
xrpl::SHAMapNodeID
Identifies a node inside a SHAMap.
Definition
SHAMapNodeID.h:20
xrpl::SHAMapNodeID::isPrefixOf
bool isPrefixOf(UInt256 const &key) const
Test whether this node ID lies on the path to the given leaf key.
Definition
libxrpl/shamap/SHAMapNodeID.cpp:122
xrpl::SHAMapNodeID::getChildNodeID
SHAMapNodeID getChildNodeID(unsigned int branch) const
Definition
libxrpl/shamap/SHAMapNodeID.cpp:94
xrpl::SHAMapNodeID::createID
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.
Definition
libxrpl/shamap/SHAMapNodeID.cpp:171
xrpl::SHAMapNodeID::getDepth
unsigned int getDepth() const
Definition
SHAMapNodeID.h:44
xrpl::SHAMapNodeID::getRawString
std::string getRawString() const
Definition
libxrpl/shamap/SHAMapNodeID.cpp:85
xrpl::SHAMap::kLeafDepth
static constexpr unsigned int kLeafDepth
The depth of the hash map: data is only present in the leaves.
Definition
SHAMap.h:144
xrpl::SHAMap::kBranchFactor
static constexpr unsigned int kBranchFactor
Number of children each non-leaf node has (the 'radix tree' part of the map).
Definition
SHAMap.h:139
xrpl::Serializer
Definition
Serializer.h:23
xrpl::Serializer::addBitString
int addBitString(BaseUInt< Bits, Tag > const &v)
Definition
Serializer.h:202
xrpl::Serializer::getString
std::string getString() const
Definition
Serializer.h:309
xrpl::Serializer::add8
int add8(unsigned char byteValue)
Definition
libxrpl/protocol/Serializer.cpp:146
std::logic_error
xrpl::tests
Definition
LedgerNodeHelpers_test.cpp:21
xrpl::tests::kTestKey
constexpr UInt256 kTestKey("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8")
xrpl::tests::TEST
TEST(IntrusiveSharedTest, basics)
Definition
IntrusiveShared.cpp:193
xrpl::root
Number root(Number f, unsigned d)
Definition
libxrpl/basics/Number.cpp:1537
xrpl::UInt256
BaseUInt< 256 > UInt256
Definition
base_uint.h:580
xrpl::deserializeSHAMapNodeID
std::optional< SHAMapNodeID > deserializeSHAMapNodeID(void const *data, std::size_t size)
Return an object representing a serialized SHAMap Node ID.
Definition
libxrpl/shamap/SHAMapNodeID.cpp:128
xrpl::selectBranch
unsigned int selectBranch(SHAMapNodeID const &id, UInt256 const &hash)
Returns the branch that would contain the given hash.
Definition
libxrpl/shamap/SHAMapNodeID.cpp:148
stdexcept
Generated by
1.17.0