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