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#include <xrpl/basics/contract.h>
27
28namespace ripple {
29
32 SHAMapNodeType type,
33 boost::intrusive_ptr<SHAMapItem const> item,
34 std::uint32_t owner)
35{
37 return std::make_shared<SHAMapTxLeafNode>(std::move(item), owner);
38
40 return std::make_shared<SHAMapTxPlusMetaLeafNode>(
41 std::move(item), owner);
42
44 return std::make_shared<SHAMapAccountStateLeafNode>(
45 std::move(item), owner);
46
48 "Attempt to create leaf node of unknown type " +
50 static_cast<std::underlying_type_t<SHAMapNodeType>>(type)));
51}
52
54 : f_(f), journal_(f.journal()), state_(SHAMapState::Modifying), type_(t)
55{
56 root_ = std::make_shared<SHAMapInnerNode>(cowid_);
57}
58
59// The `hash` parameter is unused. It is part of the interface so it's clear
60// from the parameters that this is the constructor to use when the hash is
61// known. The fact that the parameter is unused is an implementation detail that
62// should not change the interface.
64 : f_(f), journal_(f.journal()), state_(SHAMapState::Synching), type_(t)
65{
66 root_ = std::make_shared<SHAMapInnerNode>(cowid_);
67}
68
69SHAMap::SHAMap(SHAMap const& other, bool isMutable)
70 : f_(other.f_)
71 , journal_(other.f_.journal())
72 , cowid_(other.cowid_ + 1)
73 , ledgerSeq_(other.ledgerSeq_)
74 , root_(other.root_)
75 , state_(isMutable ? SHAMapState::Modifying : SHAMapState::Immutable)
76 , type_(other.type_)
77 , backed_(other.backed_)
78{
79 // If either map may change, they cannot share nodes
82 {
83 unshare();
84 }
85}
86
88SHAMap::snapShot(bool isMutable) const
89{
90 return std::make_shared<SHAMap>(*this, isMutable);
91}
92
93void
95 SharedPtrNodeStack& stack,
96 uint256 const& target,
98{
99 // walk the tree up from through the inner nodes to the root_
100 // update hashes and links
101 // stack is a path of inner nodes up to, but not including, child
102 // child can be an inner node or a leaf
103
104 XRPL_ASSERT(
106 "ripple::SHAMap::dirtyUp : valid state");
107 XRPL_ASSERT(
108 child && (child->cowid() == cowid_),
109 "ripple::SHAMap::dirtyUp : valid child input");
110
111 while (!stack.empty())
112 {
113 auto node =
114 std::dynamic_pointer_cast<SHAMapInnerNode>(stack.top().first);
115 SHAMapNodeID nodeID = stack.top().second;
116 stack.pop();
117 XRPL_ASSERT(node, "ripple::SHAMap::dirtyUp : non-null node");
118
119 int branch = selectBranch(nodeID, target);
120 XRPL_ASSERT(branch >= 0, "ripple::SHAMap::dirtyUp : valid branch");
121
122 node = unshareNode(std::move(node), nodeID);
123 node->setChild(branch, std::move(child));
124
125 child = std::move(node);
126 }
127}
128
131{
132 XRPL_ASSERT(
133 stack == nullptr || stack->empty(),
134 "ripple::SHAMap::walkTowardsKey : empty stack input");
135 auto inNode = root_;
136 SHAMapNodeID nodeID;
137
138 while (inNode->isInner())
139 {
140 if (stack != nullptr)
141 stack->push({inNode, nodeID});
142
143 auto const inner = std::static_pointer_cast<SHAMapInnerNode>(inNode);
144 auto const branch = selectBranch(nodeID, id);
145 if (inner->isEmptyBranch(branch))
146 return nullptr;
147
148 inNode = descendThrow(inner, branch);
149 nodeID = nodeID.getChildNodeID(branch);
150 }
151
152 if (stack != nullptr)
153 stack->push({inNode, nodeID});
154 return static_cast<SHAMapLeafNode*>(inNode.get());
155}
156
158SHAMap::findKey(uint256 const& id) const
159{
160 SHAMapLeafNode* leaf = walkTowardsKey(id);
161 if (leaf && leaf->peekItem()->key() != id)
162 leaf = nullptr;
163 return leaf;
164}
165
168{
169 XRPL_ASSERT(backed_, "ripple::SHAMap::fetchNodeFromDB : is backed");
170 auto obj = f_.db().fetchNodeObject(hash.as_uint256(), ledgerSeq_);
171 return finishFetch(hash, obj);
172}
173
176 SHAMapHash const& hash,
177 std::shared_ptr<NodeObject> const& object) const
178{
179 XRPL_ASSERT(backed_, "ripple::SHAMap::finishFetch : is backed");
180
181 try
182 {
183 if (!object)
184 {
185 if (full_)
186 {
187 full_ = false;
189 }
190 return {};
191 }
192
193 auto node =
194 SHAMapTreeNode::makeFromPrefix(makeSlice(object->getData()), hash);
195 if (node)
196 canonicalize(hash, node);
197 return node;
198 }
199 catch (std::runtime_error const& e)
200 {
201 JLOG(journal_.warn()) << "finishFetch exception: " << e.what();
202 }
203 catch (...)
204 {
205 JLOG(journal_.warn())
206 << "finishFetch exception: unknonw exception: " << hash;
207 }
208
209 return {};
210}
211
212// See if a sync filter has a node
215{
216 if (auto nodeData = filter->getNode(hash))
217 {
218 try
219 {
220 auto node =
222 if (node)
223 {
224 filter->gotNode(
225 true,
226 hash,
228 std::move(*nodeData),
229 node->getType());
230 if (backed_)
231 canonicalize(hash, node);
232 }
233 return node;
234 }
235 catch (std::exception const& x)
236 {
237 JLOG(f_.journal().warn())
238 << "Invalid node/data, hash=" << hash << ": " << x.what();
239 }
240 }
241 return {};
242}
243
244// Get a node without throwing
245// Used on maps where missing nodes are expected
248{
249 auto node = cacheLookup(hash);
250 if (node)
251 return node;
252
253 if (backed_)
254 {
255 node = fetchNodeFromDB(hash);
256 if (node)
257 {
258 canonicalize(hash, node);
259 return node;
260 }
261 }
262
263 if (filter)
264 node = checkFilter(hash, filter);
265
266 return node;
267}
268
271{
272 auto node = cacheLookup(hash);
273
274 if (!node && backed_)
275 node = fetchNodeFromDB(hash);
276
277 return node;
278}
279
280// Throw if the node is missing
283{
284 auto node = fetchNodeNT(hash);
285
286 if (!node)
287 Throw<SHAMapMissingNode>(type_, hash);
288
289 return node;
290}
291
293SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const
294{
295 SHAMapTreeNode* ret = descend(parent, branch);
296
297 if (!ret && !parent->isEmptyBranch(branch))
298 Throw<SHAMapMissingNode>(type_, parent->getChildHash(branch));
299
300 return ret;
301}
302
305 const
306{
307 std::shared_ptr<SHAMapTreeNode> ret = descend(parent, branch);
308
309 if (!ret && !parent->isEmptyBranch(branch))
310 Throw<SHAMapMissingNode>(type_, parent->getChildHash(branch));
311
312 return ret;
313}
314
316SHAMap::descend(SHAMapInnerNode* parent, int branch) const
317{
318 SHAMapTreeNode* ret = parent->getChildPointer(branch);
319 if (ret || !backed_)
320 return ret;
321
323 fetchNodeNT(parent->getChildHash(branch));
324 if (!node)
325 return nullptr;
326
327 node = parent->canonicalizeChild(branch, std::move(node));
328 return node.get();
329}
330
333 const
334{
335 std::shared_ptr<SHAMapTreeNode> node = parent->getChild(branch);
336 if (node || !backed_)
337 return node;
338
339 node = fetchNode(parent->getChildHash(branch));
340 if (!node)
341 return nullptr;
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.
352 int branch) const
353{
354 std::shared_ptr<SHAMapTreeNode> ret = parent->getChild(branch);
355 if (!ret && backed_)
356 ret = fetchNode(parent->getChildHash(branch));
357 return ret;
358}
359
362 SHAMapInnerNode* parent,
363 SHAMapNodeID const& parentID,
364 int branch,
365 SHAMapSyncFilter* filter) const
366{
367 XRPL_ASSERT(
368 parent->isInner(), "ripple::SHAMap::descend : valid parent input");
369 XRPL_ASSERT(
370 (branch >= 0) && (branch < branchFactor),
371 "ripple::SHAMap::descend : valid branch input");
372 XRPL_ASSERT(
373 !parent->isEmptyBranch(branch),
374 "ripple::SHAMap::descend : parent branch is non-empty");
375
376 SHAMapTreeNode* child = parent->getChildPointer(branch);
377
378 if (!child)
379 {
380 auto const& childHash = parent->getChildHash(branch);
382 fetchNodeNT(childHash, filter);
383
384 if (childNode)
385 {
386 childNode = parent->canonicalizeChild(branch, std::move(childNode));
387 child = childNode.get();
388 }
389 }
390
391 return std::make_pair(child, parentID.getChildNodeID(branch));
392}
393
396 SHAMapInnerNode* parent,
397 int branch,
398 SHAMapSyncFilter* filter,
399 bool& pending,
400 descendCallback&& callback) const
401{
402 pending = false;
403
404 SHAMapTreeNode* ret = parent->getChildPointer(branch);
405 if (ret)
406 return ret;
407
408 auto const& hash = parent->getChildHash(branch);
409
410 auto ptr = cacheLookup(hash);
411 if (!ptr)
412 {
413 if (filter)
414 ptr = checkFilter(hash, filter);
415
416 if (!ptr && backed_)
417 {
418 f_.db().asyncFetch(
419 hash.as_uint256(),
421 [this, hash, cb{std::move(callback)}](
422 std::shared_ptr<NodeObject> const& object) {
423 auto node = finishFetch(hash, object);
424 cb(node, hash);
425 });
426 pending = true;
427 return nullptr;
428 }
429 }
430
431 if (ptr)
432 ptr = parent->canonicalizeChild(branch, std::move(ptr));
433
434 return ptr.get();
435}
436
437template <class Node>
440{
441 // make sure the node is suitable for the intended operation (copy on write)
442 XRPL_ASSERT(
443 node->cowid() <= cowid_,
444 "ripple::SHAMap::unshareNode : node valid for cowid");
445 if (node->cowid() != cowid_)
446 {
447 // have a CoW
448 XRPL_ASSERT(
450 "ripple::SHAMap::unshareNode : not immutable");
451 node = std::static_pointer_cast<Node>(node->clone(cowid_));
452 if (nodeID.isRoot())
453 root_ = node;
454 }
455 return node;
456}
457
461 SharedPtrNodeStack& stack,
462 int branch,
463 std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const&
464 loopParams) const
465{
466 auto& [init, cmp, incr] = loopParams;
467 if (node->isLeaf())
468 {
469 auto n = std::static_pointer_cast<SHAMapLeafNode>(node);
470 stack.push({node, {leafDepth, n->peekItem()->key()}});
471 return n.get();
472 }
473 auto inner = std::static_pointer_cast<SHAMapInnerNode>(node);
474 if (stack.empty())
475 stack.push({inner, SHAMapNodeID{}});
476 else
477 stack.push({inner, stack.top().second.getChildNodeID(branch)});
478 for (int i = init; cmp(i);)
479 {
480 if (!inner->isEmptyBranch(i))
481 {
482 node = descendThrow(inner, i);
483 XRPL_ASSERT(
484 !stack.empty(),
485 "ripple::SHAMap::belowHelper : non-empty stack");
486 if (node->isLeaf())
487 {
488 auto n = std::static_pointer_cast<SHAMapLeafNode>(node);
489 stack.push({n, {leafDepth, n->peekItem()->key()}});
490 return n.get();
491 }
492 inner = std::static_pointer_cast<SHAMapInnerNode>(node);
493 stack.push({inner, stack.top().second.getChildNodeID(branch)});
494 i = init; // descend and reset loop
495 }
496 else
497 incr(i); // scan next branch
498 }
499 return nullptr;
500}
504 SharedPtrNodeStack& stack,
505 int branch) const
506{
507 auto init = branchFactor - 1;
508 auto cmp = [](int i) { return i >= 0; };
509 auto incr = [](int& i) { --i; };
510
511 return belowHelper(node, stack, branch, {init, cmp, incr});
512}
516 SharedPtrNodeStack& stack,
517 int branch) const
518{
519 auto init = 0;
520 auto cmp = [](int i) { return i <= branchFactor; };
521 auto incr = [](int& i) { ++i; };
522
523 return belowHelper(node, stack, branch, {init, cmp, incr});
524}
525static const boost::intrusive_ptr<SHAMapItem const> no_item;
526
527boost::intrusive_ptr<SHAMapItem const> const&
529{
530 // If there is only one item below this node, return it
531
532 while (!node->isLeaf())
533 {
534 SHAMapTreeNode* nextNode = nullptr;
535 auto inner = static_cast<SHAMapInnerNode*>(node);
536 for (int i = 0; i < branchFactor; ++i)
537 {
538 if (!inner->isEmptyBranch(i))
539 {
540 if (nextNode)
541 return no_item;
542
543 nextNode = descendThrow(inner, i);
544 }
545 }
546
547 if (!nextNode)
548 {
549 UNREACHABLE("ripple::SHAMap::onlyBelow : no next node");
550 return no_item;
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 = std::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 = std::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 = std::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 = std::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 std::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 const int 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(i, nullptr);
778 break;
779 }
780 }
781
782 prevNode = makeTypedLeaf(type, item, node->cowid());
783 }
784 else
785 {
786 prevNode = std::move(node);
787 }
788 }
789 else
790 {
791 // This node is now the end of the branch
792 prevNode = std::move(node);
793 }
794 }
795 }
796
797 return true;
798}
799
800bool
802 SHAMapNodeType type,
803 boost::intrusive_ptr<SHAMapItem const> item)
804{
805 XRPL_ASSERT(
807 "ripple::SHAMap::addGiveItem : not immutable");
808 XRPL_ASSERT(
810 "ripple::SHAMap::addGiveItem : valid type input");
811
812 // add the specified item, does not update
813 uint256 tag = item->key();
814
815 SharedPtrNodeStack stack;
816 walkTowardsKey(tag, &stack);
817
818 if (stack.empty())
819 Throw<SHAMapMissingNode>(type_, tag);
820
821 auto [node, nodeID] = stack.top();
822 stack.pop();
823
824 if (node->isLeaf())
825 {
826 auto leaf = std::static_pointer_cast<SHAMapLeafNode>(node);
827 if (leaf->peekItem()->key() == tag)
828 return false;
829 }
830 node = unshareNode(std::move(node), nodeID);
831 if (node->isInner())
832 {
833 // easy case, we end on an inner node
834 auto inner = std::static_pointer_cast<SHAMapInnerNode>(node);
835 int branch = selectBranch(nodeID, tag);
836 XRPL_ASSERT(
837 inner->isEmptyBranch(branch),
838 "ripple::SHAMap::addGiveItem : inner branch is empty");
839 inner->setChild(branch, makeTypedLeaf(type, std::move(item), cowid_));
840 }
841 else
842 {
843 // this is a leaf node that has to be made an inner node holding two
844 // items
845 auto leaf = std::static_pointer_cast<SHAMapLeafNode>(node);
846 auto otherItem = leaf->peekItem();
847 XRPL_ASSERT(
848 otherItem && (tag != otherItem->key()),
849 "ripple::SHAMap::addGiveItem : non-null item");
850
851 node = std::make_shared<SHAMapInnerNode>(node->cowid());
852
853 unsigned int b1, b2;
854
855 while ((b1 = selectBranch(nodeID, tag)) ==
856 (b2 = selectBranch(nodeID, otherItem->key())))
857 {
858 stack.push({node, nodeID});
859
860 // we need a new inner node, since both go on same branch at this
861 // level
862 nodeID = nodeID.getChildNodeID(b1);
863 node = std::make_shared<SHAMapInnerNode>(cowid_);
864 }
865
866 // we can add the two leaf nodes here
867 XRPL_ASSERT(
868 node->isInner(), "ripple::SHAMap::addGiveItem : node is inner");
869
870 auto inner = static_cast<SHAMapInnerNode*>(node.get());
871 inner->setChild(b1, makeTypedLeaf(type, std::move(item), cowid_));
872 inner->setChild(b2, makeTypedLeaf(type, std::move(otherItem), cowid_));
873 }
874
875 dirtyUp(stack, tag, node);
876 return true;
877}
878
879bool
881 SHAMapNodeType type,
882 boost::intrusive_ptr<SHAMapItem const> item)
883{
884 return addGiveItem(type, std::move(item));
885}
886
889{
890 auto hash = root_->getHash();
891 if (hash.isZero())
892 {
893 const_cast<SHAMap&>(*this).unshare();
894 hash = root_->getHash();
895 }
896 return hash;
897}
898
899bool
901 SHAMapNodeType type,
902 boost::intrusive_ptr<SHAMapItem const> item)
903{
904 // can't change the tag but can change the hash
905 uint256 tag = item->key();
906
907 XRPL_ASSERT(
909 "ripple::SHAMap::updateGiveItem : not immutable");
910
911 SharedPtrNodeStack stack;
912 walkTowardsKey(tag, &stack);
913
914 if (stack.empty())
915 Throw<SHAMapMissingNode>(type_, tag);
916
917 auto node = std::dynamic_pointer_cast<SHAMapLeafNode>(stack.top().first);
918 auto nodeID = stack.top().second;
919 stack.pop();
920
921 if (!node || (node->peekItem()->key() != tag))
922 {
923 UNREACHABLE("ripple::SHAMap::updateGiveItem : invalid node");
924 return false;
925 }
926
927 if (node->getType() != type)
928 {
929 JLOG(journal_.fatal()) << "SHAMap::updateGiveItem: cross-type change!";
930 return false;
931 }
932
933 node = unshareNode(std::move(node), nodeID);
934
935 if (node->setItem(item))
936 dirtyUp(stack, tag, node);
937
938 return true;
939}
940
941bool
943{
944 if (hash == root_->getHash())
945 return true;
946
947 if (auto stream = journal_.trace())
948 {
950 {
951 stream << "Fetch root TXN node " << hash;
952 }
953 else if (type_ == SHAMapType::STATE)
954 {
955 stream << "Fetch root STATE node " << hash;
956 }
957 else
958 {
959 stream << "Fetch root SHAMap node " << hash;
960 }
961 }
962
963 auto newRoot = fetchNodeNT(hash, filter);
964
965 if (newRoot)
966 {
967 root_ = newRoot;
968 XRPL_ASSERT(
969 root_->getHash() == hash,
970 "ripple::SHAMap::fetchRoot : root hash do match");
971 return true;
972 }
973
974 return false;
975}
976
991{
992 XRPL_ASSERT(
993 node->cowid() == 0, "ripple::SHAMap::writeNode : valid input node");
994 XRPL_ASSERT(backed_, "ripple::SHAMap::writeNode : is backed");
995
996 canonicalize(node->getHash(), node);
997
998 Serializer s;
999 node->serializeWithPrefix(s);
1000 f_.db().store(
1001 t, std::move(s.modData()), node->getHash().as_uint256(), ledgerSeq_);
1002 return node;
1003}
1004
1005// We can't modify an inner node someone else might have a
1006// pointer to because flushing modifies inner nodes -- it
1007// makes them point to canonical/shared nodes.
1008template <class Node>
1011{
1012 // A shared node should never need to be flushed
1013 // because that would imply someone modified it
1014 XRPL_ASSERT(
1015 node->cowid(), "ripple::SHAMap::preFlushNode : valid input node");
1016
1017 if (node->cowid() != cowid_)
1018 {
1019 // Node is not uniquely ours, so unshare it before
1020 // possibly modifying it
1021 node = std::static_pointer_cast<Node>(node->clone(cowid_));
1022 }
1023 return node;
1024}
1025
1026int
1028{
1029 // Don't share nodes with parent map
1030 return walkSubTree(false, hotUNKNOWN);
1031}
1032
1033int
1035{
1036 // We only write back if this map is backed.
1037 return walkSubTree(backed_, t);
1038}
1039
1040int
1042{
1043 XRPL_ASSERT(
1044 !doWrite || backed_, "ripple::SHAMap::walkSubTree : valid input");
1045
1046 int flushed = 0;
1047
1048 if (!root_ || (root_->cowid() == 0))
1049 return flushed;
1050
1051 if (root_->isLeaf())
1052 { // special case -- root_ is leaf
1053 root_ = preFlushNode(std::move(root_));
1054 root_->updateHash();
1055 root_->unshare();
1056
1057 if (doWrite)
1058 root_ = writeNode(t, std::move(root_));
1059
1060 return 1;
1061 }
1062
1063 auto node = std::static_pointer_cast<SHAMapInnerNode>(root_);
1064
1065 if (node->isEmpty())
1066 { // replace empty root with a new empty root
1067 root_ = std::make_shared<SHAMapInnerNode>(0);
1068 return 1;
1069 }
1070
1071 // Stack of {parent,index,child} pointers representing
1072 // inner nodes we are in the process of flushing
1073 using StackEntry = std::pair<std::shared_ptr<SHAMapInnerNode>, int>;
1075
1076 node = preFlushNode(std::move(node));
1077
1078 int pos = 0;
1079
1080 // We can't flush an inner node until we flush its children
1081 while (1)
1082 {
1083 while (pos < branchFactor)
1084 {
1085 if (node->isEmptyBranch(pos))
1086 {
1087 ++pos;
1088 }
1089 else
1090 {
1091 // No need to do I/O. If the node isn't linked,
1092 // it can't need to be flushed
1093 int branch = pos;
1094 auto child = node->getChild(pos++);
1095
1096 if (child && (child->cowid() != 0))
1097 {
1098 // This is a node that needs to be flushed
1099
1100 child = preFlushNode(std::move(child));
1101
1102 if (child->isInner())
1103 {
1104 // save our place and work on this node
1105
1106 stack.emplace(std::move(node), branch);
1107 // The semantics of this changes when we move to c++-20
1108 // Right now no move will occur; With c++-20 child will
1109 // be moved from.
1110 node = std::static_pointer_cast<SHAMapInnerNode>(
1111 std::move(child));
1112 pos = 0;
1113 }
1114 else
1115 {
1116 // flush this leaf
1117 ++flushed;
1118
1119 XRPL_ASSERT(
1120 node->cowid() == cowid_,
1121 "ripple::SHAMap::walkSubTree : node cowid do "
1122 "match");
1123 child->updateHash();
1124 child->unshare();
1125
1126 if (doWrite)
1127 child = writeNode(t, std::move(child));
1128
1129 node->shareChild(branch, child);
1130 }
1131 }
1132 }
1133 }
1134
1135 // update the hash of this inner node
1136 node->updateHashDeep();
1137
1138 // This inner node can now be shared
1139 node->unshare();
1140
1141 if (doWrite)
1142 node = std::static_pointer_cast<SHAMapInnerNode>(
1143 writeNode(t, std::move(node)));
1144
1145 ++flushed;
1146
1147 if (stack.empty())
1148 break;
1149
1150 auto parent = std::move(stack.top().first);
1151 pos = stack.top().second;
1152 stack.pop();
1153
1154 // Hook this inner node to its parent
1155 XRPL_ASSERT(
1156 parent->cowid() == cowid_,
1157 "ripple::SHAMap::walkSubTree : parent cowid do match");
1158 parent->shareChild(pos, node);
1159
1160 // Continue with parent's next child, if any
1161 node = std::move(parent);
1162 ++pos;
1163 }
1164
1165 // Last inner node is the new root_
1166 root_ = std::move(node);
1167
1168 return flushed;
1169}
1170
1171void
1172SHAMap::dump(bool hash) const
1173{
1174 int leafCount = 0;
1175 JLOG(journal_.info()) << " MAP Contains";
1176
1178 stack.push({root_.get(), SHAMapNodeID()});
1179
1180 do
1181 {
1182 auto [node, nodeID] = stack.top();
1183 stack.pop();
1184
1185 JLOG(journal_.info()) << node->getString(nodeID);
1186 if (hash)
1187 {
1188 JLOG(journal_.info()) << "Hash: " << node->getHash();
1189 }
1190
1191 if (node->isInner())
1192 {
1193 auto inner = static_cast<SHAMapInnerNode*>(node);
1194 for (int i = 0; i < branchFactor; ++i)
1195 {
1196 if (!inner->isEmptyBranch(i))
1197 {
1198 auto child = inner->getChildPointer(i);
1199 if (child)
1200 {
1201 XRPL_ASSERT(
1202 child->getHash() == inner->getChildHash(i),
1203 "ripple::SHAMap::dump : child hash do match");
1204 stack.push({child, nodeID.getChildNodeID(i)});
1205 }
1206 }
1207 }
1208 }
1209 else
1210 ++leafCount;
1211 } while (!stack.empty());
1212
1213 JLOG(journal_.info()) << leafCount << " resident leaves";
1214}
1215
1218{
1219 auto ret = f_.getTreeNodeCache()->fetch(hash.as_uint256());
1220 XRPL_ASSERT(
1221 !ret || !ret->cowid(),
1222 "ripple::SHAMap::cacheLookup : not found or zero cowid");
1223 return ret;
1224}
1225
1226void
1228 SHAMapHash const& hash,
1230{
1231 XRPL_ASSERT(backed_, "ripple::SHAMap::canonicalize : is backed");
1232 XRPL_ASSERT(
1233 node->cowid() == 0, "ripple::SHAMap::canonicalize : valid node input");
1234 XRPL_ASSERT(
1235 node->getHash() == hash,
1236 "ripple::SHAMap::canonicalize : node hash do match");
1237
1238 f_.getTreeNodeCache()->canonicalize_replace_client(hash.as_uint256(), node);
1239}
1240
1241void
1243{
1244 (void)getHash(); // update node hashes
1245 auto node = root_.get();
1246 XRPL_ASSERT(node, "ripple::SHAMap::invariants : non-null root node");
1247 XRPL_ASSERT(
1248 !node->isLeaf(), "ripple::SHAMap::invariants : root node is not leaf");
1249 SharedPtrNodeStack stack;
1250 for (auto leaf = peekFirstItem(stack); leaf != nullptr;
1251 leaf = peekNextItem(leaf->peekItem()->key(), stack))
1252 ;
1253 node->invariants(true);
1254}
1255
1256} // namespace ripple
Stream fatal() const
Definition: Journal.h:341
Stream info() const
Definition: Journal.h:323
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
Stream warn() const
Definition: Journal.h:329
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:183
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:239
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.
bool isEmptyBranch(int m) const
void setChild(int m, std::shared_ptr< SHAMapTreeNode > child)
SHAMapHash const & getChildHash(int m) const
std::shared_ptr< SHAMapTreeNode > canonicalizeChild(int branch, std::shared_ptr< SHAMapTreeNode > node)
SHAMapTreeNode * getChildPointer(int branch)
boost::intrusive_ptr< SHAMapItem const > const & peekItem() const
Identifies a node inside a SHAMap.
Definition: SHAMapNodeID.h:34
bool isRoot() const
Definition: SHAMapNodeID.h:48
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 std::shared_ptr< 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:96
std::shared_ptr< SHAMapTreeNode > fetchNodeNT(SHAMapHash const &hash) const
Definition: SHAMap.cpp:270
SHAMapTreeNode * descendAsync(SHAMapInnerNode *parent, int branch, SHAMapSyncFilter *filter, bool &pending, descendCallback &&) const
Definition: SHAMap.cpp:395
bool hasItem(uint256 const &id) const
Does the tree have an item with the given ID?
Definition: SHAMap.cpp:715
bool backed_
Definition: SHAMap.h:110
std::shared_ptr< SHAMapTreeNode > checkFilter(SHAMapHash const &hash, SHAMapSyncFilter *filter) const
Definition: SHAMap.cpp:214
beast::Journal journal_
Definition: SHAMap.h:99
static constexpr unsigned int leafDepth
The depth of the hash map: data is only present in the leaves.
Definition: SHAMap.h:120
void dump(bool withHashes=false) const
Definition: SHAMap.cpp:1172
SHAMapLeafNode * firstBelow(std::shared_ptr< SHAMapTreeNode >, SharedPtrNodeStack &stack, int branch=0) const
Definition: SHAMap.cpp:514
boost::intrusive_ptr< SHAMapItem const > const & onlyBelow(SHAMapTreeNode *) const
If there is only one leaf below this node, get its contents.
Definition: SHAMap.cpp:528
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition: SHAMap.cpp:293
void dirtyUp(SharedPtrNodeStack &stack, uint256 const &target, std::shared_ptr< SHAMapTreeNode > terminal)
Update hashes up to the root.
Definition: SHAMap.cpp:94
boost::intrusive_ptr< SHAMapItem const > const & peekItem(uint256 const &id) const
Definition: SHAMap.cpp:617
SHAMapLeafNode * belowHelper(std::shared_ptr< SHAMapTreeNode > node, SharedPtrNodeStack &stack, int branch, std::tuple< int, std::function< bool(int)>, std::function< void(int &)> > const &loopParams) const
Definition: SHAMap.cpp:459
SHAMapLeafNode * lastBelow(std::shared_ptr< SHAMapTreeNode > node, SharedPtrNodeStack &stack, int branch=branchFactor) const
Definition: SHAMap.cpp:502
std::shared_ptr< SHAMapTreeNode > writeNode(NodeObjectType t, std::shared_ptr< SHAMapTreeNode > node) const
write and canonicalize modified node
Definition: SHAMap.cpp:990
SHAMapType const type_
Definition: SHAMap.h:109
SHAMapState state_
Definition: SHAMap.h:108
bool full_
Definition: SHAMap.h:111
Family & f_
Definition: SHAMap.h:98
bool addGiveItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition: SHAMap.cpp:801
SHAMapLeafNode * walkTowardsKey(uint256 const &id, SharedPtrNodeStack *stack=nullptr) const
Walk towards the specified id, returning the node.
Definition: SHAMap.cpp:130
SHAMapTreeNode * descend(SHAMapInnerNode *, int branch) const
Definition: SHAMap.cpp:316
SHAMapLeafNode const * peekNextItem(uint256 const &id, SharedPtrNodeStack &stack) const
Definition: SHAMap.cpp:581
std::shared_ptr< Node > preFlushNode(std::shared_ptr< Node > node) const
prepare a node to be modified before flushing
Definition: SHAMap.cpp:1010
std::shared_ptr< SHAMapTreeNode > root_
Definition: SHAMap.h:107
int walkSubTree(bool doWrite, NodeObjectType t)
Definition: SHAMap.cpp:1041
std::shared_ptr< SHAMapTreeNode > fetchNode(SHAMapHash const &hash) const
Definition: SHAMap.cpp:282
const_iterator end() const
Definition: SHAMap.h:759
void invariants() const
Definition: SHAMap.cpp:1242
bool addItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition: SHAMap.cpp:880
const_iterator upper_bound(uint256 const &id) const
Find the first item after the given item.
Definition: SHAMap.cpp:640
std::uint32_t cowid_
ID to distinguish this map for all others we're sharing nodes with.
Definition: SHAMap.h:102
SHAMapHash getHash() const
Definition: SHAMap.cpp:888
SHAMap()=delete
bool updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item)
Definition: SHAMap.cpp:900
std::shared_ptr< SHAMapTreeNode > descendNoStore(std::shared_ptr< SHAMapInnerNode > const &, int branch) const
Definition: SHAMap.cpp:350
SHAMapLeafNode const * peekFirstItem(SharedPtrNodeStack &stack) const
Definition: SHAMap.cpp:566
std::shared_ptr< SHAMapTreeNode > cacheLookup(SHAMapHash const &hash) const
Definition: SHAMap.cpp:1217
std::shared_ptr< SHAMapTreeNode > fetchNodeFromDB(SHAMapHash const &hash) const
Definition: SHAMap.cpp:167
std::uint32_t ledgerSeq_
The sequence of the ledger that this map references, if any.
Definition: SHAMap.h:105
void canonicalize(SHAMapHash const &hash, std::shared_ptr< SHAMapTreeNode > &) const
Definition: SHAMap.cpp:1227
bool delItem(uint256 const &id)
Definition: SHAMap.cpp:721
std::shared_ptr< Node > unshareNode(std::shared_ptr< Node >, SHAMapNodeID const &nodeID)
Unshare the node, allowing it to be modified.
Definition: SHAMap.cpp:439
bool fetchRoot(SHAMapHash const &hash, SHAMapSyncFilter *filter)
Definition: SHAMap.cpp:942
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
std::shared_ptr< SHAMap > snapShot(bool isMutable) const
Definition: SHAMap.cpp:88
int flushDirty(NodeObjectType t)
Flush modified nodes to the nodestore and convert them to shared.
Definition: SHAMap.cpp:1034
int unshare()
Convert any modified nodes to shared.
Definition: SHAMap.cpp:1027
SHAMapLeafNode * findKey(uint256 const &id) const
Return nullptr if key not found.
Definition: SHAMap.cpp:158
static constexpr unsigned int branchFactor
Number of children each non-leaf node has (the 'radix tree' part of the map)
Definition: SHAMap.h:116
std::shared_ptr< SHAMapTreeNode > finishFetch(SHAMapHash const &hash, std::shared_ptr< NodeObject > const &object) const
Definition: SHAMap.cpp:175
T emplace(T... args)
T empty(T... args)
T get(T... args)
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
unsigned int selectBranch(SHAMapNodeID const &id, uint256 const &hash)
Returns the branch that would contain the given hash.
static const boost::intrusive_ptr< SHAMapItem const > no_item
Definition: SHAMap.cpp:525
SHAMapState
Describes the current state of a given SHAMap.
Definition: SHAMap.h:46
@ 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.
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:243
std::shared_ptr< SHAMapLeafNode > makeTypedLeaf(SHAMapNodeType type, boost::intrusive_ptr< SHAMapItem const > item, std::uint32_t owner)
Definition: SHAMap.cpp:31
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
T pop(T... args)
T push(T... args)
T reset(T... args)
T to_string(T... args)
T top(T... args)
T what(T... args)