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{
23 intr_ptr::SharedPtr<SHAMapTreeNode>* children = nullptr;
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 = nullptr, *thisHashes = nullptr;
65 intr_ptr::SharedPtr<SHAMapTreeNode>*cloneChildren = nullptr, *thisChildren = nullptr;
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 const 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 {
129 ret->hash_ = hash;
130 }
131 else
132 {
133 ret->updateHash();
134 }
135
136 return ret;
137}
138
141{
142 // A compressed inner node is serialized as a series of 33 byte chunks,
143 // representing a one byte "position" and a 256-bit hash:
144 constexpr std::size_t chunkSize = uint256::bytes + 1;
145
146 if (auto const s = data.size(); (s % chunkSize != 0) || (s > chunkSize * branchFactor))
147 Throw<std::runtime_error>("Invalid CI node");
148
149 SerialIter si(data);
150
151 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
152
153 auto hashes = ret->hashesAndChildren_.getHashes();
154
155 while (!si.empty())
156 {
157 auto const hash = si.getBitString<256>();
158 auto const pos = si.get8();
159
160 if (pos >= branchFactor)
161 Throw<std::runtime_error>("invalid CI node");
162
163 hashes[pos].as_uint256() = hash;
164
165 if (hashes[pos].isNonZero())
166 ret->isBranch_ |= (1 << pos);
167 }
168
169 ret->resizeChildArrays(ret->getBranchCount());
170 ret->updateHash();
171 return ret;
172}
173
174void
176{
177 uint256 nh;
178 if (isBranch_ != 0)
179 {
181 using beast::hash_append;
183 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
184 nh = static_cast<typename sha512_half_hasher::result_type>(h);
185 }
186 hash_ = SHAMapHash{nh};
187}
188
189void
191{
192 SHAMapHash* hashes = nullptr;
193 intr_ptr::SharedPtr<SHAMapTreeNode>* children = nullptr;
194 // structured bindings can't be captured in c++ 17; use tie instead
195 std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren();
196 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
197 if (auto p = children[indexNum].get())
198 hashes[indexNum] = p->getHash();
199 });
200 updateHash();
201}
202
203void
205{
206 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeForWire : is non-empty");
207
208 // If the node is sparse, then only send non-empty branches:
209 if (getBranchCount() < 12)
210 {
211 // compressed node
212 auto hashes = hashesAndChildren_.getHashes();
213 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
214 s.addBitString(hashes[indexNum].as_uint256());
215 s.add8(branchNum);
216 });
218 }
219 else
220 {
221 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
223 }
224}
225
226void
228{
229 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeWithPrefix : is non-empty");
230
232 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
233}
234
237{
239 auto hashes = hashesAndChildren_.getHashes();
240 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
241 ret += "\nb";
242 ret += std::to_string(branchNum);
243 ret += " = ";
244 ret += to_string(hashes[indexNum]);
245 });
246 return ret;
247}
248
249// We are modifying an inner node
250void
252{
253 XRPL_ASSERT(
254 (m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input");
255 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::setChild : nonzero cowid");
256 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::setChild : valid child input");
257
258 auto const dstIsBranch = [&] {
259 if (child)
260 {
261 return isBranch_ | (1u << m);
262 }
263
264 return isBranch_ & ~(1u << m);
265 }();
266
267 auto const dstToAllocate = popcnt16(dstIsBranch);
268 // change hashesAndChildren to remove the element, or make room for the
269 // added element, if necessary
271 TaggedPointer(std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
272
273 isBranch_ = dstIsBranch;
274
275 if (child)
276 {
277 auto const childIndex = *getChildIndex(m);
278 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
279 hashes[childIndex].zero();
280 children[childIndex] = std::move(child);
281 }
282
283 hash_.zero();
284
285 XRPL_ASSERT(
287 "xrpl::SHAMapInnerNode::setChild : maximum branch count");
288}
289
290// finished modifying, now make shareable
291void
293{
294 XRPL_ASSERT(
295 (m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input");
296 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::shareChild : nonzero cowid");
297 XRPL_ASSERT(child, "xrpl::SHAMapInnerNode::shareChild : non-null child input");
298 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::shareChild : valid child input");
299
300 XRPL_ASSERT(!isEmptyBranch(m), "xrpl::SHAMapInnerNode::shareChild : non-empty branch input");
302}
303
306{
307 XRPL_ASSERT(
308 branch >= 0 && branch < branchFactor,
309 "xrpl::SHAMapInnerNode::getChildPointer : valid branch input");
310 XRPL_ASSERT(
311 !isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChildPointer : non-empty branch input");
312
313 auto const index = *getChildIndex(branch);
314
315 packed_spinlock sl(lock_, index);
316 std::lock_guard const lock(sl);
317 return hashesAndChildren_.getChildren()[index].get();
318}
319
322{
323 XRPL_ASSERT(
324 branch >= 0 && branch < branchFactor,
325 "xrpl::SHAMapInnerNode::getChild : valid branch input");
326 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChild : non-empty branch input");
327
328 auto const index = *getChildIndex(branch);
329
330 packed_spinlock sl(lock_, index);
331 std::lock_guard const lock(sl);
332 return hashesAndChildren_.getChildren()[index];
333}
334
335SHAMapHash const&
337{
338 XRPL_ASSERT(
339 (m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::getChildHash : valid branch input");
340 if (auto const i = getChildIndex(m))
341 return hashesAndChildren_.getHashes()[*i];
342
343 return zeroSHAMapHash;
344}
345
348{
349 XRPL_ASSERT(
350 branch >= 0 && branch < branchFactor,
351 "xrpl::SHAMapInnerNode::canonicalizeChild : valid branch input");
352 XRPL_ASSERT(node != nullptr, "xrpl::SHAMapInnerNode::canonicalizeChild : valid node input");
353 XRPL_ASSERT(
354 !isEmptyBranch(branch),
355 "xrpl::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
356 auto const childIndex = *getChildIndex(branch);
357 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
358 XRPL_ASSERT(
359 node->getHash() == hashes[childIndex],
360 "xrpl::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
361 "hash do match");
362
363 packed_spinlock sl(lock_, childIndex);
364 std::lock_guard const lock(sl);
365
366 if (children[childIndex])
367 {
368 // There is already a node hooked up, return it
369 node = children[childIndex];
370 }
371 else
372 {
373 // Hook this node up
374 children[childIndex] = node;
375 }
376 return node;
377}
378
379void
381{
382 [[maybe_unused]] unsigned count = 0;
383 auto [numAllocated, hashes, children] = hashesAndChildren_.getHashesAndChildren();
384
385 if (numAllocated != branchFactor)
386 {
387 auto const branchCount = getBranchCount();
388 for (int i = 0; i < branchCount; ++i)
389 {
390 XRPL_ASSERT(
391 hashes[i].isNonZero(),
392 "xrpl::SHAMapInnerNode::invariants : nonzero hash in branch");
393 if (children[i] != nullptr)
394 children[i]->invariants();
395 ++count;
396 }
397 }
398 else
399 {
400 for (int i = 0; i < branchFactor; ++i)
401 {
402 if (hashes[i].isNonZero())
403 {
404 XRPL_ASSERT(
405 (isBranch_ & (1 << i)),
406 "xrpl::SHAMapInnerNode::invariants : valid branch when "
407 "nonzero hash");
408 if (children[i] != nullptr)
409 children[i]->invariants();
410 ++count;
411 }
412 else
413 {
414 XRPL_ASSERT(
415 (isBranch_ & (1 << i)) == 0,
416 "xrpl::SHAMapInnerNode::invariants : valid branch when "
417 "zero hash");
418 }
419 }
420 }
421
422 if (!is_root)
423 {
424 XRPL_ASSERT(hash_.isNonZero(), "xrpl::SHAMapInnerNode::invariants : nonzero hash");
425 XRPL_ASSERT(count >= 1, "xrpl::SHAMapInnerNode::invariants : minimum count");
426 }
427 XRPL_ASSERT(
428 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
429 "xrpl::SHAMapInnerNode::invariants : hash and count do match");
430}
431
432} // 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
bool 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:602
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)