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