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