rippled
Loading...
Searching...
No Matches
LedgerTrie.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED
21#define RIPPLE_APP_CONSENSUS_LEDGERS_TRIE_H_INCLUDED
22
23#include <xrpl/basics/ToString.h>
24#include <xrpl/beast/utility/instrumentation.h>
25#include <xrpl/json/json_value.h>
26
27#include <algorithm>
28#include <iomanip>
29#include <memory>
30#include <optional>
31#include <sstream>
32#include <stack>
33#include <vector>
34
35namespace ripple {
36
39template <class Ledger>
41{
42public:
43 using Seq = typename Ledger::Seq;
44 using ID = typename Ledger::ID;
45
46 SpanTip(Seq s, ID i, Ledger const lgr)
47 : seq{s}, id{i}, ledger{std::move(lgr)}
48 {
49 }
50
51 // The sequence number of the tip ledger
53 // The ID of the tip ledger
55
64 ID
65 ancestor(Seq const& s) const
66 {
67 XRPL_ASSERT(s <= seq, "ripple::SpanTip::ancestor : valid input");
68 return ledger[s];
69 }
70
71private:
73};
74
75namespace ledger_trie_detail {
76
77// Represents a span of ancestry of a ledger
78template <class Ledger>
79class Span
80{
81 using Seq = typename Ledger::Seq;
82 using ID = typename Ledger::ID;
83
84 // The span is the half-open interval [start,end) of ledger_
88
89public:
90 Span() : ledger_{typename Ledger::MakeGenesis{}}
91 {
92 // Require default ledger to be genesis seq
93 XRPL_ASSERT(
94 ledger_.seq() == start_, "ripple::Span::Span : ledger is genesis");
95 }
96
97 Span(Ledger ledger)
98 : start_{0}, end_{ledger.seq() + Seq{1}}, ledger_{std::move(ledger)}
99 {
100 }
101
102 Span(Span const& s) = default;
103 Span(Span&& s) = default;
104 Span&
105 operator=(Span const&) = default;
106 Span&
107 operator=(Span&&) = default;
108
109 Seq
110 start() const
111 {
112 return start_;
113 }
114
115 Seq
116 end() const
117 {
118 return end_;
119 }
120
121 // Return the Span from [spot,end_) or none if no such valid span
123 from(Seq spot) const
124 {
125 return sub(spot, end_);
126 }
127
128 // Return the Span from [start_,spot) or none if no such valid span
130 before(Seq spot) const
131 {
132 return sub(start_, spot);
133 }
134
135 // Return the ID of the ledger that starts this span
136 ID
137 startID() const
138 {
139 return ledger_[start_];
140 }
141
142 // Return the ledger sequence number of the first possible difference
143 // between this span and a given ledger.
144 Seq
145 diff(Ledger const& o) const
146 {
147 return clamp(mismatch(ledger_, o));
148 }
149
150 // The tip of this span
152 tip() const
153 {
154 Seq tipSeq{end_ - Seq{1}};
155 return SpanTip<Ledger>{tipSeq, ledger_[tipSeq], ledger_};
156 }
157
158private:
160 : start_{start}, end_{end}, ledger_{l}
161 {
162 // Spans cannot be empty
163 XRPL_ASSERT(start < end, "ripple::Span::Span : non-empty span input");
164 }
165
166 Seq
167 clamp(Seq val) const
168 {
169 return std::min(std::max(start_, val), end_);
170 }
171
172 // Return a span of this over the half-open interval [from,to)
174 sub(Seq from, Seq to) const
175 {
176 Seq newFrom = clamp(from);
177 Seq newTo = clamp(to);
178 if (newFrom < newTo)
179 return Span(newFrom, newTo, ledger_);
180 return std::nullopt;
181 }
182
184 operator<<(std::ostream& o, Span const& s)
185 {
186 return o << s.tip().id << "[" << s.start_ << "," << s.end_ << ")";
187 }
188
189 friend Span
190 merge(Span const& a, Span const& b)
191 {
192 // Return combined span, using ledger_ from higher sequence span
193 if (a.end_ < b.end_)
194 return Span(std::min(a.start_, b.start_), b.end_, b.ledger_);
195
196 return Span(std::min(a.start_, b.start_), a.end_, a.ledger_);
197 }
198};
199
200// A node in the trie
201template <class Ledger>
202struct Node
203{
204 Node() = default;
205
206 explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1}
207 {
208 }
209
210 explicit Node(Span<Ledger> s) : span{std::move(s)}
211 {
212 }
213
217
219 Node* parent = nullptr;
220
227 void
228 erase(Node const* child)
229 {
230 auto it = std::find_if(
231 children.begin(),
232 children.end(),
233 [child](std::unique_ptr<Node> const& curr) {
234 return curr.get() == child;
235 });
236 XRPL_ASSERT(it != children.end(), "ripple::Node::erase : valid input");
237 std::swap(*it, children.back());
238 children.pop_back();
239 }
240
242 operator<<(std::ostream& o, Node const& s)
243 {
244 return o << s.span << "(T:" << s.tipSupport << ",B:" << s.branchSupport
245 << ")";
246 }
247
249 getJson() const
250 {
251 Json::Value res;
253 sps << span;
254 res["span"] = sps.str();
255 res["startID"] = to_string(span.startID());
256 res["seq"] = static_cast<std::uint32_t>(span.tip().seq);
257 res["tipSupport"] = tipSupport;
258 res["branchSupport"] = branchSupport;
259 if (!children.empty())
260 {
261 Json::Value& cs = (res["children"] = Json::arrayValue);
262 for (auto const& child : children)
263 {
264 cs.append(child->getJson());
265 }
266 }
267 return res;
268 }
269};
270} // namespace ledger_trie_detail
271
349template <class Ledger>
351{
352 using Seq = typename Ledger::Seq;
353 using ID = typename Ledger::ID;
354
357
358 // The root of the trie. The root is allowed to break the no-single child
359 // invariant.
361
362 // Count of the tip support for each sequence number
364
372 find(Ledger const& ledger) const
373 {
374 Node* curr = root.get();
375
376 // Root is always defined and is in common with all ledgers
377 XRPL_ASSERT(curr, "ripple::LedgerTrie::find : non-null root");
378 Seq pos = curr->span.diff(ledger);
379
380 bool done = false;
381
382 // Continue searching for a better span as long as the current position
383 // matches the entire span
384 while (!done && pos == curr->span.end())
385 {
386 done = true;
387 // Find the child with the longest ancestry match
388 for (std::unique_ptr<Node> const& child : curr->children)
389 {
390 auto const childPos = child->span.diff(ledger);
391 if (childPos > pos)
392 {
393 done = false;
394 pos = childPos;
395 curr = child.get();
396 break;
397 }
398 }
399 }
400 return std::make_pair(curr, pos);
401 }
402
409 Node*
410 findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const
411 {
412 if (!parent)
413 parent = root.get();
414 if (ledger.id() == parent->span.tip().id)
415 return parent;
416 for (auto const& child : parent->children)
417 {
418 auto cl = findByLedgerID(ledger, child.get());
419 if (cl)
420 return cl;
421 }
422 return nullptr;
423 }
424
425 void
426 dumpImpl(std::ostream& o, std::unique_ptr<Node> const& curr, int offset)
427 const
428 {
429 if (curr)
430 {
431 if (offset > 0)
432 o << std::setw(offset) << "|-";
433
435 ss << *curr;
436 o << ss.str() << std::endl;
437 for (std::unique_ptr<Node> const& child : curr->children)
438 dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
439 }
440 }
441
442public:
443 LedgerTrie() : root{std::make_unique<Node>()}
444 {
445 }
446
452 void
453 insert(Ledger const& ledger, std::uint32_t count = 1)
454 {
455 auto const [loc, diffSeq] = find(ledger);
456
457 // There is always a place to insert
458 XRPL_ASSERT(loc, "ripple::LedgerTrie::insert : valid input ledger");
459
460 // Node from which to start incrementing branchSupport
461 Node* incNode = loc;
462
463 // loc->span has the longest common prefix with Span{ledger} of all
464 // existing nodes in the trie. The optional<Span>'s below represent
465 // the possible common suffixes between loc->span and Span{ledger}.
466 //
467 // loc->span
468 // a b c | d e f
469 // prefix | oldSuffix
470 //
471 // Span{ledger}
472 // a b c | g h i
473 // prefix | newSuffix
474
475 std::optional<Span> prefix = loc->span.before(diffSeq);
476 std::optional<Span> oldSuffix = loc->span.from(diffSeq);
477 std::optional<Span> newSuffix = Span{ledger}.from(diffSeq);
478
479 if (oldSuffix)
480 {
481 // Have
482 // abcdef -> ....
483 // Inserting
484 // abc
485 // Becomes
486 // abc -> def -> ...
487
488 // Create oldSuffix node that takes over loc
489 auto newNode = std::make_unique<Node>(*oldSuffix);
490 newNode->tipSupport = loc->tipSupport;
491 newNode->branchSupport = loc->branchSupport;
492 newNode->children = std::move(loc->children);
493 XRPL_ASSERT(
494 loc->children.empty(),
495 "ripple::LedgerTrie::insert : moved-from children");
496 for (std::unique_ptr<Node>& child : newNode->children)
497 child->parent = newNode.get();
498
499 // Loc truncates to prefix and newNode is its child
500 XRPL_ASSERT(prefix, "ripple::LedgerTrie::insert : prefix is set");
501 loc->span = *prefix;
502 newNode->parent = loc;
503 loc->children.emplace_back(std::move(newNode));
504 loc->tipSupport = 0;
505 }
506 if (newSuffix)
507 {
508 // Have
509 // abc -> ...
510 // Inserting
511 // abcdef-> ...
512 // Becomes
513 // abc -> ...
514 // \-> def
515
516 auto newNode = std::make_unique<Node>(*newSuffix);
517 newNode->parent = loc;
518 // increment support starting from the new node
519 incNode = newNode.get();
520 loc->children.push_back(std::move(newNode));
521 }
522
523 incNode->tipSupport += count;
524 while (incNode)
525 {
526 incNode->branchSupport += count;
527 incNode = incNode->parent;
528 }
529
530 seqSupport[ledger.seq()] += count;
531 }
532
540 bool
541 remove(Ledger const& ledger, std::uint32_t count = 1)
542 {
543 Node* loc = findByLedgerID(ledger);
544 // Must be exact match with tip support
545 if (!loc || loc->tipSupport == 0)
546 return false;
547
548 // found our node, remove it
549 count = std::min(count, loc->tipSupport);
550 loc->tipSupport -= count;
551
552 auto const it = seqSupport.find(ledger.seq());
553 XRPL_ASSERT(
554 it != seqSupport.end() && it->second >= count,
555 "ripple::LedgerTrie::remove : valid input ledger");
556 it->second -= count;
557 if (it->second == 0)
558 seqSupport.erase(it->first);
559
560 Node* decNode = loc;
561 while (decNode)
562 {
563 decNode->branchSupport -= count;
564 decNode = decNode->parent;
565 }
566
567 while (loc->tipSupport == 0 && loc != root.get())
568 {
569 Node* parent = loc->parent;
570 if (loc->children.empty())
571 {
572 // this node can be erased
573 parent->erase(loc);
574 }
575 else if (loc->children.size() == 1)
576 {
577 // This node can be combined with its child
578 std::unique_ptr<Node> child = std::move(loc->children.front());
579 child->span = merge(loc->span, child->span);
580 child->parent = parent;
581 parent->children.emplace_back(std::move(child));
582 parent->erase(loc);
583 }
584 else
585 break;
586 loc = parent;
587 }
588 return true;
589 }
590
597 tipSupport(Ledger const& ledger) const
598 {
599 if (auto const* loc = findByLedgerID(ledger))
600 return loc->tipSupport;
601 return 0;
602 }
603
611 branchSupport(Ledger const& ledger) const
612 {
613 Node const* loc = findByLedgerID(ledger);
614 if (!loc)
615 {
616 Seq diffSeq;
617 std::tie(loc, diffSeq) = find(ledger);
618 // Check that ledger is a proper prefix of loc
619 if (!(diffSeq > ledger.seq() && ledger.seq() < loc->span.end()))
620 loc = nullptr;
621 }
622 return loc ? loc->branchSupport : 0;
623 }
624
685 getPreferred(Seq const largestIssued) const
686 {
687 if (empty())
688 return std::nullopt;
689
690 Node* curr = root.get();
691
692 bool done = false;
693
694 std::uint32_t uncommitted = 0;
695 auto uncommittedIt = seqSupport.begin();
696
697 while (curr && !done)
698 {
699 // Within a single span, the preferred by branch strategy is simply
700 // to continue along the span as long as the branch support of
701 // the next ledger exceeds the uncommitted support for that ledger.
702 {
703 // Add any initial uncommitted support prior for ledgers
704 // earlier than nextSeq or earlier than largestIssued
705 Seq nextSeq = curr->span.start() + Seq{1};
706 while (uncommittedIt != seqSupport.end() &&
707 uncommittedIt->first < std::max(nextSeq, largestIssued))
708 {
709 uncommitted += uncommittedIt->second;
710 uncommittedIt++;
711 }
712
713 // Advance nextSeq along the span
714 while (nextSeq < curr->span.end() &&
715 curr->branchSupport > uncommitted)
716 {
717 // Jump to the next seqSupport change
718 if (uncommittedIt != seqSupport.end() &&
719 uncommittedIt->first < curr->span.end())
720 {
721 nextSeq = uncommittedIt->first + Seq{1};
722 uncommitted += uncommittedIt->second;
723 uncommittedIt++;
724 }
725 else // otherwise we jump to the end of the span
726 nextSeq = curr->span.end();
727 }
728 // We did not consume the entire span, so we have found the
729 // preferred ledger
730 if (nextSeq < curr->span.end())
731 return curr->span.before(nextSeq)->tip();
732 }
733
734 // We have reached the end of the current span, so we need to
735 // find the best child
736 Node* best = nullptr;
737 std::uint32_t margin = 0;
738 if (curr->children.size() == 1)
739 {
740 best = curr->children[0].get();
741 margin = best->branchSupport;
742 }
743 else if (!curr->children.empty())
744 {
745 // Sort placing children with largest branch support in the
746 // front, breaking ties with the span's starting ID
748 curr->children.begin(),
749 curr->children.begin() + 2,
750 curr->children.end(),
751 [](std::unique_ptr<Node> const& a,
752 std::unique_ptr<Node> const& b) {
753 return std::make_tuple(
754 a->branchSupport, a->span.startID()) >
755 std::make_tuple(
756 b->branchSupport, b->span.startID());
757 });
758
759 best = curr->children[0].get();
760 margin = curr->children[0]->branchSupport -
761 curr->children[1]->branchSupport;
762
763 // If best holds the tie-breaker, gets one larger margin
764 // since the second best needs additional branchSupport
765 // to overcome the tie
766 if (best->span.startID() > curr->children[1]->span.startID())
767 margin++;
768 }
769
770 // If the best child has margin exceeding the uncommitted support,
771 // continue from that child, otherwise we are done
772 if (best && ((margin > uncommitted) || (uncommitted == 0)))
773 curr = best;
774 else // current is the best
775 done = true;
776 }
777 return curr->span.tip();
778 }
779
782 bool
783 empty() const
784 {
785 return !root || root->branchSupport == 0;
786 }
787
790 void
792 {
793 dumpImpl(o, root, 0);
794 }
795
799 getJson() const
800 {
801 Json::Value res;
802 res["trie"] = root->getJson();
803 res["seq_support"] = Json::objectValue;
804 for (auto const& [seq, sup] : seqSupport)
805 res["seq_support"][to_string(seq)] = sup;
806 return res;
807 }
808
811 bool
813 {
814 std::map<Seq, std::uint32_t> expectedSeqSupport;
815
817 nodes.push(root.get());
818 while (!nodes.empty())
819 {
820 Node const* curr = nodes.top();
821 nodes.pop();
822 if (!curr)
823 continue;
824
825 // Node with 0 tip support must have multiple children
826 // unless it is the root node
827 if (curr != root.get() && curr->tipSupport == 0 &&
828 curr->children.size() < 2)
829 return false;
830
831 // branchSupport = tipSupport + sum(child->branchSupport)
832 std::size_t support = curr->tipSupport;
833 if (curr->tipSupport != 0)
834 expectedSeqSupport[curr->span.end() - Seq{1}] +=
835 curr->tipSupport;
836
837 for (auto const& child : curr->children)
838 {
839 if (child->parent != curr)
840 return false;
841
842 support += child->branchSupport;
843 nodes.push(child.get());
844 }
845 if (support != curr->branchSupport)
846 return false;
847 }
848 return expectedSeqSupport == seqSupport;
849 }
850};
851
852} // namespace ripple
853#endif
T begin(T... args)
Represents a JSON value.
Definition json_value.h:149
Value & append(Value const &value)
Append value to array at the end.
Ancestry trie of ledgers.
Definition LedgerTrie.h:351
void dumpImpl(std::ostream &o, std::unique_ptr< Node > const &curr, int offset) const
Definition LedgerTrie.h:426
bool empty() const
Return whether the trie is tracking any ledgers.
Definition LedgerTrie.h:783
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:410
std::unique_ptr< Node > root
Definition LedgerTrie.h:360
Json::Value getJson() const
Dump JSON representation of trie state.
Definition LedgerTrie.h:799
std::uint32_t tipSupport(Ledger const &ledger) const
Return count of tip support for the specific ledger.
Definition LedgerTrie.h:597
void insert(Ledger const &ledger, std::uint32_t count=1)
Insert and/or increment the support for the given ledger.
Definition LedgerTrie.h:453
std::map< Seq, std::uint32_t > seqSupport
Definition LedgerTrie.h:363
std::optional< SpanTip< Ledger > > getPreferred(Seq const largestIssued) const
Return the preferred ledger ID.
Definition LedgerTrie.h:685
std::uint32_t branchSupport(Ledger const &ledger) const
Return the count of branch support for the specific ledger.
Definition LedgerTrie.h:611
typename Ledger::ID ID
Definition LedgerTrie.h:353
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:372
typename Ledger::Seq Seq
Definition LedgerTrie.h:352
bool remove(Ledger const &ledger, std::uint32_t count=1)
Decrease support for a ledger, removing and compressing if possible.
Definition LedgerTrie.h:541
ledger_trie_detail::Node< Ledger > Node
Definition LedgerTrie.h:355
bool checkInvariants() const
Check the compressed trie and support invariants.
Definition LedgerTrie.h:812
void dump(std::ostream &o) const
Dump an ascii representation of the trie to the stream.
Definition LedgerTrie.h:791
Holds a ledger.
Definition Ledger.h:80
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:118
The tip of a span of ledger ancestry.
Definition LedgerTrie.h:41
Ledger const ledger
Definition LedgerTrie.h:72
typename Ledger::ID ID
Definition LedgerTrie.h:44
ID ancestor(Seq const &s) const
Lookup the ID of an ancestor of the tip ledger.
Definition LedgerTrie.h:65
SpanTip(Seq s, ID i, Ledger const lgr)
Definition LedgerTrie.h:46
typename Ledger::Seq Seq
Definition LedgerTrie.h:43
std::optional< Span > before(Seq spot) const
Definition LedgerTrie.h:130
Span & operator=(Span &&)=default
SpanTip< Ledger > tip() const
Definition LedgerTrie.h:152
Span & operator=(Span const &)=default
Span(Seq start, Seq end, Ledger const &l)
Definition LedgerTrie.h:159
std::optional< Span > sub(Seq from, Seq to) const
Definition LedgerTrie.h:174
std::optional< Span > from(Seq spot) const
Definition LedgerTrie.h:123
Seq diff(Ledger const &o) const
Definition LedgerTrie.h:145
friend Span merge(Span const &a, Span const &b)
Definition LedgerTrie.h:190
friend std::ostream & operator<<(std::ostream &o, Span const &s)
Definition LedgerTrie.h:184
typename Ledger::Seq Seq
Definition LedgerTrie.h:81
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:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
RCLValidatedLedger::Seq mismatch(RCLValidatedLedger const &a, RCLValidatedLedger const &b)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
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:228
friend std::ostream & operator<<(std::ostream &o, Node const &s)
Definition LedgerTrie.h:242
Json::Value getJson() const
Definition LedgerTrie.h:249
std::vector< std::unique_ptr< Node > > children
Definition LedgerTrie.h:218
T swap(T... args)
T tie(T... args)
T top(T... args)