rippled
Loading...
Searching...
No Matches
InboundLedger.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 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#include <xrpld/app/ledger/AccountStateSF.h>
21#include <xrpld/app/ledger/InboundLedger.h>
22#include <xrpld/app/ledger/InboundLedgers.h>
23#include <xrpld/app/ledger/LedgerMaster.h>
24#include <xrpld/app/ledger/TransactionStateSF.h>
25#include <xrpld/app/main/Application.h>
26#include <xrpld/core/JobQueue.h>
27#include <xrpld/overlay/Overlay.h>
28#include <xrpld/shamap/SHAMapNodeID.h>
29
30#include <xrpl/basics/Log.h>
31#include <xrpl/protocol/HashPrefix.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/resource/Fees.h>
34
35#include <boost/iterator/function_output_iterator.hpp>
36
37#include <algorithm>
38#include <random>
39
40namespace ripple {
41
42using namespace std::chrono_literals;
43
44enum {
45 // Number of peers to start with
47
48 // Number of peers to add on a timeout
49 ,
50 peerCountAdd = 3
51
52 // how many timeouts before we give up
53 ,
55
56 // how many timeouts before we get aggressive
57 ,
59
60 // Number of nodes to find initially
61 ,
63
64 // Number of nodes to request for a reply
65 ,
66 reqNodesReply = 128
67
68 // Number of nodes to request blindly
69 ,
70 reqNodes = 12
71};
72
73// millisecond for each ledger timeout
74auto constexpr ledgerAcquireTimeout = 3000ms;
75
77 Application& app,
78 uint256 const& hash,
79 std::uint32_t seq,
80 Reason reason,
81 clock_type& clock,
84 app,
85 hash,
87 {jtLEDGER_DATA, "InboundLedger", 5},
88 app.journal("InboundLedger"))
89 , m_clock(clock)
90 , mHaveHeader(false)
91 , mHaveState(false)
92 , mHaveTransactions(false)
93 , mSignaled(false)
94 , mByHash(true)
95 , mSeq(seq)
96 , mReason(reason)
97 , mReceiveDispatched(false)
98 , mPeerSet(std::move(peerSet))
99{
100 JLOG(journal_.trace()) << "Acquiring ledger " << hash_;
101 touch();
102}
103
104void
106{
108 collectionLock.unlock();
109
111 if (failed_)
112 return;
113
114 if (!complete_)
115 {
116 addPeers();
117 queueJob(sl);
118 return;
119 }
120
121 JLOG(journal_.debug()) << "Acquiring ledger we already have in "
122 << " local store. " << hash_;
123 XRPL_ASSERT(
124 mLedger->info().seq < XRP_LEDGER_EARLIEST_FEES ||
125 mLedger->read(keylet::fees()),
126 "ripple::InboundLedger::init : valid ledger fees");
127 mLedger->setImmutable();
128
130 return;
131
133
134 // Check if this could be a newer fully-validated ledger
137}
138
141{
142 auto const& peerIds = mPeerSet->getPeerIds();
143 return std::count_if(peerIds.begin(), peerIds.end(), [this](auto id) {
144 return (app_.overlay().findPeerByShortID(id) != nullptr);
145 });
146}
147
148void
150{
152
153 // If we didn't know the sequence number, but now do, save it
154 if ((seq != 0) && (mSeq == 0))
155 mSeq = seq;
156
157 // Prevent this from being swept
158 touch();
159}
160
161bool
163{
165 if (!isDone())
166 {
167 if (mLedger)
168 tryDB(mLedger->stateMap().family().db());
169 else
171 if (failed_ || complete_)
172 {
173 done();
174 return true;
175 }
176 }
177 return false;
178}
179
181{
182 // Save any received AS data not processed. It could be useful
183 // for populating a different ledger
184 for (auto& entry : mReceivedData)
185 {
186 if (entry.second->type() == protocol::liAS_NODE)
187 app_.getInboundLedgers().gotStaleData(entry.second);
188 }
189 if (!isDone())
190 {
191 JLOG(journal_.debug())
192 << "Acquire " << hash_ << " abort "
193 << ((timeouts_ == 0) ? std::string()
194 : (std::string("timeouts:") +
196 << mStats.get();
197 }
198}
199
202 uint256 const& root,
203 SHAMap& map,
204 int max,
205 SHAMapSyncFilter* filter)
206{
208
209 if (!root.isZero())
210 {
211 if (map.getHash().isZero())
212 ret.push_back(root);
213 else
214 {
215 auto mn = map.getMissingNodes(max, filter);
216 ret.reserve(mn.size());
217 for (auto const& n : mn)
218 ret.push_back(n.second);
219 }
220 }
221
222 return ret;
223}
224
227{
228 return neededHashes(mLedger->info().txHash, mLedger->txMap(), max, filter);
229}
230
233{
234 return neededHashes(
235 mLedger->info().accountHash, mLedger->stateMap(), max, filter);
236}
237
238// See how much of the ledger data is stored locally
239// Data found in a fetch pack will be stored
240void
242{
243 if (!mHaveHeader)
244 {
245 auto makeLedger = [&, this](Blob const& data) {
246 JLOG(journal_.trace()) << "Ledger header found in fetch pack";
247 mLedger = std::make_shared<Ledger>(
249 app_.config(),
251 if (mLedger->info().hash != hash_ ||
252 (mSeq != 0 && mSeq != mLedger->info().seq))
253 {
254 // We know for a fact the ledger can never be acquired
255 JLOG(journal_.warn())
256 << "hash " << hash_ << " seq " << std::to_string(mSeq)
257 << " cannot be a ledger";
258 mLedger.reset();
259 failed_ = true;
260 }
261 };
262
263 // Try to fetch the ledger header from the DB
264 if (auto nodeObject = srcDB.fetchNodeObject(hash_, mSeq))
265 {
266 JLOG(journal_.trace()) << "Ledger header found in local store";
267
268 makeLedger(nodeObject->getData());
269 if (failed_)
270 return;
271
272 // Store the ledger header if the source and destination differ
273 auto& dstDB{mLedger->stateMap().family().db()};
274 if (std::addressof(dstDB) != std::addressof(srcDB))
275 {
276 Blob blob{nodeObject->getData()};
277 dstDB.store(
278 hotLEDGER, std::move(blob), hash_, mLedger->info().seq);
279 }
280 }
281 else
282 {
283 // Try to fetch the ledger header from a fetch pack
284 auto data = app_.getLedgerMaster().getFetchPack(hash_);
285 if (!data)
286 return;
287
288 JLOG(journal_.trace()) << "Ledger header found in fetch pack";
289
290 makeLedger(*data);
291 if (failed_)
292 return;
293
294 // Store the ledger header in the ledger's database
295 mLedger->stateMap().family().db().store(
296 hotLEDGER, std::move(*data), hash_, mLedger->info().seq);
297 }
298
299 if (mSeq == 0)
300 mSeq = mLedger->info().seq;
301 mLedger->stateMap().setLedgerSeq(mSeq);
302 mLedger->txMap().setLedgerSeq(mSeq);
303 mHaveHeader = true;
304 }
305
307 {
308 if (mLedger->info().txHash.isZero())
309 {
310 JLOG(journal_.trace()) << "No TXNs to fetch";
311 mHaveTransactions = true;
312 }
313 else
314 {
315 TransactionStateSF filter(
316 mLedger->txMap().family().db(), app_.getLedgerMaster());
317 if (mLedger->txMap().fetchRoot(
318 SHAMapHash{mLedger->info().txHash}, &filter))
319 {
320 if (neededTxHashes(1, &filter).empty())
321 {
322 JLOG(journal_.trace()) << "Had full txn map locally";
323 mHaveTransactions = true;
324 }
325 }
326 }
327 }
328
329 if (!mHaveState)
330 {
331 if (mLedger->info().accountHash.isZero())
332 {
333 JLOG(journal_.fatal())
334 << "We are acquiring a ledger with a zero account hash";
335 failed_ = true;
336 return;
337 }
338 AccountStateSF filter(
339 mLedger->stateMap().family().db(), app_.getLedgerMaster());
340 if (mLedger->stateMap().fetchRoot(
341 SHAMapHash{mLedger->info().accountHash}, &filter))
342 {
343 if (neededStateHashes(1, &filter).empty())
344 {
345 JLOG(journal_.trace()) << "Had full AS map locally";
346 mHaveState = true;
347 }
348 }
349 }
350
352 {
353 JLOG(journal_.debug()) << "Had everything locally";
354 complete_ = true;
355 XRPL_ASSERT(
356 mLedger->info().seq < XRP_LEDGER_EARLIEST_FEES ||
357 mLedger->read(keylet::fees()),
358 "ripple::InboundLedger::tryDB : valid ledger fees");
359 mLedger->setImmutable();
360 }
361}
362
365void
367{
368 mRecentNodes.clear();
369
370 if (isDone())
371 {
372 JLOG(journal_.info()) << "Already done " << hash_;
373 return;
374 }
375
377 {
378 if (mSeq != 0)
379 {
380 JLOG(journal_.warn())
381 << timeouts_ << " timeouts for ledger " << mSeq;
382 }
383 else
384 {
385 JLOG(journal_.warn())
386 << timeouts_ << " timeouts for ledger " << hash_;
387 }
388 failed_ = true;
389 done();
390 return;
391 }
392
393 if (!wasProgress)
394 {
395 checkLocal();
396
397 mByHash = true;
398
400 JLOG(journal_.debug())
401 << "No progress(" << pc << ") for ledger " << hash_;
402
403 // addPeers triggers if the reason is not HISTORY
404 // So if the reason IS HISTORY, need to trigger after we add
405 // otherwise, we need to trigger before we add
406 // so each peer gets triggered once
409 addPeers();
412 }
413}
414
416void
418{
419 mPeerSet->addPeers(
421 [this](auto peer) { return peer->hasLedger(hash_, mSeq); },
422 [this](auto peer) {
423 // For historical nodes, do not trigger too soon
424 // since a fetch pack is probably coming
427 });
428}
429
432{
433 return shared_from_this();
434}
435
436void
438{
439 if (mSignaled)
440 return;
441
442 mSignaled = true;
443 touch();
444
445 JLOG(journal_.debug()) << "Acquire " << hash_ << (failed_ ? " fail " : " ")
446 << ((timeouts_ == 0)
447 ? std::string()
448 : (std::string("timeouts:") +
450 << mStats.get();
451
452 XRPL_ASSERT(
454 "ripple::InboundLedger::done : complete or failed");
455
456 if (complete_ && !failed_ && mLedger)
457 {
458 XRPL_ASSERT(
459 mLedger->info().seq < XRP_LEDGER_EARLIEST_FEES ||
460 mLedger->read(keylet::fees()),
461 "ripple::InboundLedger::done : valid ledger fees");
462 mLedger->setImmutable();
463 switch (mReason)
464 {
465 case Reason::HISTORY:
467 break;
468 default:
470 break;
471 }
472 }
473
474 // We hold the PeerSet lock, so must dispatch
476 jtLEDGER_DATA, "AcquisitionDone", [self = shared_from_this()]() {
477 if (self->complete_ && !self->failed_)
478 {
479 self->app_.getLedgerMaster().checkAccept(self->getLedger());
480 self->app_.getLedgerMaster().tryAdvance();
481 }
482 else
483 self->app_.getInboundLedgers().logFailure(
484 self->hash_, self->mSeq);
485 });
486}
487
490void
492{
494
495 if (isDone())
496 {
497 JLOG(journal_.debug())
498 << "Trigger on ledger: " << hash_ << (complete_ ? " completed" : "")
499 << (failed_ ? " failed" : "");
500 return;
501 }
502
503 if (auto stream = journal_.debug())
504 {
505 stream << "Trigger acquiring ledger " << hash_;
506 if (peer)
507 stream << " from " << peer;
508
509 if (complete_ || failed_)
510 stream << "complete=" << complete_ << " failed=" << failed_;
511 else
512 stream << "header=" << mHaveHeader << " tx=" << mHaveTransactions
513 << " as=" << mHaveState;
514 }
515
516 if (!mHaveHeader)
517 {
519 if (failed_)
520 {
521 JLOG(journal_.warn()) << " failed local for " << hash_;
522 return;
523 }
524 }
525
526 protocol::TMGetLedger tmGL;
527 tmGL.set_ledgerhash(hash_.begin(), hash_.size());
528
529 if (timeouts_ != 0)
530 {
531 // Be more aggressive if we've timed out at least once
532 tmGL.set_querytype(protocol::qtINDIRECT);
533
534 if (!progress_ && !failed_ && mByHash &&
536 {
537 auto need = getNeededHashes();
538
539 if (!need.empty())
540 {
541 protocol::TMGetObjectByHash tmBH;
542 bool typeSet = false;
543 tmBH.set_query(true);
544 tmBH.set_ledgerhash(hash_.begin(), hash_.size());
545 for (auto const& p : need)
546 {
547 JLOG(journal_.debug()) << "Want: " << p.second;
548
549 if (!typeSet)
550 {
551 tmBH.set_type(p.first);
552 typeSet = true;
553 }
554
555 if (p.first == tmBH.type())
556 {
557 protocol::TMIndexedObject* io = tmBH.add_objects();
558 io->set_hash(p.second.begin(), p.second.size());
559 if (mSeq != 0)
560 io->set_ledgerseq(mSeq);
561 }
562 }
563
564 auto packet =
565 std::make_shared<Message>(tmBH, protocol::mtGET_OBJECTS);
566 auto const& peerIds = mPeerSet->getPeerIds();
568 peerIds.begin(), peerIds.end(), [this, &packet](auto id) {
569 if (auto p = app_.overlay().findPeerByShortID(id))
570 {
571 mByHash = false;
572 p->send(packet);
573 }
574 });
575 }
576 else
577 {
578 JLOG(journal_.info())
579 << "getNeededHashes says acquire is complete";
580 mHaveHeader = true;
581 mHaveTransactions = true;
582 mHaveState = true;
583 complete_ = true;
584 }
585 }
586 }
587
588 // We can't do much without the header data because we don't know the
589 // state or transaction root hashes.
590 if (!mHaveHeader && !failed_)
591 {
592 tmGL.set_itype(protocol::liBASE);
593 if (mSeq != 0)
594 tmGL.set_ledgerseq(mSeq);
595 JLOG(journal_.trace()) << "Sending header request to "
596 << (peer ? "selected peer" : "all peers");
597 mPeerSet->sendRequest(tmGL, peer);
598 return;
599 }
600
601 if (mLedger)
602 tmGL.set_ledgerseq(mLedger->info().seq);
603
604 if (reason != TriggerReason::reply)
605 {
606 // If we're querying blind, don't query deep
607 tmGL.set_querydepth(0);
608 }
609 else if (peer && peer->isHighLatency())
610 {
611 // If the peer has high latency, query extra deep
612 tmGL.set_querydepth(2);
613 }
614 else
615 tmGL.set_querydepth(1);
616
617 // Get the state data first because it's the most likely to be useful
618 // if we wind up abandoning this fetch.
619 if (mHaveHeader && !mHaveState && !failed_)
620 {
621 XRPL_ASSERT(
622 mLedger,
623 "ripple::InboundLedger::trigger : non-null ledger to read state "
624 "from");
625
626 if (!mLedger->stateMap().isValid())
627 {
628 failed_ = true;
629 }
630 else if (mLedger->stateMap().getHash().isZero())
631 {
632 // we need the root node
633 tmGL.set_itype(protocol::liAS_NODE);
634 *tmGL.add_nodeids() = SHAMapNodeID().getRawString();
635 JLOG(journal_.trace()) << "Sending AS root request to "
636 << (peer ? "selected peer" : "all peers");
637 mPeerSet->sendRequest(tmGL, peer);
638 return;
639 }
640 else
641 {
642 AccountStateSF filter(
643 mLedger->stateMap().family().db(), app_.getLedgerMaster());
644
645 // Release the lock while we process the large state map
646 sl.unlock();
647 auto nodes =
648 mLedger->stateMap().getMissingNodes(missingNodesFind, &filter);
649 sl.lock();
650
651 // Make sure nothing happened while we released the lock
652 if (!failed_ && !complete_ && !mHaveState)
653 {
654 if (nodes.empty())
655 {
656 if (!mLedger->stateMap().isValid())
657 failed_ = true;
658 else
659 {
660 mHaveState = true;
661
662 if (mHaveTransactions)
663 complete_ = true;
664 }
665 }
666 else
667 {
668 filterNodes(nodes, reason);
669
670 if (!nodes.empty())
671 {
672 tmGL.set_itype(protocol::liAS_NODE);
673 for (auto const& id : nodes)
674 {
675 *(tmGL.add_nodeids()) = id.first.getRawString();
676 }
677
678 JLOG(journal_.trace())
679 << "Sending AS node request (" << nodes.size()
680 << ") to "
681 << (peer ? "selected peer" : "all peers");
682 mPeerSet->sendRequest(tmGL, peer);
683 return;
684 }
685 else
686 {
687 JLOG(journal_.trace()) << "All AS nodes filtered";
688 }
689 }
690 }
691 }
692 }
693
694 if (mHaveHeader && !mHaveTransactions && !failed_)
695 {
696 XRPL_ASSERT(
697 mLedger,
698 "ripple::InboundLedger::trigger : non-null ledger to read "
699 "transactions from");
700
701 if (!mLedger->txMap().isValid())
702 {
703 failed_ = true;
704 }
705 else if (mLedger->txMap().getHash().isZero())
706 {
707 // we need the root node
708 tmGL.set_itype(protocol::liTX_NODE);
709 *(tmGL.add_nodeids()) = SHAMapNodeID().getRawString();
710 JLOG(journal_.trace()) << "Sending TX root request to "
711 << (peer ? "selected peer" : "all peers");
712 mPeerSet->sendRequest(tmGL, peer);
713 return;
714 }
715 else
716 {
717 TransactionStateSF filter(
718 mLedger->txMap().family().db(), app_.getLedgerMaster());
719
720 auto nodes =
721 mLedger->txMap().getMissingNodes(missingNodesFind, &filter);
722
723 if (nodes.empty())
724 {
725 if (!mLedger->txMap().isValid())
726 failed_ = true;
727 else
728 {
729 mHaveTransactions = true;
730
731 if (mHaveState)
732 complete_ = true;
733 }
734 }
735 else
736 {
737 filterNodes(nodes, reason);
738
739 if (!nodes.empty())
740 {
741 tmGL.set_itype(protocol::liTX_NODE);
742 for (auto const& n : nodes)
743 {
744 *(tmGL.add_nodeids()) = n.first.getRawString();
745 }
746 JLOG(journal_.trace())
747 << "Sending TX node request (" << nodes.size()
748 << ") to " << (peer ? "selected peer" : "all peers");
749 mPeerSet->sendRequest(tmGL, peer);
750 return;
751 }
752 else
753 {
754 JLOG(journal_.trace()) << "All TX nodes filtered";
755 }
756 }
757 }
758 }
759
760 if (complete_ || failed_)
761 {
762 JLOG(journal_.debug())
763 << "Done:" << (complete_ ? " complete" : "")
764 << (failed_ ? " failed " : " ") << mLedger->info().seq;
765 sl.unlock();
766 done();
767 }
768}
769
770void
771InboundLedger::filterNodes(
773 TriggerReason reason)
774{
775 // Sort nodes so that the ones we haven't recently
776 // requested come before the ones we have.
777 auto dup = std::stable_partition(
778 nodes.begin(), nodes.end(), [this](auto const& item) {
779 return mRecentNodes.count(item.second) == 0;
780 });
781
782 // If everything is a duplicate we don't want to send
783 // any query at all except on a timeout where we need
784 // to query everyone:
785 if (dup == nodes.begin())
786 {
787 JLOG(journal_.trace()) << "filterNodes: all duplicates";
788
789 if (reason != TriggerReason::timeout)
790 {
791 nodes.clear();
792 return;
793 }
794 }
795 else
796 {
797 JLOG(journal_.trace()) << "filterNodes: pruning duplicates";
798
799 nodes.erase(dup, nodes.end());
800 }
801
802 std::size_t const limit =
803 (reason == TriggerReason::reply) ? reqNodesReply : reqNodes;
804
805 if (nodes.size() > limit)
806 nodes.resize(limit);
807
808 for (auto const& n : nodes)
809 mRecentNodes.insert(n.second);
810}
811
815// data must not have hash prefix
816bool
817InboundLedger::takeHeader(std::string const& data)
818{
819 // Return value: true=normal, false=bad data
820 JLOG(journal_.trace()) << "got header acquiring ledger " << hash_;
821
822 if (complete_ || failed_ || mHaveHeader)
823 return true;
824
825 auto* f = &app_.getNodeFamily();
826 mLedger = std::make_shared<Ledger>(
827 deserializeHeader(makeSlice(data)), app_.config(), *f);
828 if (mLedger->info().hash != hash_ ||
829 (mSeq != 0 && mSeq != mLedger->info().seq))
830 {
831 JLOG(journal_.warn())
832 << "Acquire hash mismatch: " << mLedger->info().hash
833 << "!=" << hash_;
834 mLedger.reset();
835 return false;
836 }
837 if (mSeq == 0)
838 mSeq = mLedger->info().seq;
839 mLedger->stateMap().setLedgerSeq(mSeq);
840 mLedger->txMap().setLedgerSeq(mSeq);
841 mHaveHeader = true;
842
843 Serializer s(data.size() + 4);
844 s.add32(HashPrefix::ledgerMaster);
845 s.addRaw(data.data(), data.size());
846 f->db().store(hotLEDGER, std::move(s.modData()), hash_, mSeq);
847
848 if (mLedger->info().txHash.isZero())
849 mHaveTransactions = true;
850
851 if (mLedger->info().accountHash.isZero())
852 mHaveState = true;
853
854 mLedger->txMap().setSynching();
855 mLedger->stateMap().setSynching();
856
857 return true;
858}
859
863void
864InboundLedger::receiveNode(protocol::TMLedgerData& packet, SHAMapAddNode& san)
865{
866 if (!mHaveHeader)
867 {
868 JLOG(journal_.warn()) << "Missing ledger header";
869 san.incInvalid();
870 return;
871 }
872 if (packet.type() == protocol::liTX_NODE)
873 {
874 if (mHaveTransactions || failed_)
875 {
876 san.incDuplicate();
877 return;
878 }
879 }
880 else if (mHaveState || failed_)
881 {
882 san.incDuplicate();
883 return;
884 }
885
886 auto [map, rootHash, filter] = [&]()
888 if (packet.type() == protocol::liTX_NODE)
889 return {
890 mLedger->txMap(),
891 SHAMapHash{mLedger->info().txHash},
892 std::make_unique<TransactionStateSF>(
893 mLedger->txMap().family().db(), app_.getLedgerMaster())};
894 return {
895 mLedger->stateMap(),
896 SHAMapHash{mLedger->info().accountHash},
897 std::make_unique<AccountStateSF>(
898 mLedger->stateMap().family().db(), app_.getLedgerMaster())};
899 }();
900
901 try
902 {
903 auto const f = filter.get();
904
905 for (auto const& node : packet.nodes())
906 {
907 auto const nodeID = deserializeSHAMapNodeID(node.nodeid());
908
909 if (!nodeID)
910 throw std::runtime_error("data does not properly deserialize");
911
912 if (nodeID->isRoot())
913 {
914 san += map.addRootNode(rootHash, makeSlice(node.nodedata()), f);
915 }
916 else
917 {
918 san += map.addKnownNode(*nodeID, makeSlice(node.nodedata()), f);
919 }
920
921 if (!san.isGood())
922 {
923 JLOG(journal_.warn()) << "Received bad node data";
924 return;
925 }
926 }
927 }
928 catch (std::exception const& e)
929 {
930 JLOG(journal_.error()) << "Received bad node data: " << e.what();
931 san.incInvalid();
932 return;
933 }
934
935 if (!map.isSynching())
936 {
937 if (packet.type() == protocol::liTX_NODE)
938 mHaveTransactions = true;
939 else
940 mHaveState = true;
941
942 if (mHaveTransactions && mHaveState)
943 {
944 complete_ = true;
945 done();
946 }
947 }
948}
949
953bool
954InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san)
955{
956 if (failed_ || mHaveState)
957 {
958 san.incDuplicate();
959 return true;
960 }
961
962 if (!mHaveHeader)
963 {
964 UNREACHABLE("ripple::InboundLedger::takeAsRootNode : no ledger header");
965 return false;
966 }
967
968 AccountStateSF filter(
969 mLedger->stateMap().family().db(), app_.getLedgerMaster());
970 san += mLedger->stateMap().addRootNode(
971 SHAMapHash{mLedger->info().accountHash}, data, &filter);
972 return san.isGood();
973}
974
978bool
979InboundLedger::takeTxRootNode(Slice const& data, SHAMapAddNode& san)
980{
981 if (failed_ || mHaveTransactions)
982 {
983 san.incDuplicate();
984 return true;
985 }
986
987 if (!mHaveHeader)
988 {
989 UNREACHABLE("ripple::InboundLedger::takeTxRootNode : no ledger header");
990 return false;
991 }
992
993 TransactionStateSF filter(
994 mLedger->txMap().family().db(), app_.getLedgerMaster());
995 san += mLedger->txMap().addRootNode(
996 SHAMapHash{mLedger->info().txHash}, data, &filter);
997 return san.isGood();
998}
999
1001InboundLedger::getNeededHashes()
1002{
1004
1005 if (!mHaveHeader)
1006 {
1007 ret.push_back(
1008 std::make_pair(protocol::TMGetObjectByHash::otLEDGER, hash_));
1009 return ret;
1010 }
1011
1012 if (!mHaveState)
1013 {
1014 AccountStateSF filter(
1015 mLedger->stateMap().family().db(), app_.getLedgerMaster());
1016 for (auto const& h : neededStateHashes(4, &filter))
1017 {
1018 ret.push_back(
1019 std::make_pair(protocol::TMGetObjectByHash::otSTATE_NODE, h));
1020 }
1021 }
1022
1023 if (!mHaveTransactions)
1024 {
1025 TransactionStateSF filter(
1026 mLedger->txMap().family().db(), app_.getLedgerMaster());
1027 for (auto const& h : neededTxHashes(4, &filter))
1028 {
1030 protocol::TMGetObjectByHash::otTRANSACTION_NODE, h));
1031 }
1032 }
1033
1034 return ret;
1035}
1036
1040bool
1041InboundLedger::gotData(
1044{
1045 std::lock_guard sl(mReceivedDataLock);
1046
1047 if (isDone())
1048 return false;
1049
1050 mReceivedData.emplace_back(peer, data);
1051
1052 if (mReceiveDispatched)
1053 return false;
1054
1055 mReceiveDispatched = true;
1056 return true;
1057}
1058
1062// VFALCO NOTE, it is not necessary to pass the entire Peer,
1063// we can get away with just a Resource::Consumer endpoint.
1064//
1065// TODO Change peer to Consumer
1066//
1067int
1068InboundLedger::processData(
1070 protocol::TMLedgerData& packet)
1071{
1072 if (packet.type() == protocol::liBASE)
1073 {
1074 if (packet.nodes().empty())
1075 {
1076 JLOG(journal_.warn()) << peer->id() << ": empty header data";
1077 peer->charge(
1078 Resource::feeMalformedRequest, "ledger_data empty header");
1079 return -1;
1080 }
1081
1082 SHAMapAddNode san;
1083
1084 ScopedLockType sl(mtx_);
1085
1086 try
1087 {
1088 if (!mHaveHeader)
1089 {
1090 if (!takeHeader(packet.nodes(0).nodedata()))
1091 {
1092 JLOG(journal_.warn()) << "Got invalid header data";
1093 peer->charge(
1094 Resource::feeMalformedRequest,
1095 "ledger_data invalid header");
1096 return -1;
1097 }
1098
1099 san.incUseful();
1100 }
1101
1102 if (!mHaveState && (packet.nodes().size() > 1) &&
1103 !takeAsRootNode(makeSlice(packet.nodes(1).nodedata()), san))
1104 {
1105 JLOG(journal_.warn()) << "Included AS root invalid";
1106 }
1107
1108 if (!mHaveTransactions && (packet.nodes().size() > 2) &&
1109 !takeTxRootNode(makeSlice(packet.nodes(2).nodedata()), san))
1110 {
1111 JLOG(journal_.warn()) << "Included TX root invalid";
1112 }
1113 }
1114 catch (std::exception const& ex)
1115 {
1116 JLOG(journal_.warn())
1117 << "Included AS/TX root invalid: " << ex.what();
1118 using namespace std::string_literals;
1119 peer->charge(Resource::feeInvalidData, "ledger_data "s + ex.what());
1120 return -1;
1121 }
1122
1123 if (san.isUseful())
1124 progress_ = true;
1125
1126 mStats += san;
1127 return san.getGood();
1128 }
1129
1130 if ((packet.type() == protocol::liTX_NODE) ||
1131 (packet.type() == protocol::liAS_NODE))
1132 {
1133 std::string type = packet.type() == protocol::liTX_NODE ? "liTX_NODE: "
1134 : "liAS_NODE: ";
1135
1136 if (packet.nodes().empty())
1137 {
1138 JLOG(journal_.info()) << peer->id() << ": response with no nodes";
1139 peer->charge(Resource::feeMalformedRequest, "ledger_data no nodes");
1140 return -1;
1141 }
1142
1143 ScopedLockType sl(mtx_);
1144
1145 // Verify node IDs and data are complete
1146 for (auto const& node : packet.nodes())
1147 {
1148 if (!node.has_nodeid() || !node.has_nodedata())
1149 {
1150 JLOG(journal_.warn()) << "Got bad node";
1151 peer->charge(
1152 Resource::feeMalformedRequest, "ledger_data bad node");
1153 return -1;
1154 }
1155 }
1156
1157 SHAMapAddNode san;
1158 receiveNode(packet, san);
1159
1160 JLOG(journal_.debug())
1161 << "Ledger "
1162 << ((packet.type() == protocol::liTX_NODE) ? "TX" : "AS")
1163 << " node stats: " << san.get();
1164
1165 if (san.isUseful())
1166 progress_ = true;
1167
1168 mStats += san;
1169 return san.getGood();
1170 }
1171
1172 return -1;
1173}
1174
1175namespace detail {
1176// Track the amount of useful data that each peer returns
1178{
1179 // Map from peer to amount of useful the peer returned
1181 // The largest amount of useful data that any peer returned
1182 int maxCount = 0;
1183
1184 // Update the data count for a peer
1185 void
1186 update(std::shared_ptr<Peer>&& peer, int dataCount)
1187 {
1188 if (dataCount <= 0)
1189 return;
1190 maxCount = std::max(maxCount, dataCount);
1191 auto i = counts.find(peer);
1192 if (i == counts.end())
1193 {
1194 counts.emplace(std::move(peer), dataCount);
1195 return;
1196 }
1197 i->second = std::max(i->second, dataCount);
1198 }
1199
1200 // Prune all the peers that didn't return enough data.
1201 void
1203 {
1204 // Remove all the peers that didn't return at least half as much data as
1205 // the best peer
1206 auto const thresh = maxCount / 2;
1207 auto i = counts.begin();
1208 while (i != counts.end())
1209 {
1210 if (i->second < thresh)
1211 i = counts.erase(i);
1212 else
1213 ++i;
1214 }
1215 }
1216
1217 // call F with the `peer` parameter with a random sample of at most n values
1218 // of the counts vector.
1219 template <class F>
1220 void
1222 {
1223 if (counts.empty())
1224 return;
1225
1226 auto outFunc = [&f](auto&& v) { f(v.first); };
1228#if _MSC_VER
1230 s.reserve(n);
1232 counts.begin(), counts.end(), std::back_inserter(s), n, rng);
1233 for (auto& v : s)
1234 {
1235 outFunc(v);
1236 }
1237#else
1239 counts.begin(),
1240 counts.end(),
1241 boost::make_function_output_iterator(outFunc),
1242 n,
1243 rng);
1244#endif
1245 }
1246};
1247} // namespace detail
1248
1252void
1253InboundLedger::runData()
1254{
1255 // Maximum number of peers to request data from
1256 constexpr std::size_t maxUsefulPeers = 6;
1257
1258 decltype(mReceivedData) data;
1259
1260 // Reserve some memory so the first couple iterations don't reallocate
1261 data.reserve(8);
1262
1263 detail::PeerDataCounts dataCounts;
1264
1265 for (;;)
1266 {
1267 data.clear();
1268
1269 {
1270 std::lock_guard sl(mReceivedDataLock);
1271
1272 if (mReceivedData.empty())
1273 {
1274 mReceiveDispatched = false;
1275 break;
1276 }
1277
1278 data.swap(mReceivedData);
1279 }
1280
1281 for (auto& entry : data)
1282 {
1283 if (auto peer = entry.first.lock())
1284 {
1285 int count = processData(peer, *(entry.second));
1286 dataCounts.update(std::move(peer), count);
1287 }
1288 }
1289 }
1290
1291 // Select a random sample of the peers that gives us the most nodes that are
1292 // useful
1293 dataCounts.prune();
1294 dataCounts.sampleN(maxUsefulPeers, [&](std::shared_ptr<Peer> const& peer) {
1295 trigger(peer, TriggerReason::reply);
1296 });
1297}
1298
1300InboundLedger::getJson(int)
1301{
1303
1304 ScopedLockType sl(mtx_);
1305
1306 ret[jss::hash] = to_string(hash_);
1307
1308 if (complete_)
1309 ret[jss::complete] = true;
1310
1311 if (failed_)
1312 ret[jss::failed] = true;
1313
1314 if (!complete_ && !failed_)
1315 ret[jss::peers] = static_cast<int>(mPeerSet->getPeerIds().size());
1316
1317 ret[jss::have_header] = mHaveHeader;
1318
1319 if (mHaveHeader)
1320 {
1321 ret[jss::have_state] = mHaveState;
1322 ret[jss::have_transactions] = mHaveTransactions;
1323 }
1324
1325 ret[jss::timeouts] = timeouts_;
1326
1327 if (mHaveHeader && !mHaveState)
1328 {
1330 for (auto const& h : neededStateHashes(16, nullptr))
1331 {
1332 hv.append(to_string(h));
1333 }
1334 ret[jss::needed_state_hashes] = hv;
1335 }
1336
1337 if (mHaveHeader && !mHaveTransactions)
1338 {
1340 for (auto const& h : neededTxHashes(16, nullptr))
1341 {
1342 hv.append(to_string(h));
1343 }
1344 ret[jss::needed_transaction_hashes] = hv;
1345 }
1346
1347 return ret;
1348}
1349
1350} // namespace ripple
T addressof(T... args)
T back_inserter(T... args)
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:150
Value & append(Value const &value)
Append value to array at the end.
Definition: json_value.cpp:910
ValueType type() const
Definition: json_value.cpp:363
Stream fatal() const
Definition: Journal.h:352
Stream debug() const
Definition: Journal.h:328
Stream info() const
Definition: Journal.h:334
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Stream warn() const
Definition: Journal.h:340
virtual Config & config()=0
virtual JobQueue & getJobQueue()=0
virtual InboundLedgers & getInboundLedgers()=0
virtual Family & getNodeFamily()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual NodeStore::Database & db()=0
std::size_t getPeerCount() const
void trigger(std::shared_ptr< Peer > const &, TriggerReason)
Request more nodes, perhaps from a specific peer.
void init(ScopedLockType &collectionLock)
std::set< uint256 > mRecentNodes
void addPeers()
Add more peers to the set, if possible.
std::shared_ptr< Ledger > mLedger
std::vector< uint256 > neededTxHashes(int max, SHAMapSyncFilter *filter) const
InboundLedger(Application &app, uint256 const &hash, std::uint32_t seq, Reason reason, clock_type &, std::unique_ptr< PeerSet > peerSet)
SHAMapAddNode mStats
void tryDB(NodeStore::Database &srcDB)
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 *filter) const
std::weak_ptr< TimeoutCounter > pmDowncast() override
Return a weak pointer to this.
std::vector< std::pair< std::weak_ptr< Peer >, std::shared_ptr< protocol::TMLedgerData > > > mReceivedData
std::vector< neededHash_t > getNeededHashes()
void update(std::uint32_t seq)
std::unique_ptr< PeerSet > mPeerSet
virtual void gotStaleData(std::shared_ptr< protocol::TMLedgerData > packet)=0
virtual void onLedgerFetched()=0
Called when a complete ledger is obtained.
bool addJob(JobType type, std::string const &name, JobHandler &&jobHandler)
Adds a job to the JobQueue.
Definition: JobQueue.h:166
void checkAccept(std::shared_ptr< Ledger const > const &ledger)
std::optional< Blob > getFetchPack(uint256 const &hash) override
Retrieves partial ledger data of the coresponding hash from peers.
bool storeLedger(std::shared_ptr< Ledger const > ledger)
Persistency layer for NodeObject.
Definition: Database.h:52
std::shared_ptr< NodeObject > fetchNodeObject(uint256 const &hash, std::uint32_t ledgerSeq=0, FetchType fetchType=FetchType::synchronous, bool duplicate=false)
Fetch a node object.
Definition: Database.cpp:241
std::string get() const
bool isZero() const
Definition: SHAMapHash.h:54
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition: SHAMap.h:98
SHAMapHash getHash() const
Definition: SHAMap.cpp:889
std::vector< std::pair< SHAMapNodeID, uint256 > > getMissingNodes(int maxNodes, SHAMapSyncFilter *filter)
Check for nodes in the SHAMap not available.
Definition: SHAMapSync.cpp:318
int addRaw(Blob const &vector)
Definition: Serializer.cpp:88
An immutable linear range of bytes.
Definition: Slice.h:46
This class is an "active" object.
void queueJob(ScopedLockType &)
Queue a job to call invokeOnTimer().
bool progress_
Whether forward progress has been made.
beast::Journal journal_
uint256 const hash_
The hash of the object (in practice, always a ledger) we are trying to fetch.
std::recursive_mutex mtx_
iterator begin()
Definition: base_uint.h:136
static constexpr std::size_t size()
Definition: base_uint.h:526
T count_if(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T for_each(T... args)
T make_pair(T... args)
T max(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
Keylet const & fees() noexcept
The (fixed) index of the object containing the ledger fees.
Definition: Indexes.cpp:222
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
LedgerHeader deserializePrefixedHeader(Slice data, bool hasHash=false)
Deserialize a ledger header (prefixed with 4 bytes) from a byte array.
@ peerCountStart
@ ledgerBecomeAggressiveThreshold
@ ledgerTimeoutRetriesMax
@ missingNodesFind
std::optional< SHAMapNodeID > deserializeSHAMapNodeID(void const *data, std::size_t size)
Return an object representing a serialized SHAMap Node ID.
auto constexpr ledgerAcquireTimeout
@ hotLEDGER
Definition: NodeObject.h:34
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_FEES
The XRP Ledger mainnet's earliest ledger with a FeeSettings object.
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:244
Number root(Number f, unsigned d)
Definition: Number.cpp:636
@ jtLEDGER_DATA
Definition: Job.h:66
LedgerHeader deserializeHeader(Slice data, bool hasHash=false)
Deserialize a ledger header from a byte array.
static std::vector< uint256 > neededHashes(uint256 const &root, SHAMap &map, int max, SHAMapSyncFilter *filter)
T push_back(T... args)
T reserve(T... args)
T reset(T... args)
T sample(T... args)
T stable_partition(T... args)
std::unordered_map< std::shared_ptr< Peer >, int > counts
void sampleN(std::size_t n, F &&f)
void update(std::shared_ptr< Peer > &&peer, int dataCount)
T to_string(T... args)
T unlock(T... args)
T what(T... args)