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 <iomanip>
9#include <memory>
10#include <optional>
11#include <sstream>
12#include <stack>
13#include <utility>
14#include <vector>
15
16namespace xrpl {
17
20template <class Ledger>
22{
23public:
24 using Seq = Ledger::Seq;
25 using ID = Ledger::ID;
26
27 SpanTip(Seq s, ID i, Ledger const lgr) : seq{s}, id{i}, ledger_{std::move(lgr)}
28 {
29 }
30
31 // The sequence number of the tip ledger
33 // The ID of the tip ledger
35
44 [[nodiscard]] ID
45 ancestor(Seq const& s) const
46 {
47 XRPL_ASSERT(s <= seq, "xrpl::SpanTip::ancestor : valid input");
48 return ledger_[s];
49 }
50
51private:
53};
54
56
57// Represents a span of ancestry of a ledger
58template <class Ledger>
59class Span
60{
61 using Seq = Ledger::Seq;
62 using ID = Ledger::ID;
63
64 // The span is the half-open interval [start,end) of ledger_
68
69public:
70 Span() : ledger_{typename Ledger::MakeGenesis{}}
71 {
72 // Require default ledger to be genesis seq
73 XRPL_ASSERT(ledger_.seq() == start_, "xrpl::Span::Span : ledger is genesis");
74 }
75
76 Span(Ledger ledger) : end_{ledger.seq() + Seq{1}}, ledger_{std::move(ledger)}
77 {
78 }
79
80 Span(Span const& s) = default;
81 Span(Span&& s) = default;
82 Span&
83 operator=(Span const&) = default;
84 Span&
85 operator=(Span&&) = default;
86
87 [[nodiscard]] Seq
88 start() const
89 {
90 return start_;
91 }
92
93 [[nodiscard]] Seq
94 end() const
95 {
96 return end_;
97 }
98
99 // Return the Span from [spot,end_) or none if no such valid span
100 [[nodiscard]] std::optional<Span>
101 from(Seq spot) const
102 {
103 return sub(spot, end_);
104 }
105
106 // Return the Span from [start_,spot) or none if no such valid span
107 [[nodiscard]] std::optional<Span>
108 before(Seq spot) const
109 {
110 return sub(start_, spot);
111 }
112
113 // Return the ID of the ledger that starts this span
114 [[nodiscard]] ID
115 startID() const
116 {
117 return ledger_[start_];
118 }
119
120 // Return the ledger sequence number of the first possible difference
121 // between this span and a given ledger.
122 [[nodiscard]] Seq
123 diff(Ledger const& o) const
124 {
125 return clamp(mismatch(ledger_, o));
126 }
127
128 // The tip of this span
129 [[nodiscard]] SpanTip<Ledger>
130 tip() const
131 {
132 Seq const tipSeq{end_ - Seq{1}};
133 return SpanTip<Ledger>{tipSeq, ledger_[tipSeq], ledger_};
134 }
135
136private:
138 {
139 // Spans cannot be empty
140 XRPL_ASSERT(start < end, "xrpl::Span::Span : non-empty span input");
141 }
142
143 [[nodiscard]] Seq
144 clamp(Seq val) const
145 {
146 return std::min(std::max(start_, val), end_);
147 }
148
149 // Return a span of this over the half-open interval [from,to)
150 [[nodiscard]] std::optional<Span>
151 sub(Seq from, Seq to) const
152 {
153 Seq const newFrom = clamp(from);
154 Seq const newTo = clamp(to);
155 if (newFrom < newTo)
156 return Span(newFrom, newTo, ledger_);
157 return std::nullopt;
158 }
159
161 operator<<(std::ostream& o, Span const& s)
162 {
163 return o << s.tip().id << "[" << s.start_ << "," << s.end_ << ")";
164 }
165
166 friend Span
167 merge(Span const& a, Span const& b)
168 {
169 // Return combined span, using ledger_ from higher sequence span
170 if (a.end_ < b.end_)
171 return Span(std::min(a.start_, b.start_), b.end_, b.ledger_);
172
173 return Span(std::min(a.start_, b.start_), a.end_, a.ledger_);
174 }
175};
176
177// A node in the trie
178template <class Ledger>
179struct Node
180{
181 Node() = default;
182
183 explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1}
184 {
185 }
186
187 explicit Node(Span<Ledger> s) : span{std::move(s)}
188 {
189 }
190
194
196 Node* parent = nullptr;
197
204 void
205 erase(Node const* child)
206 {
207 auto it = std::ranges::find_if(
208 children, [child](std::unique_ptr<Node> const& curr) { return curr.get() == child; });
209 XRPL_ASSERT(it != children.end(), "xrpl::Node::erase : valid input");
210 std::swap(*it, children.back());
211 children.pop_back();
212 }
213
215 operator<<(std::ostream& o, Node const& s)
216 {
217 return o << s.span << "(T:" << s.tipSupport << ",B:" << s.branchSupport << ")";
218 }
219
220 [[nodiscard]] json::Value
221 getJson() const
222 {
223 json::Value res;
225 sps << span;
226 res["span"] = sps.str();
227 res["startID"] = to_string(span.startID());
228 res["seq"] = static_cast<std::uint32_t>(span.tip().seq);
229 res["tipSupport"] = tipSupport;
230 res["branchSupport"] = branchSupport;
231 if (!children.empty())
232 {
233 json::Value& cs = (res["children"] = json::ValueType::Array);
234 for (auto const& child : children)
235 {
236 cs.append(child->getJson());
237 }
238 }
239 return res;
240 }
241};
242} // namespace ledger_trie_detail
243
321template <class Ledger>
323{
324 using Seq = Ledger::Seq;
325 using ID = Ledger::ID;
326
329
330 // The root of the trie. The root is allowed to break the no-single child
331 // invariant.
333
334 // Count of the tip support for each sequence number
336
343 [[nodiscard]] std::pair<Node*, Seq>
344 find(Ledger const& ledger) const
345 {
346 // NOLINTNEXTLINE(misc-const-correctness)
347 Node* curr = root_.get();
348
349 // Root is always defined and is in common with all ledgers
350 XRPL_ASSERT(curr, "xrpl::LedgerTrie::find : non-null root");
351 Seq pos = curr->span.diff(ledger);
352
353 bool done = false;
354
355 // Continue searching for a better span as long as the current position
356 // matches the entire span
357 while (!done && pos == curr->span.end())
358 {
359 done = true;
360 // Find the child with the longest ancestry match
361 for (std::unique_ptr<Node> const& child : curr->children)
362 {
363 auto const childPos = child->span.diff(ledger);
364 if (childPos > pos)
365 {
366 done = false;
367 pos = childPos;
368 curr = child.get();
369 break;
370 }
371 }
372 }
373 return std::make_pair(curr, pos);
374 }
375
382 Node*
383 findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const
384 {
385 if (parent == nullptr)
386 parent = root_.get();
387 if (ledger.id() == parent->span.tip().id)
388 return parent;
389 for (auto const& child : parent->children)
390 {
391 auto cl = findByLedgerID(ledger, child.get());
392 if (cl)
393 return cl;
394 }
395 return nullptr;
396 }
397
398 void
399 dumpImpl(std::ostream& o, std::unique_ptr<Node> const& curr, int offset) const
400 {
401 if (curr)
402 {
403 if (offset > 0)
404 o << std::setw(offset) << "|-";
405
407 ss << *curr;
408 o << ss.str() << std::endl;
409 for (std::unique_ptr<Node> const& child : curr->children)
410 dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
411 }
412 }
413
414public:
415 LedgerTrie() : root_{std::make_unique<Node>()}
416 {
417 }
418
424 void
425 insert(Ledger const& ledger, std::uint32_t count = 1)
426 {
427 auto const [loc, diffSeq] = find(ledger);
428
429 // There is always a place to insert
430 XRPL_ASSERT(loc, "xrpl::LedgerTrie::insert : valid input ledger");
431
432 // Node from which to start incrementing branchSupport
433 Node* incNode = loc;
434
435 // loc->span has the longest common prefix with Span{ledger} of all
436 // existing nodes in the trie. The optional<Span>'s below represent
437 // the possible common suffixes between loc->span and Span{ledger}.
438 //
439 // loc->span
440 // a b c | d e f
441 // prefix | oldSuffix
442 //
443 // Span{ledger}
444 // a b c | g h i
445 // prefix | newSuffix
446
447 std::optional<Span> prefix = loc->span.before(diffSeq);
448 std::optional<Span> oldSuffix = loc->span.from(diffSeq);
449 std::optional<Span> newSuffix = Span{ledger}.from(diffSeq);
450
451 if (oldSuffix)
452 {
453 // Have
454 // abcdef -> ....
455 // Inserting
456 // abc
457 // Becomes
458 // abc -> def -> ...
459
460 // Create oldSuffix node that takes over loc
461 auto newNode = std::make_unique<Node>(*oldSuffix);
462 newNode->tipSupport = loc->tipSupport;
463 newNode->branchSupport = loc->branchSupport;
464 newNode->children = std::move(loc->children);
465 XRPL_ASSERT(loc->children.empty(), "xrpl::LedgerTrie::insert : moved-from children");
466 for (std::unique_ptr<Node>& child : newNode->children)
467 child->parent = newNode.get();
468
469 // Loc truncates to prefix and newNode is its child
470 XRPL_ASSERT(prefix, "xrpl::LedgerTrie::insert : prefix is set");
471 loc->span = *prefix; // NOLINT(bugprone-unchecked-optional-access) assert above
472 newNode->parent = loc;
473 loc->children.emplace_back(std::move(newNode));
474 loc->tipSupport = 0;
475 }
476 if (newSuffix)
477 {
478 // Have
479 // abc -> ...
480 // Inserting
481 // abcdef-> ...
482 // Becomes
483 // abc -> ...
484 // \-> def
485
486 auto newNode = std::make_unique<Node>(*newSuffix);
487 newNode->parent = loc;
488 // increment support starting from the new node
489 incNode = newNode.get();
490 loc->children.push_back(std::move(newNode));
491 }
492
493 incNode->tipSupport += count;
494 while (incNode)
495 {
496 incNode->branchSupport += count;
497 incNode = incNode->parent;
498 }
499
500 seqSupport_[ledger.seq()] += count;
501 }
502
510 bool
511 remove(Ledger const& ledger, std::uint32_t count = 1)
512 {
513 Node* loc = findByLedgerID(ledger);
514 // Must be exact match with tip support
515 if ((loc == nullptr) || loc->tipSupport == 0)
516 return false;
517
518 // found our node, remove it
519 count = std::min(count, loc->tipSupport);
520 loc->tipSupport -= count;
521
522 auto const it = seqSupport_.find(ledger.seq());
523 XRPL_ASSERT(
524 it != seqSupport_.end() && it->second >= count,
525 "xrpl::LedgerTrie::remove : valid input ledger");
526 it->second -= count;
527 if (it->second == 0)
528 seqSupport_.erase(it->first);
529
530 Node* decNode = loc;
531 while (decNode)
532 {
533 decNode->branchSupport -= count;
534 decNode = decNode->parent;
535 }
536
537 while (loc->tipSupport == 0 && loc != root_.get())
538 {
539 Node* parent = loc->parent;
540 if (loc->children.empty())
541 {
542 // this node can be erased
543 parent->erase(loc);
544 }
545 else if (loc->children.size() == 1)
546 {
547 // This node can be combined with its child
548 std::unique_ptr<Node> child = std::move(loc->children.front());
549 child->span = merge(loc->span, child->span);
550 child->parent = parent;
551 parent->children.emplace_back(std::move(child));
552 parent->erase(loc);
553 }
554 else
555 {
556 break;
557 }
558 loc = parent;
559 }
560 return true;
561 }
562
568 [[nodiscard]] std::uint32_t
569 tipSupport(Ledger const& ledger) const
570 {
571 if (auto const* loc = findByLedgerID(ledger))
572 return loc->tipSupport;
573 return 0;
574 }
575
582 [[nodiscard]] std::uint32_t
583 branchSupport(Ledger const& ledger) const
584 {
585 Node const* loc = findByLedgerID(ledger);
586 if (loc == nullptr)
587 {
588 Seq diffSeq;
589 std::tie(loc, diffSeq) = find(ledger);
590 // Check that ledger is a proper prefix of loc
591 if (!(diffSeq > ledger.seq() && ledger.seq() < loc->span.end()))
592 loc = nullptr;
593 }
594 return loc ? loc->branchSupport : 0;
595 }
596
656 [[nodiscard]] std::optional<SpanTip<Ledger>>
657 getPreferred(Seq const largestIssued) const
658 {
659 if (empty())
660 return std::nullopt;
661
662 Node* curr = root_.get();
663
664 bool done = false;
665
666 std::uint32_t uncommitted = 0;
667 auto uncommittedIt = seqSupport_.begin();
668
669 while (curr && !done)
670 {
671 // Within a single span, the preferred by branch strategy is simply
672 // to continue along the span as long as the branch support of
673 // the next ledger exceeds the uncommitted support for that ledger.
674 {
675 // Add any initial uncommitted support prior for ledgers
676 // earlier than nextSeq or earlier than largestIssued
677 Seq nextSeq = curr->span.start() + Seq{1};
678 while (uncommittedIt != seqSupport_.end() &&
679 uncommittedIt->first < std::max(nextSeq, largestIssued))
680 {
681 uncommitted += uncommittedIt->second;
682 uncommittedIt++;
683 }
684
685 // Advance nextSeq along the span
686 while (nextSeq < curr->span.end() && curr->branchSupport > uncommitted)
687 {
688 // Jump to the next seqSupport change
689 if (uncommittedIt != seqSupport_.end() &&
690 uncommittedIt->first < curr->span.end())
691 {
692 nextSeq = uncommittedIt->first + Seq{1};
693 uncommitted += uncommittedIt->second;
694 uncommittedIt++;
695 }
696 else
697 { // otherwise we jump to the end of the span
698 nextSeq = curr->span.end();
699 }
700 }
701 // We did not consume the entire span, so we have found the
702 // preferred ledger
703 if (nextSeq < curr->span.end())
704 {
705 // nextSeq within span guarantees before() is set
706 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
707 return curr->span.before(nextSeq)->tip();
708 }
709 }
710
711 // We have reached the end of the current span, so we need to
712 // find the best child
713 Node* best = nullptr;
714 std::uint32_t margin = 0;
715 if (curr->children.size() == 1)
716 {
717 best = curr->children[0].get();
718 margin = best->branchSupport;
719 }
720 else if (!curr->children.empty())
721 {
722 // Sort placing children with largest branch support in the
723 // front, breaking ties with the span's starting ID
725 curr->children.begin(),
726 curr->children.begin() + 2,
727 curr->children.end(),
728 [](std::unique_ptr<Node> const& a, std::unique_ptr<Node> const& b) {
729 return std::make_tuple(a->branchSupport, a->span.startID()) >
730 std::make_tuple(b->branchSupport, b->span.startID());
731 });
732
733 best = curr->children[0].get();
734 margin = curr->children[0]->branchSupport - curr->children[1]->branchSupport;
735
736 // If best holds the tie-breaker, gets one larger margin
737 // since the second best needs additional branchSupport
738 // to overcome the tie
739 if (best->span.startID() > curr->children[1]->span.startID())
740 margin++;
741 }
742
743 // If the best child has margin exceeding the uncommitted support,
744 // continue from that child, otherwise we are done
745 if (best && ((margin > uncommitted) || (uncommitted == 0)))
746 {
747 curr = best;
748 }
749 else
750 { // current is the best
751 done = true;
752 }
753 }
754 return curr->span.tip();
755 }
756
759 [[nodiscard]] bool
760 empty() const
761 {
762 return !root_ || root_->branchSupport == 0;
763 }
764
767 void
769 {
770 dumpImpl(o, root_, 0);
771 }
772
775 [[nodiscard]] json::Value
776 getJson() const
777 {
778 json::Value res;
779 res["trie"] = root_->getJson();
780 res["seq_support"] = json::ValueType::Object;
781 for (auto const& [seq, sup] : seqSupport_)
782 res["seq_support"][to_string(seq)] = sup;
783 return res;
784 }
785
788 [[nodiscard]] bool
790 {
791 std::map<Seq, std::uint32_t> expectedSeqSupport;
792
794 nodes.push(root_.get());
795 while (!nodes.empty())
796 {
797 Node const* curr = nodes.top();
798 nodes.pop();
799 if (curr == nullptr)
800 continue;
801
802 // Node with 0 tip support must have multiple children
803 // unless it is the root node
804 if (curr != root_.get() && curr->tipSupport == 0 && curr->children.size() < 2)
805 return false;
806
807 // branchSupport = tipSupport + sum(child->branchSupport)
808 std::size_t support = curr->tipSupport;
809 if (curr->tipSupport != 0)
810 expectedSeqSupport[curr->span.end() - Seq{1}] += curr->tipSupport;
811
812 for (auto const& child : curr->children)
813 {
814 if (child->parent != curr)
815 return false;
816
817 support += child->branchSupport;
818 nodes.push(child.get());
819 }
820 if (support != curr->branchSupport)
821 return false;
822 }
823 return expectedSeqSupport == seqSupport_;
824 }
825};
826
827} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
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:569
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:383
bool empty() const
Return whether the trie is tracking any ledgers.
Definition LedgerTrie.h:760
bool checkInvariants() const
Check the compressed trie and support invariants.
Definition LedgerTrie.h:789
Ledger::ID ID
Definition LedgerTrie.h:325
std::uint32_t branchSupport(Ledger const &ledger) const
Return the count of branch support for the specific ledger.
Definition LedgerTrie.h:583
ledger_trie_detail::Span< Ledger > Span
Definition LedgerTrie.h:328
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:344
Ledger::Seq Seq
Definition LedgerTrie.h:324
std::optional< SpanTip< Ledger > > getPreferred(Seq const largestIssued) const
Return the preferred ledger ID.
Definition LedgerTrie.h:657
void dumpImpl(std::ostream &o, std::unique_ptr< Node > const &curr, int offset) const
Definition LedgerTrie.h:399
json::Value getJson() const
Dump JSON representation of trie state.
Definition LedgerTrie.h:776
void dump(std::ostream &o) const
Dump an ascii representation of the trie to the stream.
Definition LedgerTrie.h:768
void insert(Ledger const &ledger, std::uint32_t count=1)
Insert and/or increment the support for the given ledger.
Definition LedgerTrie.h:425
std::unique_ptr< Node > root_
Definition LedgerTrie.h:332
std::map< Seq, std::uint32_t > seqSupport_
Definition LedgerTrie.h:335
bool remove(Ledger const &ledger, std::uint32_t count=1)
Decrease support for a ledger, removing and compressing if possible.
Definition LedgerTrie.h:511
ledger_trie_detail::Node< Ledger > Node
Definition LedgerTrie.h:327
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:97
The tip of a span of ledger ancestry.
Definition LedgerTrie.h:22
SpanTip(Seq s, ID i, Ledger const lgr)
Definition LedgerTrie.h:27
Ledger const ledger_
Definition LedgerTrie.h:52
Ledger::Seq Seq
Definition LedgerTrie.h:24
Ledger::ID ID
Definition LedgerTrie.h:25
ID ancestor(Seq const &s) const
Lookup the ID of an ancestor of the tip ledger.
Definition LedgerTrie.h:45
Span(Seq start, Seq end, Ledger l)
Definition LedgerTrie.h:137
Seq diff(Ledger const &o) const
Definition LedgerTrie.h:123
Span & operator=(Span const &)=default
std::optional< Span > before(Seq spot) const
Definition LedgerTrie.h:108
std::optional< Span > from(Seq spot) const
Definition LedgerTrie.h:101
SpanTip< Ledger > tip() const
Definition LedgerTrie.h:130
std::optional< Span > sub(Seq from, Seq to) const
Definition LedgerTrie.h:151
Span & operator=(Span &&)=default
friend Span merge(Span const &a, Span const &b)
Definition LedgerTrie.h:167
friend std::ostream & operator<<(std::ostream &o, Span const &s)
Definition LedgerTrie.h:161
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:25
@ Object
object value (collection of name/value pairs).
Definition json_value.h:26
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:633
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:215
void erase(Node const *child)
Remove the given node from this Node's children.
Definition LedgerTrie.h:205
std::vector< std::unique_ptr< Node > > children
Definition LedgerTrie.h:195
json::Value getJson() const
Definition LedgerTrie.h:221
T swap(T... args)
T tie(T... args)
T top(T... args)