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#include <xrpld/shamap/SHAMapTreeNode.h>
22#include <xrpld/shamap/detail/TaggedPointer.ipp>
23#include <xrpl/basics/Slice.h>
24#include <xrpl/basics/contract.h>
25#include <xrpl/basics/spinlock.h>
26#include <xrpl/protocol/HashPrefix.h>
27#include <xrpl/protocol/digest.h>
28
29#include <algorithm>
30#include <iterator>
31
32namespace ripple {
33
35 std::uint32_t cowid,
36 std::uint8_t numAllocatedChildren)
37 : SHAMapTreeNode(cowid), hashesAndChildren_(numAllocatedChildren)
38{
39}
40
42
43template <class F>
44void
46{
47 hashesAndChildren_.iterChildren(isBranch_, std::forward<F>(f));
48}
49
50template <class F>
51void
53{
55}
56
57void
59{
61 TaggedPointer(std::move(hashesAndChildren_), isBranch_, toAllocate);
62}
63
66{
68}
69
72{
73 auto const branchCount = getBranchCount();
74 auto const thisIsSparse = !hashesAndChildren_.isDense();
75 auto p = std::make_shared<SHAMapInnerNode>(cowid, branchCount);
76 p->hash_ = hash_;
77 p->isBranch_ = isBranch_;
78 p->fullBelowGen_ = fullBelowGen_;
79 SHAMapHash *cloneHashes, *thisHashes;
80 std::shared_ptr<SHAMapTreeNode>*cloneChildren, *thisChildren;
81 // structured bindings can't be captured in c++ 17; use tie instead
82 std::tie(std::ignore, cloneHashes, cloneChildren) =
83 p->hashesAndChildren_.getHashesAndChildren();
84 std::tie(std::ignore, thisHashes, thisChildren) =
86
87 if (thisIsSparse)
88 {
89 int cloneChildIndex = 0;
90 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
91 cloneHashes[cloneChildIndex++] = thisHashes[indexNum];
92 });
93 }
94 else
95 {
96 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
97 cloneHashes[branchNum] = thisHashes[indexNum];
98 });
99 }
100
101 spinlock sl(lock_);
102 std::lock_guard lock(sl);
103
104 if (thisIsSparse)
105 {
106 int cloneChildIndex = 0;
107 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
108 cloneChildren[cloneChildIndex++] = thisChildren[indexNum];
109 });
110 }
111 else
112 {
113 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
114 cloneChildren[branchNum] = thisChildren[indexNum];
115 });
116 }
117
118 return p;
119}
120
123 Slice data,
124 SHAMapHash const& hash,
125 bool hashValid)
126{
127 // A full inner node is serialized as 16 256-bit hashes, back to back:
128 if (data.size() != branchFactor * uint256::bytes)
129 Throw<std::runtime_error>("Invalid FI node");
130
131 auto ret = std::make_shared<SHAMapInnerNode>(0, branchFactor);
132
133 SerialIter si(data);
134
135 auto hashes = ret->hashesAndChildren_.getHashes();
136
137 for (int i = 0; i < branchFactor; ++i)
138 {
139 hashes[i].as_uint256() = si.getBitString<256>();
140
141 if (hashes[i].isNonZero())
142 ret->isBranch_ |= (1 << i);
143 }
144
145 ret->resizeChildArrays(ret->getBranchCount());
146
147 if (hashValid)
148 ret->hash_ = hash;
149 else
150 ret->updateHash();
151
152 return ret;
153}
154
157{
158 // A compressed inner node is serialized as a series of 33 byte chunks,
159 // representing a one byte "position" and a 256-bit hash:
160 constexpr std::size_t chunkSize = uint256::bytes + 1;
161
162 if (auto const s = data.size();
163 (s % chunkSize != 0) || (s > chunkSize * branchFactor))
164 Throw<std::runtime_error>("Invalid CI node");
165
166 SerialIter si(data);
167
168 auto ret = std::make_shared<SHAMapInnerNode>(0, branchFactor);
169
170 auto hashes = ret->hashesAndChildren_.getHashes();
171
172 while (!si.empty())
173 {
174 auto const hash = si.getBitString<256>();
175 auto const pos = si.get8();
176
177 if (pos >= branchFactor)
178 Throw<std::runtime_error>("invalid CI node");
179
180 hashes[pos].as_uint256() = hash;
181
182 if (hashes[pos].isNonZero())
183 ret->isBranch_ |= (1 << pos);
184 }
185
186 ret->resizeChildArrays(ret->getBranchCount());
187 ret->updateHash();
188 return ret;
189}
190
191void
193{
194 uint256 nh;
195 if (isBranch_ != 0)
196 {
198 using beast::hash_append;
200 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
201 nh = static_cast<typename sha512_half_hasher::result_type>(h);
202 }
203 hash_ = SHAMapHash{nh};
204}
205
206void
208{
209 SHAMapHash* hashes;
211 // structured bindings can't be captured in c++ 17; use tie instead
212 std::tie(std::ignore, hashes, children) =
214 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
215 if (children[indexNum] != nullptr)
216 hashes[indexNum] = children[indexNum]->getHash();
217 });
218 updateHash();
219}
220
221void
223{
224 XRPL_ASSERT(
225 !isEmpty(), "ripple::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].as_uint256());
234 s.add8(branchNum);
235 });
237 }
238 else
239 {
241 [&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
243 }
244}
245
246void
248{
249 XRPL_ASSERT(
250 !isEmpty(),
251 "ripple::SHAMapInnerNode::serializeWithPrefix : is non-empty");
252
255 [&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
256}
257
260{
262 auto hashes = hashesAndChildren_.getHashes();
263 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
264 ret += "\nb";
265 ret += std::to_string(branchNum);
266 ret += " = ";
267 ret += to_string(hashes[indexNum]);
268 });
269 return ret;
270}
271
272// We are modifying an inner node
273void
275{
276 XRPL_ASSERT(
277 (m >= 0) && (m < branchFactor),
278 "ripple::SHAMapInnerNode::setChild : valid branch input");
279 XRPL_ASSERT(cowid_, "ripple::SHAMapInnerNode::setChild : nonzero cowid");
280 XRPL_ASSERT(
281 child.get() != this,
282 "ripple::SHAMapInnerNode::setChild : valid child input");
283
284 auto const dstIsBranch = [&] {
285 if (child)
286 return isBranch_ | (1u << m);
287 else
288 return isBranch_ & ~(1u << m);
289 }();
290
291 auto const dstToAllocate = popcnt16(dstIsBranch);
292 // change hashesAndChildren to remove the element, or make room for the
293 // added element, if necessary
295 std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
296
297 isBranch_ = dstIsBranch;
298
299 if (child)
300 {
301 auto const childIndex = *getChildIndex(m);
302 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
303 hashes[childIndex].zero();
304 children[childIndex] = std::move(child);
305 }
306
307 hash_.zero();
308
309 XRPL_ASSERT(
311 "ripple::SHAMapInnerNode::setChild : maximum branch count");
312}
313
314// finished modifying, now make shareable
315void
317{
318 XRPL_ASSERT(
319 (m >= 0) && (m < branchFactor),
320 "ripple::SHAMapInnerNode::shareChild : valid branch input");
321 XRPL_ASSERT(cowid_, "ripple::SHAMapInnerNode::shareChild : nonzero cowid");
322 XRPL_ASSERT(
323 child, "ripple::SHAMapInnerNode::shareChild : non-null child input");
324 XRPL_ASSERT(
325 child.get() != this,
326 "ripple::SHAMapInnerNode::shareChild : valid child input");
327
328 XRPL_ASSERT(
329 !isEmptyBranch(m),
330 "ripple::SHAMapInnerNode::shareChild : non-empty branch input");
332}
333
336{
337 XRPL_ASSERT(
338 branch >= 0 && branch < branchFactor,
339 "ripple::SHAMapInnerNode::getChildPointer : valid branch input");
340 XRPL_ASSERT(
341 !isEmptyBranch(branch),
342 "ripple::SHAMapInnerNode::getChildPointer : non-empty branch input");
343
344 auto const index = *getChildIndex(branch);
345
346 packed_spinlock sl(lock_, index);
347 std::lock_guard lock(sl);
348 return hashesAndChildren_.getChildren()[index].get();
349}
350
353{
354 XRPL_ASSERT(
355 branch >= 0 && branch < branchFactor,
356 "ripple::SHAMapInnerNode::getChild : valid branch input");
357 XRPL_ASSERT(
358 !isEmptyBranch(branch),
359 "ripple::SHAMapInnerNode::getChild : non-empty branch input");
360
361 auto const index = *getChildIndex(branch);
362
363 packed_spinlock sl(lock_, index);
364 std::lock_guard lock(sl);
365 return hashesAndChildren_.getChildren()[index];
366}
367
368SHAMapHash const&
370{
371 XRPL_ASSERT(
372 (m >= 0) && (m < branchFactor),
373 "ripple::SHAMapInnerNode::getChildHash : valid branch input");
374 if (auto const i = getChildIndex(m))
375 return hashesAndChildren_.getHashes()[*i];
376
377 return zeroSHAMapHash;
378}
379
382 int branch,
384{
385 XRPL_ASSERT(
386 branch >= 0 && branch < branchFactor,
387 "ripple::SHAMapInnerNode::canonicalizeChild : valid branch input");
388 XRPL_ASSERT(
389 node != nullptr,
390 "ripple::SHAMapInnerNode::canonicalizeChild : valid node input");
391 XRPL_ASSERT(
392 !isEmptyBranch(branch),
393 "ripple::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
394 auto const childIndex = *getChildIndex(branch);
395 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
396 XRPL_ASSERT(
397 node->getHash() == hashes[childIndex],
398 "ripple::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
399 "hash do match");
400
401 packed_spinlock sl(lock_, childIndex);
402 std::lock_guard lock(sl);
403
404 if (children[childIndex])
405 {
406 // There is already a node hooked up, return it
407 node = children[childIndex];
408 }
409 else
410 {
411 // Hook this node up
412 children[childIndex] = node;
413 }
414 return node;
415}
416
417void
419{
420 [[maybe_unused]] unsigned count = 0;
421 auto [numAllocated, hashes, children] =
423
424 if (numAllocated != branchFactor)
425 {
426 auto const branchCount = getBranchCount();
427 for (int i = 0; i < branchCount; ++i)
428 {
429 XRPL_ASSERT(
430 hashes[i].isNonZero(),
431 "ripple::SHAMapInnerNode::invariants : nonzero hash in branch");
432 if (children[i] != nullptr)
433 children[i]->invariants();
434 ++count;
435 }
436 }
437 else
438 {
439 for (int i = 0; i < branchFactor; ++i)
440 {
441 if (hashes[i].isNonZero())
442 {
443 XRPL_ASSERT(
444 (isBranch_ & (1 << i)),
445 "ripple::SHAMapInnerNode::invariants : valid branch when "
446 "nonzero hash");
447 if (children[i] != nullptr)
448 children[i]->invariants();
449 ++count;
450 }
451 else
452 {
453 XRPL_ASSERT(
454 (isBranch_ & (1 << i)) == 0,
455 "ripple::SHAMapInnerNode::invariants : valid branch when "
456 "zero hash");
457 }
458 }
459 }
460
461 if (!is_root)
462 {
463 XRPL_ASSERT(
465 "ripple::SHAMapInnerNode::invariants : nonzero hash");
466 XRPL_ASSERT(
467 count >= 1, "ripple::SHAMapInnerNode::invariants : minimum count");
468 }
469 XRPL_ASSERT(
470 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
471 "ripple::SHAMapInnerNode::invariants : hash and count do match");
472}
473
474} // 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:460
unsigned char get8()
Definition: Serializer.cpp:353
std::size_t empty() const noexcept
Definition: Serializer.h:368
int addBitString(base_uint< Bits, Tag > const &v)
Definition: Serializer.h:132
int add8(unsigned char i)
Definition: Serializer.cpp:156
An immutable linear range of bytes.
Definition: Slice.h:46
TaggedPointer is a combination of a pointer and a mask stored in the lowest two bits.
Definition: TaggedPointer.h:59
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:108
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition: spinlock.h:91
A spinlock implemented on top of an atomic integer.
Definition: spinlock.h:167
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:237
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:199
static constexpr unsigned char const wireTypeCompressedInner
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
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:173
T tie(T... args)
T to_string(T... args)