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) = p->hashesAndChildren_.getHashesAndChildren();
68 std::tie(std::ignore, thisHashes, thisChildren) = hashesAndChildren_.getHashesAndChildren();
69
70 if (thisIsSparse)
71 {
72 int cloneChildIndex = 0;
74 [&](auto branchNum, auto indexNum) { cloneHashes[cloneChildIndex++] = thisHashes[indexNum]; });
75 }
76 else
77 {
78 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { cloneHashes[branchNum] = thisHashes[indexNum]; });
79 }
80
81 spinlock sl(lock_);
82 std::lock_guard lock(sl);
83
84 if (thisIsSparse)
85 {
86 int cloneChildIndex = 0;
88 [&](auto branchNum, auto indexNum) { cloneChildren[cloneChildIndex++] = thisChildren[indexNum]; });
89 }
90 else
91 {
93 [&](auto branchNum, auto indexNum) { cloneChildren[branchNum] = thisChildren[indexNum]; });
94 }
95
96 return p;
97}
98
100SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid)
101{
102 // A full inner node is serialized as 16 256-bit hashes, back to back:
103 if (data.size() != branchFactor * uint256::bytes)
104 Throw<std::runtime_error>("Invalid FI node");
105
106 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
107
108 SerialIter si(data);
109
110 auto hashes = ret->hashesAndChildren_.getHashes();
111
112 for (int i = 0; i < branchFactor; ++i)
113 {
114 hashes[i].as_uint256() = si.getBitString<256>();
115
116 if (hashes[i].isNonZero())
117 ret->isBranch_ |= (1 << i);
118 }
119
120 ret->resizeChildArrays(ret->getBranchCount());
121
122 if (hashValid)
123 ret->hash_ = hash;
124 else
125 ret->updateHash();
126
127 return ret;
128}
129
132{
133 // A compressed inner node is serialized as a series of 33 byte chunks,
134 // representing a one byte "position" and a 256-bit hash:
135 constexpr std::size_t chunkSize = uint256::bytes + 1;
136
137 if (auto const s = data.size(); (s % chunkSize != 0) || (s > chunkSize * branchFactor))
138 Throw<std::runtime_error>("Invalid CI node");
139
140 SerialIter si(data);
141
142 auto ret = intr_ptr::make_shared<SHAMapInnerNode>(0, branchFactor);
143
144 auto hashes = ret->hashesAndChildren_.getHashes();
145
146 while (!si.empty())
147 {
148 auto const hash = si.getBitString<256>();
149 auto const pos = si.get8();
150
151 if (pos >= branchFactor)
152 Throw<std::runtime_error>("invalid CI node");
153
154 hashes[pos].as_uint256() = hash;
155
156 if (hashes[pos].isNonZero())
157 ret->isBranch_ |= (1 << pos);
158 }
159
160 ret->resizeChildArrays(ret->getBranchCount());
161 ret->updateHash();
162 return ret;
163}
164
165void
167{
168 uint256 nh;
169 if (isBranch_ != 0)
170 {
172 using beast::hash_append;
174 iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); });
175 nh = static_cast<typename sha512_half_hasher::result_type>(h);
176 }
177 hash_ = SHAMapHash{nh};
178}
179
180void
182{
183 SHAMapHash* hashes;
185 // structured bindings can't be captured in c++ 17; use tie instead
186 std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren();
187 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
188 if (auto p = children[indexNum].get())
189 hashes[indexNum] = p->getHash();
190 });
191 updateHash();
192}
193
194void
196{
197 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeForWire : is non-empty");
198
199 // If the node is sparse, then only send non-empty branches:
200 if (getBranchCount() < 12)
201 {
202 // compressed node
203 auto hashes = hashesAndChildren_.getHashes();
204 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
205 s.addBitString(hashes[indexNum].as_uint256());
206 s.add8(branchNum);
207 });
209 }
210 else
211 {
212 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
214 }
215}
216
217void
219{
220 XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeWithPrefix : is non-empty");
221
223 iterChildren([&](SHAMapHash const& hh) { s.addBitString(hh.as_uint256()); });
224}
225
228{
230 auto hashes = hashesAndChildren_.getHashes();
231 iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
232 ret += "\nb";
233 ret += std::to_string(branchNum);
234 ret += " = ";
235 ret += to_string(hashes[indexNum]);
236 });
237 return ret;
238}
239
240// We are modifying an inner node
241void
243{
244 XRPL_ASSERT((m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input");
245 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::setChild : nonzero cowid");
246 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::setChild : valid child input");
247
248 auto const dstIsBranch = [&] {
249 if (child)
250 return isBranch_ | (1u << m);
251 else
252 return isBranch_ & ~(1u << m);
253 }();
254
255 auto const dstToAllocate = popcnt16(dstIsBranch);
256 // change hashesAndChildren to remove the element, or make room for the
257 // added element, if necessary
258 hashesAndChildren_ = TaggedPointer(std::move(hashesAndChildren_), isBranch_, dstIsBranch, dstToAllocate);
259
260 isBranch_ = dstIsBranch;
261
262 if (child)
263 {
264 auto const childIndex = *getChildIndex(m);
265 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
266 hashes[childIndex].zero();
267 children[childIndex] = std::move(child);
268 }
269
270 hash_.zero();
271
272 XRPL_ASSERT(
273 getBranchCount() <= hashesAndChildren_.capacity(), "xrpl::SHAMapInnerNode::setChild : maximum branch count");
274}
275
276// finished modifying, now make shareable
277void
279{
280 XRPL_ASSERT((m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input");
281 XRPL_ASSERT(cowid_, "xrpl::SHAMapInnerNode::shareChild : nonzero cowid");
282 XRPL_ASSERT(child, "xrpl::SHAMapInnerNode::shareChild : non-null child input");
283 XRPL_ASSERT(child.get() != this, "xrpl::SHAMapInnerNode::shareChild : valid child input");
284
285 XRPL_ASSERT(!isEmptyBranch(m), "xrpl::SHAMapInnerNode::shareChild : non-empty branch input");
287}
288
291{
292 XRPL_ASSERT(branch >= 0 && branch < branchFactor, "xrpl::SHAMapInnerNode::getChildPointer : valid branch input");
293 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChildPointer : non-empty branch input");
294
295 auto const index = *getChildIndex(branch);
296
297 packed_spinlock sl(lock_, index);
298 std::lock_guard lock(sl);
299 return hashesAndChildren_.getChildren()[index].get();
300}
301
304{
305 XRPL_ASSERT(branch >= 0 && branch < branchFactor, "xrpl::SHAMapInnerNode::getChild : valid branch input");
306 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::getChild : non-empty branch input");
307
308 auto const index = *getChildIndex(branch);
309
310 packed_spinlock sl(lock_, index);
311 std::lock_guard lock(sl);
312 return hashesAndChildren_.getChildren()[index];
313}
314
315SHAMapHash const&
317{
318 XRPL_ASSERT((m >= 0) && (m < branchFactor), "xrpl::SHAMapInnerNode::getChildHash : valid branch input");
319 if (auto const i = getChildIndex(m))
320 return hashesAndChildren_.getHashes()[*i];
321
322 return zeroSHAMapHash;
323}
324
327{
328 XRPL_ASSERT(branch >= 0 && branch < branchFactor, "xrpl::SHAMapInnerNode::canonicalizeChild : valid branch input");
329 XRPL_ASSERT(node != nullptr, "xrpl::SHAMapInnerNode::canonicalizeChild : valid node input");
330 XRPL_ASSERT(!isEmptyBranch(branch), "xrpl::SHAMapInnerNode::canonicalizeChild : non-empty branch input");
331 auto const childIndex = *getChildIndex(branch);
332 auto [_, hashes, children] = hashesAndChildren_.getHashesAndChildren();
333 XRPL_ASSERT(
334 node->getHash() == hashes[childIndex],
335 "xrpl::SHAMapInnerNode::canonicalizeChild : node and branch inputs "
336 "hash do match");
337
338 packed_spinlock sl(lock_, childIndex);
339 std::lock_guard lock(sl);
340
341 if (children[childIndex])
342 {
343 // There is already a node hooked up, return it
344 node = children[childIndex];
345 }
346 else
347 {
348 // Hook this node up
349 children[childIndex] = node;
350 }
351 return node;
352}
353
354void
356{
357 [[maybe_unused]] unsigned count = 0;
358 auto [numAllocated, hashes, children] = hashesAndChildren_.getHashesAndChildren();
359
360 if (numAllocated != branchFactor)
361 {
362 auto const branchCount = getBranchCount();
363 for (int i = 0; i < branchCount; ++i)
364 {
365 XRPL_ASSERT(hashes[i].isNonZero(), "xrpl::SHAMapInnerNode::invariants : nonzero hash in branch");
366 if (children[i] != nullptr)
367 children[i]->invariants();
368 ++count;
369 }
370 }
371 else
372 {
373 for (int i = 0; i < branchFactor; ++i)
374 {
375 if (hashes[i].isNonZero())
376 {
377 XRPL_ASSERT(
378 (isBranch_ & (1 << i)),
379 "xrpl::SHAMapInnerNode::invariants : valid branch when "
380 "nonzero hash");
381 if (children[i] != nullptr)
382 children[i]->invariants();
383 ++count;
384 }
385 else
386 {
387 XRPL_ASSERT(
388 (isBranch_ & (1 << i)) == 0,
389 "xrpl::SHAMapInnerNode::invariants : valid branch when "
390 "zero hash");
391 }
392 }
393 }
394
395 if (!is_root)
396 {
397 XRPL_ASSERT(hash_.isNonZero(), "xrpl::SHAMapInnerNode::invariants : nonzero hash");
398 XRPL_ASSERT(count >= 1, "xrpl::SHAMapInnerNode::invariants : minimum count");
399 }
400 XRPL_ASSERT(
401 (count == 0) ? hash_.isZero() : hash_.isNonZero(),
402 "xrpl::SHAMapInnerNode::invariants : hash and count do match");
403}
404
405} // namespace xrpl
bool isNonZero() const
Definition SHAMapHash.h:40
uint256 const & as_uint256() const
Definition SHAMapHash.h:25
bool isZero() const
Definition SHAMapHash.h:35
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:341
base_uint< Bits, Tag > getBitString()
Definition Serializer.h:433
unsigned char get8()
int addBitString(base_uint< Bits, Tag > const &v)
Definition Serializer.h:106
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:27
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:85
Classes to handle arrays of spinlocks packed into a single atomic integer:
Definition spinlock.h:76
A spinlock implemented on top of an atomic integer.
Definition spinlock.h:149
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:6
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
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:153
T tie(T... args)
T to_string(T... args)