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