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