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