xrpld
Loading...
Searching...
No Matches
libxrpl/shamap/SHAMapSync.cpp
1#include <xrpl/basics/Blob.h>
2#include <xrpl/basics/IntrusivePointer.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/random.h>
7#include <xrpl/basics/safe_cast.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/protocol/Serializer.h>
10#include <xrpl/shamap/SHAMap.h>
11#include <xrpl/shamap/SHAMapAddNode.h>
12#include <xrpl/shamap/SHAMapInnerNode.h>
13#include <xrpl/shamap/SHAMapItem.h>
14#include <xrpl/shamap/SHAMapLeafNode.h>
15#include <xrpl/shamap/SHAMapNodeID.h>
16#include <xrpl/shamap/SHAMapSyncFilter.h>
17#include <xrpl/shamap/SHAMapTreeNode.h>
18
19#include <boost/smart_ptr/intrusive_ptr.hpp>
20
21#include <cstdint>
22#include <exception>
23#include <functional>
24#include <iterator>
25#include <mutex>
26#include <optional>
27#include <stack>
28#include <tuple>
29#include <utility>
30#include <vector>
31
32namespace xrpl {
33
34void
36 std::function<void(boost::intrusive_ptr<SHAMapItem const> const& item)> const& leafFunction)
37 const
38{
39 visitNodes([&leafFunction](SHAMapTreeNode& node) {
40 if (!node.isInner())
41 leafFunction(safeDowncast<SHAMapLeafNode&>(node).peekItem());
42 return true;
43 });
44}
45
46void
47SHAMap::visitNodes(std::function<bool(SHAMapTreeNode&)> const& function) const
48{
49 if (!root_)
50 return;
51
52 function(*root_);
53
54 if (!root_->isInner())
55 return;
56
59
61 auto pos = 0u;
62
63 while (true)
64 {
65 while (pos < kBranchFactor)
66 {
67 if (!node->isEmptyBranch(pos))
68 {
69 SHAMapTreeNodePtr const child = descendNoStore(*node, pos);
70 if (!function(*child))
71 return;
72
73 if (child->isLeaf())
74 {
75 ++pos;
76 }
77 else
78 {
79 // If there are no more children, don't push this node
80 while ((pos != kBranchFactor - 1u) && (node->isEmptyBranch(pos + 1)))
81 ++pos;
82
83 if (pos != kBranchFactor - 1u)
84 {
85 // save next position to resume at
86 stack.emplace(pos + 1, std::move(node));
87 }
88
89 // descend to the child's first position
91 pos = 0;
92 }
93 }
94 else
95 {
96 ++pos; // move to next position
97 }
98 }
99
100 if (stack.empty())
101 break;
102
103 std::tie(pos, node) = stack.top();
104 stack.pop();
105 }
106}
107
108void
110 SHAMap const* map,
111 std::function<bool(SHAMapTreeNode const&)> const& function) const
112{
113 // Visit every node in this SHAMap that is not present
114 // in the specified SHAMap
115 if (!root_)
116 return;
117
118 if (root_->getHash().isZero())
119 return;
120
121 if ((map != nullptr) && (root_->getHash() == map->root_->getHash()))
122 return;
123
124 if (root_->isLeaf())
125 {
127 if ((map == nullptr) || !map->hasLeafNode(leaf->peekItem()->key(), leaf->getHash()))
128 function(*root_);
129 return;
130 }
131 // contains unexplored non-matching inner node entries
134
136
137 while (!stack.empty())
138 {
139 auto const [node, nodeID] = stack.top();
140 stack.pop();
141
142 // 1) Add this node to the pack
143 if (!function(*node))
144 return;
145
146 // Nibbles run out at kLeafDepth, so only a leaf belongs there. A well-formed map never
147 // holds an inner node at that depth: addKnownNode marks the map invalid rather than hooking
148 // one in, and fetch-pack data is hash-verified against a validated root, so reaching this
149 // means a defect or a corrupt store, not something a peer can provoke. Report the node
150 // anyway - the wire form carries no depth, and the recipient hooks blobs in by hash - but
151 // skip the children rather than letting getChildNodeID throw on them.
152 if (nodeID.getDepth() >= kLeafDepth)
153 {
154 // LCOV_EXCL_START
155 UNREACHABLE("xrpl::SHAMap::visitDifferences : inner node at leaf depth");
156 continue;
157 // LCOV_EXCL_STOP
158 }
159
160 // 2) push non-matching child inner nodes
161 for (auto i = 0u; i < kBranchFactor; ++i)
162 {
163 if (!node->isEmptyBranch(i))
164 {
165 auto const& childHash = node->getChildHash(i);
166 auto const childID = nodeID.getChildNodeID(i);
167 auto next = descendThrow(node, i);
168
169 if (next->isInner())
170 {
171 if ((map == nullptr) || !map->hasInnerNode(childID, childHash))
172 stack.emplace(safeDowncast<SHAMapInnerNode*>(next), childID);
173 }
174 else if ((map == nullptr) || !map->hasLeafNode(leafKey(*next), childHash))
175 {
176 if (!function(*next))
177 return;
178 }
179 }
180 }
181 }
182}
183
184// Starting at the position referred to by the specfied
185// StackEntry, process that node and its first resident
186// children, descending the SHAMap until we complete the
187// processing of a node.
188void
190{
191 SHAMapInnerNode*& node = std::get<0>(se);
192 SHAMapNodeID& nodeID = std::get<1>(se);
193 auto& firstChild = std::get<2>(se);
194 auto& currentChild = std::get<3>(se);
195 bool& fullBelow = std::get<4>(se);
196
197 while (currentChild < kBranchFactor)
198 {
199 auto const branch = (firstChild + currentChild++) % kBranchFactor;
200 if (node->isEmptyBranch(branch))
201 continue;
202
203 auto const& childHash = node->getChildHash(branch);
204
205 if (mn.missingHashes.contains(childHash))
206 {
207 // we already know this child node is missing
208 fullBelow = false;
209 }
210 else if (!backed_ || !f_.getFullBelowCache()->touchIfExists(childHash.asUInt256()))
211 {
212 bool pending = false;
213 auto d = descendAsync(
214 node,
215 branch,
216 mn.filter,
217 pending,
218 [node, nodeID, branch, &mn](SHAMapTreeNodePtr found, SHAMapHash const&) {
219 // a read completed asynchronously
220 std::unique_lock<std::mutex> const lock{mn.deferLock};
221 mn.finishedReads.emplace_back(node, nodeID, branch, std::move(found));
223 });
224
225 if (pending)
226 {
227 fullBelow = false;
228 ++mn.deferred;
229 }
230 else if (d == nullptr)
231 {
232 // node is not in database
233
234 fullBelow = false; // for now, not known full below
235 mn.missingHashes.insert(childHash);
236 mn.missingNodes.emplace_back(nodeID.getChildNodeID(branch), childHash.asUInt256());
237
238 if (--mn.max <= 0)
239 return;
240 }
241 else if (d->isInner() && !safeDowncast<SHAMapInnerNode*>(d)->isFullBelow(mn.generation))
242 {
243 mn.stack.push(se);
244
245 // Switch to processing the child node
247 nodeID = nodeID.getChildNodeID(branch);
248 firstChild = randInt(255);
249 currentChild = 0;
250 fullBelow = true;
251 }
252 }
253 }
254
255 // We have finished processing an inner node
256 // and thus (for now) all its children
257
258 if (fullBelow)
259 { // No partial node encountered below this node
260 node->setFullBelowGen(mn.generation);
261 if (backed_)
262 {
263 f_.getFullBelowCache()->insert(node->getHash().asUInt256());
264 }
265 }
266
267 node = nullptr;
268}
269
270// Wait for deferred reads to finish and
271// process their results
272void
274{
275 // Process all deferred reads
276 int complete = 0;
277 while (complete != mn.deferred)
278 {
279 MissingNodes::DeferredNode deferredNode;
280 {
282
283 while (mn.finishedReads.size() <= complete)
284 mn.deferCondVar.wait(lock);
285 deferredNode = std::move(mn.finishedReads[complete++]);
286 }
287
288 auto parent = std::get<0>(deferredNode);
289 auto const& parentID = std::get<1>(deferredNode);
290 auto branch = std::get<2>(deferredNode);
291 auto nodePtr = std::get<3>(deferredNode);
292 auto const& nodeHash = parent->getChildHash(branch);
293
294 if (nodePtr)
295 { // Got the node
296 nodePtr = parent->canonicalizeChild(branch, std::move(nodePtr));
297
298 // When we finish this stack, we need to restart
299 // with the parent of this node
300 mn.resumes[parent] = parentID;
301 }
302 else if ((mn.max > 0) && (mn.missingHashes.insert(nodeHash).second))
303 {
304 mn.missingNodes.emplace_back(parentID.getChildNodeID(branch), nodeHash.asUInt256());
305 --mn.max;
306 }
307 }
308
309 mn.finishedReads.clear();
310 mn.finishedReads.reserve(mn.maxDefer);
311 mn.deferred = 0;
312}
313
321{
322 XRPL_ASSERT(root_->getHash().isNonZero(), "xrpl::SHAMap::getMissingNodes : nonzero root hash");
323 XRPL_ASSERT(max > 0, "xrpl::SHAMap::getMissingNodes : valid max input");
324
325 MissingNodes mn(
326 max,
327 filter,
328 512, // number of async reads per pass
329 f_.getFullBelowCache()->getGeneration());
330
331 if (!root_->isInner() ||
333 {
335 return std::move(mn.missingNodes);
336 }
337
338 // Start at the root.
339 // The firstChild value is selected randomly so if multiple threads
340 // are traversing the map, each thread will start at a different
341 // (randomly selected) inner node. This increases the likelihood
342 // that the two threads will produce different request sets (which is
343 // more efficient than sending identical requests).
346 auto& node = std::get<0>(pos);
347 auto& nextChild = std::get<3>(pos);
348 auto& fullBelow = std::get<4>(pos);
349
350 // Traverse the map without blocking
351 do
352 {
353 while ((node != nullptr) && (mn.deferred <= mn.maxDefer))
354 {
355 gmnProcessNodes(mn, pos);
356
357 if (mn.max <= 0)
358 break;
359
360 if ((node == nullptr) && !mn.stack.empty())
361 {
362 // Pick up where we left off with this node's parent
363 bool const was = fullBelow; // was full below
364
365 pos = mn.stack.top();
366 mn.stack.pop();
367 if (nextChild == 0)
368 {
369 // This is a node we are processing for the first time
370 fullBelow = true;
371 }
372 else
373 {
374 // This is a node we are continuing to process
375 fullBelow = fullBelow && was; // was and still is
376 }
377 XRPL_ASSERT(node, "xrpl::SHAMap::getMissingNodes : first non-null node");
378 }
379 }
380
381 // We have either emptied the stack or
382 // posted as many deferred reads as we can
383 if (mn.deferred != 0)
385
386 if (mn.max <= 0)
387 return std::move(mn.missingNodes);
388
389 if (node == nullptr)
390 { // We weren't in the middle of processing a node
391
392 if (mn.stack.empty() && !mn.resumes.empty())
393 {
394 // Recheck nodes we could not finish before
395 for (auto const& [innerNode, nodeId] : mn.resumes)
396 {
397 if (!innerNode->isFullBelow(mn.generation))
398 mn.stack.emplace(innerNode, nodeId, randInt(255), 0, true);
399 }
400
401 mn.resumes.clear();
402 }
403
404 if (!mn.stack.empty())
405 {
406 // Resume at the top of the stack
407 pos = mn.stack.top();
408 mn.stack.pop();
409 XRPL_ASSERT(node, "xrpl::SHAMap::getMissingNodes : second non-null node");
410 }
411 }
412
413 // node will only still be nullptr if
414 // we finished the current node, the stack is empty
415 // and we have no nodes to resume
416
417 } while (node != nullptr);
418
419 if (mn.missingNodes.empty())
421
422 return std::move(mn.missingNodes);
423}
424
425bool
427 SHAMapNodeID const& wanted,
429 bool fatLeaves,
430 std::uint32_t depth) const
431{
432 // Gets a node and some of its children
433 // to a specified depth
434
435 auto node = root_.get();
436 SHAMapNodeID nodeID;
437
438 while ((node != nullptr) && node->isInner() && (nodeID.getDepth() < wanted.getDepth()))
439 {
440 auto const branch = selectBranch(nodeID, wanted.getNodeID());
441 auto inner = safeDowncast<SHAMapInnerNode*>(node);
442 if (inner->isEmptyBranch(branch))
443 return false;
444 node = descendThrow(inner, branch);
445 nodeID = nodeID.getChildNodeID(branch);
446 }
447
448 if (node == nullptr || wanted != nodeID)
449 {
450 JLOG(journal_.info()) << "peer requested node that is not in the map: " << wanted
451 << " but found " << nodeID;
452 return false;
453 }
454
455 if (node->isInner() && safeDowncast<SHAMapInnerNode*>(node)->isEmpty())
456 {
457 JLOG(journal_.warn()) << "peer requests empty node";
458 return false;
459 }
460
462 stack.emplace(node, nodeID, depth);
463
464 Serializer s(8192);
465
466 while (!stack.empty())
467 {
468 std::tie(node, nodeID, depth) = stack.top();
469 stack.pop();
470
471 // Add this node to the reply
472 s.erase();
473 node->serializeForWire(s);
474 data.emplace_back(nodeID, node->isLeaf(), s.getData());
475
476 if (node->isInner())
477 {
478 // We descend inner nodes with only a single child
479 // without decrementing the depth
480 auto inner = safeDowncast<SHAMapInnerNode*>(node);
481 auto const bc = inner->getBranchCount();
482
483 if ((depth > 0) || (bc == 1))
484 {
485 // We need to process this node's children
486 for (auto i = 0u; i < kBranchFactor; ++i)
487 {
488 if (!inner->isEmptyBranch(i))
489 {
490 auto const childNode = descendThrow(inner, i);
491 auto const childID = nodeID.getChildNodeID(i);
492
493 if (childNode->isInner() && ((depth > 1) || (bc == 1)))
494 {
495 // If there's more than one child, reduce the depth
496 // If only one child, follow the chain
497 stack.emplace(childNode, childID, (bc > 1) ? (depth - 1) : depth);
498 }
499 else if (childNode->isInner() || fatLeaves)
500 {
501 // Just include this node
502 s.erase();
503 childNode->serializeForWire(s);
504 data.emplace_back(childID, childNode->isLeaf(), s.getData());
505 }
506 }
507 }
508 }
509 }
510 }
511
512 return true;
513}
514
515void
517{
518 root_->serializeForWire(s);
519}
520
523 SHAMapHash const& hash,
524 SHAMapTreeNodePtr rootNode,
525 SHAMapSyncFilter const* filter)
526{
527 XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid");
528 XRPL_ASSERT(rootNode, "xrpl::SHAMap::addRootNode : non-null root node");
529
530 // we already have a root_ node
531 if (root_->getHash().isNonZero())
532 {
533 JLOG(journal_.trace()) << "Got root node, already have one";
534 XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash");
536 }
537
538 if (rootNode->getHash() != hash)
539 {
540 JLOG(journal_.warn()) << "Corrupt root node received: expected hash " << hash << ", got "
541 << rootNode->getHash();
542 return SHAMapAddNode::invalid();
543 }
544
545 if (backed_)
546 canonicalize(hash, rootNode);
547
548 root_ = std::move(rootNode);
549
550 if (root_->isLeaf())
552
553 if (filter != nullptr)
554 {
555 Serializer s;
556 root_->serializeWithPrefix(s);
557 filter->gotNode(
558 false, root_->getHash(), ledgerSeq_, std::move(s.modData()), root_->getType());
559 }
560
561 return SHAMapAddNode::useful();
562}
563
566 SHAMapNodeID const& nodeID,
567 SHAMapTreeNodePtr treeNode,
568 SHAMapSyncFilter const* filter)
569{
570 XRPL_ASSERT(!nodeID.isRoot(), "xrpl::SHAMap::addKnownNode : valid node");
571 XRPL_ASSERT(treeNode, "xrpl::SHAMap::addKnownNode : non-null tree node");
572 XRPL_ASSERT_IF(
573 treeNode->isLeaf(),
574 nodeID.isPrefixOf(leafKey(*treeNode)),
575 "xrpl::SHAMap::addKnownNode : leaf position consistent with node ID");
576
577 if (!isSynching())
578 {
579 JLOG(journal_.trace()) << "AddKnownNode while not synching";
581 }
582
583 auto const generation = f_.getFullBelowCache()->getGeneration();
584 SHAMapNodeID currNodeID;
585 auto currNode = root_.get();
586
587 while (currNode->isInner() &&
588 !safeDowncast<SHAMapInnerNode*>(currNode)->isFullBelow(generation) &&
589 (currNodeID.getDepth() < nodeID.getDepth()))
590 {
591 auto const branch = selectBranch(currNodeID, nodeID.getNodeID());
592 auto inner = safeDowncast<SHAMapInnerNode*>(currNode);
593 if (inner->isEmptyBranch(branch))
594 {
595 JLOG(journal_.warn()) << "Add known node " << nodeID << " for empty branch " << branch
596 << " at " << currNodeID;
597 return SHAMapAddNode::invalid();
598 }
599
600 auto childHash = inner->getChildHash(branch);
601 if (f_.getFullBelowCache()->touchIfExists(childHash.asUInt256()))
602 {
604 }
605
606 auto prevNode = inner;
607 std::tie(currNode, currNodeID) = descend(inner, currNodeID, branch, filter);
608
609 if (currNode != nullptr)
610 continue;
611
612 if (childHash != treeNode->getHash())
613 {
614 JLOG(journal_.warn()) << "Corrupt node " << nodeID << " received: expected hash "
615 << childHash << ", got " << treeNode->getHash();
616 return SHAMapAddNode::invalid();
617 }
618
619 // Inner nodes must be at a level strictly less than 64
620 // but leaf nodes (while notionally at level 64) can be
621 // at any depth up to and including 64:
622 if ((currNodeID.getDepth() > kLeafDepth) ||
623 (treeNode->isInner() && currNodeID.getDepth() == kLeafDepth))
624 {
625 // Map is provably invalid
627 return SHAMapAddNode::useful();
628 }
629
630 if (currNodeID != nodeID)
631 {
632 // Either this node is broken or we didn't request it (yet)
633 JLOG(journal_.warn()) << "unable to hook node " << nodeID;
634 JLOG(journal_.info()) << " stuck at " << currNodeID;
635 JLOG(journal_.info()) << "got depth=" << nodeID.getDepth()
636 << ", walked to= " << currNodeID.getDepth();
637 return SHAMapAddNode::useful();
638 }
639
640 if (backed_)
641 canonicalize(childHash, treeNode);
642
643 treeNode = prevNode->canonicalizeChild(branch, std::move(treeNode));
644
645 if (filter != nullptr)
646 {
647 Serializer s;
648 treeNode->serializeWithPrefix(s);
649 filter->gotNode(
650 false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
651 }
652
653 return SHAMapAddNode::useful();
654 }
655
656 JLOG(journal_.trace()) << "got node, already had it (late)";
658}
659
660bool
662{
663 // Intended for debug/test only
665
666 stack.emplace(root_.get(), other.root_.get());
667
668 while (!stack.empty())
669 {
670 auto const [node, otherNode] = stack.top();
671 stack.pop();
672
673 if ((node == nullptr) || (otherNode == nullptr))
674 {
675 JLOG(journal_.info()) << "unable to fetch node";
676 return false;
677 }
678 if (otherNode->getHash() != node->getHash())
679 {
680 JLOG(journal_.warn()) << "node hash mismatch";
681 return false;
682 }
683
684 if (node->isLeaf())
685 {
686 if (!otherNode->isLeaf())
687 return false;
688 auto& nodePeek = safeDowncast<SHAMapLeafNode*>(node)->peekItem();
689 auto& otherNodePeek = safeDowncast<SHAMapLeafNode*>(otherNode)->peekItem();
690 if (nodePeek->key() != otherNodePeek->key())
691 return false;
692 if (nodePeek->slice() != otherNodePeek->slice())
693 return false;
694 }
695 else if (node->isInner())
696 {
697 if (!otherNode->isInner())
698 return false;
699 auto nodeInner = safeDowncast<SHAMapInnerNode*>(node);
700 auto otherInner = safeDowncast<SHAMapInnerNode*>(otherNode);
701 for (auto i = 0u; i < kBranchFactor; ++i)
702 {
703 if (nodeInner->isEmptyBranch(i))
704 {
705 if (!otherInner->isEmptyBranch(i))
706 return false;
707 }
708 else
709 {
710 if (otherInner->isEmptyBranch(i))
711 return false;
712
713 auto next = descend(nodeInner, i);
714 auto otherNext = other.descend(otherInner, i);
715 if ((next == nullptr) || (otherNext == nullptr))
716 {
717 JLOG(journal_.warn()) << "unable to fetch inner node";
718 return false;
719 }
720 stack.emplace(next, otherNext);
721 }
722 }
723 }
724 }
725
726 return true;
727}
728
732bool
733SHAMap::hasInnerNode(SHAMapNodeID const& targetNodeID, SHAMapHash const& targetNodeHash) const
734{
735 auto node = root_.get();
736 SHAMapNodeID nodeID;
737
738 while (node->isInner() && (nodeID.getDepth() < targetNodeID.getDepth()))
739 {
740 auto const branch = selectBranch(nodeID, targetNodeID.getNodeID());
741 auto inner = safeDowncast<SHAMapInnerNode*>(node);
742 if (inner->isEmptyBranch(branch))
743 return false;
744
745 node = descendThrow(inner, branch);
746 nodeID = nodeID.getChildNodeID(branch);
747 }
748
749 return (node->isInner()) && (node->getHash() == targetNodeHash);
750}
751
755bool
756SHAMap::hasLeafNode(UInt256 const& tag, SHAMapHash const& targetNodeHash) const
757{
758 auto node = root_.get();
759 SHAMapNodeID nodeID;
760
761 if (!node->isInner()) // only one leaf node in the tree
762 return node->getHash() == targetNodeHash;
763
764 do
765 {
766 // Same kLeafDepth hazard as in visitDifferences above. That guard bounds the caller's own
767 // traversal, not the map queried here, and the loop below descends from this map's root
768 // independently, so this check is what keeps a malformed map from reaching getChildNodeID.
769 if (nodeID.getDepth() >= kLeafDepth)
770 {
771 // LCOV_EXCL_START
772 UNREACHABLE("xrpl::SHAMap::hasLeafNode : inner node at leaf depth");
773 return false;
774 // LCOV_EXCL_STOP
775 }
776
777 auto const branch = selectBranch(nodeID, tag);
778 auto inner = safeDowncast<SHAMapInnerNode*>(node);
779 if (inner->isEmptyBranch(branch))
780 return false; // Dead end, node must not be here
781
782 if (inner->getChildHash(branch) == targetNodeHash) // Matching leaf, no need to retrieve it
783 return true;
784
785 node = descendThrow(inner, branch);
786 nodeID = nodeID.getChildNodeID(branch);
787 } while (node->isInner());
788
789 return false; // If this was a matching leaf, we would have caught it
790 // already
791}
792
795{
796 NodePathStack stack;
797 walkTowardsKey(key, &stack);
798
799 if (stack.empty())
800 {
801 JLOG(journal_.debug()) << "no path to " << key;
802 return {};
803 }
804
805 if (auto const& node = stack.top().first; !node || node->isInner() ||
806 intr_ptr::staticPointerCast<SHAMapLeafNode>(node)->peekItem()->key() != key)
807 {
808 JLOG(journal_.debug()) << "no path to " << key;
809 return {};
810 }
811
813 path.reserve(stack.size());
814 while (!stack.empty())
815 {
816 Serializer s;
817 stack.top().first->serializeForWire(s);
818 path.emplace_back(std::move(s.modData()));
819 stack.pop();
820 }
821
822 JLOG(journal_.debug()) << "getPath for key " << key << ", path length " << path.size();
823 return path;
824}
825
826bool
828{
829 if (path.empty() || path.size() > kLeafDepth + 1u)
830 return false;
831
832 SHAMapHash hash{rootHash};
833 try
834 {
835 for (auto rit = path.rbegin(); rit != path.rend(); ++rit)
836 {
837 auto const& blob = *rit;
838 auto node = SHAMapTreeNode::makeFromWire(makeSlice(blob));
839 if (!node)
840 return false;
841 node->updateHash();
842 if (node->getHash() != hash)
843 return false;
844
845 auto const depth = static_cast<unsigned int>(std::distance(path.rbegin(), rit));
846 if (node->isInner())
847 {
848 // Nibbles run out at kLeafDepth, so only the leaf terminating the path may sit
849 // there. These nodes come off the wire, so a peer can still claim an inner one;
850 // reject it rather than passing this depth to selectBranch.
851 SOMETIMES(
852 depth >= kLeafDepth, "xrpl::SHAMap::verifyProofPath : inner at leaf depth");
853 if (depth >= kLeafDepth)
854 return false;
855
856 auto nodeId = SHAMapNodeID::createID(depth, key);
857 hash = safeDowncast<SHAMapInnerNode*>(node.get())
858 ->getChildHash(selectBranch(nodeId, key));
859 }
860 else
861 {
862 // The hash chain up to rootHash only proves this leaf sits where the path claims,
863 // not that it is the leaf for `key`: a peer could substitute any other leaf whose
864 // subtree hashes to the same value at every level above it. Checking the terminal
865 // leaf's own key is what ties the proof to `key` specifically.
866 if (leafKey(*node) != key)
867 return false;
868
869 // should exhaust all the blobs now
870 return depth + 1 == path.size();
871 }
872 }
873 }
874 catch (std::exception const&)
875 {
876 // the data in the path may come from the network,
877 // exception could be thrown when parsing the data
878 return false;
879 }
880 return false;
881}
882
883} // namespace xrpl
static SHAMapAddNode duplicate()
static SHAMapAddNode useful()
static SHAMapAddNode invalid()
SHAMapHash const & getChildHash(unsigned int branch) const
bool isEmptyBranch(unsigned int branch) const
Identifies a node inside a SHAMap.
bool isPrefixOf(UInt256 const &key) const
Test whether this node ID lies on the path to the given leaf key.
SHAMapNodeID getChildNodeID(unsigned int branch) const
UInt256 const & getNodeID() const
static SHAMapNodeID createID(unsigned int depth, UInt256 const &key)
Create a SHAMapNodeID of a node with the depth of the node and the key of a leaf.
unsigned int getDepth() const
bool isRoot() const
virtual void gotNode(bool fromFilter, SHAMapHash const &nodeHash, std::uint32_t ledgerSeq, Blob &&nodeData, SHAMapNodeType type) const =0
static SHAMapTreeNodePtr makeFromWire(Slice rawNode)
virtual bool isInner() const =0
Determines if this is an inner node.
A path from the root of the map down to some node, pairing each node with the ID naming its position.
Definition SHAMap.h:433
std::pair< SHAMapTreeNodePtr, SHAMapNodeID > const & top() const
Definition SHAMap.h:448
std::size_t size() const
Definition SHAMap.h:442
SHAMapState state_
Definition SHAMap.h:129
SHAMapTreeNode * descendAsync(SHAMapInnerNode *parent, unsigned int branch, SHAMapSyncFilter const *filter, bool &pending, DescendCallback &&) const
bool hasLeafNode(UInt256 const &tag, SHAMapHash const &hash) const
Does this map have this leaf node?
SHAMapTreeNode * descend(SHAMapInnerNode *, unsigned int branch) const
std::uint32_t cowid_
ID to distinguish this map for all others we're sharing nodes with.
Definition SHAMap.h:121
static constexpr unsigned int kLeafDepth
The depth of the hash map: data is only present in the leaves.
Definition SHAMap.h:144
Family & f_
Definition SHAMap.h:115
bool getNodeFat(SHAMapNodeID const &wanted, std::vector< SHAMapNodeData > &data, bool fatLeaves, std::uint32_t depth) const
static void gmnProcessDeferredReads(MissingNodes &)
static constexpr unsigned int kBranchFactor
Number of children each non-leaf node has (the 'radix tree' part of the map).
Definition SHAMap.h:139
SHAMapTreeNodePtr descendNoStore(SHAMapInnerNode &, unsigned int branch) const
void visitDifferences(SHAMap const *have, std::function< bool(SHAMapTreeNode const &)> const &) const
Visit every node in this SHAMap that is not present in the specified SHAMap.
bool hasInnerNode(SHAMapNodeID const &nodeID, SHAMapHash const &hash) const
Does this map have this inner node?
bool isSynching() const
Definition SHAMap.h:761
bool deepCompare(SHAMap &other) const
void gmnProcessNodes(MissingNodes &, MissingNodes::StackEntry &node)
beast::Journal journal_
Definition SHAMap.h:116
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, unsigned int branch) const
SHAMapAddNode addRootNode(SHAMapHash const &hash, SHAMapTreeNodePtr rootNode, SHAMapSyncFilter const *filter)
Add a root node to the SHAMap during synchronization.
void serializeRoot(Serializer &s) const
Serializes the root in a format appropriate for sending over the wire.
bool backed_
Definition SHAMap.h:131
void visitLeaves(std::function< void(boost::intrusive_ptr< SHAMapItem const > const &)> const &) const
Visit every leaf node in this SHAMap.
SHAMapLeafNode * walkTowardsKey(UInt256 const &id, NodePathStack *stack=nullptr) const
Walk towards the specified id, returning the node.
SHAMap()=delete
SHAMapAddNode addKnownNode(SHAMapNodeID const &nodeID, SHAMapTreeNodePtr treeNode, SHAMapSyncFilter const *filter)
Add a known node at a specific position in the SHAMap during synchronization.
void canonicalize(SHAMapHash const &hash, SHAMapTreeNodePtr &) const
void clearSynching()
Definition SHAMap.h:773
SHAMapTreeNodePtr root_
Definition SHAMap.h:128
std::uint32_t ledgerSeq_
The sequence of the ledger that this map references, if any.
Definition SHAMap.h:126
std::vector< std::pair< SHAMapNodeID, UInt256 > > getMissingNodes(int maxNodes, SHAMapSyncFilter const *filter)
Check for nodes in the SHAMap not available.
void visitNodes(std::function< bool(SHAMapTreeNode &)> const &function) const
Visit every node in this SHAMap.
std::optional< std::vector< Blob > > getProofPath(UInt256 const &key) const
Get the proof path of the key.
boost::intrusive_ptr< SHAMapItem const > const & peekItem(UInt256 const &id) const
static bool verifyProofPath(UInt256 const &rootHash, UInt256 const &key, std::vector< Blob > const &path)
Verify the proof path.
Blob getData() const
Definition Serializer.h:278
T * get() const
Get the raw pointer.
T distance(T... args)
T emplace(T... args)
T empty(T... args)
SharedPtr< T > staticPointerCast(TT const &v)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
intr_ptr::SharedPtr< SHAMapTreeNode > SHAMapTreeNodePtr
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
BaseUInt< 256 > UInt256
Definition base_uint.h:580
UInt256 const & leafKey(SHAMapTreeNode const &node)
Return the key of the item held by a SHAMap leaf node.
Integral randInt(Engine &engine, Integral min, Integral max)
Return a uniformly distributed random integer.
Dest safeDowncast(Src *s) noexcept
Definition safe_cast.h:84
unsigned int selectBranch(SHAMapNodeID const &id, UInt256 const &hash)
Returns the branch that would contain the given hash.
@ Invalid
The map is known to not be valid.
Definition SHAMap.h:70
T pop(T... args)
std::tuple< SHAMapInnerNode *, SHAMapNodeID, unsigned int, unsigned int, bool > StackEntry
Definition SHAMap.h:688
std::vector< std::pair< SHAMapNodeID, UInt256 > > missingNodes
Definition SHAMap.h:684
std::set< SHAMapHash > missingHashes
Definition SHAMap.h:685
std::tuple< SHAMapInnerNode *, SHAMapNodeID, unsigned int, SHAMapTreeNodePtr > DeferredNode
Definition SHAMap.h:703
std::vector< DeferredNode > finishedReads
Definition SHAMap.h:712
std::uint32_t generation
Definition SHAMap.h:681
SHAMapSyncFilter const * filter
Definition SHAMap.h:679
std::map< SHAMapInnerNode *, SHAMapNodeID > resumes
Definition SHAMap.h:716
std::stack< StackEntry, std::deque< StackEntry > > stack
Definition SHAMap.h:700
std::condition_variable deferCondVar
Definition SHAMap.h:711
T tie(T... args)
T top(T... args)