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
67SHAMapInnerNode::getChildIndex(unsigned int i) const
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 auto cloneChildIndex = 0u;
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 auto cloneChildIndex = 0u;
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 (auto i = 0u; i < kBranchFactor; ++i)
137 {
138 hashes[i].asUInt256() = si.getBitString<256>();
139
140 if (hashes[i].isNonZero())
141 ret->isBranch_ |= (1u << 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_ |= (1u << 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<Sha512HalfHasher::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(branch < kBranchFactor, "xrpl::SHAMapInnerNode::setChild : valid branch input");
273 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::setChild : nonzero cowid");
274 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::setChild : valid child input");
275
276 auto const dstIsBranch = [&] {
277 if (child)
278 {
279 return isBranch_ | (1u << branch);
280 }
281
282 return isBranch_ & ~(1u << branch);
283 }();
284
285 auto const dstToAllocate = popcnt16(dstIsBranch);
286 // change hashesAndChildren to remove the element, or make room for the
287 // added element, if necessary
289 TaggedPointer(std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
290
291 isBranch_ = dstIsBranch;
292
293 if (child)
294 {
295 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) isBranch_ set above
296 auto const childIndex = *getChildIndex(branch);
297 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
298 hashes[childIndex].zero();
299 children[childIndex] = std::move(child);
300 }
301
302 hash_.zero();
303
304 XRPL_ASSERT(
305 getBranchCount() <= hashesAndChildren_.capacity(),
306 "xrpl::SHAMapInnerNode::setChild : maximum branch count");
307}
308
309// finished modifying, now make shareable
310void
311SHAMapInnerNode::shareChild(unsigned int branch, SHAMapTreeNodePtr const& child)
312{
313 XRPL_ASSERT(branch < kBranchFactor, "xrpl::SHAMapInnerNode::shareChild : valid branch input");
314 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::shareChild : nonzero cowid");
315 XRPL_ASSERT(child, "xrpl::SHAMapInnerNode::shareChild : non-null child input");
316 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::shareChild : valid child input");
317
318 XRPL_ASSERT(
319 !isEmptyBranch(branch), "xrpl::SHAMapInnerNode::shareChild : non-empty branch input");
320 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) assert above
321 hashesAndChildren_.getChildren()[*getChildIndex(branch)] = child;
322}
323
326{
327 XRPL_ASSERT(
328 branch < kBranchFactor, "xrpl::SHAMapInnerNode::getChildPointer : valid branch input");
329 XRPL_ASSERT(
330 !isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChildPointer : non-empty branch input");
331
332 auto const index =
333 *getChildIndex(branch); // NOLINT(bugprone-unchecked-optional-access) assert above
334
335 PackedSpinlock sl(lock_, index);
336 std::scoped_lock const lock(sl);
337 return hashesAndChildren_.getChildren()[index].get();
338}
339
341SHAMapInnerNode::getChild(unsigned int branch)
342{
343 XRPL_ASSERT(branch < kBranchFactor, "xrpl::SHAMapInnerNode::getChild : valid branch input");
344 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChild : non-empty branch input");
345
346 auto const index =
347 *getChildIndex(branch); // NOLINT(bugprone-unchecked-optional-access) assert above
348
349 PackedSpinlock sl(lock_, index);
350 std::scoped_lock const lock(sl);
351 return hashesAndChildren_.getChildren()[index];
352}
353
354SHAMapHash const&
355SHAMapInnerNode::getChildHash(unsigned int branch) const
356{
357 XRPL_ASSERT(branch < kBranchFactor, "xrpl::SHAMapInnerNode::getChildHash : valid branch input");
358 if (auto const i = getChildIndex(branch))
359 return hashesAndChildren_.getHashes()[*i];
360
361 return kZeroShaMapHash;
362}
363
366{
367 XRPL_ASSERT(
368 branch < kBranchFactor, "xrpl::SHAMapInnerNode::canonicalizeChild : valid branch input");
369 XRPL_ASSERT(node != nullptr, "xrpl::SHAMapInnerNode::canonicalizeChild : valid node input");
370 XRPL_ASSERT(
371 !isEmptyBranch(branch),
372 "xrpl::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
373 auto const childIndex =
374 *getChildIndex(branch); // NOLINT(bugprone-unchecked-optional-access) assert above
375 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
376 XRPL_ASSERT(
377 node->getHash() == hashes[childIndex],
378 "xrpl::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
379 "hash do match");
380
381 PackedSpinlock sl(lock_, childIndex);
382 std::scoped_lock const lock(sl);
383
384 if (children[childIndex])
385 {
386 // There is already a node hooked up, return it
387 node = children[childIndex];
388 }
389 else
390 {
391 // Hook this node up
392 children[childIndex] = node;
393 }
394 return node;
395}
396
397void
399{
400 [[maybe_unused]] unsigned count = 0;
401 auto [numAllocated, hashes, children] = hashesAndChildren_.getHashesAndChildren();
402
403 if (numAllocated != kBranchFactor)
404 {
405 auto const branchCount = getBranchCount();
406 for (auto i = 0u; i < branchCount; ++i)
407 {
408 XRPL_ASSERT(
409 hashes[i].isNonZero(),
410 "xrpl::SHAMapInnerNode::invariants : nonzero hash in branch");
411 if (children[i] != nullptr)
412 children[i]->invariants();
413 ++count;
414 }
415 }
416 else
417 {
418 for (auto i = 0u; i < kBranchFactor; ++i)
419 {
420 if (hashes[i].isNonZero())
421 {
422 XRPL_ASSERT(
423 (isBranch_ & (1u << i)),
424 "xrpl::SHAMapInnerNode::invariants : valid branch when "
425 "nonzero hash");
426 if (children[i] != nullptr)
427 children[i]->invariants();
428 ++count;
429 }
430 else
431 {
432 XRPL_ASSERT(
433 (isBranch_ & (1u << i)) == 0u,
434 "xrpl::SHAMapInnerNode::invariants : valid branch when "
435 "zero hash");
436 }
437 }
438 }
439
440 if (!isRoot)
441 {
442 XRPL_ASSERT(hash_.isNonZero(), "xrpl::SHAMapInnerNode::invariants : nonzero hash");
443 XRPL_ASSERT(count >= 1, "xrpl::SHAMapInnerNode::invariants : minimum count");
444 }
445 XRPL_ASSERT(
446 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
447 "xrpl::SHAMapInnerNode::invariants : hash and count do match");
448}
449
450} // namespace xrpl
static constexpr std::size_t kBytes
Definition base_uint.h:100
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition spinlock.h:78
UInt256 const & asUInt256() const
Definition SHAMapHash.h:26
std::optional< unsigned int > getChildIndex(unsigned int i) const
Get the child's index inside the hashes or children array (stored in hashesAndChildren_.
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...
SHAMapHash const & getChildHash(unsigned int branch) const
unsigned int getBranchCount() const
static constexpr unsigned int kBranchFactor
Each inner node has 16 children (the 'radix tree' part of the map).
~SHAMapInnerNode() override
void updateHashDeep()
Recalculate the hash of all children and this node.
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.
void shareChild(unsigned int branch, SHAMapTreeNodePtr const &child)
static SHAMapTreeNodePtr makeCompressedInner(Slice data)
std::string getString(SHAMapNodeID const &) const override
SHAMapInnerNode(std::uint32_t cowid, std::uint8_t numAllocatedChildren=2)
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
void setChild(unsigned int branch, SHAMapTreeNodePtr child)
std::uint32_t fullBelowGen_
void partialDestructor() override
SHAMapTreeNodePtr getChild(unsigned int branch)
std::atomic< std::uint16_t > lock_
A bitlock for the children of this node, with one bit per child.
SHAMapTreeNode * getChildPointer(unsigned int branch)
void serializeForWire(Serializer &) const override
Serialize the node in a format appropriate for sending over the wire.
SHAMapTreeNodePtr canonicalizeChild(unsigned int branch, SHAMapTreeNodePtr node)
static SHAMapTreeNodePtr makeFullInner(Slice data, SHAMapHash const &hash, bool hashValid)
bool isEmptyBranch(unsigned int branch) 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:597
bool empty() const noexcept
Definition Serializer.h:499
int addBitString(BaseUInt< Bits, Tag > const &v)
Definition Serializer.h:202
int add8(unsigned char byteValue)
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:28
A spinlock implemented on top of an atomic integer.
Definition spinlock.h:155
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.
void 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
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:657
BaseUInt< 256 > UInt256
Definition base_uint.h:580
unsigned int popcnt16(std::uint16_t a)
detail::BasicSha512HalfHasher< false > Sha512HalfHasher
Definition digest.h:203
void hash_append(Hasher &h, Slice const &v)
Definition Slice.h:194
@ InnerNode
inner node in V1 tree
Definition HashPrefix.h:54
static constexpr unsigned char const kWireTypeInner
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T tie(T... args)
T to_string(T... args)