ouroboros-network
Safe HaskellNone
LanguageHaskell2010

Ouroboros.Network.BlockFetch.Decision

Synopsis

Deciding what to fetch

fetchDecisions :: (Ord peer, Hashable peer, HasHeader header, HeaderHash header ~ HeaderHash block) => FetchDecisionPolicy header -> PraosFetchMode -> AnchoredFragment header -> (Point block -> Bool) -> MaxSlotNo -> [(AnchoredFragment header, PeerInfo header peer extra)] -> [(FetchDecision (FetchRequest header), PeerInfo header peer extra)] Source #

data PraosFetchMode #

Constructors

FetchModeBulkSync

Use this mode when we are catching up on the chain but are stil well behind. In this mode the fetch logic will optimise for throughput rather than latency.

FetchModeDeadline

Use this mode for block-producing nodes that have a known deadline to produce a block and need to get the best chain before that. In this mode the fetch logic will optimise for picking the best chain within the given deadline.

type PeerInfo header peer extra = (PeerFetchStatus header, PeerFetchInFlight header, PeerGSV, peer, extra) Source #

peerInfoPeer :: PeerInfo header peer extra -> peer Source #

type FetchDecision result = Either FetchDecline result Source #

Throughout the decision making process we accumulate reasons to decline to fetch any blocks. This type is used to wrap intermediate and final results.

data FetchDecline Source #

All the various reasons we can decide not to fetch blocks from a peer.

It is worth highlighting which of these reasons result from competition among upstream peers.

  • FetchDeclineInFlightOtherPeer: decline this peer because all the unfetched blocks of its candidate chain have already been requested from other peers. This reason reflects the least-consequential competition among peers: the competition that determines merely which upstream peer to burden with the request (eg the one with the best DeltaQ metrics). The consequences are relatively minor because the unfetched blocks on this peer's candidate chain will be requested regardless; it's merely a question of "From who?". (One exception: if an adversarial peer wins this competition such that the blocks are only requested from them, then it may be possible that this decision determines whether the blocks are ever received. But that depends on details of timeouts, a longer competing chain being soon received within those timeouts, and so on.)
  • FetchDeclineChainNotPlausible: decline this peer because the node has already fetched, validated, and selected a chain better than its candidate chain from other peers (or from the node's own block forge). Because the node's current selection is influenced by what blocks other peers have recently served (or it recently minted), this reason reflects that peers indirectly compete by serving as long of a chain as possible and as promptly as possible. When the tips of the peers' selections are all within their respective forecast horizons (see ledgerViewForecastAt), then the length of their candidate chains will typically be the length of their selections, since the ChainSync is free to race ahead (in contrast, the BlockFetch pipeline depth is bounded such that it will, for a syncing node, not be able to request all blocks between the selection and the end of the forecast window). But if one or more of their tips is beyond the horizon, then the relative length of the candidate chains is more complicated, influenced by both the relative density of the chains' suffixes and the relative age of the chains' intersection with the node's selection (since each peer's forecast horizon is a fixed number of slots after the candidate's successor of that intersection).
  • FetchDeclineConcurrencyLimit: decline this peer while the node has already fully allocated the artificially scarce maxConcurrentFetchPeers resource amongst its other peers. This reason reflects the least-fundamental competition: it's the only way a node would decline a candidate chain C that it would immediately switch to if C had somehow already been fetched (and any better current candidates hadn't). It is possible that this peer's candidate fragment is better than the candidate fragments of other peers, but that should only happen ephemerally (eg for a brief while immediately after first connecting to this peer).
  • FetchDeclineChainIntersectionTooDeep: decline this peer because the node's selection has more than K blocks that are not on this peer's candidate chain. Typically, this reason occurs after the node has been declined---ie lost the above competitions---for a long enough duration. This decision only arises if the BlockFetch decision logic wins a harmless race against the ChainSync client once the node's selection gets longer, since ForkTooDeep disconnects from such a peer.

Constructors

FetchDeclineChainNotPlausible

This peer's candidate chain is not longer than our chain. For more details see mkBlockFetchConsensusInterface which implements plausibleCandidateChain.

FetchDeclineChainIntersectionTooDeep

Switching to this peer's candidate chain would require rolling back more than K blocks.

FetchDeclineAlreadyFetched

Every block on this peer's candidate chain has already been fetched.

FetchDeclineInFlightThisPeer

This peer's candidate chain has already been requested from this peer.

FetchDeclineInFlightOtherPeer

Some blocks on this peer's candidate chain have not yet been fetched, but all of those have already been requested from other peers.

FetchDeclinePeerShutdown

This peer's BlockFetch client is shutting down, see PeerFetchStatusShutdown.

FetchDeclinePeerStarting

Blockfetch is starting up and waiting on corresponding Chainsync.

FetchDeclinePeerSlow

This peer is in a potentially-temporary state in which it has not responded to us within a certain expected time limit, see PeerFetchStatusAberrant.

FetchDeclineReqsInFlightLimit !Word

This peer is not under the maxInFlightReqsPerPeer limit.

The argument is the maxInFlightReqsPerPeer constant.

FetchDeclineBytesInFlightLimit !SizeInBytes !SizeInBytes !SizeInBytes

This peer is not under the inFlightBytesHighWatermark bytes limit.

The arguments are:

FetchDeclinePeerBusy !SizeInBytes !SizeInBytes !SizeInBytes

This peer is not under the inFlightBytesLowWatermark.

The arguments are:

FetchDeclineConcurrencyLimit !FetchMode !Word

The node is not under the maxConcurrentFetchPeers limit.

The arguments are:

Components of the decision-making process

newtype ChainSuffix header Source #

A chain suffix, obtained by intersecting a candidate chain with the current chain.

The anchor point of a ChainSuffix will be a point within the bounds of the current chain (withinFragmentBounds), indicating that it forks off in the last K blocks.

A ChainSuffix must be non-empty, as an empty suffix, i.e. the candidate chain is equal to the current chain, would not be a plausible candidate.

Constructors

ChainSuffix 

type CandidateFragments header = (ChainSuffix header, [AnchoredFragment header]) Source #

The combination of a ChainSuffix and a list of discontiguous AnchoredFragments:

filterPlausibleCandidates Source #

Arguments

:: (AnchoredFragment block -> AnchoredFragment header -> Bool) 
-> AnchoredFragment block

The current chain

-> [(AnchoredFragment header, peerinfo)] 
-> [(FetchDecision (AnchoredFragment header), peerinfo)] 

Keep only those candidate chains that are preferred over the current chain. Typically, this means that their length is longer than the length of the current chain.

selectForkSuffixes :: (HasHeader header, HasHeader block, HeaderHash header ~ HeaderHash block) => AnchoredFragment block -> [(FetchDecision (AnchoredFragment header), peerinfo)] -> [(FetchDecision (ChainSuffix header), peerinfo)] Source #

filterNotAlreadyFetched :: (HasHeader header, HeaderHash header ~ HeaderHash block) => (Point block -> Bool) -> MaxSlotNo -> [(FetchDecision (ChainSuffix header), peerinfo)] -> [(FetchDecision (CandidateFragments header), peerinfo)] Source #

Find the fragments of the chain suffix that we still need to fetch, these are the fragments covering blocks that have not yet been fetched and are not currently in the process of being fetched from this peer.

Typically this is a single fragment forming a suffix of the chain, but in the general case we can get a bunch of discontiguous chain fragments.

filterWithMaxSlotNo Source #

Arguments

:: HasHeader header 
=> (header -> Bool) 
-> MaxSlotNo
maxSlotNo
-> AnchoredFragment header 
-> [AnchoredFragment header] 

Filter a fragment. This is an optimised variant that will behave the same as filter if the following precondition is satisfied:

PRECONDITION: for all hdr in the chain fragment: if blockSlot hdr > maxSlotNo then the predicate should not hold for any header after hdr in the chain fragment.

For example, when filtering out already downloaded blocks from the fragment, it does not make sense to keep filtering after having encountered the highest slot number the ChainDB has seen so far: blocks with a greater slot number cannot have been downloaded yet. When the candidate fragments get far ahead of the current chain, e.g., 2k headers, this optimisation avoids the linear cost of filtering these headers when we know in advance they will all remain in the final fragment. In case the given slot number is NoSlotNo, no filtering takes place, as there should be no matches because we haven't downloaded any blocks yet.

For example, when filtering out blocks already in-flight for the given peer, the given maxSlotNo can correspond to the block with the highest slot number that so far has been in-flight for the given peer. When no blocks have been in-flight yet, maxSlotNo can be NoSlotNo, in which case no filtering needs to take place, which makes sense, as there are no blocks to filter out. Note that this is conservative: if a block is for some reason multiple times in-flight (maybe it has to be redownloaded) and the block's slot number matches the maxSlotNo, it will now be filtered (while the filtering might previously have stopped before encountering the block in question). This is fine, as the filter will now include the block, because according to the filtering predicate, the block is not in-flight.

prioritisePeerChains :: forall extra header peer. (HasHeader header, Hashable peer, Ord peer) => PraosFetchMode -> Int -> (AnchoredFragment header -> AnchoredFragment header -> Ordering) -> (header -> SizeInBytes) -> [(FetchDecision (CandidateFragments header), PeerFetchInFlight header, PeerGSV, peer, extra)] -> [(FetchDecision [AnchoredFragment header], extra)] Source #

filterNotAlreadyInFlightWithOtherPeers :: HasHeader header => PraosFetchMode -> [(FetchDecision [AnchoredFragment header], PeerFetchStatus header, PeerFetchInFlight header, peerinfo)] -> [(FetchDecision [AnchoredFragment header], peerinfo)] Source #

A penultimate step of filtering, but this time across peers, rather than individually for each peer. If we're following the parallel fetch mode then we filter out blocks that are already in-flight with other peers.

Note that this does not cover blocks that are proposed to be fetched in this round of decisions. That step is covered in fetchRequestDecisions.

fetchRequestDecisions :: forall extra header peer. (Hashable peer, HasHeader header, Ord peer) => FetchDecisionPolicy header -> PraosFetchMode -> [(FetchDecision [AnchoredFragment header], PeerFetchStatus header, PeerFetchInFlight header, PeerGSV, peer, extra)] -> [(FetchDecision (FetchRequest header), extra)] Source #