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