rippled
Loading...
Searching...
No Matches
SHAMapInnerNode.cpp
1#include <xrpl/basics/IntrusivePointer.ipp>
2#include <xrpl/basics/Slice.h>
3#include <xrpl/basics/contract.h>
4#include <xrpl/basics/spinlock.h>
5#include <xrpl/protocol/HashPrefix.h>
6#include <xrpl/protocol/digest.h>
7#include <xrpl/shamap/SHAMapInnerNode.h>
8#include <xrpl/shamap/SHAMapTreeNode.h>
9#include <xrpl/shamap/detail/TaggedPointer.ipp>
10
11namespace ripple {
12
14 std::uint32_t cowid,
15 std::uint8_t numAllocatedChildren)
16 : SHAMapTreeNode(cowid), hashesAndChildren_(numAllocatedChildren)
17{
18}
19
21
22void
24{
26 // structured bindings can't be captured in c++ 17; use tie instead
27 std::tie(std::ignore, std::ignore, children) =
30 [&](auto branchNum, auto indexNum) { children[indexNum].reset(); });
31}
32
33template <class F>
34void
39
40template <class F>
41void
46
47void
53
59
62{
63 auto const branchCount = getBranchCount();
64 auto const thisIsSparse = !hashesAndChildren_.isDense();
65 auto p = intr_ptr::make_shared<SHAMapInnerNode>(cowid, branchCount);
66 p->hash_ = hash_;
67 p->isBranch_ = isBranch_;
68 p->fullBelowGen_ = fullBelowGen_;
69 SHAMapHash *cloneHashes, *thisHashes;
70 intr_ptr::SharedPtr<SHAMapTreeNode>*cloneChildren, *thisChildren;
71 // structured bindings can't be captured in c++ 17; use tie instead
72 std::tie(std::ignore, cloneHashes, cloneChildren) =
73 p->hashesAndChildren_.getHashesAndChildren();
74 std::tie(std::ignore, thisHashes, thisChildren) =
76
77 if (thisIsSparse)
78 {
79 int cloneChildIndex = 0;
80 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
81 cloneHashes[cloneChildIndex++] = thisHashes[indexNum];
82 });
83 }
84 else
85 {
86 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
87 cloneHashes[branchNum] = thisHashes[indexNum];
88 });
89 }
90
91 spinlock sl(lock_);
92 std::lock_guard lock(sl);
93
94 if (thisIsSparse)
95 {
96 int cloneChildIndex = 0;
97 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
98 cloneChildren[cloneChildIndex++] = thisChildren[indexNum];
99 });
100 }
101 else
102 {
103 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
104 cloneChildren[branchNum] = thisChildren[indexNum];
105 });
106 }
107
108 return p;
109}
110
113 Slice data,
114 SHAMapHash const& hash,
115 bool hashValid)
116{
117 // A full inner node is serialized as 16 256-bit hashes, back to back:
118 if (data.size() != branchFactor * uint256::bytes)
119 Throw<std::runtime_error>("Invalid FI node");
120
121 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
122
123 SerialIter si(data);
124
125 auto hashes = ret->hashesAndChildren_.getHashes();
126
127 for (int i = 0; i < branchFactor; ++i)
128 {
129 hashes[i].as_uint256() = si.getBitString<256>();
130
131 if (hashes[i].isNonZero())
132 ret->isBranch_ |= (1 << i);
133 }
134
135 ret->resizeChildArrays(ret->getBranchCount());
136
137 if (hashValid)
138 ret->hash_ = hash;
139 else
140 ret->updateHash();
141
142 return ret;
143}
144
147{
148 // A compressed inner node is serialized as a series of 33 byte chunks,
149 // representing a one byte "position" and a 256-bit hash:
150 constexpr std::size_t chunkSize = uint256::bytes + 1;
151
152 if (auto const s = data.size();
153 (s % chunkSize != 0) || (s > chunkSize * branchFactor))
154 Throw<std::runtime_error>("Invalid CI node");
155
156 SerialIter si(data);
157
158 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
159
160 auto hashes = ret->hashesAndChildren_.getHashes();
161
162 while (!si.empty())
163 {
164 auto const hash = si.getBitString<256>();
165 auto const pos = si.get8();
166
167 if (pos >= branchFactor)
168 Throw<std::runtime_error>("invalid CI node");
169
170 hashes[pos].as_uint256() = hash;
171
172 if (hashes[pos].isNonZero())
173 ret->isBranch_ |= (1 << pos);
174 }
175
176 ret->resizeChildArrays(ret->getBranchCount());
177 ret->updateHash();
178 return ret;
179}
180
181void
183{
184 uint256 nh;
185 if (isBranch_ != 0)
186 {
188 using beast::hash_append;
190 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
191 nh = static_cast<typename sha512_half_hasher::result_type>(h);
192 }
193 hash_ = SHAMapHash{nh};
194}
195
196void
198{
199 SHAMapHash* hashes;
201 // structured bindings can't be captured in c++ 17; use tie instead
202 std::tie(std::ignore, hashes, children) =
204 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
205 if (auto p = children[indexNum].get())
206 hashes[indexNum] = p->getHash();
207 });
208 updateHash();
209}
210
211void
213{
214 XRPL_ASSERT(
215 !isEmpty(), "ripple::SHAMapInnerNode::serializeForWire : is non-empty");
216
217 // If the node is sparse, then only send non-empty branches:
218 if (getBranchCount() < 12)
219 {
220 // compressed node
221 auto hashes = hashesAndChildren_.getHashes();
222 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
223 s.addBitString(hashes[indexNum].as_uint256());
224 s.add8(branchNum);
225 });
227 }
228 else
229 {
231 [&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
233 }
234}
235
236void
238{
239 XRPL_ASSERT(
240 !isEmpty(),
241 "ripple::SHAMapInnerNode::serializeWithPrefix : is non-empty");
242
245 [&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
246}
247
250{
252 auto hashes = hashesAndChildren_.getHashes();
253 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
254 ret += "\nb";
255 ret += std::to_string(branchNum);
256 ret += " = ";
257 ret += to_string(hashes[indexNum]);
258 });
259 return ret;
260}
261
262// We are modifying an inner node
263void
265{
266 XRPL_ASSERT(
267 (m >= 0) && (m < branchFactor),
268 "ripple::SHAMapInnerNode::setChild : valid branch input");
269 XRPL_ASSERT(cowid_, "ripple::SHAMapInnerNode::setChild : nonzero cowid");
270 XRPL_ASSERT(
271 child.get() != this,
272 "ripple::SHAMapInnerNode::setChild : valid child input");
273
274 auto const dstIsBranch = [&] {
275 if (child)
276 return isBranch_ | (1u << m);
277 else
278 return isBranch_ & ~(1u << m);
279 }();
280
281 auto const dstToAllocate = popcnt16(dstIsBranch);
282 // change hashesAndChildren to remove the element, or make room for the
283 // added element, if necessary
285 std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
286
287 isBranch_ = dstIsBranch;
288
289 if (child)
290 {
291 auto const childIndex = *getChildIndex(m);
292 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
293 hashes[childIndex].zero();
294 children[childIndex] = std::move(child);
295 }
296
297 hash_.zero();
298
299 XRPL_ASSERT(
301 "ripple::SHAMapInnerNode::setChild : maximum branch count");
302}
303
304// finished modifying, now make shareable
305void
307 int m,
309{
310 XRPL_ASSERT(
311 (m >= 0) && (m < branchFactor),
312 "ripple::SHAMapInnerNode::shareChild : valid branch input");
313 XRPL_ASSERT(cowid_, "ripple::SHAMapInnerNode::shareChild : nonzero cowid");
314 XRPL_ASSERT(
315 child, "ripple::SHAMapInnerNode::shareChild : non-null child input");
316 XRPL_ASSERT(
317 child.get() != this,
318 "ripple::SHAMapInnerNode::shareChild : valid child input");
319
320 XRPL_ASSERT(
321 !isEmptyBranch(m),
322 "ripple::SHAMapInnerNode::shareChild : non-empty branch input");
324}
325
328{
329 XRPL_ASSERT(
330 branch >= 0 && branch < branchFactor,
331 "ripple::SHAMapInnerNode::getChildPointer : valid branch input");
332 XRPL_ASSERT(
333 !isEmptyBranch(branch),
334 "ripple::SHAMapInnerNode::getChildPointer : non-empty branch input");
335
336 auto const index = *getChildIndex(branch);
337
338 packed_spinlock sl(lock_, index);
339 std::lock_guard lock(sl);
340 return hashesAndChildren_.getChildren()[index].get();
341}
342
345{
346 XRPL_ASSERT(
347 branch >= 0 && branch < branchFactor,
348 "ripple::SHAMapInnerNode::getChild : valid branch input");
349 XRPL_ASSERT(
350 !isEmptyBranch(branch),
351 "ripple::SHAMapInnerNode::getChild : non-empty branch input");
352
353 auto const index = *getChildIndex(branch);
354
355 packed_spinlock sl(lock_, index);
356 std::lock_guard lock(sl);
357 return hashesAndChildren_.getChildren()[index];
358}
359
360SHAMapHash const&
362{
363 XRPL_ASSERT(
364 (m >= 0) && (m < branchFactor),
365 "ripple::SHAMapInnerNode::getChildHash : valid branch input");
366 if (auto const i = getChildIndex(m))
367 return hashesAndChildren_.getHashes()[*i];
368
369 return zeroSHAMapHash;
370}
371
374 int branch,
376{
377 XRPL_ASSERT(
378 branch >= 0 && branch < branchFactor,
379 "ripple::SHAMapInnerNode::canonicalizeChild : valid branch input");
380 XRPL_ASSERT(
381 node != nullptr,
382 "ripple::SHAMapInnerNode::canonicalizeChild : valid node input");
383 XRPL_ASSERT(
384 !isEmptyBranch(branch),
385 "ripple::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
386 auto const childIndex = *getChildIndex(branch);
387 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
388 XRPL_ASSERT(
389 node->getHash() == hashes[childIndex],
390 "ripple::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
391 "hash do match");
392
393 packed_spinlock sl(lock_, childIndex);
394 std::lock_guard lock(sl);
395
396 if (children[childIndex])
397 {
398 // There is already a node hooked up, return it
399 node = children[childIndex];
400 }
401 else
402 {
403 // Hook this node up
404 children[childIndex] = node;
405 }
406 return node;
407}
408
409void
411{
412 [[maybe_unused]] unsigned count = 0;
413 auto [numAllocated, hashes, children] =
415
416 if (numAllocated != branchFactor)
417 {
418 auto const branchCount = getBranchCount();
419 for (int i = 0; i < branchCount; ++i)
420 {
421 XRPL_ASSERT(
422 hashes[i].isNonZero(),
423 "ripple::SHAMapInnerNode::invariants : nonzero hash in branch");
424 if (children[i] != nullptr)
425 children[i]->invariants();
426 ++count;
427 }
428 }
429 else
430 {
431 for (int i = 0; i < branchFactor; ++i)
432 {
433 if (hashes[i].isNonZero())
434 {
435 XRPL_ASSERT(
436 (isBranch_ & (1 << i)),
437 "ripple::SHAMapInnerNode::invariants : valid branch when "
438 "nonzero hash");
439 if (children[i] != nullptr)
440 children[i]->invariants();
441 ++count;
442 }
443 else
444 {
445 XRPL_ASSERT(
446 (isBranch_ & (1 << i)) == 0,
447 "ripple::SHAMapInnerNode::invariants : valid branch when "
448 "zero hash");
449 }
450 }
451 }
452
453 if (!is_root)
454 {
455 XRPL_ASSERT(
457 "ripple::SHAMapInnerNode::invariants : nonzero hash");
458 XRPL_ASSERT(
459 count >= 1, "ripple::SHAMapInnerNode::invariants : minimum count");
460 }
461 XRPL_ASSERT(
462 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
463 "ripple::SHAMapInnerNode::invariants : hash and count do match");
464}
465
466} // namespace ripple
bool isNonZero() const
Definition SHAMapHash.h:40
uint256 const & as_uint256() const
Definition SHAMapHash.h:25
bool isZero() const
Definition SHAMapHash.h:35
std::optional< int > getChildIndex(int i) const
Get the child's index inside the hashes or children array (stored in hashesAndChildren_.
SHAMapInnerNode(std::uint32_t cowid, std::uint8_t numAllocatedChildren=2)
void setChild(int m, intr_ptr::SharedPtr< SHAMapTreeNode > child)
static intr_ptr::SharedPtr< SHAMapTreeNode > makeCompressedInner(Slice data)
intr_ptr::SharedPtr< SHAMapTreeNode > clone(std::uint32_t cowid) const override
Make a copy of this node, setting the owner.
static constexpr unsigned int branchFactor
Each inner node has 16 children (the 'radix tree' part of the map)
bool isEmptyBranch(int m) const
void iterNonEmptyChildIndexes(F &&f) const
Call the f callback for all non-empty branches.
void serializeWithPrefix(Serializer &) const override
Serialize the node in a format appropriate for hashing.
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 updateHash() override
Recalculate the hash of this node.
intr_ptr::SharedPtr< SHAMapTreeNode > getChild(int branch)
void partialDestructor() override
void shareChild(int m, intr_ptr::SharedPtr< SHAMapTreeNode > const &child)
SHAMapHash const & getChildHash(int m) const
void invariants(bool is_root=false) const override
std::string getString(SHAMapNodeID const &) const override
intr_ptr::SharedPtr< SHAMapTreeNode > canonicalizeChild(int branch, intr_ptr::SharedPtr< SHAMapTreeNode > node)
TaggedPointer hashesAndChildren_
Opaque type that contains the hashes array (array of type SHAMapHash) and the children array (array o...
void serializeForWire(Serializer &) const override
Serialize the node in a format appropriate for sending over the wire.
void updateHashDeep()
Recalculate the hash of all children and this node.
SHAMapTreeNode * getChildPointer(int branch)
std::atomic< std::uint16_t > lock_
A bitlock for the children of this node, with one bit per child.
static intr_ptr::SharedPtr< SHAMapTreeNode > makeFullInner(Slice data, SHAMapHash const &hash, bool hashValid)
Identifies a node inside a SHAMap.
virtual std::string getString(SHAMapNodeID const &) const
std::uint32_t cowid_
Determines the owning SHAMap, if any.
base_uint< Bits, Tag > getBitString()
Definition Serializer.h:440
unsigned char get8()
std::size_t empty() const noexcept
Definition Serializer.h:348
int addBitString(base_uint< Bits, Tag > const &v)
Definition Serializer.h:112
int add8(unsigned char i)
A shared intrusive pointer class that supports weak pointers.
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:27
TaggedPointer is a combination of a pointer and a mask stored in the lowest two bits.
void iterNonEmptyChildIndexes(std::uint16_t isBranch, F &&f) const
Call the f callback for all non-empty branches.
std::optional< int > getChildIndex(std::uint16_t isBranch, int i) const
Get the child's index inside the hashes or children array (which may or may not be sparse).
std::tuple< std::uint8_t, SHAMapHash *, intr_ptr::SharedPtr< SHAMapTreeNode > * > getHashesAndChildren() const
Get the number of elements in each array and a pointer to the start of each array.
SHAMapHash * getHashes() const
Get the hashes array.
std::uint8_t capacity() const
Get the number of elements allocated for each array.
void iterChildren(std::uint16_t isBranch, F &&f) const
Call the f callback for all 16 (branchFactor) branches - even if the branch is empty.
bool isDense() const
Check if the arrays have a dense format.
intr_ptr::SharedPtr< SHAMapTreeNode > * getChildren() const
Get the children array.
static std::size_t constexpr bytes
Definition base_uint.h:89
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition spinlock.h:76
A spinlock implemented on top of an atomic integer.
Definition spinlock.h:152
std::uint32_t cowid() const
Returns the SHAMap that owns this node.
T is_same_v
std::enable_if_t< is_contiguously_hashable< T, Hasher >::value > hash_append(Hasher &h, T const &t) noexcept
Logically concatenate input data to a Hasher.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
void hash_append(Hasher &h, Slice const &v)
Definition Slice.h:180
static constexpr unsigned char const wireTypeCompressedInner
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
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 wireTypeInner
@ innerNode
inner node in V1 tree
int popcnt16(std::uint16_t a)
Returns the SHA512-Half digest of a message.
Definition digest.h:153
T tie(T... args)
T to_string(T... args)