xrpld
Loading...
Searching...
No Matches
InboundLedger.cpp
1#include <xrpld/app/ledger/InboundLedger.h>
2
3#include <xrpld/app/ledger/AccountStateSF.h>
4#include <xrpld/app/ledger/InboundLedgers.h>
5#include <xrpld/app/ledger/LedgerMaster.h>
6#include <xrpld/app/ledger/LedgerNodeHelpers.h>
7#include <xrpld/app/ledger/TransactionStateSF.h>
8#include <xrpld/app/ledger/detail/TimeoutCounter.h>
9#include <xrpld/app/main/Application.h>
10#include <xrpld/overlay/Message.h>
11#include <xrpld/overlay/Overlay.h>
12#include <xrpld/overlay/PeerSet.h>
13
14#include <xrpl/basics/Blob.h>
15#include <xrpl/basics/Log.h>
16#include <xrpl/basics/Slice.h>
17#include <xrpl/basics/base_uint.h>
18#include <xrpl/beast/utility/instrumentation.h>
19#include <xrpl/core/Job.h>
20#include <xrpl/core/JobQueue.h>
21#include <xrpl/json/json_value.h>
22#include <xrpl/nodestore/Database.h>
23#include <xrpl/nodestore/NodeObject.h>
24#include <xrpl/protocol/HashPrefix.h>
25#include <xrpl/protocol/Indexes.h> // IWYU pragma: keep
26#include <xrpl/protocol/LedgerHeader.h>
27#include <xrpl/protocol/Rules.h>
28#include <xrpl/protocol/Serializer.h>
29#include <xrpl/protocol/SystemParameters.h> // IWYU pragma: keep
30#include <xrpl/protocol/jss.h>
31#include <xrpl/resource/Fees.h>
32#include <xrpl/shamap/SHAMapNodeID.h>
33#include <xrpl/shamap/SHAMapSyncFilter.h>
34
35#include <boost/iterator/function_output_iterator.hpp>
36
37#include <xrpl.pb.h>
38
39#include <algorithm>
40#include <chrono>
41#include <cstddef>
42#include <cstdint>
43#include <exception>
44#include <memory>
45#include <mutex>
46#include <random>
47#include <sstream>
48#include <string>
49#include <string_view>
50#include <tuple>
51#include <unordered_map>
52#include <utility>
53#include <vector>
54
55namespace xrpl {
56
57using namespace std::chrono_literals;
58
59static constexpr auto kPeerCountStart = 5; // Number of peers to start with
60static constexpr auto kPeerCountAdd = 3; // Number of peers to add on a timeout
61static constexpr auto kLedgerTimeoutRetriesMax = 6; // how many timeouts before we give up
62static constexpr auto kLedgerBecomeAggressiveThreshold =
63 4; // how many timeouts before we get aggressive
64static constexpr auto kMissingNodesFind = 256; // Number of nodes to find initially
65static constexpr auto kReqNodesReply = 128; // Number of nodes to request for a reply
66static constexpr auto kReqNodes = 12; // Number of nodes to request blindly
67
68// millisecond for each ledger timeout
69constexpr auto kLedgerAcquireTimeout = 3000ms;
70
72 Application& app,
73 uint256 const& hash,
74 std::uint32_t seq,
75 Reason reason,
76 clock_type& clock,
79 app,
80 hash,
82 {.jobType = JtLedgerData, .jobName = "InboundLedger", .jobLimit = 5},
83 app.getJournal("InboundLedger"))
84 , clock_(clock)
85 , seq_(seq)
86 , reason_(reason)
87 , peerSet_(std::move(peerSet))
88{
89 JLOG(journal_.trace()) << "Acquiring ledger " << hash_;
90 touch();
91}
92
93void
95{
97 collectionLock.unlock();
98
99 tryDB(app_.getNodeFamily().db());
100 if (failed_)
101 return;
102
103 if (!complete_)
104 {
105 addPeers();
106 queueJob(sl);
107 return;
108 }
109
110 JLOG(journal_.debug()) << "Acquiring ledger we already have in "
111 << " local store. " << hash_;
112 XRPL_ASSERT(
113 ledger_->header().seq < kXrpLedgerEarliestFees || ledger_->read(keylet::feeSettings()),
114 "xrpl::InboundLedger::init : valid ledger fees");
115 ledger_->setImmutable();
116
118 return;
119
120 app_.getLedgerMaster().storeLedger(ledger_);
121
122 // Check if this could be a newer fully-validated ledger
124 app_.getLedgerMaster().checkAccept(ledger_);
125}
126
129{
130 auto const& peerIds = peerSet_->getPeerIds();
132 peerIds, [this](auto id) { return (app_.getOverlay().findPeerByShortID(id) != nullptr); });
133}
134
135void
137{
138 ScopedLockType const sl(mtx_);
139
140 // If we didn't know the sequence number, but now do, save it
141 if ((seq != 0) && (seq_ == 0))
142 seq_ = seq;
143
144 // Prevent this from being swept
145 touch();
146}
147
148bool
150{
151 ScopedLockType const sl(mtx_);
152 if (!isDone())
153 {
154 if (ledger_)
155 {
156 tryDB(ledger_->stateMap().family().db());
157 }
158 else
159 {
160 tryDB(app_.getNodeFamily().db());
161 }
162 if (failed_ || complete_)
163 {
164 done();
165 return true;
166 }
167 }
168 return false;
169}
170
172{
173 // Save any received AS data not processed. It could be useful
174 // for populating a different ledger
175 for (auto& entry : receivedData_)
176 {
177 if (entry.second->type() == protocol::liAS_NODE)
178 app_.getInboundLedgers().gotStaleData(entry.second);
179 }
180 if (!isDone())
181 {
182 JLOG(journal_.debug()) << "Acquire " << hash_ << " abort "
183 << ((timeouts_ == 0) ? std::string()
184 : (std::string("timeouts:") +
186 << stats_.get();
187 }
188}
189
191neededHashes(uint256 const& root, SHAMap& map, int max, SHAMapSyncFilter const* filter)
192{
194
195 if (!root.isZero())
196 {
197 if (map.getHash().isZero())
198 {
199 ret.push_back(root);
200 }
201 else
202 {
203 auto mn = map.getMissingNodes(max, filter);
204 ret.reserve(mn.size());
205 for (auto const& n : mn)
206 ret.push_back(n.second);
207 }
208 }
209
210 return ret;
211}
212
215{
216 return neededHashes(ledger_->header().txHash, ledger_->txMap(), max, filter);
217}
218
221{
222 return neededHashes(ledger_->header().accountHash, ledger_->stateMap(), max, filter);
223}
224
225// See how much of the ledger data is stored locally
226// Data found in a fetch pack will be stored
227void
229{
230 if (!haveHeader_)
231 {
232 auto makeLedger = [&, this](Blob const& data) {
233 JLOG(journal_.trace()) << "Ledger header found in fetch pack";
234 Rules const rules{app_.config().features};
236 deserializePrefixedHeader(makeSlice(data)), rules, app_.getNodeFamily());
237 if (ledger_->header().hash != hash_ || (seq_ != 0 && seq_ != ledger_->header().seq))
238 {
239 // We know for a fact the ledger can never be acquired
240 JLOG(journal_.warn())
241 << "hash " << hash_ << " seq " << std::to_string(seq_) << " cannot be a ledger";
242 ledger_.reset();
243 failed_ = true;
244 }
245 };
246
247 // Try to fetch the ledger header from the DB
248 if (auto nodeObject = srcDB.fetchNodeObject(hash_, seq_))
249 {
250 JLOG(journal_.trace()) << "Ledger header found in local store";
251
252 makeLedger(nodeObject->getData());
253 if (failed_)
254 return;
255
256 // Store the ledger header if the source and destination differ
257 auto& dstDB{ledger_->stateMap().family().db()};
258 if (std::addressof(dstDB) != std::addressof(srcDB))
259 {
260 Blob blob{nodeObject->getData()};
261 dstDB.store(NodeObjectType::Ledger, std::move(blob), hash_, ledger_->header().seq);
262 }
263 }
264 else
265 {
266 // Try to fetch the ledger header from a fetch pack
267 auto data = app_.getLedgerMaster().getFetchPack(hash_);
268 if (!data)
269 return;
270
271 JLOG(journal_.trace()) << "Ledger header found in fetch pack";
272
273 makeLedger(*data);
274 if (failed_)
275 return;
276
277 // Store the ledger header in the ledger's database
278 ledger_->stateMap().family().db().store(
279 NodeObjectType::Ledger, std::move(*data), hash_, ledger_->header().seq);
280 }
281
282 if (seq_ == 0)
283 seq_ = ledger_->header().seq;
284 ledger_->stateMap().setLedgerSeq(seq_);
285 ledger_->txMap().setLedgerSeq(seq_);
286 haveHeader_ = true;
287 }
288
290 {
291 if (ledger_->header().txHash.isZero())
292 {
293 JLOG(journal_.trace()) << "No TXNs to fetch";
294 haveTransactions_ = true;
295 }
296 else
297 {
298 TransactionStateSF filter(ledger_->txMap().family().db(), app_.getLedgerMaster());
299 if (ledger_->txMap().fetchRoot(SHAMapHash{ledger_->header().txHash}, &filter))
300 {
301 if (neededTxHashes(1, &filter).empty())
302 {
303 JLOG(journal_.trace()) << "Had full txn map locally";
304 haveTransactions_ = true;
305 }
306 }
307 }
308 }
309
310 if (!haveState_)
311 {
312 if (ledger_->header().accountHash.isZero())
313 {
314 JLOG(journal_.fatal()) << "We are acquiring a ledger with a zero account hash";
315 failed_ = true;
316 return;
317 }
318 AccountStateSF filter(ledger_->stateMap().family().db(), app_.getLedgerMaster());
319 if (ledger_->stateMap().fetchRoot(SHAMapHash{ledger_->header().accountHash}, &filter))
320 {
321 if (neededStateHashes(1, &filter).empty())
322 {
323 JLOG(journal_.trace()) << "Had full AS map locally";
324 haveState_ = true;
325 }
326 }
327 }
328
330 {
331 JLOG(journal_.debug()) << "Had everything locally";
332 complete_ = true;
333 XRPL_ASSERT(
334 ledger_->header().seq < kXrpLedgerEarliestFees || ledger_->read(keylet::feeSettings()),
335 "xrpl::InboundLedger::tryDB : valid ledger fees");
336 ledger_->setImmutable();
337 }
338}
339
343void
345{
346 recentNodes_.clear();
347
348 if (isDone())
349 {
350 JLOG(journal_.info()) << "Already done " << hash_;
351 return;
352 }
353
355 {
356 if (seq_ != 0)
357 {
358 JLOG(journal_.warn()) << timeouts_ << " timeouts for ledger " << seq_;
359 }
360 else
361 {
362 JLOG(journal_.warn()) << timeouts_ << " timeouts for ledger " << hash_;
363 }
364 failed_ = true;
365 done();
366 return;
367 }
368
369 if (!wasProgress)
370 {
371 checkLocal();
372
373 byHash_ = true;
374
375 std::size_t const pc = getPeerCount();
376 JLOG(journal_.debug()) << "No progress(" << pc << ") for ledger " << hash_;
377
378 // addPeers triggers if the reason is not HISTORY
379 // So if the reason IS HISTORY, need to trigger after we add
380 // otherwise, we need to trigger before we add
381 // so each peer gets triggered once
384 addPeers();
387 }
388}
389
393void
395{
396 peerSet_->addPeers(
398 [this](auto peer) { return peer->hasLedger(hash_, seq_); },
399 [this](auto peer) {
400 // For historical nodes, do not trigger too soon
401 // since a fetch pack is probably coming
404 });
405}
406
412
413void
415{
416 if (signaled_)
417 return;
418
419 signaled_ = true;
420 touch();
421
422 JLOG(journal_.debug()) << "Acquire " << hash_ << (failed_ ? " fail " : " ")
423 << ((timeouts_ == 0)
424 ? std::string()
425 : (std::string("timeouts:") + std::to_string(timeouts_) + " "))
426 << stats_.get();
427
428 XRPL_ASSERT(complete_ || failed_, "xrpl::InboundLedger::done : complete or failed");
429
430 if (complete_ && !failed_ && ledger_)
431 {
432 XRPL_ASSERT(
433 ledger_->header().seq < kXrpLedgerEarliestFees || ledger_->read(keylet::feeSettings()),
434 "xrpl::InboundLedger::done : valid ledger fees");
435 ledger_->setImmutable();
436 switch (reason_)
437 {
438 case Reason::HISTORY:
439 app_.getInboundLedgers().onLedgerFetched();
440 break;
441 default:
442 app_.getLedgerMaster().storeLedger(ledger_);
443 break;
444 }
445 }
446
447 // We hold the PeerSet lock, so must dispatch
448 app_.getJobQueue().addJob(JtLedgerData, "AcqDone", [self = shared_from_this()]() {
449 if (self->complete_ && !self->failed_)
450 {
451 self->app_.getLedgerMaster().checkAccept(self->getLedger());
452 self->app_.getLedgerMaster().tryAdvance();
453 }
454 else
455 {
456 self->app_.getInboundLedgers().logFailure(self->hash_, self->seq_);
457 }
458 });
459}
460
464void
466{
468
469 if (isDone())
470 {
471 JLOG(journal_.debug()) << "Trigger on ledger: " << hash_ << (complete_ ? " completed" : "")
472 << (failed_ ? " failed" : "");
473 return;
474 }
475
476 if (auto stream = journal_.debug())
477 {
479 ss << "Trigger acquiring ledger " << hash_;
480 if (peer)
481 ss << " from " << peer;
482
483 if (complete_ || failed_)
484 {
485 ss << " complete=" << complete_ << " failed=" << failed_;
486 }
487 else
488 {
489 ss << " header=" << haveHeader_ << " tx=" << haveTransactions_ << " as=" << haveState_;
490 }
491 stream << ss.str();
492 }
493
494 if (!haveHeader_)
495 {
496 tryDB(app_.getNodeFamily().db());
497 if (failed_)
498 {
499 JLOG(journal_.warn()) << " failed local for " << hash_;
500 return;
501 }
502 }
503
504 protocol::TMGetLedger tmGL;
505 tmGL.set_ledgerhash(hash_.begin(), hash_.size());
506
507 if (timeouts_ != 0)
508 {
509 // Be more aggressive if we've timed out at least once
510 tmGL.set_querytype(protocol::qtINDIRECT);
511
513 {
514 auto need = getNeededHashes();
515
516 if (!need.empty())
517 {
518 protocol::TMGetObjectByHash tmBH;
519 bool typeSet = false;
520 tmBH.set_query(true);
521 tmBH.set_ledgerhash(hash_.begin(), hash_.size());
522 for (auto const& p : need)
523 {
524 JLOG(journal_.debug()) << "Want: " << p.second;
525
526 if (!typeSet)
527 {
528 tmBH.set_type(p.first);
529 typeSet = true;
530 }
531
532 if (p.first == tmBH.type())
533 {
534 protocol::TMIndexedObject* io = tmBH.add_objects();
535 io->set_hash(p.second.begin(), p.second.size());
536 if (seq_ != 0)
537 io->set_ledgerseq(seq_);
538 }
539 }
540
541 auto packet = std::make_shared<Message>(tmBH, protocol::mtGET_OBJECTS);
542 auto const& peerIds = peerSet_->getPeerIds();
543 std::ranges::for_each(peerIds, [this, &packet](auto id) {
544 if (auto p = app_.getOverlay().findPeerByShortID(id))
545 {
546 byHash_ = false;
547 p->send(packet);
548 }
549 });
550 }
551 else
552 {
553 JLOG(journal_.info()) << "getNeededHashes says acquire is complete";
554 haveHeader_ = true;
555 haveTransactions_ = true;
556 haveState_ = true;
557 complete_ = true;
558 }
559 }
560 }
561
562 // We can't do much without the header data because we don't know the
563 // state or transaction root hashes.
564 if (!haveHeader_ && !failed_)
565 {
566 tmGL.set_itype(protocol::liBASE);
567 if (seq_ != 0)
568 tmGL.set_ledgerseq(seq_);
569 JLOG(journal_.trace()) << "Sending header request to "
570 << (peer ? "selected peer" : "all peers");
571 peerSet_->sendRequest(tmGL, peer);
572 return;
573 }
574
575 if (ledger_)
576 tmGL.set_ledgerseq(ledger_->header().seq);
577
578 if (reason != TriggerReason::Reply)
579 {
580 // If we're querying blind, don't query deep
581 tmGL.set_querydepth(0);
582 }
583 else if (peer && peer->isHighLatency())
584 {
585 // If the peer has high latency, query extra deep
586 tmGL.set_querydepth(2);
587 }
588 else
589 {
590 tmGL.set_querydepth(1);
591 }
592
593 // Get the state data first because it's the most likely to be useful
594 // if we wind up abandoning this fetch.
595 if (haveHeader_ && !haveState_ && !failed_)
596 {
597 XRPL_ASSERT(
598 ledger_,
599 "xrpl::InboundLedger::trigger : non-null ledger to read state "
600 "from");
601
602 if (!ledger_->stateMap().isValid())
603 {
604 failed_ = true;
605 }
606 else if (ledger_->stateMap().getHash().isZero())
607 {
608 // we need the root node
609 tmGL.set_itype(protocol::liAS_NODE);
610 *tmGL.add_nodeids() = SHAMapNodeID().getRawString();
611 JLOG(journal_.trace())
612 << "Sending AS root request to " << (peer ? "selected peer" : "all peers");
613 peerSet_->sendRequest(tmGL, peer);
614 return;
615 }
616 else
617 {
618 AccountStateSF filter(ledger_->stateMap().family().db(), app_.getLedgerMaster());
619
620 // Release the lock while we process the large state map
621 sl.unlock();
622 auto nodes = ledger_->stateMap().getMissingNodes(kMissingNodesFind, &filter);
623 sl.lock();
624
625 // Make sure nothing happened while we released the lock
626 if (!failed_ && !complete_ && !haveState_)
627 {
628 if (nodes.empty())
629 {
630 if (!ledger_->stateMap().isValid())
631 {
632 failed_ = true;
633 }
634 else
635 {
636 haveState_ = true;
637
639 complete_ = true;
640 }
641 }
642 else
643 {
644 filterNodes(nodes, reason);
645
646 if (!nodes.empty())
647 {
648 tmGL.set_itype(protocol::liAS_NODE);
649 for (auto const& id : nodes)
650 {
651 *(tmGL.add_nodeids()) = id.first.getRawString();
652 }
653
654 JLOG(journal_.trace()) << "Sending AS node request (" << nodes.size()
655 << ") to " << (peer ? "selected peer" : "all peers");
656 peerSet_->sendRequest(tmGL, peer);
657 return;
658 }
659
660 JLOG(journal_.trace()) << "All AS nodes filtered";
661 }
662 }
663 }
664 }
665
667 {
668 XRPL_ASSERT(
669 ledger_,
670 "xrpl::InboundLedger::trigger : non-null ledger to read "
671 "transactions from");
672
673 if (!ledger_->txMap().isValid())
674 {
675 failed_ = true;
676 }
677 else if (ledger_->txMap().getHash().isZero())
678 {
679 // we need the root node
680 tmGL.set_itype(protocol::liTX_NODE);
681 *(tmGL.add_nodeids()) = SHAMapNodeID().getRawString();
682 JLOG(journal_.trace())
683 << "Sending TX root request to " << (peer ? "selected peer" : "all peers");
684 peerSet_->sendRequest(tmGL, peer);
685 return;
686 }
687 else
688 {
689 TransactionStateSF filter(ledger_->txMap().family().db(), app_.getLedgerMaster());
690
691 auto nodes = ledger_->txMap().getMissingNodes(kMissingNodesFind, &filter);
692
693 if (nodes.empty())
694 {
695 if (!ledger_->txMap().isValid())
696 {
697 failed_ = true;
698 }
699 else
700 {
701 haveTransactions_ = true;
702
703 if (haveState_)
704 complete_ = true;
705 }
706 }
707 else
708 {
709 filterNodes(nodes, reason);
710
711 if (!nodes.empty())
712 {
713 tmGL.set_itype(protocol::liTX_NODE);
714 for (auto const& n : nodes)
715 {
716 *(tmGL.add_nodeids()) = n.first.getRawString();
717 }
718 JLOG(journal_.trace()) << "Sending TX node request (" << nodes.size() << ") to "
719 << (peer ? "selected peer" : "all peers");
720 peerSet_->sendRequest(tmGL, peer);
721 return;
722 }
723
724 JLOG(journal_.trace()) << "All TX nodes filtered";
725 }
726 }
727 }
728
729 if (complete_ || failed_)
730 {
731 JLOG(journal_.debug()) << "Done:" << (complete_ ? " complete" : "")
732 << (failed_ ? " failed " : " ") << ledger_->header().seq;
733 sl.unlock();
734 done();
735 }
736}
737
738void
741 TriggerReason reason)
742{
743 // Sort nodes so that the ones we haven't recently
744 // requested come before the ones we have.
746 nodes, [this](auto const& item) { return recentNodes_.count(item.second) == 0; });
747
748 // If everything is a duplicate we don't want to send
749 // any query at all except on a timeout where we need
750 // to query everyone:
751 if (dup.begin() == nodes.begin())
752 {
753 JLOG(journal_.trace()) << "filterNodes: all duplicates";
754
755 if (reason != TriggerReason::Timeout)
756 {
757 nodes.clear();
758 return;
759 }
760 }
761 else
762 {
763 JLOG(journal_.trace()) << "filterNodes: pruning duplicates";
764
765 nodes.erase(dup.begin(), dup.end());
766 }
767
768 std::size_t const limit = (reason == TriggerReason::Reply) ? kReqNodesReply : kReqNodes;
769
770 if (nodes.size() > limit)
771 nodes.resize(limit);
772
773 for (auto const& n : nodes)
774 recentNodes_.insert(n.second);
775}
776
781// data must not have hash prefix
782bool
784{
785 // Return value: true=normal, false=bad data
786 JLOG(journal_.trace()) << "got header acquiring ledger " << hash_;
787
788 if (complete_ || failed_ || haveHeader_)
789 return true;
790
791 auto* f = &app_.getNodeFamily();
792 Rules const rules{app_.config().features};
794 if (ledger_->header().hash != hash_ || (seq_ != 0 && seq_ != ledger_->header().seq))
795 {
796 JLOG(journal_.warn()) << "Acquire hash mismatch: " << ledger_->header().hash
797 << "!=" << hash_;
798 ledger_.reset();
799 return false;
800 }
801 if (seq_ == 0)
802 seq_ = ledger_->header().seq;
803 ledger_->stateMap().setLedgerSeq(seq_);
804 ledger_->txMap().setLedgerSeq(seq_);
805 haveHeader_ = true;
806
807 Serializer s(data.size() + 4);
809 s.addRaw(data.data(), data.size());
810 f->db().store(NodeObjectType::Ledger, std::move(s.modData()), hash_, seq_);
811
812 if (ledger_->header().txHash.isZero())
813 haveTransactions_ = true;
814
815 if (ledger_->header().accountHash.isZero())
816 haveState_ = true;
817
818 ledger_->txMap().setSynching();
819 ledger_->stateMap().setSynching();
820
821 return true;
822}
823
828void
830 std::shared_ptr<Peer> const& peer,
831 protocol::TMLedgerData const& packet,
832 SHAMapAddNode& san)
833{
834 if (!haveHeader_)
835 {
836 JLOG(journal_.warn()) << "Missing ledger header";
837 san.incInvalid();
838 return;
839 }
840 if (packet.type() == protocol::liTX_NODE)
841 {
843 {
844 san.incDuplicate();
845 return;
846 }
847 }
848 else if (haveState_ || failed_)
849 {
850 san.incDuplicate();
851 return;
852 }
853
854 auto [map, rootHash, filter] =
856 if (packet.type() == protocol::liTX_NODE)
857 {
858 return {
859 ledger_->txMap(),
860 SHAMapHash{ledger_->header().txHash},
862 ledger_->txMap().family().db(), app_.getLedgerMaster())};
863 }
864 return {
865 ledger_->stateMap(),
866 SHAMapHash{ledger_->header().accountHash},
868 ledger_->stateMap().family().db(), app_.getLedgerMaster())};
869 }();
870
871 try
872 {
873 auto const f = filter.get();
874
875 for (auto const& ledgerNode : packet.nodes())
876 {
877 auto treeNode = getTreeNode(ledgerNode.nodedata());
878 if (!treeNode)
879 {
880 JLOG(journal_.warn())
881 << "Got invalid node data for ledger " << hash_ << " from peer " << peer->id();
882 peer->charge(resource::kFeeInvalidData, "ledger_node.node_data invalid");
883 san.incInvalid();
884 return;
885 }
886
887 auto const nodeID = getSHAMapNodeID(ledgerNode, *treeNode);
888 if (!nodeID)
889 {
890 JLOG(journal_.warn())
891 << "Got invalid node id for ledger " << hash_ << " from peer " << peer->id();
892 peer->charge(resource::kFeeInvalidData, "ledger_node.node_id invalid");
893 san.incInvalid();
894 return;
895 }
896
897 auto const result = nodeID->isRoot()
898 ? map.addRootNode(rootHash, std::move(treeNode), f)
899 : map.addKnownNode(*nodeID, std::move(treeNode), f);
900 san += result;
901
902 if (result.isInvalid())
903 {
904 JLOG(journal_.warn()) << "Got invalid node " << *nodeID << " for ledger " << hash_
905 << " from peer " << peer->id();
906 peer->charge(resource::kFeeInvalidData, "ledger_node invalid");
907 return;
908 }
909 }
910 }
911 catch (std::exception const& e)
912 {
913 // If we get here it is not necessarily because the node was bad, so don't charge the peer.
914 JLOG(journal_.error()) << "Could not process node for ledger " << hash_ << " from peer "
915 << peer->id() << ": " << e.what();
916 san.incInvalid();
917 return;
918 }
919
920 if (!map.isSynching())
921 {
922 if (packet.type() == protocol::liTX_NODE)
923 {
924 haveTransactions_ = true;
925 }
926 else
927 {
928 haveState_ = true;
929 }
930
932 {
933 complete_ = true;
934 done();
935 }
936 }
937}
938
943bool
945{
946 if (failed_ || haveState_)
947 {
948 san.incDuplicate();
949 return true;
950 }
951
952 if (!haveHeader_)
953 {
954 // LCOV_EXCL_START
955 UNREACHABLE("xrpl::InboundLedger::takeAsRootNode : no ledger header");
956 return false;
957 // LCOV_EXCL_STOP
958 }
959
960 auto treeNode = getTreeNode(data);
961 if (!treeNode)
962 {
963 JLOG(journal_.warn()) << "Got invalid AS root node data for ledger " << hash_;
964 san.incInvalid();
965 return false;
966 }
967
968 AccountStateSF filter(ledger_->stateMap().family().db(), app_.getLedgerMaster());
969 auto const result = ledger_->stateMap().addRootNode(
970 SHAMapHash{ledger_->header().accountHash}, std::move(treeNode), &filter);
971 san += result;
972 return !result.isInvalid();
973}
974
979bool
981{
983 {
984 san.incDuplicate();
985 return true;
986 }
987
988 if (!haveHeader_)
989 {
990 // LCOV_EXCL_START
991 UNREACHABLE("xrpl::InboundLedger::takeTxRootNode : no ledger header");
992 return false;
993 // LCOV_EXCL_STOP
994 }
995
996 auto treeNode = getTreeNode(data);
997 if (!treeNode)
998 {
999 JLOG(journal_.warn()) << "Got invalid TX root node data for ledger " << hash_;
1000 san.incInvalid();
1001 return false;
1002 }
1003
1004 TransactionStateSF filter(ledger_->txMap().family().db(), app_.getLedgerMaster());
1005 auto const result = ledger_->txMap().addRootNode(
1006 SHAMapHash{ledger_->header().txHash}, std::move(treeNode), &filter);
1007 san += result;
1008 return !result.isInvalid();
1009}
1010
1013{
1015
1016 if (!haveHeader_)
1017 {
1018 ret.emplace_back(protocol::TMGetObjectByHash::otLEDGER, hash_);
1019 return ret;
1020 }
1021
1022 if (!haveState_)
1023 {
1024 AccountStateSF filter(ledger_->stateMap().family().db(), app_.getLedgerMaster());
1025 for (auto const& h : neededStateHashes(4, &filter))
1026 {
1027 ret.emplace_back(protocol::TMGetObjectByHash::otSTATE_NODE, h);
1028 }
1029 }
1030
1031 if (!haveTransactions_)
1032 {
1033 TransactionStateSF filter(ledger_->txMap().family().db(), app_.getLedgerMaster());
1034 for (auto const& h : neededTxHashes(4, &filter))
1035 {
1036 ret.emplace_back(protocol::TMGetObjectByHash::otTRANSACTION_NODE, h);
1037 }
1038 }
1039
1040 return ret;
1041}
1042
1047bool
1051{
1053
1054 if (isDone())
1055 return false;
1056
1057 receivedData_.emplace_back(peer, data);
1058
1060 return false;
1061
1062 receiveDispatched_ = true;
1063 return true;
1064}
1065
1070// VFALCO NOTE, it is not necessary to pass the entire Peer,
1071// we can get away with just a resource::Consumer endpoint.
1072//
1073// TODO Change peer to Consumer
1074//
1075int
1076InboundLedger::processData(std::shared_ptr<Peer> peer, protocol::TMLedgerData const& packet)
1077{
1078 if (packet.type() == protocol::liBASE)
1079 {
1080 if (packet.nodes().empty())
1081 {
1082 JLOG(journal_.warn()) << peer->id() << ": empty header data";
1083 peer->charge(resource::kFeeMalformedRequest, "ledger_data empty header");
1084 return -1;
1085 }
1086
1087 SHAMapAddNode san;
1088
1089 ScopedLockType const sl(mtx_);
1090
1091 try
1092 {
1093 if (!haveHeader_)
1094 {
1095 if (!takeHeader(packet.nodes(0).nodedata()))
1096 {
1097 JLOG(journal_.warn()) << "Got invalid header data";
1098 peer->charge(resource::kFeeMalformedRequest, "ledger_data invalid header");
1099 return -1;
1100 }
1101
1102 san.incUseful();
1103 }
1104
1105 if (!haveState_ && (packet.nodes().size() > 1) &&
1106 !takeAsRootNode(packet.nodes(1).nodedata(), san))
1107 {
1108 JLOG(journal_.warn()) << "Included AS root invalid for ledger " << hash_
1109 << " from peer " << peer->id();
1110 if (san.isInvalid())
1111 {
1112 peer->charge(resource::kFeeInvalidData, "ledger_data invalid AS root");
1113 return -1;
1114 }
1115 }
1116
1117 if (!haveTransactions_ && (packet.nodes().size() > 2) &&
1118 !takeTxRootNode(packet.nodes(2).nodedata(), san))
1119 {
1120 JLOG(journal_.warn()) << "Included TX root invalid for ledger " << hash_
1121 << " from peer " << peer->id();
1122 if (san.isInvalid())
1123 {
1124 peer->charge(resource::kFeeInvalidData, "ledger_data invalid TX root");
1125 return -1;
1126 }
1127 }
1128 }
1129 catch (std::exception const& ex)
1130 {
1131 JLOG(journal_.warn()) << "Included AS/TX root invalid for ledger " << hash_
1132 << " from peer " << peer->id() << ": " << ex.what();
1133 using namespace std::string_literals;
1134 peer->charge(resource::kFeeInvalidData, "ledger_data "s + ex.what());
1135 return -1;
1136 }
1137
1138 if (san.isUseful())
1139 progress_ = true;
1140
1141 stats_ += san;
1142 return san.getGood();
1143 }
1144
1145 if ((packet.type() == protocol::liTX_NODE) || (packet.type() == protocol::liAS_NODE))
1146 {
1147 if (packet.nodes().empty())
1148 {
1149 JLOG(journal_.info()) << peer->id() << ": response with no nodes";
1150 peer->charge(resource::kFeeMalformedRequest, "ledger_data no nodes");
1151 return -1;
1152 }
1153
1154 ScopedLockType const sl(mtx_);
1155
1156 SHAMapAddNode san;
1157 receiveNode(peer, packet, san);
1158
1159 JLOG(journal_.debug()) << "Ledger "
1160 << ((packet.type() == protocol::liTX_NODE) ? "TX" : "AS")
1161 << " node stats: " << san.get();
1162
1163 // `san` accumulates across the whole packet, so `isInvalid()` (bad_ > 0) does not mean the
1164 // packet had no useful nodes: credit whatever good/useful nodes were sent rather than
1165 // discarding everything because one node in an otherwise-good packet was bad.
1166 // Note: Peer charges for invalid/malformed data are issued from within receiveNode at the
1167 // exact failure site, so the peer is only charged for problems they are responsible for.
1168 if (san.isUseful())
1169 progress_ = true;
1170
1171 stats_ += san;
1172 return san.getGood();
1173 }
1174
1175 return -1;
1176}
1177
1178namespace detail {
1179// Track the amount of useful data that each peer returns
1181{
1182 // Map from peer to amount of useful the peer returned
1184 // The largest amount of useful data that any peer returned
1185 int maxCount = 0;
1186
1187 // Update the data count for a peer
1188 void
1189 update(std::shared_ptr<Peer>&& peer, int dataCount)
1190 {
1191 if (dataCount <= 0)
1192 return;
1193 maxCount = std::max(maxCount, dataCount);
1194 auto i = counts.find(peer);
1195 if (i == counts.end())
1196 {
1197 counts.emplace(std::move(peer), dataCount);
1198 return;
1199 }
1200 i->second = std::max(i->second, dataCount);
1201 }
1202
1203 // Prune all the peers that didn't return enough data.
1204 void
1206 {
1207 // Remove all the peers that didn't return at least half as much data as
1208 // the best peer
1209 auto const thresh = maxCount / 2;
1210 auto i = counts.begin();
1211 while (i != counts.end())
1212 {
1213 if (i->second < thresh)
1214 {
1215 i = counts.erase(i);
1216 }
1217 else
1218 {
1219 ++i;
1220 }
1221 }
1222 }
1223
1224 // call F with the `peer` parameter with a random sample of at most n values
1225 // of the counts vector.
1226 template <class F>
1227 void
1229 {
1230 if (counts.empty())
1231 return;
1232
1233 auto outFunc = [&f](auto&& v) { f(v.first); };
1235#if _MSC_VER
1237 s.reserve(n);
1238 std::sample(counts.begin(), counts.end(), std::back_inserter(s), n, rng);
1239 for (auto& v : s)
1240 {
1241 outFunc(v);
1242 }
1243#else
1245 counts.begin(), counts.end(), boost::make_function_output_iterator(outFunc), n, rng);
1246#endif
1247 }
1248};
1249} // namespace detail
1250
1255void
1257{
1258 // Maximum number of peers to request data from
1259 static constexpr std::size_t kMaxUsefulPeers = 6;
1260
1261 decltype(receivedData_) data;
1262
1263 // Reserve some memory so the first couple iterations don't reallocate
1264 data.reserve(8);
1265
1266 detail::PeerDataCounts dataCounts;
1267
1268 for (;;)
1269 {
1270 data.clear();
1271
1272 {
1274
1275 if (receivedData_.empty())
1276 {
1277 receiveDispatched_ = false;
1278 break;
1279 }
1280
1281 data.swap(receivedData_);
1282 }
1283
1284 for (auto& entry : data)
1285 {
1286 if (auto peer = entry.first.lock())
1287 {
1288 int const count = processData(peer, *(entry.second));
1289 dataCounts.update(std::move(peer), count);
1290 }
1291 }
1292 }
1293
1294 // Select a random sample of the peers that gives us the most nodes that are
1295 // useful
1296 dataCounts.prune();
1297 dataCounts.sampleN(kMaxUsefulPeers, [&](std::shared_ptr<Peer> const& peer) {
1299 });
1300}
1301
1304{
1306
1307 ScopedLockType const sl(mtx_);
1308
1309 ret[jss::hash] = to_string(hash_);
1310
1311 if (complete_)
1312 ret[jss::complete] = true;
1313
1314 if (failed_)
1315 ret[jss::failed] = true;
1316
1317 if (!complete_ && !failed_)
1318 ret[jss::peers] = static_cast<int>(peerSet_->getPeerIds().size());
1319
1320 ret[jss::have_header] = haveHeader_;
1321
1322 if (haveHeader_)
1323 {
1324 ret[jss::have_state] = haveState_;
1325 ret[jss::have_transactions] = haveTransactions_;
1326 }
1327
1328 ret[jss::timeouts] = timeouts_;
1329
1330 if (haveHeader_ && !haveState_)
1331 {
1333 for (auto const& h : neededStateHashes(16, nullptr))
1334 {
1335 hv.append(to_string(h));
1336 }
1337 ret[jss::needed_state_hashes] = hv;
1338 }
1339
1341 {
1343 for (auto const& h : neededTxHashes(16, nullptr))
1344 {
1345 hv.append(to_string(h));
1346 }
1347 ret[jss::needed_transaction_hashes] = hv;
1348 }
1349
1350 return ret;
1351}
1352
1353} // namespace xrpl
T addressof(T... args)
T back_inserter(T... args)
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
ValueType type() const
json::Value getJson(int)
Return a json::ValueType::Object.
void tryDB(node_store::Database &srcDB)
void trigger(std::shared_ptr< Peer > const &, TriggerReason)
Request more nodes, perhaps from a specific peer.
InboundLedger(Application &app, uint256 const &hash, std::uint32_t seq, Reason reason, clock_type &, std::unique_ptr< PeerSet > peerSet)
std::weak_ptr< TimeoutCounter > pmDowncast() override
Return a weak pointer to this.
void runData()
Process pending TMLedgerData Query the a random sample of the 'best' peers.
std::size_t getPeerCount() const
void onTimer(bool progress, ScopedLockType &peerSetLock) override
Called with a lock by the PeerSet when the timer expires.
std::vector< uint256 > neededStateHashes(int max, SHAMapSyncFilter const *filter) const
void receiveNode(std::shared_ptr< Peer > const &peer, protocol::TMLedgerData const &packet, SHAMapAddNode &san)
Process node data received from a peer Call with a lock.
SHAMapAddNode stats_
int processData(std::shared_ptr< Peer > peer, protocol::TMLedgerData const &data)
Process one TMLedgerData Returns the number of useful nodes.
bool takeHeader(std::string_view data)
Take ledger header data Call with a lock.
bool takeAsRootNode(std::string_view data, SHAMapAddNode &san)
Process AS root node received from a peer Call with a lock.
void filterNodes(std::vector< std::pair< SHAMapNodeID, uint256 > > &nodes, TriggerReason reason)
std::shared_ptr< Ledger > ledger_
std::vector< std::pair< std::weak_ptr< Peer >, std::shared_ptr< protocol::TMLedgerData > > > receivedData_
std::mutex receivedDataLock_
std::unique_ptr< PeerSet > peerSet_
void addPeers()
Add more peers to the set, if possible.
bool takeTxRootNode(std::string_view data, SHAMapAddNode &san)
Process AS root node received from a peer Call with a lock.
beast::AbstractClock< std::chrono::steady_clock > clock_type
void init(ScopedLockType &collectionLock)
void update(std::uint32_t seq)
std::vector< uint256 > neededTxHashes(int max, SHAMapSyncFilter const *filter) const
std::set< uint256 > recentNodes_
bool gotData(std::weak_ptr< Peer >, std::shared_ptr< protocol::TMLedgerData > const &)
Stash a TMLedgerData received from a peer for later processing Returns 'true' if we need to dispatch.
std::vector< neededHash_t > getNeededHashes()
Rules controlling protocol behavior.
Definition Rules.h:40
bool isInvalid() const
std::string get() const
bool isUseful() const
bool isZero() const
Definition SHAMapHash.h:36
Identifies a node inside a SHAMap.
std::string getRawString() const
std::vector< std::pair< SHAMapNodeID, uint256 > > getMissingNodes(int maxNodes, SHAMapSyncFilter const *filter)
Check for nodes in the SHAMap not available.
SHAMapHash getHash() const
int addRaw(Blob const &vector)
TimeoutCounter(Application &app, uint256 const &targetHash, std::chrono::milliseconds timeoutInterval, QueueJobParameter &&jobParameter, beast::Journal journal)
std::recursive_mutex mtx_
std::unique_lock< std::recursive_mutex > ScopedLockType
uint256 const hash_
The hash of the object (in practice, always a ledger) we are trying to fetch.
void queueJob(ScopedLockType &)
Queue a job to call invokeOnTimer().
bool progress_
Whether forward progress has been made.
beast::Journal journal_
Persistency layer for NodeObject.
Definition Database.h:45
std::shared_ptr< NodeObject > fetchNodeObject(uint256 const &hash, std::uint32_t ledgerSeq=0, FetchType fetchType=FetchType::Synchronous, bool duplicate=false)
Fetch a node object.
T count_if(T... args)
T emplace_back(T... args)
T for_each(T... args)
T lock(T... args)
T make_shared(T... args)
T make_unique(T... args)
T max(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
Keylet const & feeSettings() noexcept
The (fixed) index of the object containing the ledger fees.
Definition Indexes.cpp:233
Charge const kFeeMalformedRequest
Schedule of fees charged for imposing load on the server.
Charge const kFeeInvalidData
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static constexpr auto kReqNodesReply
Number root(Number f, unsigned d)
std::optional< SHAMapNodeID > getSHAMapNodeID(protocol::TMLedgerNode const &ledgerNode, SHAMapTreeNode const &treeNode)
Extracts or reconstructs the SHAMapNodeID from a ledger node proto message.
LedgerHeader deserializeHeader(Slice data, bool hasHash=false)
Deserialize a ledger header from a byte array.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
static constexpr std::uint32_t kXrpLedgerEarliestFees
The XRP Ledger mainnet's earliest ledger with a FeeSettings object.
SHAMapTreeNodePtr getTreeNode(std::string_view data)
Deserializes a SHAMapTreeNode from wire format data.
static constexpr auto kMissingNodesFind
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
@ JtLedgerData
Definition Job.h:52
static constexpr auto kLedgerBecomeAggressiveThreshold
static std::vector< uint256 > neededHashes(uint256 const &root, SHAMap &map, int max, SHAMapSyncFilter const *filter)
static constexpr auto kPeerCountAdd
static constexpr auto kLedgerTimeoutRetriesMax
static constexpr auto kPeerCountStart
@ LedgerMaster
ledger master data for signing
Definition HashPrefix.h:59
constexpr auto kLedgerAcquireTimeout
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition Blob.h:11
static constexpr auto kReqNodes
BaseUInt< 256 > uint256
Definition base_uint.h:580
LedgerHeader deserializePrefixedHeader(Slice data, bool hasHash=false)
Deserialize a ledger header (prefixed with 4 bytes) from a byte array.
T push_back(T... args)
T reserve(T... args)
T sample(T... args)
T stable_partition(T... args)
T str(T... args)
std::unordered_map< std::shared_ptr< Peer >, int > counts
void update(std::shared_ptr< Peer > &&peer, int dataCount)
void sampleN(std::size_t n, F &&f)
T to_string(T... args)
T unlock(T... args)
T what(T... args)