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