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 xrpl {
12
14 : SHAMapTreeNode(cowid), hashesAndChildren_(numAllocatedChildren)
15{
16}
17
19
20void
22{
24 // structured bindings can't be captured in c++ 17; use tie instead
25 std::tie(std::ignore, std::ignore, children) = hashesAndChildren_.getHashesAndChildren();
26 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { children[indexNum].reset(); });
27}
28
29template <class F>
30void
35
36template <class F>
37void
42
43void
48
54
57{
58 auto const branchCount = getBranchCount();
59 auto const thisIsSparse = !hashesAndChildren_.isDense();
60 auto p = intr_ptr::make_shared<SHAMapInnerNode>(cowid, branchCount);
61 p->hash_ = hash_;
62 p->isBranch_ = isBranch_;
63 p->fullBelowGen_ = fullBelowGen_;
64 SHAMapHash *cloneHashes, *thisHashes;
65 intr_ptr::SharedPtr<SHAMapTreeNode>*cloneChildren, *thisChildren;
66 // structured bindings can't be captured in c++ 17; use tie instead
67 std::tie(std::ignore, cloneHashes, cloneChildren) =
68 p->hashesAndChildren_.getHashesAndChildren();
69 std::tie(std::ignore, thisHashes, thisChildren) = hashesAndChildren_.getHashesAndChildren();
70
71 if (thisIsSparse)
72 {
73 int cloneChildIndex = 0;
74 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
75 cloneHashes[cloneChildIndex++] = thisHashes[indexNum];
76 });
77 }
78 else
79 {
81 [&](auto branchNum, auto indexNum) { cloneHashes[branchNum] = thisHashes[indexNum]; });
82 }
83
84 spinlock sl(lock_);
85 std::lock_guard lock(sl);
86
87 if (thisIsSparse)
88 {
89 int cloneChildIndex = 0;
90 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
91 cloneChildren[cloneChildIndex++] = thisChildren[indexNum];
92 });
93 }
94 else
95 {
96 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
97 cloneChildren[branchNum] = thisChildren[indexNum];
98 });
99 }
100
101 return p;
102}
103
105SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid)
106{
107 // A full inner node is serialized as 16 256-bit hashes, back to back:
108 if (data.size() != branchFactor * uint256::bytes)
109 Throw<std::runtime_error>("Invalid FI node");
110
111 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
112
113 SerialIter si(data);
114
115 auto hashes = ret->hashesAndChildren_.getHashes();
116
117 for (int i = 0; i < branchFactor; ++i)
118 {
119 hashes[i].as_uint256() = si.getBitString<256>();
120
121 if (hashes[i].isNonZero())
122 ret->isBranch_ |= (1 << i);
123 }
124
125 ret->resizeChildArrays(ret->getBranchCount());
126
127 if (hashValid)
128 ret->hash_ = hash;
129 else
130 ret->updateHash();
131
132 return ret;
133}
134
137{
138 // A compressed inner node is serialized as a series of 33 byte chunks,
139 // representing a one byte "position" and a 256-bit hash:
140 constexpr std::size_t chunkSize = uint256::bytes + 1;
141
142 if (auto const s = data.size(); (s % chunkSize != 0) || (s > chunkSize * branchFactor))
143 Throw<std::runtime_error>("Invalid CI node");
144
145 SerialIter si(data);
146
147 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
148
149 auto hashes = ret->hashesAndChildren_.getHashes();
150
151 while (!si.empty())
152 {
153 auto const hash = si.getBitString<256>();
154 auto const pos = si.get8();
155
156 if (pos >= branchFactor)
157 Throw<std::runtime_error>("invalid CI node");
158
159 hashes[pos].as_uint256() = hash;
160
161 if (hashes[pos].isNonZero())
162 ret->isBranch_ |= (1 << pos);
163 }
164
165 ret->resizeChildArrays(ret->getBranchCount());
166 ret->updateHash();
167 return ret;
168}
169
170void
172{
173 uint256 nh;
174 if (isBranch_ != 0)
175 {
177 using beast::hash_append;
179 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
180 nh = static_cast<typename sha512_half_hasher::result_type>(h);
181 }
182 hash_ = SHAMapHash{nh};
183}
184
185void
187{
188 SHAMapHash* hashes;
190 // structured bindings can't be captured in c++ 17; use tie instead
191 std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren();
192 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
193 if (auto p = children[indexNum].get())
194 hashes[indexNum] = p->getHash();
195 });
196 updateHash();
197}
198
199void
201{
202 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeForWire : is non-empty");
203
204 // If the node is sparse, then only send non-empty branches:
205 if (getBranchCount() < 12)
206 {
207 // compressed node
208 auto hashes = hashesAndChildren_.getHashes();
209 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
210 s.addBitString(hashes[indexNum].as_uint256());
211 s.add8(branchNum);
212 });
214 }
215 else
216 {
217 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
219 }
220}
221
222void
224{
225 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeWithPrefix : is non-empty");
226
228 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
229}
230
233{
235 auto hashes = hashesAndChildren_.getHashes();
236 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
237 ret += "\nb";
238 ret += std::to_string(branchNum);
239 ret += " = ";
240 ret += to_string(hashes[indexNum]);
241 });
242 return ret;
243}
244
245// We are modifying an inner node
246void
248{
249 XRPL_ASSERT(
250 (m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input");
251 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::setChild : nonzero cowid");
252 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::setChild : valid child input");
253
254 auto const dstIsBranch = [&] {
255 if (child)
256 return isBranch_ | (1u << m);
257 else
258 return isBranch_ & ~(1u << m);
259 }();
260
261 auto const dstToAllocate = popcnt16(dstIsBranch);
262 // change hashesAndChildren to remove the element, or make room for the
263 // added element, if necessary
265 TaggedPointer(std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
266
267 isBranch_ = dstIsBranch;
268
269 if (child)
270 {
271 auto const childIndex = *getChildIndex(m);
272 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
273 hashes[childIndex].zero();
274 children[childIndex] = std::move(child);
275 }
276
277 hash_.zero();
278
279 XRPL_ASSERT(
281 "xrpl::SHAMapInnerNode::setChild : maximum branch count");
282}
283
284// finished modifying, now make shareable
285void
287{
288 XRPL_ASSERT(
289 (m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input");
290 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::shareChild : nonzero cowid");
291 XRPL_ASSERT(child, "xrpl::SHAMapInnerNode::shareChild : non-null child input");
292 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::shareChild : valid child input");
293
294 XRPL_ASSERT(!isEmptyBranch(m), "xrpl::SHAMapInnerNode::shareChild : non-empty branch input");
296}
297
300{
301 XRPL_ASSERT(
302 branch >= 0 && branch < branchFactor,
303 "xrpl::SHAMapInnerNode::getChildPointer : valid branch input");
304 XRPL_ASSERT(
305 !isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChildPointer : non-empty branch input");
306
307 auto const index = *getChildIndex(branch);
308
309 packed_spinlock sl(lock_, index);
310 std::lock_guard lock(sl);
311 return hashesAndChildren_.getChildren()[index].get();
312}
313
316{
317 XRPL_ASSERT(
318 branch >= 0 && branch < branchFactor,
319 "xrpl::SHAMapInnerNode::getChild : valid branch input");
320 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChild : non-empty branch input");
321
322 auto const index = *getChildIndex(branch);
323
324 packed_spinlock sl(lock_, index);
325 std::lock_guard lock(sl);
326 return hashesAndChildren_.getChildren()[index];
327}
328
329SHAMapHash const&
331{
332 XRPL_ASSERT(
333 (m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::getChildHash : valid branch input");
334 if (auto const i = getChildIndex(m))
335 return hashesAndChildren_.getHashes()[*i];
336
337 return zeroSHAMapHash;
338}
339
342{
343 XRPL_ASSERT(
344 branch >= 0 && branch < branchFactor,
345 "xrpl::SHAMapInnerNode::canonicalizeChild : valid branch input");
346 XRPL_ASSERT(node != nullptr, "xrpl::SHAMapInnerNode::canonicalizeChild : valid node input");
347 XRPL_ASSERT(
348 !isEmptyBranch(branch),
349 "xrpl::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
350 auto const childIndex = *getChildIndex(branch);
351 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
352 XRPL_ASSERT(
353 node->getHash() == hashes[childIndex],
354 "xrpl::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
355 "hash do match");
356
357 packed_spinlock sl(lock_, childIndex);
358 std::lock_guard lock(sl);
359
360 if (children[childIndex])
361 {
362 // There is already a node hooked up, return it
363 node = children[childIndex];
364 }
365 else
366 {
367 // Hook this node up
368 children[childIndex] = node;
369 }
370 return node;
371}
372
373void
375{
376 [[maybe_unused]] unsigned count = 0;
377 auto [numAllocated, hashes, children] = hashesAndChildren_.getHashesAndChildren();
378
379 if (numAllocated != branchFactor)
380 {
381 auto const branchCount = getBranchCount();
382 for (int i = 0; i < branchCount; ++i)
383 {
384 XRPL_ASSERT(
385 hashes[i].isNonZero(),
386 "xrpl::SHAMapInnerNode::invariants : nonzero hash in branch");
387 if (children[i] != nullptr)
388 children[i]->invariants();
389 ++count;
390 }
391 }
392 else
393 {
394 for (int i = 0; i < branchFactor; ++i)
395 {
396 if (hashes[i].isNonZero())
397 {
398 XRPL_ASSERT(
399 (isBranch_ & (1 << i)),
400 "xrpl::SHAMapInnerNode::invariants : valid branch when "
401 "nonzero hash");
402 if (children[i] != nullptr)
403 children[i]->invariants();
404 ++count;
405 }
406 else
407 {
408 XRPL_ASSERT(
409 (isBranch_ & (1 << i)) == 0,
410 "xrpl::SHAMapInnerNode::invariants : valid branch when "
411 "zero hash");
412 }
413 }
414 }
415
416 if (!is_root)
417 {
418 XRPL_ASSERT(hash_.isNonZero(), "xrpl::SHAMapInnerNode::invariants : nonzero hash");
419 XRPL_ASSERT(count >= 1, "xrpl::SHAMapInnerNode::invariants : minimum count");
420 }
421 XRPL_ASSERT(
422 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
423 "xrpl::SHAMapInnerNode::invariants : hash and count do match");
424}
425
426} // namespace xrpl
bool isNonZero() const
Definition SHAMapHash.h:39
uint256 const & as_uint256() const
Definition SHAMapHash.h:24
bool isZero() const
Definition SHAMapHash.h:34
void serializeWithPrefix(Serializer &) const override
Serialize the node in a format appropriate for hashing.
void updateHash() override
Recalculate the hash of this node.
static intr_ptr::SharedPtr< SHAMapTreeNode > makeFullInner(Slice data, SHAMapHash const &hash, bool hashValid)
TaggedPointer hashesAndChildren_
Opaque type that contains the hashes array (array of type SHAMapHash) and the children array (array o...
static intr_ptr::SharedPtr< SHAMapTreeNode > makeCompressedInner(Slice data)
SHAMapHash const & getChildHash(int m) const
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.
SHAMapTreeNode * getChildPointer(int branch)
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.
static constexpr unsigned int branchFactor
Each inner node has 16 children (the 'radix tree' part of the map)
void resizeChildArrays(std::uint8_t toAllocate)
Convert arrays stored in hashesAndChildren_ so they can store the requested number of children.
std::uint32_t fullBelowGen_
void partialDestructor() override
std::atomic< std::uint16_t > lock_
A bitlock for the children of this node, with one bit per child.
intr_ptr::SharedPtr< SHAMapTreeNode > canonicalizeChild(int branch, intr_ptr::SharedPtr< SHAMapTreeNode > node)
void shareChild(int m, intr_ptr::SharedPtr< SHAMapTreeNode > const &child)
intr_ptr::SharedPtr< SHAMapTreeNode > clone(std::uint32_t cowid) const override
Make a copy of this node, setting the owner.
intr_ptr::SharedPtr< SHAMapTreeNode > getChild(int branch)
void setChild(int m, intr_ptr::SharedPtr< SHAMapTreeNode > child)
void serializeForWire(Serializer &) const override
Serialize the node in a format appropriate for sending over the wire.
std::optional< int > getChildIndex(int i) const
Get the child's index inside the hashes or children array (stored in hashesAndChildren_.
void invariants(bool is_root=false) const override
bool isEmptyBranch(int m) const
Identifies a node inside a SHAMap.
std::uint32_t cowid_
Determines the owning SHAMap, if any.
virtual std::string getString(SHAMapNodeID const &) const
std::size_t empty() const noexcept
Definition Serializer.h:340
base_uint< Bits, Tag > getBitString()
Definition Serializer.h:432
unsigned char get8()
int addBitString(base_uint< Bits, Tag > const &v)
Definition Serializer.h:105
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:26
TaggedPointer is a combination of a pointer and a mask stored in the lowest two bits.
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).
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::uint8_t capacity() const
Get the number of elements allocated for each array.
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.
intr_ptr::SharedPtr< SHAMapTreeNode > * getChildren() const
Get the children array.
bool isDense() const
Check if the arrays have a dense format.
void iterNonEmptyChildIndexes(std::uint16_t isBranch, F &&f) const
Call the f callback for all non-empty branches.
static std::size_t constexpr bytes
Definition base_uint.h:84
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition spinlock.h:75
A spinlock implemented on top of an atomic integer.
Definition spinlock.h:151
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:5
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:600
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
int popcnt16(std::uint16_t a)
static constexpr unsigned char const wireTypeCompressedInner
void hash_append(Hasher &h, Slice const &v)
Definition Slice.h:175
@ innerNode
inner node in V1 tree
Returns the SHA512-Half digest of a message.
Definition digest.h:152
T tie(T... args)
T to_string(T... args)