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 UNREACHABLE("ripple::SHAMap::onlyBelow : no next node");
549 return no_item;
550 }
551
552 node = nextNode;
553 }
554
555 // An inner node must have at least one leaf
556 // below it, unless it's the root_
557 auto const leaf = static_cast<SHAMapLeafNode const*>(node);
558 XRPL_ASSERT(
559 leaf->peekItem() || (leaf == root_.get()),
560 "ripple::SHAMap::onlyBelow : valid inner node");
561 return leaf->peekItem();
562}
563
564SHAMapLeafNode const*
566{
567 XRPL_ASSERT(
568 stack.empty(), "ripple::SHAMap::peekFirstItem : empty stack input");
569 SHAMapLeafNode* node = firstBelow(root_, stack);
570 if (!node)
571 {
572 while (!stack.empty())
573 stack.pop();
574 return nullptr;
575 }
576 return node;
577}
578
579SHAMapLeafNode const*
581{
582 XRPL_ASSERT(
583 !stack.empty(), "ripple::SHAMap::peekNextItem : non-empty stack input");
584 XRPL_ASSERT(
585 stack.top().first->isLeaf(),
586 "ripple::SHAMap::peekNextItem : stack starts with leaf");
587 stack.pop();
588 while (!stack.empty())
589 {
590 auto [node, nodeID] = stack.top();
591 XRPL_ASSERT(
592 !node->isLeaf(),
593 "ripple::SHAMap::peekNextItem : another node is not leaf");
594 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
595 for (auto i = selectBranch(nodeID, id) + 1; i < branchFactor; ++i)
596 {
597 if (!inner->isEmptyBranch(i))
598 {
599 node = descendThrow(*inner, i);
600 auto leaf = firstBelow(node, stack, i);
601 if (!leaf)
602 Throw<SHAMapMissingNode>(type_, id);
603 XRPL_ASSERT(
604 leaf->isLeaf(),
605 "ripple::SHAMap::peekNextItem : leaf is valid");
606 return leaf;
607 }
608 }
609 stack.pop();
610 }
611 // must be last item
612 return nullptr;
613}
614
615boost::intrusive_ptr<SHAMapItem const> const&
616SHAMap::peekItem(uint256 const& id) const
617{
618 SHAMapLeafNode* leaf = findKey(id);
619
620 if (!leaf)
621 return no_item;
622
623 return leaf->peekItem();
624}
625
626boost::intrusive_ptr<SHAMapItem const> const&
627SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const
628{
629 SHAMapLeafNode* leaf = findKey(id);
630
631 if (!leaf)
632 return no_item;
633
634 hash = leaf->getHash();
635 return leaf->peekItem();
636}
637
640{
641 SharedPtrNodeStack stack;
642 walkTowardsKey(id, &stack);
643 while (!stack.empty())
644 {
645 auto [node, nodeID] = stack.top();
646 if (node->isLeaf())
647 {
648 auto leaf = static_cast<SHAMapLeafNode*>(node.get());
649 if (leaf->peekItem()->key() > id)
650 return const_iterator(
651 this, leaf->peekItem().get(), std::move(stack));
652 }
653 else
654 {
655 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
656 for (auto branch = selectBranch(nodeID, id) + 1;
657 branch < branchFactor;
658 ++branch)
659 {
660 if (!inner->isEmptyBranch(branch))
661 {
662 node = descendThrow(*inner, branch);
663 auto leaf = firstBelow(node, stack, branch);
664 if (!leaf)
665 Throw<SHAMapMissingNode>(type_, id);
666 return const_iterator(
667 this, leaf->peekItem().get(), std::move(stack));
668 }
669 }
670 }
671 stack.pop();
672 }
673 return end();
674}
677{
678 SharedPtrNodeStack stack;
679 walkTowardsKey(id, &stack);
680 while (!stack.empty())
681 {
682 auto [node, nodeID] = stack.top();
683 if (node->isLeaf())
684 {
685 auto leaf = static_cast<SHAMapLeafNode*>(node.get());
686 if (leaf->peekItem()->key() < id)
687 return const_iterator(
688 this, leaf->peekItem().get(), std::move(stack));
689 }
690 else
691 {
692 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
693 for (int branch = selectBranch(nodeID, id) - 1; branch >= 0;
694 --branch)
695 {
696 if (!inner->isEmptyBranch(branch))
697 {
698 node = descendThrow(*inner, branch);
699 auto leaf = lastBelow(node, stack, branch);
700 if (!leaf)
701 Throw<SHAMapMissingNode>(type_, id);
702 return const_iterator(
703 this, leaf->peekItem().get(), std::move(stack));
704 }
705 }
706 }
707 stack.pop();
708 }
709 // TODO: what to return here?
710 return end();
711}
712
713bool
714SHAMap::hasItem(uint256 const& id) const
715{
716 return (findKey(id) != nullptr);
717}
718
719bool
721{
722 // delete the item with this ID
723 XRPL_ASSERT(
725 "ripple::SHAMap::delItem : not immutable");
726
727 SharedPtrNodeStack stack;
728 walkTowardsKey(id, &stack);
729
730 if (stack.empty())
731 Throw<SHAMapMissingNode>(type_, id);
732
733 auto leaf =
734 intr_ptr::dynamic_pointer_cast<SHAMapLeafNode>(stack.top().first);
735 stack.pop();
736
737 if (!leaf || (leaf->peekItem()->key() != id))
738 return false;
739
740 SHAMapNodeType type = leaf->getType();
741
742 // What gets attached to the end of the chain
743 // (For now, nothing, since we deleted the leaf)
745
746 while (!stack.empty())
747 {
748 auto node =
749 intr_ptr::static_pointer_cast<SHAMapInnerNode>(stack.top().first);
750 SHAMapNodeID nodeID = stack.top().second;
751 stack.pop();
752
753 node = unshareNode(std::move(node), nodeID);
754 node->setChild(selectBranch(nodeID, id), std::move(prevNode));
755
756 if (!nodeID.isRoot())
757 {
758 // we may have made this a node with 1 or 0 children
759 // And, if so, we need to remove this branch
760 int const bc = node->getBranchCount();
761 if (bc == 0)
762 {
763 // no children below this branch
764 prevNode.reset();
765 }
766 else if (bc == 1)
767 {
768 // If there's only one item, pull up on the thread
769 auto item = onlyBelow(node.get());
770
771 if (item)
772 {
773 for (int i = 0; i < branchFactor; ++i)
774 {
775 if (!node->isEmptyBranch(i))
776 {
777 node->setChild(
779 break;
780 }
781 }
782
783 prevNode = makeTypedLeaf(type, item, node->cowid());
784 }
785 else
786 {
787 prevNode = std::move(node);
788 }
789 }
790 else
791 {
792 // This node is now the end of the branch
793 prevNode = std::move(node);
794 }
795 }
796 }
797
798 return true;
799}
800
801bool
803 SHAMapNodeType type,
804 boost::intrusive_ptr<SHAMapItem const> item)
805{
806 XRPL_ASSERT(
808 "ripple::SHAMap::addGiveItem : not immutable");
809 XRPL_ASSERT(
811 "ripple::SHAMap::addGiveItem : valid type input");
812
813 // add the specified item, does not update
814 uint256 tag = item->key();
815
816 SharedPtrNodeStack stack;
817 walkTowardsKey(tag, &stack);
818
819 if (stack.empty())
820 Throw<SHAMapMissingNode>(type_, tag);
821
822 auto [node, nodeID] = stack.top();
823 stack.pop();
824
825 if (node->isLeaf())
826 {
827 auto leaf = intr_ptr::static_pointer_cast<SHAMapLeafNode>(node);
828 if (leaf->peekItem()->key() == tag)
829 return false;
830 }
831 node = unshareNode(std::move(node), nodeID);
832 if (node->isInner())
833 {
834 // easy case, we end on an inner node
835 auto inner = intr_ptr::static_pointer_cast<SHAMapInnerNode>(node);
836 int branch = selectBranch(nodeID, tag);
837 XRPL_ASSERT(
838 inner->isEmptyBranch(branch),
839 "ripple::SHAMap::addGiveItem : inner branch is empty");
840 inner->setChild(branch, makeTypedLeaf(type, std::move(item), cowid_));
841 }
842 else
843 {
844 // this is a leaf node that has to be made an inner node holding two
845 // items
846 auto leaf = intr_ptr::static_pointer_cast<SHAMapLeafNode>(node);
847 auto otherItem = leaf->peekItem();
848 XRPL_ASSERT(
849 otherItem && (tag != otherItem->key()),
850 "ripple::SHAMap::addGiveItem : non-null item");
851
852 node = intr_ptr::make_shared<SHAMapInnerNode>(node->cowid());
853
854 unsigned int b1, b2;
855
856 while ((b1 = selectBranch(nodeID, tag)) ==
857 (b2 = selectBranch(nodeID, otherItem->key())))
858 {
859 stack.push({node, nodeID});
860
861 // we need a new inner node, since both go on same branch at this
862 // level
863 nodeID = nodeID.getChildNodeID(b1);
864 node = intr_ptr::make_shared<SHAMapInnerNode>(cowid_);
865 }
866
867 // we can add the two leaf nodes here
868 XRPL_ASSERT(
869 node->isInner(), "ripple::SHAMap::addGiveItem : node is inner");
870
871 auto inner = static_cast<SHAMapInnerNode*>(node.get());
872 inner->setChild(b1, makeTypedLeaf(type, std::move(item), cowid_));
873 inner->setChild(b2, makeTypedLeaf(type, std::move(otherItem), cowid_));
874 }
875
876 dirtyUp(stack, tag, node);
877 return true;
878}
879
880bool
882 SHAMapNodeType type,
883 boost::intrusive_ptr<SHAMapItem const> item)
884{
885 return addGiveItem(type, std::move(item));
886}
887
890{
891 auto hash = root_->getHash();
892 if (hash.isZero())
893 {
894 const_cast<SHAMap&>(*this).unshare();
895 hash = root_->getHash();
896 }
897 return hash;
898}
899
900bool
902 SHAMapNodeType type,
903 boost::intrusive_ptr<SHAMapItem const> item)
904{
905 // can't change the tag but can change the hash
906 uint256 tag = item->key();
907
908 XRPL_ASSERT(
910 "ripple::SHAMap::updateGiveItem : not immutable");
911
912 SharedPtrNodeStack stack;
913 walkTowardsKey(tag, &stack);
914
915 if (stack.empty())
916 Throw<SHAMapMissingNode>(type_, tag);
917
918 auto node =
919 intr_ptr::dynamic_pointer_cast<SHAMapLeafNode>(stack.top().first);
920 auto nodeID = stack.top().second;
921 stack.pop();
922
923 if (!node || (node->peekItem()->key() != tag))
924 {
925 UNREACHABLE("ripple::SHAMap::updateGiveItem : invalid node");
926 return false;
927 }
928
929 if (node->getType() != type)
930 {
931 JLOG(journal_.fatal()) << "SHAMap::updateGiveItem: cross-type change!";
932 return false;
933 }
934
935 node = unshareNode(std::move(node), nodeID);
936
937 if (node->setItem(item))
938 dirtyUp(stack, tag, node);
939
940 return true;
941}
942
943bool
945{
946 if (hash == root_->getHash())
947 return true;
948
949 if (auto stream = journal_.trace())
950 {
952 {
953 stream << "Fetch root TXN node " << hash;
954 }
955 else if (type_ == SHAMapType::STATE)
956 {
957 stream << "Fetch root STATE node " << hash;
958 }
959 else
960 {
961 stream << "Fetch root SHAMap node " << hash;
962 }
963 }
964
965 auto newRoot = fetchNodeNT(hash, filter);
966
967 if (newRoot)
968 {
969 root_ = newRoot;
970 XRPL_ASSERT(
971 root_->getHash() == hash,
972 "ripple::SHAMap::fetchRoot : root hash do match");
973 return true;
974 }
975
976 return false;
977}
978
993 const
994{
995 XRPL_ASSERT(
996 node->cowid() == 0, "ripple::SHAMap::writeNode : valid input node");
997 XRPL_ASSERT(backed_, "ripple::SHAMap::writeNode : is backed");
998
999 canonicalize(node->getHash(), node);
1000
1001 Serializer s;
1002 node->serializeWithPrefix(s);
1003 f_.db().store(
1004 t, std::move(s.modData()), node->getHash().as_uint256(), ledgerSeq_);
1005 return node;
1006}
1007
1008// We can't modify an inner node someone else might have a
1009// pointer to because flushing modifies inner nodes -- it
1010// makes them point to canonical/shared nodes.
1011template <class Node>
1014{
1015 // A shared node should never need to be flushed
1016 // because that would imply someone modified it
1017 XRPL_ASSERT(
1018 node->cowid(), "ripple::SHAMap::preFlushNode : valid input node");
1019
1020 if (node->cowid() != cowid_)
1021 {
1022 // Node is not uniquely ours, so unshare it before
1023 // possibly modifying it
1024 node = intr_ptr::static_pointer_cast<Node>(node->clone(cowid_));
1025 }
1026 return node;
1027}
1028
1029int
1031{
1032 // Don't share nodes with parent map
1033 return walkSubTree(false, hotUNKNOWN);
1034}
1035
1036int
1038{
1039 // We only write back if this map is backed.
1040 return walkSubTree(backed_, t);
1041}
1042
1043int
1045{
1046 XRPL_ASSERT(
1047 !doWrite || backed_, "ripple::SHAMap::walkSubTree : valid input");
1048
1049 int flushed = 0;
1050
1051 if (!root_ || (root_->cowid() == 0))
1052 return flushed;
1053
1054 if (root_->isLeaf())
1055 { // special case -- root_ is leaf
1056 root_ = preFlushNode(std::move(root_));
1057 root_->updateHash();
1058 root_->unshare();
1059
1060 if (doWrite)
1061 root_ = writeNode(t, std::move(root_));
1062
1063 return 1;
1064 }
1065
1066 auto node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_);
1067
1068 if (node->isEmpty())
1069 { // replace empty root with a new empty root
1070 root_ = intr_ptr::make_shared<SHAMapInnerNode>(0);
1071 return 1;
1072 }
1073
1074 // Stack of {parent,index,child} pointers representing
1075 // inner nodes we are in the process of flushing
1076 using StackEntry = std::pair<intr_ptr::SharedPtr<SHAMapInnerNode>, int>;
1078
1079 node = preFlushNode(std::move(node));
1080
1081 int pos = 0;
1082
1083 // We can't flush an inner node until we flush its children
1084 while (1)
1085 {
1086 while (pos < branchFactor)
1087 {
1088 if (node->isEmptyBranch(pos))
1089 {
1090 ++pos;
1091 }
1092 else
1093 {
1094 // No need to do I/O. If the node isn't linked,
1095 // it can't need to be flushed
1096 int branch = pos;
1097 auto child = node->getChild(pos++);
1098
1099 if (child && (child->cowid() != 0))
1100 {
1101 // This is a node that needs to be flushed
1102
1103 child = preFlushNode(std::move(child));
1104
1105 if (child->isInner())
1106 {
1107 // save our place and work on this node
1108
1109 stack.emplace(std::move(node), branch);
1110 // The semantics of this changes when we move to c++-20
1111 // Right now no move will occur; With c++-20 child will
1112 // be moved from.
1113 node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(
1114 std::move(child));
1115 pos = 0;
1116 }
1117 else
1118 {
1119 // flush this leaf
1120 ++flushed;
1121
1122 XRPL_ASSERT(
1123 node->cowid() == cowid_,
1124 "ripple::SHAMap::walkSubTree : node cowid do "
1125 "match");
1126 child->updateHash();
1127 child->unshare();
1128
1129 if (doWrite)
1130 child = writeNode(t, std::move(child));
1131
1132 node->shareChild(branch, child);
1133 }
1134 }
1135 }
1136 }
1137
1138 // update the hash of this inner node
1139 node->updateHashDeep();
1140
1141 // This inner node can now be shared
1142 node->unshare();
1143
1144 if (doWrite)
1145 node = intr_ptr::static_pointer_cast<SHAMapInnerNode>(
1146 writeNode(t, std::move(node)));
1147
1148 ++flushed;
1149
1150 if (stack.empty())
1151 break;
1152
1153 auto parent = std::move(stack.top().first);
1154 pos = stack.top().second;
1155 stack.pop();
1156
1157 // Hook this inner node to its parent
1158 XRPL_ASSERT(
1159 parent->cowid() == cowid_,
1160 "ripple::SHAMap::walkSubTree : parent cowid do match");
1161 parent->shareChild(pos, node);
1162
1163 // Continue with parent's next child, if any
1164 node = std::move(parent);
1165 ++pos;
1166 }
1167
1168 // Last inner node is the new root_
1169 root_ = std::move(node);
1170
1171 return flushed;
1172}
1173
1174void
1175SHAMap::dump(bool hash) const
1176{
1177 int leafCount = 0;
1178 JLOG(journal_.info()) << " MAP Contains";
1179
1181 stack.push({root_.get(), SHAMapNodeID()});
1182
1183 do
1184 {
1185 auto [node, nodeID] = stack.top();
1186 stack.pop();
1187
1188 JLOG(journal_.info()) << node->getString(nodeID);
1189 if (hash)
1190 {
1191 JLOG(journal_.info()) << "Hash: " << node->getHash();
1192 }
1193
1194 if (node->isInner())
1195 {
1196 auto inner = static_cast<SHAMapInnerNode*>(node);
1197 for (int i = 0; i < branchFactor; ++i)
1198 {
1199 if (!inner->isEmptyBranch(i))
1200 {
1201 auto child = inner->getChildPointer(i);
1202 if (child)
1203 {
1204 XRPL_ASSERT(
1205 child->getHash() == inner->getChildHash(i),
1206 "ripple::SHAMap::dump : child hash do match");
1207 stack.push({child, nodeID.getChildNodeID(i)});
1208 }
1209 }
1210 }
1211 }
1212 else
1213 ++leafCount;
1214 } while (!stack.empty());
1215
1216 JLOG(journal_.info()) << leafCount << " resident leaves";
1217}
1218
1221{
1222 auto ret = f_.getTreeNodeCache()->fetch(hash.as_uint256());
1223 XRPL_ASSERT(
1224 !ret || !ret->cowid(),
1225 "ripple::SHAMap::cacheLookup : not found or zero cowid");
1226 return ret;
1227}
1228
1229void
1231 SHAMapHash const& hash,
1233{
1234 XRPL_ASSERT(backed_, "ripple::SHAMap::canonicalize : is backed");
1235 XRPL_ASSERT(
1236 node->cowid() == 0, "ripple::SHAMap::canonicalize : valid node input");
1237 XRPL_ASSERT(
1238 node->getHash() == hash,
1239 "ripple::SHAMap::canonicalize : node hash do match");
1240
1241 f_.getTreeNodeCache()->canonicalize_replace_client(hash.as_uint256(), node);
1242}
1243
1244void
1246{
1247 (void)getHash(); // update node hashes
1248 auto node = root_.get();
1249 XRPL_ASSERT(node, "ripple::SHAMap::invariants : non-null root node");
1250 XRPL_ASSERT(
1251 !node->isLeaf(), "ripple::SHAMap::invariants : root node is not leaf");
1252 SharedPtrNodeStack stack;
1253 for (auto leaf = peekFirstItem(stack); leaf != nullptr;
1254 leaf = peekNextItem(leaf->peekItem()->key(), stack))
1255 ;
1256 node->invariants(true);
1257}
1258
1259} // 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:43
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:1013
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:714
intr_ptr::SharedPtr< SHAMapTreeNode > cacheLookup(SHAMapHash const &hash) const
Definition SHAMap.cpp:1220
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:1175
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:616
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:802
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:580
void canonicalize(SHAMapHash const &hash, intr_ptr::SharedPtr< SHAMapTreeNode > &) const
Definition SHAMap.cpp:1230
int walkSubTree(bool doWrite, NodeObjectType t)
Definition SHAMap.cpp:1044
const_iterator end() const
Definition SHAMap.h:763
void invariants() const
Definition SHAMap.cpp:1245
bool addItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition SHAMap.cpp:881
const_iterator upper_bound(uint256 const &id) const
Find the first item after the given item.
Definition SHAMap.cpp:639
intr_ptr::SharedPtr< SHAMapTreeNode > writeNode(NodeObjectType t, intr_ptr::SharedPtr< SHAMapTreeNode > node) const
write and canonicalize modified node
Definition SHAMap.cpp:992
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:889
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:901
SHAMapLeafNode const * peekFirstItem(SharedPtrNodeStack &stack) const
Definition SHAMap.cpp:565
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:720
bool fetchRoot(SHAMapHash const &hash, SHAMapSyncFilter *filter)
Definition SHAMap.cpp:944
const_iterator lower_bound(uint256 const &id) const
Find the object with the greatest object id smaller than the input id.
Definition SHAMap.cpp:676
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:1037
int unshare()
Convert any modified nodes to shared.
Definition SHAMap.cpp:1030
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)