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) : start_{0}, 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 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 newFrom = clamp(from);
153 Seq 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(children.begin(), children.end(), [child](std::unique_ptr<Node> const& curr) {
207 return curr.get() == child;
208 });
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
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::arrayValue);
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 = typename Ledger::Seq;
325 using ID = typename 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
344 find(Ledger const& ledger) const
345 {
346 Node* curr = root.get();
347
348 // Root is always defined and is in common with all ledgers
349 XRPL_ASSERT(curr, "xrpl::LedgerTrie::find : non-null root");
350 Seq pos = curr->span.diff(ledger);
351
352 bool done = false;
353
354 // Continue searching for a better span as long as the current position
355 // matches the entire span
356 while (!done && pos == curr->span.end())
357 {
358 done = true;
359 // Find the child with the longest ancestry match
360 for (std::unique_ptr<Node> const& child : curr->children)
361 {
362 auto const childPos = child->span.diff(ledger);
363 if (childPos > pos)
364 {
365 done = false;
366 pos = childPos;
367 curr = child.get();
368 break;
369 }
370 }
371 }
372 return std::make_pair(curr, pos);
373 }
374
381 Node*
382 findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const
383 {
384 if (!parent)
385 parent = root.get();
386 if (ledger.id() == parent->span.tip().id)
387 return parent;
388 for (auto const& child : parent->children)
389 {
390 auto cl = findByLedgerID(ledger, child.get());
391 if (cl)
392 return cl;
393 }
394 return nullptr;
395 }
396
397 void
398 dumpImpl(std::ostream& o, std::unique_ptr<Node> const& curr, int offset) const
399 {
400 if (curr)
401 {
402 if (offset > 0)
403 o << std::setw(offset) << "|-";
404
406 ss << *curr;
407 o << ss.str() << std::endl;
408 for (std::unique_ptr<Node> const& child : curr->children)
409 dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
410 }
411 }
412
413public:
414 LedgerTrie() : root{std::make_unique<Node>()}
415 {
416 }
417
423 void
424 insert(Ledger const& ledger, std::uint32_t count = 1)
425 {
426 auto const [loc, diffSeq] = find(ledger);
427
428 // There is always a place to insert
429 XRPL_ASSERT(loc, "xrpl::LedgerTrie::insert : valid input ledger");
430
431 // Node from which to start incrementing branchSupport
432 Node* incNode = loc;
433
434 // loc->span has the longest common prefix with Span{ledger} of all
435 // existing nodes in the trie. The optional<Span>'s below represent
436 // the possible common suffixes between loc->span and Span{ledger}.
437 //
438 // loc->span
439 // a b c | d e f
440 // prefix | oldSuffix
441 //
442 // Span{ledger}
443 // a b c | g h i
444 // prefix | newSuffix
445
446 std::optional<Span> prefix = loc->span.before(diffSeq);
447 std::optional<Span> oldSuffix = loc->span.from(diffSeq);
448 std::optional<Span> newSuffix = Span{ledger}.from(diffSeq);
449
450 if (oldSuffix)
451 {
452 // Have
453 // abcdef -> ....
454 // Inserting
455 // abc
456 // Becomes
457 // abc -> def -> ...
458
459 // Create oldSuffix node that takes over loc
460 auto newNode = std::make_unique<Node>(*oldSuffix);
461 newNode->tipSupport = loc->tipSupport;
462 newNode->branchSupport = loc->branchSupport;
463 newNode->children = std::move(loc->children);
464 XRPL_ASSERT(loc->children.empty(), "xrpl::LedgerTrie::insert : moved-from children");
465 for (std::unique_ptr<Node>& child : newNode->children)
466 child->parent = newNode.get();
467
468 // Loc truncates to prefix and newNode is its child
469 XRPL_ASSERT(prefix, "xrpl::LedgerTrie::insert : prefix is set");
470 loc->span = *prefix;
471 newNode->parent = loc;
472 loc->children.emplace_back(std::move(newNode));
473 loc->tipSupport = 0;
474 }
475 if (newSuffix)
476 {
477 // Have
478 // abc -> ...
479 // Inserting
480 // abcdef-> ...
481 // Becomes
482 // abc -> ...
483 // \-> def
484
485 auto newNode = std::make_unique<Node>(*newSuffix);
486 newNode->parent = loc;
487 // increment support starting from the new node
488 incNode = newNode.get();
489 loc->children.push_back(std::move(newNode));
490 }
491
492 incNode->tipSupport += count;
493 while (incNode)
494 {
495 incNode->branchSupport += count;
496 incNode = incNode->parent;
497 }
498
499 seqSupport[ledger.seq()] += count;
500 }
501
509 bool
510 remove(Ledger const& ledger, std::uint32_t count = 1)
511 {
512 Node* loc = findByLedgerID(ledger);
513 // Must be exact match with tip support
514 if (!loc || loc->tipSupport == 0)
515 return false;
516
517 // found our node, remove it
518 count = std::min(count, loc->tipSupport);
519 loc->tipSupport -= count;
520
521 auto const it = seqSupport.find(ledger.seq());
522 XRPL_ASSERT(it != seqSupport.end() && it->second >= count, "xrpl::LedgerTrie::remove : valid input ledger");
523 it->second -= count;
524 if (it->second == 0)
525 seqSupport.erase(it->first);
526
527 Node* decNode = loc;
528 while (decNode)
529 {
530 decNode->branchSupport -= count;
531 decNode = decNode->parent;
532 }
533
534 while (loc->tipSupport == 0 && loc != root.get())
535 {
536 Node* parent = loc->parent;
537 if (loc->children.empty())
538 {
539 // this node can be erased
540 parent->erase(loc);
541 }
542 else if (loc->children.size() == 1)
543 {
544 // This node can be combined with its child
545 std::unique_ptr<Node> child = std::move(loc->children.front());
546 child->span = merge(loc->span, child->span);
547 child->parent = parent;
548 parent->children.emplace_back(std::move(child));
549 parent->erase(loc);
550 }
551 else
552 break;
553 loc = parent;
554 }
555 return true;
556 }
557
564 tipSupport(Ledger const& ledger) const
565 {
566 if (auto const* loc = findByLedgerID(ledger))
567 return loc->tipSupport;
568 return 0;
569 }
570
578 branchSupport(Ledger const& ledger) const
579 {
580 Node const* loc = findByLedgerID(ledger);
581 if (!loc)
582 {
583 Seq diffSeq;
584 std::tie(loc, diffSeq) = find(ledger);
585 // Check that ledger is a proper prefix of loc
586 if (!(diffSeq > ledger.seq() && ledger.seq() < loc->span.end()))
587 loc = nullptr;
588 }
589 return loc ? loc->branchSupport : 0;
590 }
591
652 getPreferred(Seq const largestIssued) const
653 {
654 if (empty())
655 return std::nullopt;
656
657 Node* curr = root.get();
658
659 bool done = false;
660
661 std::uint32_t uncommitted = 0;
662 auto uncommittedIt = seqSupport.begin();
663
664 while (curr && !done)
665 {
666 // Within a single span, the preferred by branch strategy is simply
667 // to continue along the span as long as the branch support of
668 // the next ledger exceeds the uncommitted support for that ledger.
669 {
670 // Add any initial uncommitted support prior for ledgers
671 // earlier than nextSeq or earlier than largestIssued
672 Seq nextSeq = curr->span.start() + Seq{1};
673 while (uncommittedIt != seqSupport.end() && uncommittedIt->first < std::max(nextSeq, largestIssued))
674 {
675 uncommitted += uncommittedIt->second;
676 uncommittedIt++;
677 }
678
679 // Advance nextSeq along the span
680 while (nextSeq < curr->span.end() && curr->branchSupport > uncommitted)
681 {
682 // Jump to the next seqSupport change
683 if (uncommittedIt != seqSupport.end() && uncommittedIt->first < curr->span.end())
684 {
685 nextSeq = uncommittedIt->first + Seq{1};
686 uncommitted += uncommittedIt->second;
687 uncommittedIt++;
688 }
689 else // otherwise we jump to the end of the span
690 nextSeq = curr->span.end();
691 }
692 // We did not consume the entire span, so we have found the
693 // preferred ledger
694 if (nextSeq < curr->span.end())
695 return curr->span.before(nextSeq)->tip();
696 }
697
698 // We have reached the end of the current span, so we need to
699 // find the best child
700 Node* best = nullptr;
701 std::uint32_t margin = 0;
702 if (curr->children.size() == 1)
703 {
704 best = curr->children[0].get();
705 margin = best->branchSupport;
706 }
707 else if (!curr->children.empty())
708 {
709 // Sort placing children with largest branch support in the
710 // front, breaking ties with the span's starting ID
712 curr->children.begin(),
713 curr->children.begin() + 2,
714 curr->children.end(),
715 [](std::unique_ptr<Node> const& a, std::unique_ptr<Node> const& b) {
716 return std::make_tuple(a->branchSupport, a->span.startID()) >
717 std::make_tuple(b->branchSupport, b->span.startID());
718 });
719
720 best = curr->children[0].get();
721 margin = curr->children[0]->branchSupport - curr->children[1]->branchSupport;
722
723 // If best holds the tie-breaker, gets one larger margin
724 // since the second best needs additional branchSupport
725 // to overcome the tie
726 if (best->span.startID() > curr->children[1]->span.startID())
727 margin++;
728 }
729
730 // If the best child has margin exceeding the uncommitted support,
731 // continue from that child, otherwise we are done
732 if (best && ((margin > uncommitted) || (uncommitted == 0)))
733 curr = best;
734 else // current is the best
735 done = true;
736 }
737 return curr->span.tip();
738 }
739
742 bool
743 empty() const
744 {
745 return !root || root->branchSupport == 0;
746 }
747
750 void
752 {
753 dumpImpl(o, root, 0);
754 }
755
759 getJson() const
760 {
761 Json::Value res;
762 res["trie"] = root->getJson();
763 res["seq_support"] = Json::objectValue;
764 for (auto const& [seq, sup] : seqSupport)
765 res["seq_support"][to_string(seq)] = sup;
766 return res;
767 }
768
771 bool
773 {
774 std::map<Seq, std::uint32_t> expectedSeqSupport;
775
777 nodes.push(root.get());
778 while (!nodes.empty())
779 {
780 Node const* curr = nodes.top();
781 nodes.pop();
782 if (!curr)
783 continue;
784
785 // Node with 0 tip support must have multiple children
786 // unless it is the root node
787 if (curr != root.get() && curr->tipSupport == 0 && curr->children.size() < 2)
788 return false;
789
790 // branchSupport = tipSupport + sum(child->branchSupport)
791 std::size_t support = curr->tipSupport;
792 if (curr->tipSupport != 0)
793 expectedSeqSupport[curr->span.end() - Seq{1}] += curr->tipSupport;
794
795 for (auto const& child : curr->children)
796 {
797 if (child->parent != curr)
798 return false;
799
800 support += child->branchSupport;
801 nodes.push(child.get());
802 }
803 if (support != curr->branchSupport)
804 return false;
805 }
806 return expectedSeqSupport == seqSupport;
807 }
808};
809
810} // 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:323
std::uint32_t tipSupport(Ledger const &ledger) const
Return count of tip support for the specific ledger.
Definition LedgerTrie.h:564
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:382
bool empty() const
Return whether the trie is tracking any ledgers.
Definition LedgerTrie.h:743
bool checkInvariants() const
Check the compressed trie and support invariants.
Definition LedgerTrie.h:772
std::unique_ptr< Node > root
Definition LedgerTrie.h:332
std::uint32_t branchSupport(Ledger const &ledger) const
Return the count of branch support for the specific ledger.
Definition LedgerTrie.h:578
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
std::optional< SpanTip< Ledger > > getPreferred(Seq const largestIssued) const
Return the preferred ledger ID.
Definition LedgerTrie.h:652
void dumpImpl(std::ostream &o, std::unique_ptr< Node > const &curr, int offset) const
Definition LedgerTrie.h:398
void dump(std::ostream &o) const
Dump an ascii representation of the trie to the stream.
Definition LedgerTrie.h:751
void insert(Ledger const &ledger, std::uint32_t count=1)
Insert and/or increment the support for the given ledger.
Definition LedgerTrie.h:424
Json::Value getJson() const
Dump JSON representation of trie state.
Definition LedgerTrie.h:759
typename Ledger::ID ID
Definition LedgerTrie.h:325
typename Ledger::Seq Seq
Definition LedgerTrie.h:324
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:510
ledger_trie_detail::Node< Ledger > Node
Definition LedgerTrie.h:327
Holds a ledger.
Definition Ledger.h:60
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:597
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:204
std::vector< std::unique_ptr< Node > > children
Definition LedgerTrie.h:194
Json::Value getJson() const
Definition LedgerTrie.h:221
T swap(T... args)
T tie(T... args)
T top(T... args)