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