xrpld
Loading...
Searching...
No Matches
SHAMapInnerNode.cpp
1#include <xrpl/shamap/SHAMapInnerNode.h>
2
3#include <xrpl/basics/IntrusivePointer.h> // IWYU pragma: keep
4#include <xrpl/basics/IntrusivePointer.ipp> // IWYU pragma: keep
5#include <xrpl/basics/SHAMapHash.h>
6#include <xrpl/basics/Slice.h>
7#include <xrpl/basics/base_uint.h>
8#include <xrpl/basics/contract.h>
9#include <xrpl/basics/spinlock.h>
10#include <xrpl/beast/utility/instrumentation.h>
11#include <xrpl/protocol/HashPrefix.h>
12#include <xrpl/protocol/Serializer.h>
13#include <xrpl/protocol/digest.h>
14#include <xrpl/shamap/SHAMapNodeID.h>
15#include <xrpl/shamap/SHAMapTreeNode.h>
16#include <xrpl/shamap/detail/TaggedPointer.h>
17#include <xrpl/shamap/detail/TaggedPointer.ipp>
18
19#include <cstddef>
20#include <cstdint>
21#include <mutex>
22#include <optional>
23#include <stdexcept>
24#include <string>
25#include <tuple>
26#include <utility>
27
28namespace xrpl {
29
34
36
37void
39{
40 SHAMapTreeNodePtr* children = nullptr;
41 // structured bindings can't be captured in c++ 17; use tie instead
42 std::tie(std::ignore, std::ignore, children) = hashesAndChildren_.getHashesAndChildren();
43 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { children[indexNum].reset(); });
44}
45
46template <class F>
47void
52
53template <class F>
54void
56{
57 hashesAndChildren_.iterNonEmptyChildIndexes(isBranch_, std::forward<F>(f));
58}
59
60void
65
68{
69 return hashesAndChildren_.getChildIndex(isBranch_, i);
70}
71
74{
75 auto const branchCount = getBranchCount();
76 auto const thisIsSparse = !hashesAndChildren_.isDense();
77 auto p = intr_ptr::makeShared<SHAMapInnerNode>(cowid, branchCount);
78 p->hash_ = hash_;
79 p->isBranch_ = isBranch_;
80 p->fullBelowGen_ = fullBelowGen_;
81 SHAMapHash* cloneHashes = nullptr;
82 SHAMapHash* thisHashes = nullptr;
83 SHAMapTreeNodePtr* cloneChildren = nullptr;
84 SHAMapTreeNodePtr* thisChildren = nullptr;
85 // structured bindings can't be captured in c++ 17; use tie instead
86 std::tie(std::ignore, cloneHashes, cloneChildren) =
87 p->hashesAndChildren_.getHashesAndChildren();
88 std::tie(std::ignore, thisHashes, thisChildren) = hashesAndChildren_.getHashesAndChildren();
89
90 if (thisIsSparse)
91 {
92 int cloneChildIndex = 0;
93 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
94 cloneHashes[cloneChildIndex++] = thisHashes[indexNum];
95 });
96 }
97 else
98 {
100 [&](auto branchNum, auto indexNum) { cloneHashes[branchNum] = thisHashes[indexNum]; });
101 }
102
103 Spinlock sl(lock_);
104 std::scoped_lock const lock(sl);
105
106 if (thisIsSparse)
107 {
108 int cloneChildIndex = 0;
109 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
110 cloneChildren[cloneChildIndex++] = thisChildren[indexNum];
111 });
112 }
113 else
114 {
115 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
116 cloneChildren[branchNum] = thisChildren[indexNum];
117 });
118 }
119
120 return p;
121}
122
124SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid)
125{
126 // A full inner node is serialized as 16 256-bit hashes, back to back:
127 if (data.size() != kBranchFactor * uint256::kBytes)
128 Throw<std::runtime_error>("Invalid FI node");
129
131
132 SerialIter si(data);
133
134 auto hashes = ret->hashesAndChildren_.getHashes();
135
136 for (int i = 0; i < kBranchFactor; ++i)
137 {
138 hashes[i].asUInt256() = si.getBitString<256>();
139
140 if (hashes[i].isNonZero())
141 ret->isBranch_ |= (1 << i);
142 }
143
144 ret->resizeChildArrays(ret->getBranchCount());
145
146 if (hashValid)
147 {
148 ret->hash_ = hash;
149 }
150 else
151 {
152 ret->updateHash();
153 }
154
155 return ret;
156}
157
160{
161 // A compressed inner node is serialized as a series of 33 byte chunks,
162 // representing a one byte "position" and a 256-bit hash:
163 static constexpr std::size_t kChunkSize = uint256::kBytes + 1;
164
165 if (auto const s = data.size(); (s % kChunkSize != 0) || (s > kChunkSize * kBranchFactor))
166 Throw<std::runtime_error>("Invalid CI node");
167
168 SerialIter si(data);
169
171
172 auto hashes = ret->hashesAndChildren_.getHashes();
173
174 while (!si.empty())
175 {
176 auto const hash = si.getBitString<256>();
177 auto const pos = si.get8();
178
179 if (pos >= kBranchFactor)
180 Throw<std::runtime_error>("invalid CI node");
181
182 hashes[pos].asUInt256() = hash;
183
184 if (hashes[pos].isNonZero())
185 ret->isBranch_ |= (1 << pos);
186 }
187
188 ret->resizeChildArrays(ret->getBranchCount());
189 ret->updateHash();
190 return ret;
191}
192
193void
195{
196 uint256 nh;
197 if (isBranch_ != 0)
198 {
200 using beast::hash_append;
202 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
203 nh = static_cast<sha512_half_hasher::result_type>(h);
204 }
205 hash_ = SHAMapHash{nh};
206}
207
208void
210{
211 SHAMapHash* hashes = nullptr;
212 SHAMapTreeNodePtr* children = nullptr;
213 // structured bindings can't be captured in c++ 17; use tie instead
214 std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren();
215 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
216 if (auto p = children[indexNum].get())
217 hashes[indexNum] = p->getHash();
218 });
219 updateHash();
220}
221
222void
224{
225 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeForWire : is non-empty");
226
227 // If the node is sparse, then only send non-empty branches:
228 if (getBranchCount() < 12)
229 {
230 // compressed node
231 auto hashes = hashesAndChildren_.getHashes();
232 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
233 s.addBitString(hashes[indexNum].asUInt256());
234 s.add8(branchNum);
235 });
237 }
238 else
239 {
240 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.asUInt256()); });
242 }
243}
244
245void
247{
248 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeWithPrefix : is non-empty");
249
251 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.asUInt256()); });
252}
253
256{
258 auto hashes = hashesAndChildren_.getHashes();
259 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
260 ret += "\nb";
261 ret += std::to_string(branchNum);
262 ret += " = ";
263 ret += to_string(hashes[indexNum]);
264 });
265 return ret;
266}
267
268// We are modifying an inner node
269void
271{
272 XRPL_ASSERT(
273 (m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input");
274 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::setChild : nonzero cowid");
275 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::setChild : valid child input");
276
277 auto const dstIsBranch = [&] {
278 if (child)
279 {
280 return isBranch_ | (1u << m);
281 }
282
283 return isBranch_ & ~(1u << m);
284 }();
285
286 auto const dstToAllocate = popcnt16(dstIsBranch);
287 // change hashesAndChildren to remove the element, or make room for the
288 // added element, if necessary
290 TaggedPointer(std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
291
292 isBranch_ = dstIsBranch;
293
294 if (child)
295 {
296 auto const childIndex =
297 *getChildIndex(m); // NOLINT(bugprone-unchecked-optional-access) isBranch_ set above
298 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
299 hashes[childIndex].zero();
300 children[childIndex] = std::move(child);
301 }
302
303 hash_.zero();
304
305 XRPL_ASSERT(
306 getBranchCount() <= hashesAndChildren_.capacity(),
307 "xrpl::SHAMapInnerNode::setChild : maximum branch count");
308}
309
310// finished modifying, now make shareable
311void
313{
314 XRPL_ASSERT(
315 (m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input");
316 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::shareChild : nonzero cowid");
317 XRPL_ASSERT(child, "xrpl::SHAMapInnerNode::shareChild : non-null child input");
318 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::shareChild : valid child input");
319
320 XRPL_ASSERT(!isEmptyBranch(m), "xrpl::SHAMapInnerNode::shareChild : non-empty branch input");
321 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) assert above
322 hashesAndChildren_.getChildren()[*getChildIndex(m)] = child;
323}
324
327{
328 XRPL_ASSERT(
329 branch >= 0 && branch < kBranchFactor,
330 "xrpl::SHAMapInnerNode::getChildPointer : valid branch input");
331 XRPL_ASSERT(
332 !isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChildPointer : non-empty branch input");
333
334 auto const index =
335 *getChildIndex(branch); // NOLINT(bugprone-unchecked-optional-access) assert above
336
337 PackedSpinlock sl(lock_, index);
338 std::scoped_lock const lock(sl);
339 return hashesAndChildren_.getChildren()[index].get();
340}
341
344{
345 XRPL_ASSERT(
346 branch >= 0 && branch < kBranchFactor,
347 "xrpl::SHAMapInnerNode::getChild : valid branch input");
348 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChild : non-empty branch input");
349
350 auto const index =
351 *getChildIndex(branch); // NOLINT(bugprone-unchecked-optional-access) assert above
352
353 PackedSpinlock sl(lock_, index);
354 std::scoped_lock const lock(sl);
355 return hashesAndChildren_.getChildren()[index];
356}
357
358SHAMapHash const&
360{
361 XRPL_ASSERT(
362 (m >= 0) && (m < kBranchFactor),
363 "xrpl::SHAMapInnerNode::getChildHash : valid branch input");
364 if (auto const i = getChildIndex(m))
365 return hashesAndChildren_.getHashes()[*i];
366
367 return kZeroShaMapHash;
368}
369
372{
373 XRPL_ASSERT(
374 branch >= 0 && branch < kBranchFactor,
375 "xrpl::SHAMapInnerNode::canonicalizeChild : valid branch input");
376 XRPL_ASSERT(node != nullptr, "xrpl::SHAMapInnerNode::canonicalizeChild : valid node input");
377 XRPL_ASSERT(
378 !isEmptyBranch(branch),
379 "xrpl::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
380 auto const childIndex =
381 *getChildIndex(branch); // NOLINT(bugprone-unchecked-optional-access) assert above
382 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
383 XRPL_ASSERT(
384 node->getHash() == hashes[childIndex],
385 "xrpl::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
386 "hash do match");
387
388 PackedSpinlock sl(lock_, childIndex);
389 std::scoped_lock const lock(sl);
390
391 if (children[childIndex])
392 {
393 // There is already a node hooked up, return it
394 node = children[childIndex];
395 }
396 else
397 {
398 // Hook this node up
399 children[childIndex] = node;
400 }
401 return node;
402}
403
404void
406{
407 [[maybe_unused]] unsigned count = 0;
408 auto [numAllocated, hashes, children] = hashesAndChildren_.getHashesAndChildren();
409
410 if (numAllocated != kBranchFactor)
411 {
412 auto const branchCount = getBranchCount();
413 for (int i = 0; i < branchCount; ++i)
414 {
415 XRPL_ASSERT(
416 hashes[i].isNonZero(),
417 "xrpl::SHAMapInnerNode::invariants : nonzero hash in branch");
418 if (children[i] != nullptr)
419 children[i]->invariants();
420 ++count;
421 }
422 }
423 else
424 {
425 for (int i = 0; i < kBranchFactor; ++i)
426 {
427 if (hashes[i].isNonZero())
428 {
429 XRPL_ASSERT(
430 (isBranch_ & (1 << i)),
431 "xrpl::SHAMapInnerNode::invariants : valid branch when "
432 "nonzero hash");
433 if (children[i] != nullptr)
434 children[i]->invariants();
435 ++count;
436 }
437 else
438 {
439 XRPL_ASSERT(
440 (isBranch_ & (1 << i)) == 0,
441 "xrpl::SHAMapInnerNode::invariants : valid branch when "
442 "zero hash");
443 }
444 }
445 }
446
447 if (!isRoot)
448 {
449 XRPL_ASSERT(hash_.isNonZero(), "xrpl::SHAMapInnerNode::invariants : nonzero hash");
450 XRPL_ASSERT(count >= 1, "xrpl::SHAMapInnerNode::invariants : minimum count");
451 }
452 XRPL_ASSERT(
453 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
454 "xrpl::SHAMapInnerNode::invariants : hash and count do match");
455}
456
457} // namespace xrpl
static constexpr std::size_t kBytes
Definition base_uint.h:89
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition spinlock.h:75
uint256 const & asUInt256() const
Definition SHAMapHash.h:24
void serializeWithPrefix(Serializer &) const override
Serialize the node in a format appropriate for hashing.
void updateHash() override
Recalculate the hash of this node.
TaggedPointer hashesAndChildren_
Opaque type that contains the hashes array (array of type SHAMapHash) and the children array (array o...
static constexpr unsigned int kBranchFactor
Each inner node has 16 children (the 'radix tree' part of the map).
SHAMapHash const & getChildHash(int m) const
~SHAMapInnerNode() override
void updateHashDeep()
Recalculate the hash of all children and this node.
void shareChild(int m, SHAMapTreeNodePtr const &child)
void iterNonEmptyChildIndexes(F &&f) const
Call the f callback for all non-empty branches.
SHAMapTreeNodePtr clone(std::uint32_t cowid) const override
Make a copy of this node, setting the owner.
SHAMapTreeNodePtr getChild(int branch)
SHAMapTreeNodePtr canonicalizeChild(int branch, SHAMapTreeNodePtr node)
SHAMapTreeNode * getChildPointer(int branch)
static SHAMapTreeNodePtr makeCompressedInner(Slice data)
std::string getString(SHAMapNodeID const &) const override
SHAMapInnerNode(std::uint32_t cowid, std::uint8_t numAllocatedChildren=2)
void setChild(int m, SHAMapTreeNodePtr child)
void iterChildren(F &&f) const
Call the f callback for all 16 (branchFactor) branches - even if the branch is empty.
void resizeChildArrays(std::uint8_t toAllocate)
Convert arrays stored in hashesAndChildren_ so they can store the requested number of children.
void invariants(bool isRoot=false) const override
std::uint32_t fullBelowGen_
void partialDestructor() override
std::atomic< std::uint16_t > lock_
A bitlock for the children of this node, with one bit per child.
void serializeForWire(Serializer &) const override
Serialize the node in a format appropriate for sending over the wire.
std::optional< int > getChildIndex(int i) const
Get the child's index inside the hashes or children array (stored in hashesAndChildren_.
static SHAMapTreeNodePtr makeFullInner(Slice data, SHAMapHash const &hash, bool hashValid)
bool isEmptyBranch(int m) const
Identifies a node inside a SHAMap.
std::uint32_t cowid_
Determines the owning SHAMap, if any.
SHAMapTreeNode(std::uint32_t cowid) noexcept
Construct a node.
virtual std::string getString(SHAMapNodeID const &) const
BaseUInt< Bits, Tag > getBitString()
Definition Serializer.h:432
bool empty() const noexcept
Definition Serializer.h:340
unsigned char get8()
int addBitString(BaseUInt< Bits, Tag > const &v)
Definition Serializer.h:105
int add8(unsigned char i)
void reset()
Set the pointer to null, decrement the strong count, and run the appropriate release action.
An immutable linear range of bytes.
Definition Slice.h:26
A spinlock implemented on top of an atomic integer.
Definition spinlock.h:150
TaggedPointer is a combination of a pointer and a mask stored in the lowest two bits.
T forward(T... args)
std::uint32_t cowid() const
Returns the SHAMap that owns this node.
std::enable_if_t< IsContiguouslyHashable< T, Hasher >::value > hash_append(Hasher &h, T const &t) noexcept
Logically concatenate input data to a Hasher.
SharedPtr< T > makeShared(A &&... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
intr_ptr::SharedPtr< SHAMapTreeNode > SHAMapTreeNodePtr
detail::BasicSha512HalfHasher< false > sha512_half_hasher
Definition digest.h:194
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
static constexpr unsigned char const kWireTypeCompressedInner
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
int popcnt16(std::uint16_t a)
void hash_append(Hasher &h, Slice const &v)
Definition Slice.h:175
@ InnerNode
inner node in V1 tree
Definition HashPrefix.h:45
static constexpr unsigned char const kWireTypeInner
BaseUInt< 256 > uint256
Definition base_uint.h:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
T tie(T... args)
T to_string(T... args)