rippled
Loading...
Searching...
No Matches
SHAMapInnerNode.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/shamap/SHAMapInnerNode.h>
21
22#include <xrpld/shamap/SHAMapTreeNode.h>
23#include <xrpld/shamap/detail/TaggedPointer.ipp>
24#include <xrpl/basics/Log.h>
25#include <xrpl/basics/Slice.h>
26#include <xrpl/basics/contract.h>
27#include <xrpl/basics/spinlock.h>
28#include <xrpl/beast/core/LexicalCast.h>
29#include <xrpl/protocol/HashPrefix.h>
30#include <xrpl/protocol/digest.h>
31
32#include <algorithm>
33#include <iterator>
34#include <utility>
35
36namespace ripple {
37
39 std::uint32_t cowid,
40 std::uint8_t numAllocatedChildren)
41 : SHAMapTreeNode(cowid), hashesAndChildren_(numAllocatedChildren)
42{
43}
44
46
47template <class F>
48void
50{
51 hashesAndChildren_.iterChildren(isBranch_, std::forward<F>(f));
52}
53
54template <class F>
55void
57{
59}
60
61void
63{
65 TaggedPointer(std::move(hashesAndChildren_), isBranch_, toAllocate);
66}
67
70{
72}
73
76{
77 auto const branchCount = getBranchCount();
78 auto const thisIsSparse = !hashesAndChildren_.isDense();
79 auto p = std::make_shared<SHAMapInnerNode>(cowid, branchCount);
80 p->hash_ = hash_;
81 p->isBranch_ = isBranch_;
82 p->fullBelowGen_ = fullBelowGen_;
83 SHAMapHash *cloneHashes, *thisHashes;
84 std::shared_ptr<SHAMapTreeNode>*cloneChildren, *thisChildren;
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) =
90
91 if (thisIsSparse)
92 {
93 int cloneChildIndex = 0;
94 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
95 cloneHashes[cloneChildIndex++] = thisHashes[indexNum];
96 });
97 }
98 else
99 {
100 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
101 cloneHashes[branchNum] = thisHashes[indexNum];
102 });
103 }
104
105 spinlock sl(lock_);
106 std::lock_guard lock(sl);
107
108 if (thisIsSparse)
109 {
110 int cloneChildIndex = 0;
111 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
112 cloneChildren[cloneChildIndex++] = thisChildren[indexNum];
113 });
114 }
115 else
116 {
117 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
118 cloneChildren[branchNum] = thisChildren[indexNum];
119 });
120 }
121
122 return p;
123}
124
127 Slice data,
128 SHAMapHash const& hash,
129 bool hashValid)
130{
131 // A full inner node is serialized as 16 256-bit hashes, back to back:
132 if (data.size() != branchFactor * uint256::bytes)
133 Throw<std::runtime_error>("Invalid FI node");
134
135 auto ret = std::make_shared<SHAMapInnerNode>(0, branchFactor);
136
137 SerialIter si(data);
138
139 auto hashes = ret->hashesAndChildren_.getHashes();
140
141 for (int i = 0; i < branchFactor; ++i)
142 {
143 hashes[i].as_uint256() = si.getBitString<256>();
144
145 if (hashes[i].isNonZero())
146 ret->isBranch_ |= (1 << i);
147 }
148
149 ret->resizeChildArrays(ret->getBranchCount());
150
151 if (hashValid)
152 ret->hash_ = hash;
153 else
154 ret->updateHash();
155
156 return ret;
157}
158
161{
162 // A compressed inner node is serialized as a series of 33 byte chunks,
163 // representing a one byte "position" and a 256-bit hash:
164 constexpr std::size_t chunkSize = uint256::bytes + 1;
165
166 if (auto const s = data.size();
167 (s % chunkSize != 0) || (s > chunkSize * branchFactor))
168 Throw<std::runtime_error>("Invalid CI node");
169
170 SerialIter si(data);
171
172 auto ret = std::make_shared<SHAMapInnerNode>(0, branchFactor);
173
174 auto hashes = ret->hashesAndChildren_.getHashes();
175
176 while (!si.empty())
177 {
178 auto const hash = si.getBitString<256>();
179 auto const pos = si.get8();
180
181 if (pos >= branchFactor)
182 Throw<std::runtime_error>("invalid CI node");
183
184 hashes[pos].as_uint256() = hash;
185
186 if (hashes[pos].isNonZero())
187 ret->isBranch_ |= (1 << pos);
188 }
189
190 ret->resizeChildArrays(ret->getBranchCount());
191 ret->updateHash();
192 return ret;
193}
194
195void
197{
198 uint256 nh;
199 if (isBranch_ != 0)
200 {
202 using beast::hash_append;
204 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
205 nh = static_cast<typename sha512_half_hasher::result_type>(h);
206 }
207 hash_ = SHAMapHash{nh};
208}
209
210void
212{
213 SHAMapHash* hashes;
215 // structured bindings can't be captured in c++ 17; use tie instead
216 std::tie(std::ignore, hashes, children) =
218 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
219 if (children[indexNum] != nullptr)
220 hashes[indexNum] = children[indexNum]->getHash();
221 });
222 updateHash();
223}
224
225void
227{
228 XRPL_ASSERT(
229 !isEmpty(), "ripple::SHAMapInnerNode::serializeForWire : is non-empty");
230
231 // If the node is sparse, then only send non-empty branches:
232 if (getBranchCount() < 12)
233 {
234 // compressed node
235 auto hashes = hashesAndChildren_.getHashes();
236 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
237 s.addBitString(hashes[indexNum].as_uint256());
238 s.add8(branchNum);
239 });
241 }
242 else
243 {
245 [&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
247 }
248}
249
250void
252{
253 XRPL_ASSERT(
254 !isEmpty(),
255 "ripple::SHAMapInnerNode::serializeWithPrefix : is non-empty");
256
259 [&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
260}
261
264{
266 auto hashes = hashesAndChildren_.getHashes();
267 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
268 ret += "\nb";
269 ret += std::to_string(branchNum);
270 ret += " = ";
271 ret += to_string(hashes[indexNum]);
272 });
273 return ret;
274}
275
276// We are modifying an inner node
277void
279{
280 XRPL_ASSERT(
281 (m >= 0) && (m < branchFactor),
282 "ripple::SHAMapInnerNode::setChild : valid branch input");
283 XRPL_ASSERT(cowid_, "ripple::SHAMapInnerNode::setChild : nonzero cowid");
284 XRPL_ASSERT(
285 child.get() != this,
286 "ripple::SHAMapInnerNode::setChild : valid child input");
287
288 auto const dstIsBranch = [&] {
289 if (child)
290 return isBranch_ | (1u << m);
291 else
292 return isBranch_ & ~(1u << m);
293 }();
294
295 auto const dstToAllocate = popcnt16(dstIsBranch);
296 // change hashesAndChildren to remove the element, or make room for the
297 // added element, if necessary
299 std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
300
301 isBranch_ = dstIsBranch;
302
303 if (child)
304 {
305 auto const childIndex = *getChildIndex(m);
306 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
307 hashes[childIndex].zero();
308 children[childIndex] = std::move(child);
309 }
310
311 hash_.zero();
312
313 XRPL_ASSERT(
315 "ripple::SHAMapInnerNode::setChild : maximum branch count");
316}
317
318// finished modifying, now make shareable
319void
321{
322 XRPL_ASSERT(
323 (m >= 0) && (m < branchFactor),
324 "ripple::SHAMapInnerNode::shareChild : valid branch input");
325 XRPL_ASSERT(cowid_, "ripple::SHAMapInnerNode::shareChild : nonzero cowid");
326 XRPL_ASSERT(
327 child, "ripple::SHAMapInnerNode::shareChild : non-null child input");
328 XRPL_ASSERT(
329 child.get() != this,
330 "ripple::SHAMapInnerNode::shareChild : valid child input");
331
332 XRPL_ASSERT(
333 !isEmptyBranch(m),
334 "ripple::SHAMapInnerNode::shareChild : non-empty branch input");
336}
337
340{
341 XRPL_ASSERT(
342 branch >= 0 && branch < branchFactor,
343 "ripple::SHAMapInnerNode::getChildPointer : valid branch input");
344 XRPL_ASSERT(
345 !isEmptyBranch(branch),
346 "ripple::SHAMapInnerNode::getChildPointer : non-empty branch input");
347
348 auto const index = *getChildIndex(branch);
349
350 packed_spinlock sl(lock_, index);
351 std::lock_guard lock(sl);
352 return hashesAndChildren_.getChildren()[index].get();
353}
354
357{
358 XRPL_ASSERT(
359 branch >= 0 && branch < branchFactor,
360 "ripple::SHAMapInnerNode::getChild : valid branch input");
361 XRPL_ASSERT(
362 !isEmptyBranch(branch),
363 "ripple::SHAMapInnerNode::getChild : non-empty branch input");
364
365 auto const index = *getChildIndex(branch);
366
367 packed_spinlock sl(lock_, index);
368 std::lock_guard lock(sl);
369 return hashesAndChildren_.getChildren()[index];
370}
371
372SHAMapHash const&
374{
375 XRPL_ASSERT(
376 (m >= 0) && (m < branchFactor),
377 "ripple::SHAMapInnerNode::getChildHash : valid branch input");
378 if (auto const i = getChildIndex(m))
379 return hashesAndChildren_.getHashes()[*i];
380
381 return zeroSHAMapHash;
382}
383
386 int branch,
388{
389 XRPL_ASSERT(
390 branch >= 0 && branch < branchFactor,
391 "ripple::SHAMapInnerNode::canonicalizeChild : valid branch input");
392 XRPL_ASSERT(
393 node != nullptr,
394 "ripple::SHAMapInnerNode::canonicalizeChild : valid node input");
395 XRPL_ASSERT(
396 !isEmptyBranch(branch),
397 "ripple::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
398 auto const childIndex = *getChildIndex(branch);
399 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
400 XRPL_ASSERT(
401 node->getHash() == hashes[childIndex],
402 "ripple::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
403 "hash do match");
404
405 packed_spinlock sl(lock_, childIndex);
406 std::lock_guard lock(sl);
407
408 if (children[childIndex])
409 {
410 // There is already a node hooked up, return it
411 node = children[childIndex];
412 }
413 else
414 {
415 // Hook this node up
416 children[childIndex] = node;
417 }
418 return node;
419}
420
421void
423{
424 [[maybe_unused]] unsigned count = 0;
425 auto [numAllocated, hashes, children] =
427
428 if (numAllocated != branchFactor)
429 {
430 auto const branchCount = getBranchCount();
431 for (int i = 0; i < branchCount; ++i)
432 {
433 XRPL_ASSERT(
434 hashes[i].isNonZero(),
435 "ripple::SHAMapInnerNode::invariants : nonzero hash in branch");
436 if (children[i] != nullptr)
437 children[i]->invariants();
438 ++count;
439 }
440 }
441 else
442 {
443 for (int i = 0; i < branchFactor; ++i)
444 {
445 if (hashes[i].isNonZero())
446 {
447 XRPL_ASSERT(
448 (isBranch_ & (1 << i)),
449 "ripple::SHAMapInnerNode::invariants : valid branch when "
450 "nonzero hash");
451 if (children[i] != nullptr)
452 children[i]->invariants();
453 ++count;
454 }
455 else
456 {
457 XRPL_ASSERT(
458 (isBranch_ & (1 << i)) == 0,
459 "ripple::SHAMapInnerNode::invariants : valid branch when "
460 "zero hash");
461 }
462 }
463 }
464
465 if (!is_root)
466 {
467 XRPL_ASSERT(
469 "ripple::SHAMapInnerNode::invariants : nonzero hash");
470 XRPL_ASSERT(
471 count >= 1, "ripple::SHAMapInnerNode::invariants : minimum count");
472 }
473 XRPL_ASSERT(
474 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
475 "ripple::SHAMapInnerNode::invariants : hash and count do match");
476}
477
478} // namespace ripple
bool isNonZero() const
Definition: SHAMapHash.h:59
uint256 const & as_uint256() const
Definition: SHAMapHash.h:44
bool isZero() const
Definition: SHAMapHash.h:54
std::uint32_t fullBelowGen_
void shareChild(int m, std::shared_ptr< SHAMapTreeNode > const &child)
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)
std::shared_ptr< 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)
std::shared_ptr< SHAMapTreeNode > getChild(int branch)
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.
static std::shared_ptr< SHAMapTreeNode > makeCompressedInner(Slice data)
void setChild(int m, std::shared_ptr< SHAMapTreeNode > child)
SHAMapHash const & getChildHash(int m) const
void invariants(bool is_root=false) const override
std::string getString(SHAMapNodeID const &) const override
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.
std::shared_ptr< SHAMapTreeNode > canonicalizeChild(int branch, std::shared_ptr< SHAMapTreeNode > node)
static std::shared_ptr< SHAMapTreeNode > makeFullInner(Slice data, SHAMapHash const &hash, bool hashValid)
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.
Identifies a node inside a SHAMap.
Definition: SHAMapNodeID.h:34
virtual std::string getString(SHAMapNodeID const &) const
std::uint32_t cowid_
Determines the owning SHAMap, if any.
SHAMapHash const & getHash() const
Return the hash of this node.
base_uint< Bits, Tag > getBitString()
Definition: Serializer.h:459
unsigned char get8()
Definition: Serializer.cpp:340
std::size_t empty() const noexcept
Definition: Serializer.h:367
int addBitString(base_uint< Bits, Tag > const &v)
Definition: Serializer.h:131
int add8(unsigned char i)
Definition: Serializer.cpp:143
An immutable linear range of bytes.
Definition: Slice.h:45
TaggedPointer is a combination of a pointer and a mask stored in the lowest two bits.
Definition: TaggedPointer.h:60
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).
SHAMapHash * getHashes() const
Get the hashes array.
std::shared_ptr< SHAMapTreeNode > * getChildren() const
Get the children 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.
std::tuple< std::uint8_t, SHAMapHash *, std::shared_ptr< SHAMapTreeNode > * > getHashesAndChildren() const
Get the number of elements in each array and a pointer to the start of each array.
bool isDense() const
Check if the arrays have a dense format.
static std::size_t constexpr bytes
Definition: base_uint.h:107
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition: spinlock.h:90
A spinlock implemented on top of an atomic integer.
Definition: spinlock.h:166
std::uint32_t cowid() const
Returns the SHAMap that owns this node.
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.
Definition: hash_append.h:236
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
void hash_append(Hasher &h, Slice const &v)
Definition: Slice.h:198
static constexpr unsigned char const wireTypeCompressedInner
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
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:171
T tie(T... args)
T to_string(T... args)