| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Ouroboros.Consensus.MiniProtocol.LeiosNotify.Client.Announcements.Validation
Description
Validation of a relayed Leios announcement, which is carried as an RB
Header.
The announcement can arrive out-of-order with respect to the local chain, so we run only the protocol-level header validation against the immutable tip's ledger state, forecast to the announced slot; the envelope (chain-extension) check is deliberately skipped, except for the bounds a ledger view alone determines.
For a caught-up node the immutable tip is always within the forecast horizon
of a fresh announcement (by appeal to Praos Chain Growth); a LeiosNotify
client sends no request until then (see awaitImmTipCanForecastNow).
Synopsis
- data AnnouncementInvalidity blk
- data AnnouncingHeader blk
- announcementValidity ∷ ∀ (m ∷ Type → Type) blk. (IOLike m, LedgerSupportsProtocol blk, LeiosProtocol (BlockProtocol blk)) ⇒ SystemTime m → SomeHeaderInFutureCheck m blk → TopLevelConfig blk → ExtLedgerState blk EmptyMK → AnnouncingHeader blk → ExceptT (Error (AnnouncementInvalidity blk)) m OCINStaleness
- maxAnnouncementAgeRecv ∷ NominalDiffTime
- mkAnnouncingHeader ∷ (BlockSupportsProtocol blk, LeiosProtocol (BlockProtocol blk)) ⇒ TopLevelConfig blk → Header blk → Maybe (AnnouncingHeader blk)
- validateAnnouncementHeader ∷ (LedgerSupportsProtocol blk, LeiosProtocol (BlockProtocol blk)) ⇒ TopLevelConfig blk → ExtLedgerState blk EmptyMK → AnnouncingHeader blk → Except (AnnouncementInvalidity blk) OCINStaleness
Documentation
data AnnouncementInvalidity blk Source #
Reasons a LeiosNotify client would reject its upstream peer's announcement, regardless of other messages they had sent
Constructors
| OutsideHorizon !OutsideForecastRange | The announced slot is beyond the forecast horizon from the immutable tip. This should not occur for a caught-up node (by appeal to Praos Chain Growth), so it is either a bogus far-future slot or we are falling behind; either way disconnecting is acceptable (if we are behind, then either we are unhealthy or the peer is not serving us well). We cannot simply ignore it, because there is an unbounded supply of far-future slots. |
| SlotBeforeImmutableTip | The announced slot is before the immutable tip (the forecast anchor),
so it cannot be forecast/validated at all. This is a separate case from
The |
| HeaderInvalid !(ValidationErr (BlockProtocol blk)) | The header failed the relaxed, out-of-context protocol-level validation
(election proof and signature). See |
| RbHeaderEnvelopeInvalid !(OtherHeaderEnvelopeError blk) | The RbHeader failed an envelope check — chiefly, it exceeds the
protocol's max header size (see |
Instances
| (Show (ValidationErr (BlockProtocol blk)), Show (OtherHeaderEnvelopeError blk)) ⇒ Show (AnnouncementInvalidity blk) Source # | |
Defined in Ouroboros.Consensus.MiniProtocol.LeiosNotify.Client.Announcements.Validation Methods showsPrec ∷ Int → AnnouncementInvalidity blk → ShowS # show ∷ AnnouncementInvalidity blk → String # showList ∷ [AnnouncementInvalidity blk] → ShowS # | |
data AnnouncingHeader blk Source #
A header that carries an announcement.
Instances
Arguments
| ∷ ∀ (m ∷ Type → Type) blk. (IOLike m, LedgerSupportsProtocol blk, LeiosProtocol (BlockProtocol blk)) | |
| ⇒ SystemTime m | |
| → SomeHeaderInFutureCheck m blk | |
| → TopLevelConfig blk | |
| → ExtLedgerState blk EmptyMK | The immutable tip's ledger state, for the OCIN revocation check: a revocation only counts once it cannot be rolled back. |
| → AnnouncingHeader blk | |
| → ExceptT (Error (AnnouncementInvalidity blk)) m OCINStaleness |
Validate a relayed announcement, after the peer's per-election counting checks have passed.
First apply ChainSync's in-future check to the announced slot's wall-clock
onset (reusing the node's own SomeHeaderInFutureCheck):
a far-future slot raises HeaderArrivalException (disconnecting
the peer), a near-future slot blocks until the slot's onset (Ouroboros
Chronos) — blocking the per-peer handler is acceptable, as a (near-)future
announcement is the peer's fault.
A valid announcement may still be StaleOCIN, which the peer's dedup state
counts but the node does not process.
maxAnnouncementAgeRecv ∷ NominalDiffTime Source #
Disconnect an upstream peer that relays an announcement whose slot's
wall-clock onset is older than this. See
ErrTooOld.
Comfortably greater than how long an honest peer relays an announcement, so that an honest peer is never disconnected on account of transmission time or clock skew.
TODO magic number; should be a config/RunNode option... or even a protocol parameter?
mkAnnouncingHeader ∷ (BlockSupportsProtocol blk, LeiosProtocol (BlockProtocol blk)) ⇒ TopLevelConfig blk → Header blk → Maybe (AnnouncingHeader blk) Source #
Interpret a header as a relayed announcement, or Nothing if it carries
none (so it should not have been relayed as one).
validateAnnouncementHeader ∷ (LedgerSupportsProtocol blk, LeiosProtocol (BlockProtocol blk)) ⇒ TopLevelConfig blk → ExtLedgerState blk EmptyMK → AnnouncingHeader blk → Except (AnnouncementInvalidity blk) OCINStaleness Source #
Protocol-level validation of an announced RB Header against the immutable
tip's ledger state (forecast to the header's slot). Envelope check skipped;
see the module header.
The operational certificate (opcert) issue number (OCIN) governs only relay,
not acceptance. We cannot soundly constrain it as strictly as genuine header
validation does because we're using merely the immutable ledger state, which
may be out of date. Only a non-OCIN failure — a bad election proof or
signature, a slot before the immutable tip, an oversized header — rejects and
disconnects. But even if an announcement is not considered invalid, it might
otherwise be ignored. An announcement whose OCIN is at least our immutable
tip's recorded counter is FreshOCIN: processed and relayed as usual. A
lower one, or one from a key we have no recorded counter for, is StaleOCIN
(see validateLeiosAnnouncement): accepted from the peer — updating
that peer's dedup state, never disconnecting — but not processed, published,
or relayed.
Skipping OCIN validation effectively treats every one of a pool's OCINs as the pool owner's, which is contrary to the intended purpose of OCINs, so omitting the two checks that strict header validation would make needs justification.
We do not enforce the counter's upper bound: we accept that the true OCIN
may have run ahead of our imm tip's view. In fact, this logic treats distinct
OCINs as the same identity. The crux is a Catch 22 if we were to distinguish
them: we would have to either treat those identities independently (as
distinct elections) or prioritize the greater OCIN (which seems
intuitive). But an adversary can mint arbitrarily many OCINs for its own
pools and abuse either choice. The former lets them multiply the Leios load
per election; the latter lets them "partition" the network, one clique
certifying a lower OCIN's announcement while the other ignores it. So
elections are keyed merely on (slot, pool) alone (see
ElectionId).
We do not promptly enforce the counter's lower bound. An OCIN increment
revokes the old opcert immediately for Praos (at header validation/chain
adoption), but for Leios only once the increment reaches our immutable tip.
Until then our recorded counter is still the old one, so an announcement
bearing it (or greater) is FreshOCIN and we process and relay it as
usual. The downside of not being prompt is that a leaked hot key lets the
attacker equivocate the victim's announcements until the victim notices,
lands a new opcert, and waits for it to become immutable (~12 hr, <= ~36 hr)
— and during that window announcements using the revoked OCIN are fresh, so
they can even be voted for/part of equivocation proofs/etc. Not ideal, but
tolerable.
Once the increment is immutable for us, an announcement bearing the old
counter is StaleOCIN. But even then we must not reject it, only decline
to process or relay it. Different honest nodes might have different immutable
tips, so honest nodes can briefly disagree on whether the increment is yet
immutable. An adversary who bumps a pool's OCIN and then sends a ~on-time
announcement using the revoked OCIN, timed to straddle that boundary, would
otherwise make "fast" nodes (increment immutable, so StaleOCIN)
disconnect the "slow" honest peers (increment not yet immutable, so still
FreshOCIN) that legitimately relay it — an OCIN-timed partition.
That results in a diffusion asymmetry — the slow peers relay the old-counter announcement, the fast ones do not — but it is not the certify/ignore partition the upper-bound Catch 22 warned about, because once the increment is immutable even just for us the announcement can never be voted for, hence never certified:
- Once the increment is immutable for any honest node then, by the Praos Common Prefix property, it is in every healthy honest node's selection now and forever, so every honest node's up-to-date view records the incremented counter.
- A vote follows selection, and selection is driven by ChainSync
MsgRollForwardheaders. Those are not dangling: they extend the node's current chain and undergo full header validation with the exact chain-dependent OCIN bounds. Only announcements are dangling, which is precisely why only they fall back to the immutable-tip ledger view and this relaxed, report-don't-reject OCIN check.
So the old-counter RB fails MsgRollForward validation on every healthy honest
node — even the slow ones still relaying its announcement — never becomes a
selected tip, and is never voted for; the asymmetry never yields a certified
EB, so it is no Linear Leios availability violation.
Returns the OCIN staleness.