rippled
Loading...
Searching...
No Matches
SHAMap.cpp
1#include <xrpl/basics/TaggedCache.ipp>
2#include <xrpl/basics/contract.h>
3#include <xrpl/shamap/SHAMap.h>
4#include <xrpl/shamap/SHAMapAccountStateLeafNode.h>
5#include <xrpl/shamap/SHAMapNodeID.h>
6#include <xrpl/shamap/SHAMapSyncFilter.h>
7#include <xrpl/shamap/SHAMapTxLeafNode.h>
8#include <xrpl/shamap/SHAMapTxPlusMetaLeafNode.h>
9
10namespace xrpl {
11
12[[nodiscard]] intr_ptr::SharedPtr<SHAMapLeafNode>
13makeTypedLeaf(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const> item, std::uint32_t owner)
14{
16 return intr_ptr::make_shared<SHAMapTxLeafNode>(std::move(item), owner);
17
19 return intr_ptr::make_shared<SHAMapTxPlusMetaLeafNode>(std::move(item), owner);
20
22 return intr_ptr::make_shared<SHAMapAccountStateLeafNode>(std::move(item), owner);
23
25 "Attempt to create leaf node of unknown type " +
27}
28
29SHAMap::SHAMap(SHAMapType t, Family& f) : f_(f), journal_(f.journal()), state_(SHAMapState::Modifying), type_(t)
30{
31 root_ = intr_ptr::make_shared<SHAMapInnerNode>(cowid_);
32}
33
34// The `hash` parameter is unused. It is part of the interface so it's clear
35// from the parameters that this is the constructor to use when the hash is
36// known. The fact that the parameter is unused is an implementation detail that
37// should not change the interface.
39 : f_(f), journal_(f.journal()), state_(SHAMapState::Synching), type_(t)
40{
41 root_ = intr_ptr::make_shared<SHAMapInnerNode>(cowid_);
42}
43
44SHAMap::SHAMap(SHAMap const& other, bool isMutable)
45 : f_(other.f_)
46 , journal_(other.f_.journal())
47 , cowid_(other.cowid_ + 1)
48 , ledgerSeq_(other.ledgerSeq_)
49 , root_(other.root_)
50 , state_(isMutable ? SHAMapState::Modifying : SHAMapState::Immutable)
51 , type_(other.type_)
52 , backed_(other.backed_)
53{
54 // If either map may change, they cannot share nodes
56 {
57 unshare();
58 }
59}
60
62SHAMap::snapShot(bool isMutable) const
63{
64 return std::make_shared<SHAMap>(*this, isMutable);
65}
66
67void
69{
70 // walk the tree up from through the inner nodes to the root_
71 // update hashes and links
72 // stack is a path of inner nodes up to, but not including, child
73 // child can be an inner node or a leaf
74
75 XRPL_ASSERT(
76 (state_ != SHAMapState::Synching) && (state_ != SHAMapState::Immutable), "xrpl::SHAMap::dirtyUp : valid state");
77 XRPL_ASSERT(child && (child->cowid() == cowid_), "xrpl::SHAMap::dirtyUp : valid child input");
78
79 while (!stack.empty())
80 {
81 auto node = intr_ptr::dynamic_pointer_cast<SHAMapInnerNode>(stack.top().first);
82 SHAMapNodeID nodeID = stack.top().second;
83 stack.pop();
84 XRPL_ASSERT(node, "xrpl::SHAMap::dirtyUp : non-null node");
85
86 int branch = selectBranch(nodeID, target);
87 XRPL_ASSERT(branch >= 0, "xrpl::SHAMap::dirtyUp : valid branch");
88
89 node = unshareNode(std::move(node), nodeID);
90 node->setChild(branch, std::move(child));
91
92 child = std::move(node);
93 }
94}
95
98{
99 XRPL_ASSERT(stack == nullptr || stack->empty(), "xrpl::SHAMap::walkTowardsKey : empty stack input");
100 auto inNode = root_;
101 SHAMapNodeID nodeID;
102
103 while (inNode->isInner())
104 {
105 if (stack != nullptr)
106 stack->push({inNode, nodeID});
107
108 auto const inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(inNode);
109 auto const branch = selectBranch(nodeID, id);
110 if (inner->isEmptyBranch(branch))
111 return nullptr;
112
113 inNode = descendThrow(*inner, branch);
114 nodeID = nodeID.getChildNodeID(branch);
115 }
116
117 if (stack != nullptr)
118 stack->push({inNode, nodeID});
119 return static_cast<SHAMapLeafNode*>(inNode.get());
120}
121
123SHAMap::findKey(uint256 const& id) const
124{
125 SHAMapLeafNode* leaf = walkTowardsKey(id);
126 if (leaf && leaf->peekItem()->key() != id)
127 leaf = nullptr;
128 return leaf;
129}
130
133{
134 XRPL_ASSERT(backed_, "xrpl::SHAMap::fetchNodeFromDB : is backed");
135 auto obj = f_.db().fetchNodeObject(hash.as_uint256(), ledgerSeq_);
136 return finishFetch(hash, obj);
137}
138
141{
142 XRPL_ASSERT(backed_, "xrpl::SHAMap::finishFetch : is backed");
143
144 try
145 {
146 if (!object)
147 {
148 if (full_)
149 {
150 full_ = false;
152 }
153 return {};
154 }
155
156 auto node = SHAMapTreeNode::makeFromPrefix(makeSlice(object->getData()), hash);
157 if (node)
158 canonicalize(hash, node);
159 return node;
160 }
161 catch (std::runtime_error const& e)
162 {
163 JLOG(journal_.warn()) << "finishFetch exception: " << e.what();
164 }
165 catch (...)
166 {
167 JLOG(journal_.warn()) << "finishFetch exception: unknown exception: " << hash;
168 }
169
170 return {};
171}
172
173// See if a sync filter has a node
176{
177 if (auto nodeData = filter->getNode(hash))
178 {
179 try
180 {
181 auto node = SHAMapTreeNode::makeFromPrefix(makeSlice(*nodeData), hash);
182 if (node)
183 {
184 filter->gotNode(true, hash, ledgerSeq_, std::move(*nodeData), node->getType());
185 if (backed_)
186 canonicalize(hash, node);
187 }
188 return node;
189 }
190 catch (std::exception const& x)
191 {
192 JLOG(f_.journal().warn()) << "Invalid node/data, hash=" << hash << ": " << x.what();
193 }
194 }
195 return {};
196}
197
198// Get a node without throwing
199// Used on maps where missing nodes are expected
202{
203 auto node = cacheLookup(hash);
204 if (node)
205 return node;
206
207 if (backed_)
208 {
209 node = fetchNodeFromDB(hash);
210 if (node)
211 {
212 canonicalize(hash, node);
213 return node;
214 }
215 }
216
217 if (filter)
218 node = checkFilter(hash, filter);
219
220 return node;
221}
222
225{
226 auto node = cacheLookup(hash);
227
228 if (!node && backed_)
229 node = fetchNodeFromDB(hash);
230
231 return node;
232}
233
234// Throw if the node is missing
237{
238 auto node = fetchNodeNT(hash);
239
240 if (!node)
241 Throw<SHAMapMissingNode>(type_, hash);
242
243 return node;
244}
245
247SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const
248{
249 SHAMapTreeNode* ret = descend(parent, branch);
250
251 if (!ret && !parent->isEmptyBranch(branch))
252 Throw<SHAMapMissingNode>(type_, parent->getChildHash(branch));
253
254 return ret;
255}
256
258SHAMap::descendThrow(SHAMapInnerNode& parent, int branch) const
259{
260 intr_ptr::SharedPtr<SHAMapTreeNode> ret = descend(parent, branch);
261
262 if (!ret && !parent.isEmptyBranch(branch))
263 Throw<SHAMapMissingNode>(type_, parent.getChildHash(branch));
264
265 return ret;
266}
267
269SHAMap::descend(SHAMapInnerNode* parent, int branch) const
270{
271 SHAMapTreeNode* ret = parent->getChildPointer(branch);
272 if (ret || !backed_)
273 return ret;
274
276 if (!node)
277 return nullptr;
278
279 node = parent->canonicalizeChild(branch, std::move(node));
280 return node.get();
281}
282
284SHAMap::descend(SHAMapInnerNode& parent, int branch) const
285{
286 intr_ptr::SharedPtr<SHAMapTreeNode> node = parent.getChild(branch);
287 if (node || !backed_)
288 return node;
289
290 node = fetchNode(parent.getChildHash(branch));
291 if (!node)
292 return {};
293
294 node = parent.canonicalizeChild(branch, std::move(node));
295 return node;
296}
297
298// Gets the node that would be hooked to this branch,
299// but doesn't hook it up.
301SHAMap::descendNoStore(SHAMapInnerNode& parent, int branch) const
302{
304 if (!ret && backed_)
305 ret = fetchNode(parent.getChildHash(branch));
306 return ret;
307}
308
310SHAMap::descend(SHAMapInnerNode* parent, SHAMapNodeID const& parentID, int branch, SHAMapSyncFilter* filter) const
311{
312 XRPL_ASSERT(parent->isInner(), "xrpl::SHAMap::descend : valid parent input");
313 XRPL_ASSERT((branch >= 0) && (branch < branchFactor), "xrpl::SHAMap::descend : valid branch input");
314 XRPL_ASSERT(!parent->isEmptyBranch(branch), "xrpl::SHAMap::descend : parent branch is non-empty");
315
316 SHAMapTreeNode* child = parent->getChildPointer(branch);
317
318 if (!child)
319 {
320 auto const& childHash = parent->getChildHash(branch);
321 intr_ptr::SharedPtr<SHAMapTreeNode> childNode = fetchNodeNT(childHash, filter);
322
323 if (childNode)
324 {
325 childNode = parent->canonicalizeChild(branch, std::move(childNode));
326 child = childNode.get();
327 }
328 }
329
330 return std::make_pair(child, parentID.getChildNodeID(branch));
331}
332
335 SHAMapInnerNode* parent,
336 int branch,
337 SHAMapSyncFilter* filter,
338 bool& pending,
339 descendCallback&& callback) const
340{
341 pending = false;
342
343 SHAMapTreeNode* ret = parent->getChildPointer(branch);
344 if (ret)
345 return ret;
346
347 auto const& hash = parent->getChildHash(branch);
348
349 auto ptr = cacheLookup(hash);
350 if (!ptr)
351 {
352 if (filter)
353 ptr = checkFilter(hash, filter);
354
355 if (!ptr && backed_)
356 {
357 f_.db().asyncFetch(
358 hash.as_uint256(),
360 [this, hash, cb{std::move(callback)}](std::shared_ptr<NodeObject> const& object) {
361 auto node = finishFetch(hash, object);
362 cb(node, hash);
363 });
364 pending = true;
365 return nullptr;
366 }
367 }
368
369 if (ptr)
370 ptr = parent->canonicalizeChild(branch, std::move(ptr));
371
372 return ptr.get();
373}
374
375template <class Node>
378{
379 // make sure the node is suitable for the intended operation (copy on write)
380 XRPL_ASSERT(node->cowid() <= cowid_, "xrpl::SHAMap::unshareNode : node valid for cowid");
381 if (node->cowid() != cowid_)
382 {
383 // have a CoW
384 XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::unshareNode : not immutable");
385 node = intr_ptr::static_pointer_cast<Node>(node->clone(cowid_));
386 if (nodeID.isRoot())
387 root_ = node;
388 }
389 return node;
390}
391
395 SharedPtrNodeStack& stack,
396 int branch,
397 std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const& loopParams) const
398{
399 auto& [init, cmp, incr] = loopParams;
400 if (node->isLeaf())
401 {
402 auto n = intr_ptr::static_pointer_cast<SHAMapLeafNode>(node);
403 stack.push({node, {leafDepth, n->peekItem()->key()}});
404 return n.get();
405 }
406 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
407 if (stack.empty())
408 stack.push({inner, SHAMapNodeID{}});
409 else
410 stack.push({inner, stack.top().second.getChildNodeID(branch)});
411 for (int i = init; cmp(i);)
412 {
413 if (!inner->isEmptyBranch(i))
414 {
415 node.adopt(descendThrow(inner.get(), i));
416 XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack");
417 if (node->isLeaf())
418 {
419 auto n = intr_ptr::static_pointer_cast<SHAMapLeafNode>(node);
420 stack.push({n, {leafDepth, n->peekItem()->key()}});
421 return n.get();
422 }
423 inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
424 stack.push({inner, stack.top().second.getChildNodeID(branch)});
425 i = init; // descend and reset loop
426 }
427 else
428 incr(i); // scan next branch
429 }
430 return nullptr;
431}
434{
435 auto init = branchFactor - 1;
436 auto cmp = [](int i) { return i >= 0; };
437 auto incr = [](int& i) { --i; };
438
439 return belowHelper(node, stack, branch, {init, cmp, incr});
440}
443{
444 auto init = 0;
445 auto cmp = [](int i) { return i <= branchFactor; };
446 auto incr = [](int& i) { ++i; };
447
448 return belowHelper(node, stack, branch, {init, cmp, incr});
449}
450static boost::intrusive_ptr<SHAMapItem const> const no_item;
451
452boost::intrusive_ptr<SHAMapItem const> const&
454{
455 // If there is only one item below this node, return it
456
457 while (!node->isLeaf())
458 {
459 SHAMapTreeNode* nextNode = nullptr;
460 auto inner = static_cast<SHAMapInnerNode*>(node);
461 for (int i = 0; i < branchFactor; ++i)
462 {
463 if (!inner->isEmptyBranch(i))
464 {
465 if (nextNode)
466 return no_item;
467
468 nextNode = descendThrow(inner, i);
469 }
470 }
471
472 if (!nextNode)
473 {
474 // LCOV_EXCL_START
475 UNREACHABLE("xrpl::SHAMap::onlyBelow : no next node");
476 return no_item;
477 // LCOV_EXCL_STOP
478 }
479
480 node = nextNode;
481 }
482
483 // An inner node must have at least one leaf
484 // below it, unless it's the root_
485 auto const leaf = static_cast<SHAMapLeafNode const*>(node);
486 XRPL_ASSERT(leaf->peekItem() || (leaf == root_.get()), "xrpl::SHAMap::onlyBelow : valid inner node");
487 return leaf->peekItem();
488}
489
490SHAMapLeafNode const*
492{
493 XRPL_ASSERT(stack.empty(), "xrpl::SHAMap::peekFirstItem : empty stack input");
494 SHAMapLeafNode* node = firstBelow(root_, stack);
495 if (!node)
496 {
497 while (!stack.empty())
498 stack.pop();
499 return nullptr;
500 }
501 return node;
502}
503
504SHAMapLeafNode const*
506{
507 XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::peekNextItem : non-empty stack input");
508 XRPL_ASSERT(stack.top().first->isLeaf(), "xrpl::SHAMap::peekNextItem : stack starts with leaf");
509 stack.pop();
510 while (!stack.empty())
511 {
512 auto [node, nodeID] = stack.top();
513 XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::peekNextItem : another node is not leaf");
514 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
515 for (auto i = selectBranch(nodeID, id) + 1; i < branchFactor; ++i)
516 {
517 if (!inner->isEmptyBranch(i))
518 {
519 node = descendThrow(*inner, i);
520 auto leaf = firstBelow(node, stack, i);
521 if (!leaf)
522 Throw<SHAMapMissingNode>(type_, id);
523 XRPL_ASSERT(leaf->isLeaf(), "xrpl::SHAMap::peekNextItem : leaf is valid");
524 return leaf;
525 }
526 }
527 stack.pop();
528 }
529 // must be last item
530 return nullptr;
531}
532
533boost::intrusive_ptr<SHAMapItem const> const&
534SHAMap::peekItem(uint256 const& id) const
535{
536 SHAMapLeafNode* leaf = findKey(id);
537
538 if (!leaf)
539 return no_item;
540
541 return leaf->peekItem();
542}
543
544boost::intrusive_ptr<SHAMapItem const> const&
545SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const
546{
547 SHAMapLeafNode* leaf = findKey(id);
548
549 if (!leaf)
550 return no_item;
551
552 hash = leaf->getHash();
553 return leaf->peekItem();
554}
555
558{
559 SharedPtrNodeStack stack;
560 walkTowardsKey(id, &stack);
561 while (!stack.empty())
562 {
563 auto [node, nodeID] = stack.top();
564 if (node->isLeaf())
565 {
566 auto leaf = static_cast<SHAMapLeafNode*>(node.get());
567 if (leaf->peekItem()->key() > id)
568 return const_iterator(this, leaf->peekItem().get(), std::move(stack));
569 }
570 else
571 {
572 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
573 for (auto branch = selectBranch(nodeID, id) + 1; branch < branchFactor; ++branch)
574 {
575 if (!inner->isEmptyBranch(branch))
576 {
577 node = descendThrow(*inner, branch);
578 auto leaf = firstBelow(node, stack, branch);
579 if (!leaf)
580 Throw<SHAMapMissingNode>(type_, id);
581 return const_iterator(this, leaf->peekItem().get(), std::move(stack));
582 }
583 }
584 }
585 stack.pop();
586 }
587 return end();
588}
591{
592 SharedPtrNodeStack stack;
593 walkTowardsKey(id, &stack);
594 while (!stack.empty())
595 {
596 auto [node, nodeID] = stack.top();
597 if (node->isLeaf())
598 {
599 auto leaf = static_cast<SHAMapLeafNode*>(node.get());
600 if (leaf->peekItem()->key() < id)
601 return const_iterator(this, leaf->peekItem().get(), std::move(stack));
602 }
603 else
604 {
605 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
606 for (int branch = selectBranch(nodeID, id) - 1; branch >= 0; --branch)
607 {
608 if (!inner->isEmptyBranch(branch))
609 {
610 node = descendThrow(*inner, branch);
611 auto leaf = lastBelow(node, stack, branch);
612 if (!leaf)
613 Throw<SHAMapMissingNode>(type_, id);
614 return const_iterator(this, leaf->peekItem().get(), std::move(stack));
615 }
616 }
617 }
618 stack.pop();
619 }
620 // TODO: what to return here?
621 return end();
622}
623
624bool
625SHAMap::hasItem(uint256 const& id) const
626{
627 return (findKey(id) != nullptr);
628}
629
630bool
632{
633 // delete the item with this ID
634 XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable");
635
636 SharedPtrNodeStack stack;
637 walkTowardsKey(id, &stack);
638
639 if (stack.empty())
640 Throw<SHAMapMissingNode>(type_, id);
641
642 auto leaf = intr_ptr::dynamic_pointer_cast<SHAMapLeafNode>(stack.top().first);
643 stack.pop();
644
645 if (!leaf || (leaf->peekItem()->key() != id))
646 return false;
647
648 SHAMapNodeType type = leaf->getType();
649
650 // What gets attached to the end of the chain
651 // (For now, nothing, since we deleted the leaf)
653
654 while (!stack.empty())
655 {
656 auto node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(stack.top().first);
657 SHAMapNodeID nodeID = stack.top().second;
658 stack.pop();
659
660 node = unshareNode(std::move(node), nodeID);
661 node->setChild(selectBranch(nodeID, id), std::move(prevNode));
662
663 if (!nodeID.isRoot())
664 {
665 // we may have made this a node with 1 or 0 children
666 // And, if so, we need to remove this branch
667 int const bc = node->getBranchCount();
668 if (bc == 0)
669 {
670 // no children below this branch
671 prevNode.reset();
672 }
673 else if (bc == 1)
674 {
675 // If there's only one item, pull up on the thread
676 auto item = onlyBelow(node.get());
677
678 if (item)
679 {
680 for (int i = 0; i < branchFactor; ++i)
681 {
682 if (!node->isEmptyBranch(i))
683 {
684 node->setChild(i, intr_ptr::SharedPtr<SHAMapTreeNode>{});
685 break;
686 }
687 }
688
689 prevNode = makeTypedLeaf(type, item, node->cowid());
690 }
691 else
692 {
693 prevNode = std::move(node);
694 }
695 }
696 else
697 {
698 // This node is now the end of the branch
699 prevNode = std::move(node);
700 }
701 }
702 }
703
704 return true;
705}
706
707bool
708SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const> item)
709{
710 XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::addGiveItem : not immutable");
711 XRPL_ASSERT(type != SHAMapNodeType::tnINNER, "xrpl::SHAMap::addGiveItem : valid type input");
712
713 // add the specified item, does not update
714 uint256 tag = item->key();
715
716 SharedPtrNodeStack stack;
717 walkTowardsKey(tag, &stack);
718
719 if (stack.empty())
720 Throw<SHAMapMissingNode>(type_, tag);
721
722 auto [node, nodeID] = stack.top();
723 stack.pop();
724
725 if (node->isLeaf())
726 {
727 auto leaf = intr_ptr::static_pointer_cast<SHAMapLeafNode>(node);
728 if (leaf->peekItem()->key() == tag)
729 return false;
730 }
731 node = unshareNode(std::move(node), nodeID);
732 if (node->isInner())
733 {
734 // easy case, we end on an inner node
735 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
736 int branch = selectBranch(nodeID, tag);
737 XRPL_ASSERT(inner->isEmptyBranch(branch), "xrpl::SHAMap::addGiveItem : inner branch is empty");
738 inner->setChild(branch, makeTypedLeaf(type, std::move(item), cowid_));
739 }
740 else
741 {
742 // this is a leaf node that has to be made an inner node holding two
743 // items
744 auto leaf = intr_ptr::static_pointer_cast<SHAMapLeafNode>(node);
745 auto otherItem = leaf->peekItem();
746 XRPL_ASSERT(otherItem && (tag != otherItem->key()), "xrpl::SHAMap::addGiveItem : non-null item");
747
748 node = intr_ptr::make_shared<SHAMapInnerNode>(node->cowid());
749
750 unsigned int b1, b2;
751
752 while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key())))
753 {
754 stack.push({node, nodeID});
755
756 // we need a new inner node, since both go on same branch at this
757 // level
758 nodeID = nodeID.getChildNodeID(b1);
759 node = intr_ptr::make_shared<SHAMapInnerNode>(cowid_);
760 }
761
762 // we can add the two leaf nodes here
763 XRPL_ASSERT(node->isInner(), "xrpl::SHAMap::addGiveItem : node is inner");
764
765 auto inner = static_cast<SHAMapInnerNode*>(node.get());
766 inner->setChild(b1, makeTypedLeaf(type, std::move(item), cowid_));
767 inner->setChild(b2, makeTypedLeaf(type, std::move(otherItem), cowid_));
768 }
769
770 dirtyUp(stack, tag, node);
771 return true;
772}
773
774bool
775SHAMap::addItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const> item)
776{
777 return addGiveItem(type, std::move(item));
778}
779
782{
783 auto hash = root_->getHash();
784 if (hash.isZero())
785 {
786 const_cast<SHAMap&>(*this).unshare();
787 hash = root_->getHash();
788 }
789 return hash;
790}
791
792bool
793SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const> item)
794{
795 // can't change the tag but can change the hash
796 uint256 tag = item->key();
797
798 XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::updateGiveItem : not immutable");
799
800 SharedPtrNodeStack stack;
801 walkTowardsKey(tag, &stack);
802
803 if (stack.empty())
804 Throw<SHAMapMissingNode>(type_, tag);
805
806 auto node = intr_ptr::dynamic_pointer_cast<SHAMapLeafNode>(stack.top().first);
807 auto nodeID = stack.top().second;
808 stack.pop();
809
810 if (!node || (node->peekItem()->key() != tag))
811 {
812 // LCOV_EXCL_START
813 UNREACHABLE("xrpl::SHAMap::updateGiveItem : invalid node");
814 return false;
815 // LCOV_EXCL_STOP
816 }
817
818 if (node->getType() != type)
819 {
820 JLOG(journal_.fatal()) << "SHAMap::updateGiveItem: cross-type change!";
821 return false;
822 }
823
824 node = unshareNode(std::move(node), nodeID);
825
826 if (node->setItem(item))
827 dirtyUp(stack, tag, node);
828
829 return true;
830}
831
832bool
834{
835 if (hash == root_->getHash())
836 return true;
837
838 if (auto stream = journal_.trace())
839 {
841 {
842 stream << "Fetch root TXN node " << hash;
843 }
844 else if (type_ == SHAMapType::STATE)
845 {
846 stream << "Fetch root STATE node " << hash;
847 }
848 else
849 {
850 stream << "Fetch root SHAMap node " << hash;
851 }
852 }
853
854 auto newRoot = fetchNodeNT(hash, filter);
855
856 if (newRoot)
857 {
858 root_ = newRoot;
859 XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::fetchRoot : root hash do match");
860 return true;
861 }
862
863 return false;
864}
865
880{
881 XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::writeNode : valid input node");
882 XRPL_ASSERT(backed_, "xrpl::SHAMap::writeNode : is backed");
883
884 canonicalize(node->getHash(), node);
885
886 Serializer s;
887 node->serializeWithPrefix(s);
888 f_.db().store(t, std::move(s.modData()), node->getHash().as_uint256(), ledgerSeq_);
889 return node;
890}
891
892// We can't modify an inner node someone else might have a
893// pointer to because flushing modifies inner nodes -- it
894// makes them point to canonical/shared nodes.
895template <class Node>
898{
899 // A shared node should never need to be flushed
900 // because that would imply someone modified it
901 XRPL_ASSERT(node->cowid(), "xrpl::SHAMap::preFlushNode : valid input node");
902
903 if (node->cowid() != cowid_)
904 {
905 // Node is not uniquely ours, so unshare it before
906 // possibly modifying it
907 node = intr_ptr::static_pointer_cast<Node>(node->clone(cowid_));
908 }
909 return node;
910}
911
912int
914{
915 // Don't share nodes with parent map
916 return walkSubTree(false, hotUNKNOWN);
917}
918
919int
921{
922 // We only write back if this map is backed.
923 return walkSubTree(backed_, t);
924}
925
926int
928{
929 XRPL_ASSERT(!doWrite || backed_, "xrpl::SHAMap::walkSubTree : valid input");
930
931 int flushed = 0;
932
933 if (!root_ || (root_->cowid() == 0))
934 return flushed;
935
936 if (root_->isLeaf())
937 { // special case -- root_ is leaf
938 root_ = preFlushNode(std::move(root_));
939 root_->updateHash();
940 root_->unshare();
941
942 if (doWrite)
943 root_ = writeNode(t, std::move(root_));
944
945 return 1;
946 }
947
948 auto node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_);
949
950 if (node->isEmpty())
951 { // replace empty root with a new empty root
952 root_ = intr_ptr::make_shared<SHAMapInnerNode>(0);
953 return 1;
954 }
955
956 // Stack of {parent,index,child} pointers representing
957 // inner nodes we are in the process of flushing
958 using StackEntry = std::pair<intr_ptr::SharedPtr<SHAMapInnerNode>, int>;
960
961 node = preFlushNode(std::move(node));
962
963 int pos = 0;
964
965 // We can't flush an inner node until we flush its children
966 while (1)
967 {
968 while (pos < branchFactor)
969 {
970 if (node->isEmptyBranch(pos))
971 {
972 ++pos;
973 }
974 else
975 {
976 // No need to do I/O. If the node isn't linked,
977 // it can't need to be flushed
978 int branch = pos;
979 auto child = node->getChild(pos++);
980
981 if (child && (child->cowid() != 0))
982 {
983 // This is a node that needs to be flushed
984
985 child = preFlushNode(std::move(child));
986
987 if (child->isInner())
988 {
989 // save our place and work on this node
990
991 stack.emplace(std::move(node), branch);
992 // The semantics of this changes when we move to c++-20
993 // Right now no move will occur; With c++-20 child will
994 // be moved from.
995 node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(std::move(child));
996 pos = 0;
997 }
998 else
999 {
1000 // flush this leaf
1001 ++flushed;
1002
1003 XRPL_ASSERT(
1004 node->cowid() == cowid_,
1005 "xrpl::SHAMap::walkSubTree : node cowid do "
1006 "match");
1007 child->updateHash();
1008 child->unshare();
1009
1010 if (doWrite)
1011 child = writeNode(t, std::move(child));
1012
1013 node->shareChild(branch, child);
1014 }
1015 }
1016 }
1017 }
1018
1019 // update the hash of this inner node
1020 node->updateHashDeep();
1021
1022 // This inner node can now be shared
1023 node->unshare();
1024
1025 if (doWrite)
1026 node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(writeNode(t, std::move(node)));
1027
1028 ++flushed;
1029
1030 if (stack.empty())
1031 break;
1032
1033 auto parent = std::move(stack.top().first);
1034 pos = stack.top().second;
1035 stack.pop();
1036
1037 // Hook this inner node to its parent
1038 XRPL_ASSERT(parent->cowid() == cowid_, "xrpl::SHAMap::walkSubTree : parent cowid do match");
1039 parent->shareChild(pos, node);
1040
1041 // Continue with parent's next child, if any
1042 node = std::move(parent);
1043 ++pos;
1044 }
1045
1046 // Last inner node is the new root_
1047 root_ = std::move(node);
1048
1049 return flushed;
1050}
1051
1052void
1053SHAMap::dump(bool hash) const
1054{
1055 int leafCount = 0;
1056 JLOG(journal_.info()) << " MAP Contains";
1057
1059 stack.push({root_.get(), SHAMapNodeID()});
1060
1061 do
1062 {
1063 auto [node, nodeID] = stack.top();
1064 stack.pop();
1065
1066 JLOG(journal_.info()) << node->getString(nodeID);
1067 if (hash)
1068 {
1069 JLOG(journal_.info()) << "Hash: " << node->getHash();
1070 }
1071
1072 if (node->isInner())
1073 {
1074 auto inner = static_cast<SHAMapInnerNode*>(node);
1075 for (int i = 0; i < branchFactor; ++i)
1076 {
1077 if (!inner->isEmptyBranch(i))
1078 {
1079 auto child = inner->getChildPointer(i);
1080 if (child)
1081 {
1082 XRPL_ASSERT(
1083 child->getHash() == inner->getChildHash(i), "xrpl::SHAMap::dump : child hash do match");
1084 stack.push({child, nodeID.getChildNodeID(i)});
1085 }
1086 }
1087 }
1088 }
1089 else
1090 ++leafCount;
1091 } while (!stack.empty());
1092
1093 JLOG(journal_.info()) << leafCount << " resident leaves";
1094}
1095
1098{
1099 auto ret = f_.getTreeNodeCache()->fetch(hash.as_uint256());
1100 XRPL_ASSERT(!ret || !ret->cowid(), "xrpl::SHAMap::cacheLookup : not found or zero cowid");
1101 return ret;
1102}
1103
1104void
1106{
1107 XRPL_ASSERT(backed_, "xrpl::SHAMap::canonicalize : is backed");
1108 XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::canonicalize : valid node input");
1109 XRPL_ASSERT(node->getHash() == hash, "xrpl::SHAMap::canonicalize : node hash do match");
1110
1111 f_.getTreeNodeCache()->canonicalize_replace_client(hash.as_uint256(), node);
1112}
1113
1114void
1116{
1117 (void)getHash(); // update node hashes
1118 auto node = root_.get();
1119 XRPL_ASSERT(node, "xrpl::SHAMap::invariants : non-null root node");
1120 XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::invariants : root node is not leaf");
1121 SharedPtrNodeStack stack;
1122 for (auto leaf = peekFirstItem(stack); leaf != nullptr; leaf = peekNextItem(leaf->peekItem()->key(), stack))
1123 ;
1124 node->invariants(true);
1125}
1126
1127} // namespace xrpl
Stream fatal() const
Definition Journal.h:325
Stream info() const
Definition Journal.h:307
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
virtual beast::Journal const & journal()=0
virtual NodeStore::Database & db()=0
virtual void missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const &nodeHash)=0
Acquire ledger that has a missing node by ledger sequence.
virtual std::shared_ptr< TreeNodeCache > getTreeNodeCache()=0
Return a pointer to the Family Tree Node Cache.
virtual void asyncFetch(uint256 const &hash, std::uint32_t ledgerSeq, std::function< void(std::shared_ptr< NodeObject > const &)> &&callback)
Fetch an object without waiting.
Definition Database.cpp:149
std::shared_ptr< NodeObject > fetchNodeObject(uint256 const &hash, std::uint32_t ledgerSeq=0, FetchType fetchType=FetchType::synchronous, bool duplicate=false)
Fetch a node object.
Definition Database.cpp:202
virtual void store(NodeObjectType type, Blob &&data, uint256 const &hash, std::uint32_t ledgerSeq)=0
Store the object.
uint256 const & as_uint256() const
Definition SHAMapHash.h:25
SHAMapHash const & getChildHash(int m) const
SHAMapTreeNode * getChildPointer(int branch)
bool isInner() const override
Determines if this is an inner node.
intr_ptr::SharedPtr< SHAMapTreeNode > canonicalizeChild(int branch, intr_ptr::SharedPtr< SHAMapTreeNode > node)
intr_ptr::SharedPtr< SHAMapTreeNode > getChild(int branch)
void setChild(int m, intr_ptr::SharedPtr< SHAMapTreeNode > child)
bool isEmptyBranch(int m) const
boost::intrusive_ptr< SHAMapItem const > const & peekItem() const
Identifies a node inside a SHAMap.
SHAMapNodeID getChildNodeID(unsigned int m) const
bool isRoot() const
virtual std::optional< Blob > getNode(SHAMapHash const &nodeHash) const =0
virtual void gotNode(bool fromFilter, SHAMapHash const &nodeHash, std::uint32_t ledgerSeq, Blob &&nodeData, SHAMapNodeType type) const =0
static intr_ptr::SharedPtr< SHAMapTreeNode > makeFromPrefix(Slice rawNode, SHAMapHash const &hash)
SHAMapHash const & getHash() const
Return the hash of this node.
virtual bool isLeaf() const =0
Determines if this is a leaf node.
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition SHAMap.h:78
SHAMapTreeNode * descend(SHAMapInnerNode *, int branch) const
Definition SHAMap.cpp:269
SHAMapLeafNode * walkTowardsKey(uint256 const &id, SharedPtrNodeStack *stack=nullptr) const
Walk towards the specified id, returning the node.
Definition SHAMap.cpp:97
bool addItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition SHAMap.cpp:775
const_iterator upper_bound(uint256 const &id) const
Find the first item after the given item.
Definition SHAMap.cpp:557
SHAMapState state_
Definition SHAMap.h:90
const_iterator lower_bound(uint256 const &id) const
Find the object with the greatest object id smaller than the input id.
Definition SHAMap.cpp:590
bool full_
Definition SHAMap.h:93
intr_ptr::SharedPtr< SHAMapTreeNode > checkFilter(SHAMapHash const &hash, SHAMapSyncFilter *filter) const
Definition SHAMap.cpp:175
const_iterator end() const
Definition SHAMap.h:675
SHAMapLeafNode * firstBelow(intr_ptr::SharedPtr< SHAMapTreeNode >, SharedPtrNodeStack &stack, int branch=0) const
Definition SHAMap.cpp:442
intr_ptr::SharedPtr< SHAMapTreeNode > writeNode(NodeObjectType t, intr_ptr::SharedPtr< SHAMapTreeNode > node) const
write and canonicalize modified node
Definition SHAMap.cpp:879
SHAMapTreeNode * descendAsync(SHAMapInnerNode *parent, int branch, SHAMapSyncFilter *filter, bool &pending, descendCallback &&) const
Definition SHAMap.cpp:334
std::uint32_t cowid_
ID to distinguish this map for all others we're sharing nodes with.
Definition SHAMap.h:84
Family & f_
Definition SHAMap.h:80
boost::intrusive_ptr< SHAMapItem const > const & peekItem(uint256 const &id) const
Definition SHAMap.cpp:534
int flushDirty(NodeObjectType t)
Flush modified nodes to the nodestore and convert them to shared.
Definition SHAMap.cpp:920
intr_ptr::SharedPtr< SHAMapTreeNode > fetchNodeFromDB(SHAMapHash const &hash) const
Definition SHAMap.cpp:132
SHAMapLeafNode * lastBelow(intr_ptr::SharedPtr< SHAMapTreeNode > node, SharedPtrNodeStack &stack, int branch=branchFactor) const
Definition SHAMap.cpp:433
static constexpr unsigned int leafDepth
The depth of the hash map: data is only present in the leaves.
Definition SHAMap.h:101
intr_ptr::SharedPtr< SHAMapTreeNode > fetchNode(SHAMapHash const &hash) const
Definition SHAMap.cpp:236
SHAMapLeafNode const * peekNextItem(uint256 const &id, SharedPtrNodeStack &stack) const
Definition SHAMap.cpp:505
void dump(bool withHashes=false) const
Definition SHAMap.cpp:1053
beast::Journal journal_
Definition SHAMap.h:81
SHAMapLeafNode * belowHelper(intr_ptr::SharedPtr< SHAMapTreeNode > node, SharedPtrNodeStack &stack, int branch, std::tuple< int, std::function< bool(int)>, std::function< void(int &)> > const &loopParams) const
Definition SHAMap.cpp:393
bool backed_
Definition SHAMap.h:92
static constexpr unsigned int branchFactor
Number of children each non-leaf node has (the 'radix tree' part of the map)
Definition SHAMap.h:98
std::shared_ptr< SHAMap > snapShot(bool isMutable) const
Definition SHAMap.cpp:62
bool hasItem(uint256 const &id) const
Does the tree have an item with the given ID?
Definition SHAMap.cpp:625
int unshare()
Convert any modified nodes to shared.
Definition SHAMap.cpp:913
bool updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition SHAMap.cpp:793
SHAMapType const type_
Definition SHAMap.h:91
SHAMap()=delete
intr_ptr::SharedPtr< SHAMapTreeNode > finishFetch(SHAMapHash const &hash, std::shared_ptr< NodeObject > const &object) const
Definition SHAMap.cpp:140
intr_ptr::SharedPtr< SHAMapTreeNode > fetchNodeNT(SHAMapHash const &hash) const
Definition SHAMap.cpp:224
void canonicalize(SHAMapHash const &hash, intr_ptr::SharedPtr< SHAMapTreeNode > &) const
Definition SHAMap.cpp:1105
void invariants() const
Definition SHAMap.cpp:1115
SHAMapLeafNode * findKey(uint256 const &id) const
Return nullptr if key not found.
Definition SHAMap.cpp:123
bool addGiveItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition SHAMap.cpp:708
intr_ptr::SharedPtr< SHAMapTreeNode > cacheLookup(SHAMapHash const &hash) const
Definition SHAMap.cpp:1097
intr_ptr::SharedPtr< Node > preFlushNode(intr_ptr::SharedPtr< Node > node) const
prepare a node to be modified before flushing
Definition SHAMap.cpp:897
intr_ptr::SharedPtr< Node > unshareNode(intr_ptr::SharedPtr< Node >, SHAMapNodeID const &nodeID)
Unshare the node, allowing it to be modified.
Definition SHAMap.cpp:377
boost::intrusive_ptr< SHAMapItem const > const & onlyBelow(SHAMapTreeNode *) const
If there is only one leaf below this node, get its contents.
Definition SHAMap.cpp:453
void dirtyUp(SharedPtrNodeStack &stack, uint256 const &target, intr_ptr::SharedPtr< SHAMapTreeNode > terminal)
Update hashes up to the root.
Definition SHAMap.cpp:68
bool fetchRoot(SHAMapHash const &hash, SHAMapSyncFilter *filter)
Definition SHAMap.cpp:833
std::uint32_t ledgerSeq_
The sequence of the ledger that this map references, if any.
Definition SHAMap.h:87
intr_ptr::SharedPtr< SHAMapTreeNode > descendNoStore(SHAMapInnerNode &, int branch) const
Definition SHAMap.cpp:301
int walkSubTree(bool doWrite, NodeObjectType t)
Definition SHAMap.cpp:927
SHAMapHash getHash() const
Definition SHAMap.cpp:781
bool delItem(uint256 const &id)
Definition SHAMap.cpp:631
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition SHAMap.cpp:247
intr_ptr::SharedPtr< SHAMapTreeNode > root_
Definition SHAMap.h:89
SHAMapLeafNode const * peekFirstItem(SharedPtrNodeStack &stack) const
Definition SHAMap.cpp:491
A shared intrusive pointer class that supports weak pointers.
T * get() const
Get the raw pointer.
void reset()
Set the pointer to null, decrement the strong count, and run the appropriate release action.
void adopt(T *p)
Adopt the raw pointer.
T emplace(T... args)
T empty(T... args)
T is_same_v
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
static boost::intrusive_ptr< SHAMapItem const > const no_item
Definition SHAMap.cpp:450
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
@ pending
List will be valid in the future.
NodeObjectType
The types of node objects.
Definition NodeObject.h:13
@ hotUNKNOWN
Definition NodeObject.h:14
intr_ptr::SharedPtr< SHAMapLeafNode > makeTypedLeaf(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item, std::uint32_t owner)
Definition SHAMap.cpp:13
unsigned int selectBranch(SHAMapNodeID const &id, uint256 const &hash)
Returns the branch that would contain the given hash.
SHAMapState
Describes the current state of a given SHAMap.
Definition SHAMap.h:28
@ Immutable
The map is set in stone and cannot be changed.
@ Synching
The map's hash is fixed but valid nodes may be missing and can be added.
@ Modifying
The map is in flux and objects can be added and removed.
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:214
T pop(T... args)
T push(T... args)
T to_string(T... args)
T top(T... args)
T what(T... args)