xrpld
Loading...
Searching...
No Matches
LedgerTrie.h
1#pragma once
2
3#include <xrpl/basics/ToString.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/json/json_value.h>
6
7#include <algorithm>
8#include <cstddef>
9#include <cstdint>
10#include <iomanip>
11#include <map>
12#include <memory>
13#include <optional>
14#include <ostream>
15#include <sstream>
16#include <stack>
17#include <utility>
18#include <vector>
19
20namespace xrpl {
21
25template <class Ledger>
27{
28public:
29 using Seq = Ledger::Seq;
30 using ID = Ledger::ID;
31
32 SpanTip(Seq s, ID i, Ledger const lgr) : seq{s}, id{i}, ledger_{std::move(lgr)}
33 {
34 }
35
36 // The sequence number of the tip ledger
38 // The ID of the tip ledger
40
50 [[nodiscard]] ID
51 ancestor(Seq const& s) const
52 {
53 XRPL_ASSERT(s <= seq, "xrpl::SpanTip::ancestor : valid input");
54 return ledger_[s];
55 }
56
57private:
59};
60
62
63// Represents a span of ancestry of a ledger
64template <class Ledger>
65class Span
66{
67 using Seq = Ledger::Seq;
68 using ID = Ledger::ID;
69
70 // The span is the half-open interval [start,end) of ledger_
74
75public:
76 Span() : ledger_{typename Ledger::MakeGenesis{}}
77 {
78 // Require default ledger to be genesis seq
79 XRPL_ASSERT(ledger_.seq() == start_, "xrpl::Span::Span : ledger is genesis");
80 }
81
82 Span(Ledger ledger) : end_{ledger.seq() + Seq{1}}, ledger_{std::move(ledger)}
83 {
84 }
85
86 Span(Span const& s) = default;
87 Span(Span&& s) = default;
88 Span&
89 operator=(Span const&) = default;
90 Span&
91 operator=(Span&&) = default;
92
93 [[nodiscard]] Seq
94 start() const
95 {
96 return start_;
97 }
98
99 [[nodiscard]] Seq
100 end() const
101 {
102 return end_;
103 }
104
105 // Return the Span from [spot,end_) or none if no such valid span
106 [[nodiscard]] std::optional<Span>
107 from(Seq spot) const
108 {
109 return sub(spot, end_);
110 }
111
112 // Return the Span from [start_,spot) or none if no such valid span
113 [[nodiscard]] std::optional<Span>
114 before(Seq spot) const
115 {
116 return sub(start_, spot);
117 }
118
119 // Return the ID of the ledger that starts this span
120 [[nodiscard]] ID
121 startID() const
122 {
123 return ledger_[start_];
124 }
125
126 // Return the ledger sequence number of the first possible difference
127 // between this span and a given ledger.
128 [[nodiscard]] Seq
129 diff(Ledger const& o) const
130 {
131 return clamp(mismatch(ledger_, o));
132 }
133
134 // The tip of this span
135 [[nodiscard]] SpanTip<Ledger>
136 tip() const
137 {
138 Seq const tipSeq{end_ - Seq{1}};
139 return SpanTip<Ledger>{tipSeq, ledger_[tipSeq], ledger_};
140 }
141
142private:
144 {
145 // Spans cannot be empty
146 XRPL_ASSERT(start < end, "xrpl::Span::Span : non-empty span input");
147 }
148
149 [[nodiscard]] Seq
150 clamp(Seq val) const
151 {
152 return std::min(std::max(start_, val), end_);
153 }
154
155 // Return a span of this over the half-open interval [from,to)
156 [[nodiscard]] std::optional<Span>
157 sub(Seq from, Seq to) const
158 {
159 Seq const newFrom = clamp(from);
160 Seq const newTo = clamp(to);
161 if (newFrom < newTo)
162 return Span(newFrom, newTo, ledger_);
163 return std::nullopt;
164 }
165
167 operator<<(std::ostream& o, Span const& s)
168 {
169 return o << s.tip().id << "[" << s.start_ << "," << s.end_ << ")";
170 }
171
172 friend Span
173 merge(Span const& a, Span const& b)
174 {
175 // Return combined span, using ledger_ from higher sequence span
176 if (a.end_ < b.end_)
177 return Span(std::min(a.start_, b.start_), b.end_, b.ledger_);
178
179 return Span(std::min(a.start_, b.start_), a.end_, a.ledger_);
180 }
181};
182
183// A node in the trie
184template <class Ledger>
185struct Node
186{
187 Node() = default;
188
189 explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1}
190 {
191 }
192
193 explicit Node(Span<Ledger> s) : span{std::move(s)}
194 {
195 }
196
200
202 Node* parent = nullptr;
203
211 void
212 erase(Node const* child)
213 {
214 auto it = std::ranges::find_if(
215 children, [child](std::unique_ptr<Node> const& curr) { return curr.get() == child; });
216 XRPL_ASSERT(it != children.end(), "xrpl::Node::erase : valid input");
217 std::swap(*it, children.back());
218 children.pop_back();
219 }
220
222 operator<<(std::ostream& o, Node const& s)
223 {
224 return o << s.span << "(T:" << s.tipSupport << ",B:" << s.branchSupport << ")";
225 }
226
227 [[nodiscard]] json::Value
228 getJson() const
229 {
230 json::Value res;
232 sps << span;
233 res["span"] = sps.str();
234 res["startID"] = to_string(span.startID());
235 res["seq"] = static_cast<std::uint32_t>(span.tip().seq);
236 res["tipSupport"] = tipSupport;
237 res["branchSupport"] = branchSupport;
238 if (!children.empty())
239 {
240 json::Value& cs = (res["children"] = json::ValueType::Array);
241 for (auto const& child : children)
242 {
243 cs.append(child->getJson());
244 }
245 }
246 return res;
247 }
248};
249} // namespace ledger_trie_detail
250
329template <class Ledger>
331{
332 using Seq = Ledger::Seq;
333 using ID = Ledger::ID;
334
337
338 // The root of the trie. The root is allowed to break the no-single child
339 // invariant.
341
342 // Count of the tip support for each sequence number
344
352 [[nodiscard]] std::pair<Node*, Seq>
353 find(Ledger const& ledger) const
354 {
355 // NOLINTNEXTLINE(misc-const-correctness)
356 Node* curr = root_.get();
357
358 // Root is always defined and is in common with all ledgers
359 XRPL_ASSERT(curr, "xrpl::LedgerTrie::find : non-null root");
360 Seq pos = curr->span.diff(ledger);
361
362 bool done = false;
363
364 // Continue searching for a better span as long as the current position
365 // matches the entire span
366 while (!done && pos == curr->span.end())
367 {
368 done = true;
369 // Find the child with the longest ancestry match
370 for (std::unique_ptr<Node> const& child : curr->children)
371 {
372 auto const childPos = child->span.diff(ledger);
373 if (childPos > pos)
374 {
375 done = false;
376 pos = childPos;
377 curr = child.get();
378 break;
379 }
380 }
381 }
382 return std::make_pair(curr, pos);
383 }
384
392 Node*
393 findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const
394 {
395 if (parent == nullptr)
396 parent = root_.get();
397 if (ledger.id() == parent->span.tip().id)
398 return parent;
399 for (auto const& child : parent->children)
400 {
401 auto cl = findByLedgerID(ledger, child.get());
402 if (cl)
403 return cl;
404 }
405 return nullptr;
406 }
407
408 void
409 dumpImpl(std::ostream& o, std::unique_ptr<Node> const& curr, int offset) const
410 {
411 if (curr)
412 {
413 if (offset > 0)
414 o << std::setw(offset) << "|-";
415
417 ss << *curr;
418 o << ss.str() << std::endl;
419 for (std::unique_ptr<Node> const& child : curr->children)
420 dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
421 }
422 }
423
424public:
425 LedgerTrie() : root_{std::make_unique<Node>()}
426 {
427 }
428
435 void
436 insert(Ledger const& ledger, std::uint32_t count = 1)
437 {
438 auto const [loc, diffSeq] = find(ledger);
439
440 // There is always a place to insert
441 XRPL_ASSERT(loc, "xrpl::LedgerTrie::insert : valid input ledger");
442
443 // Node from which to start incrementing branchSupport
444 Node* incNode = loc;
445
446 // loc->span has the longest common prefix with Span{ledger} of all
447 // existing nodes in the trie. The optional<Span>'s below represent
448 // the possible common suffixes between loc->span and Span{ledger}.
449 //
450 // loc->span
451 // a b c | d e f
452 // prefix | oldSuffix
453 //
454 // Span{ledger}
455 // a b c | g h i
456 // prefix | newSuffix
457
458 std::optional<Span> prefix = loc->span.before(diffSeq);
459 std::optional<Span> oldSuffix = loc->span.from(diffSeq);
460 std::optional<Span> newSuffix = Span{ledger}.from(diffSeq);
461
462 if (oldSuffix)
463 {
464 // Have
465 // abcdef -> ....
466 // Inserting
467 // abc
468 // Becomes
469 // abc -> def -> ...
470
471 // Create oldSuffix node that takes over loc
472 auto newNode = std::make_unique<Node>(*oldSuffix);
473 newNode->tipSupport = loc->tipSupport;
474 newNode->branchSupport = loc->branchSupport;
475 newNode->children = std::move(loc->children);
476 XRPL_ASSERT(loc->children.empty(), "xrpl::LedgerTrie::insert : moved-from children");
477 for (std::unique_ptr<Node>& child : newNode->children)
478 child->parent = newNode.get();
479
480 // Loc truncates to prefix and newNode is its child
481 XRPL_ASSERT(prefix, "xrpl::LedgerTrie::insert : prefix is set");
482 loc->span = *prefix; // NOLINT(bugprone-unchecked-optional-access) assert above
483 newNode->parent = loc;
484 loc->children.emplace_back(std::move(newNode));
485 loc->tipSupport = 0;
486 }
487 if (newSuffix)
488 {
489 // Have
490 // abc -> ...
491 // Inserting
492 // abcdef-> ...
493 // Becomes
494 // abc -> ...
495 // \-> def
496
497 auto newNode = std::make_unique<Node>(*newSuffix);
498 newNode->parent = loc;
499 // increment support starting from the new node
500 incNode = newNode.get();
501 loc->children.push_back(std::move(newNode));
502 }
503
504 incNode->tipSupport += count;
505 while (incNode)
506 {
507 incNode->branchSupport += count;
508 incNode = incNode->parent;
509 }
510
511 seqSupport_[ledger.seq()] += count;
512 }
513
522 bool
523 remove(Ledger const& ledger, std::uint32_t count = 1)
524 {
525 Node* loc = findByLedgerID(ledger);
526 // Must be exact match with tip support
527 if ((loc == nullptr) || loc->tipSupport == 0)
528 return false;
529
530 // found our node, remove it
531 count = std::min(count, loc->tipSupport);
532 loc->tipSupport -= count;
533
534 auto const it = seqSupport_.find(ledger.seq());
535 XRPL_ASSERT(
536 it != seqSupport_.end() && it->second >= count,
537 "xrpl::LedgerTrie::remove : valid input ledger");
538 it->second -= count;
539 if (it->second == 0)
540 seqSupport_.erase(it->first);
541
542 Node* decNode = loc;
543 while (decNode)
544 {
545 decNode->branchSupport -= count;
546 decNode = decNode->parent;
547 }
548
549 while (loc->tipSupport == 0 && loc != root_.get())
550 {
551 Node* parent = loc->parent;
552 if (loc->children.empty())
553 {
554 // this node can be erased
555 parent->erase(loc);
556 }
557 else if (loc->children.size() == 1)
558 {
559 // This node can be combined with its child
560 std::unique_ptr<Node> child = std::move(loc->children.front());
561 child->span = merge(loc->span, child->span);
562 child->parent = parent;
563 parent->children.emplace_back(std::move(child));
564 parent->erase(loc);
565 }
566 else
567 {
568 break;
569 }
570 loc = parent;
571 }
572 return true;
573 }
574
581 [[nodiscard]] std::uint32_t
582 tipSupport(Ledger const& ledger) const
583 {
584 if (auto const* loc = findByLedgerID(ledger))
585 return loc->tipSupport;
586 return 0;
587 }
588
596 [[nodiscard]] std::uint32_t
597 branchSupport(Ledger const& ledger) const
598 {
599 Node const* loc = findByLedgerID(ledger);
600 if (loc == nullptr)
601 {
602 Seq diffSeq;
603 std::tie(loc, diffSeq) = find(ledger);
604 // Check that ledger is a proper prefix of loc
605 if (!(diffSeq > ledger.seq() && ledger.seq() < loc->span.end()))
606 loc = nullptr;
607 }
608 return loc ? loc->branchSupport : 0;
609 }
610
671 [[nodiscard]] std::optional<SpanTip<Ledger>>
672 getPreferred(Seq const largestIssued) const
673 {
674 if (empty())
675 return std::nullopt;
676
677 Node* curr = root_.get();
678
679 bool done = false;
680
681 std::uint32_t uncommitted = 0;
682 auto uncommittedIt = seqSupport_.begin();
683
684 while (curr && !done)
685 {
686 // Within a single span, the preferred by branch strategy is simply
687 // to continue along the span as long as the branch support of
688 // the next ledger exceeds the uncommitted support for that ledger.
689 {
690 // Add any initial uncommitted support prior for ledgers
691 // earlier than nextSeq or earlier than largestIssued
692 Seq nextSeq = curr->span.start() + Seq{1};
693 while (uncommittedIt != seqSupport_.end() &&
694 uncommittedIt->first < std::max(nextSeq, largestIssued))
695 {
696 uncommitted += uncommittedIt->second;
697 uncommittedIt++;
698 }
699
700 // Advance nextSeq along the span
701 while (nextSeq < curr->span.end() && curr->branchSupport > uncommitted)
702 {
703 // Jump to the next seqSupport change
704 if (uncommittedIt != seqSupport_.end() &&
705 uncommittedIt->first < curr->span.end())
706 {
707 nextSeq = uncommittedIt->first + Seq{1};
708 uncommitted += uncommittedIt->second;
709 uncommittedIt++;
710 }
711 else
712 { // otherwise we jump to the end of the span
713 nextSeq = curr->span.end();
714 }
715 }
716 // We did not consume the entire span, so we have found the
717 // preferred ledger
718 if (nextSeq < curr->span.end())
719 {
720 // nextSeq within span guarantees before() is set
721 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
722 return curr->span.before(nextSeq)->tip();
723 }
724 }
725
726 // We have reached the end of the current span, so we need to
727 // find the best child
728 Node* best = nullptr;
729 std::uint32_t margin = 0;
730 if (curr->children.size() == 1)
731 {
732 best = curr->children[0].get();
733 margin = best->branchSupport;
734 }
735 else if (!curr->children.empty())
736 {
737 // Sort placing children with largest branch support in the
738 // front, breaking ties with the span's starting ID
740 curr->children.begin(),
741 curr->children.begin() + 2,
742 curr->children.end(),
743 [](std::unique_ptr<Node> const& a, std::unique_ptr<Node> const& b) {
744 return std::make_tuple(a->branchSupport, a->span.startID()) >
745 std::make_tuple(b->branchSupport, b->span.startID());
746 });
747
748 best = curr->children[0].get();
749 margin = curr->children[0]->branchSupport - curr->children[1]->branchSupport;
750
751 // If best holds the tie-breaker, gets one larger margin
752 // since the second best needs additional branchSupport
753 // to overcome the tie
754 if (best->span.startID() > curr->children[1]->span.startID())
755 margin++;
756 }
757
758 // If the best child has margin exceeding the uncommitted support,
759 // continue from that child, otherwise we are done
760 if (best && ((margin > uncommitted) || (uncommitted == 0)))
761 {
762 curr = best;
763 }
764 else
765 { // current is the best
766 done = true;
767 }
768 }
769 return curr->span.tip();
770 }
771
775 [[nodiscard]] bool
776 empty() const
777 {
778 return !root_ || root_->branchSupport == 0;
779 }
780
784 void
786 {
787 dumpImpl(o, root_, 0);
788 }
789
793 [[nodiscard]] json::Value
794 getJson() const
795 {
796 json::Value res;
797 res["trie"] = root_->getJson();
798 res["seq_support"] = json::ValueType::Object;
799 for (auto const& [seq, sup] : seqSupport_)
800 res["seq_support"][to_string(seq)] = sup;
801 return res;
802 }
803
807 [[nodiscard]] bool
809 {
810 std::map<Seq, std::uint32_t> expectedSeqSupport;
811
813 nodes.push(root_.get());
814 while (!nodes.empty())
815 {
816 Node const* curr = nodes.top();
817 nodes.pop();
818 if (curr == nullptr)
819 continue;
820
821 // Node with 0 tip support must have multiple children
822 // unless it is the root node
823 if (curr != root_.get() && curr->tipSupport == 0 && curr->children.size() < 2)
824 return false;
825
826 // branchSupport = tipSupport + sum(child->branchSupport)
827 std::size_t support = curr->tipSupport;
828 if (curr->tipSupport != 0)
829 expectedSeqSupport[curr->span.end() - Seq{1}] += curr->tipSupport;
830
831 for (auto const& child : curr->children)
832 {
833 if (child->parent != curr)
834 return false;
835
836 support += child->branchSupport;
837 nodes.push(child.get());
838 }
839 if (support != curr->branchSupport)
840 return false;
841 }
842 return expectedSeqSupport == seqSupport_;
843 }
844};
845
846} // namespace xrpl
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
std::uint32_t tipSupport(Ledger const &ledger) const
Return count of tip support for the specific ledger.
Definition LedgerTrie.h:582
Node * findByLedgerID(Ledger const &ledger, Node *parent=nullptr) const
Find the node in the trie with an exact match to the given ledger ID.
Definition LedgerTrie.h:393
bool empty() const
Return whether the trie is tracking any ledgers.
Definition LedgerTrie.h:776
bool checkInvariants() const
Check the compressed trie and support invariants.
Definition LedgerTrie.h:808
Ledger::ID ID
Definition LedgerTrie.h:333
std::uint32_t branchSupport(Ledger const &ledger) const
Return the count of branch support for the specific ledger.
Definition LedgerTrie.h:597
ledger_trie_detail::Span< Ledger > Span
Definition LedgerTrie.h:336
std::pair< Node *, Seq > find(Ledger const &ledger) const
Find the node in the trie that represents the longest common ancestry with the given ledger.
Definition LedgerTrie.h:353
Ledger::Seq Seq
Definition LedgerTrie.h:332
std::optional< SpanTip< Ledger > > getPreferred(Seq const largestIssued) const
Return the preferred ledger ID.
Definition LedgerTrie.h:672
void dumpImpl(std::ostream &o, std::unique_ptr< Node > const &curr, int offset) const
Definition LedgerTrie.h:409
json::Value getJson() const
Dump JSON representation of trie state.
Definition LedgerTrie.h:794
void dump(std::ostream &o) const
Dump an ascii representation of the trie to the stream.
Definition LedgerTrie.h:785
void insert(Ledger const &ledger, std::uint32_t count=1)
Insert and/or increment the support for the given ledger.
Definition LedgerTrie.h:436
std::unique_ptr< Node > root_
Definition LedgerTrie.h:340
std::map< Seq, std::uint32_t > seqSupport_
Definition LedgerTrie.h:343
bool remove(Ledger const &ledger, std::uint32_t count=1)
Decrease support for a ledger, removing and compressing if possible.
Definition LedgerTrie.h:523
ledger_trie_detail::Node< Ledger > Node
Definition LedgerTrie.h:335
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:115
The tip of a span of ledger ancestry.
Definition LedgerTrie.h:27
SpanTip(Seq s, ID i, Ledger const lgr)
Definition LedgerTrie.h:32
Ledger const ledger_
Definition LedgerTrie.h:58
Ledger::Seq Seq
Definition LedgerTrie.h:29
Ledger::ID ID
Definition LedgerTrie.h:30
ID ancestor(Seq const &s) const
Lookup the ID of an ancestor of the tip ledger.
Definition LedgerTrie.h:51
Span(Seq start, Seq end, Ledger l)
Definition LedgerTrie.h:143
Seq diff(Ledger const &o) const
Definition LedgerTrie.h:129
Span & operator=(Span const &)=default
std::optional< Span > before(Seq spot) const
Definition LedgerTrie.h:114
std::optional< Span > from(Seq spot) const
Definition LedgerTrie.h:107
SpanTip< Ledger > tip() const
Definition LedgerTrie.h:136
std::optional< Span > sub(Seq from, Seq to) const
Definition LedgerTrie.h:157
Span & operator=(Span &&)=default
friend Span merge(Span const &a, Span const &b)
Definition LedgerTrie.h:173
friend std::ostream & operator<<(std::ostream &o, Span const &s)
Definition LedgerTrie.h:167
Span(Span const &s)=default
T empty(T... args)
T endl(T... args)
T find_if(T... args)
T get(T... args)
T make_pair(T... args)
T make_unique(T... args)
T max(T... args)
T min(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
RCLValidatedLedger::Seq mismatch(RCLValidatedLedger const &a, RCLValidatedLedger const &b)
T partial_sort(T... args)
T pop(T... args)
T push(T... args)
T setw(T... args)
T str(T... args)
friend std::ostream & operator<<(std::ostream &o, Node const &s)
Definition LedgerTrie.h:222
void erase(Node const *child)
Remove the given node from this Node's children.
Definition LedgerTrie.h:212
std::vector< std::unique_ptr< Node > > children
Definition LedgerTrie.h:201
json::Value getJson() const
Definition LedgerTrie.h:228
T swap(T... args)
T tie(T... args)
T top(T... args)