docs(blockchain): simplify Blend connectivity maintenance and derive its thresholds - #421
Draft
madxor wants to merge 8 commits into
Draft
docs(blockchain): simplify Blend connectivity maintenance and derive its thresholds#421madxor wants to merge 8 commits into
madxor wants to merge 8 commits into
Conversation
…its thresholds The connection monitoring thresholds were expressed in terms of the per-round release estimator, which conflated a frequency with a count: the maximum was below the traffic an honest connection actually carries, and the minimum sat on top of it. Both are now derived from the traffic a connection is expected to carry, and the maintenance logic is restated around two principles instead of a list of rules that mixed measurement, classification and reaction. Thresholds. F_1 gives the messages a connection is expected to carry per round, from the generation rates, the redundancy parameters and the blending operations, since every connection carries in expectation every message the network emits. The maximum relaxes that by a divergence controller sized to the two effects that compound above it, connection-level duplication and the bootstrapping rise of F_D toward F_C; their product is a floor on the controller and not a usable value for it. The minimum is set an order of magnitude below expectation and carries no penalty, because a tighter one is not safe: the traffic a connection carries depends on the topology and on the position of the node within it. Logic. A ban now requires attribution: a neighbor is disconnected and blacklisted only for what its authenticated identity is responsible for, never for silence or for traffic below the minimum, which an adversary can induce without controlling either end of the connection. The connection cap is hard and includes handshakes in progress. Classification follows the moving window, so a connection recovers without a marking to remove. Relaying verifies the public header, proof of quota included, before releasing a message, and the duplicate check runs first so a neighbor cannot force repeated proof verifications by resending a known identifier. Only nullifiers of verified messages are recorded, otherwise a forged header could suppress the genuine message carrying the same nullifier. R_C, R_D and T_E are given values, and the redundancy parameters are carried into the nullifier cache estimate that had silently assumed them to be zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y, not by connection Three gaps found reviewing the previous revision. The edge path was unmetered. Header verification now includes the proof of quota and happens before a message is relayed, but edge messages were neither counted nor rate limited, and a failed verification only closed the connection. With the specification's own example of 300 concurrent edge connections, each sending one message before closing, an adversary could impose hundreds of proof verifications per second at no cost and without ever being attributable. A core node now accepts at most a fixed number of edge connections per round. The limit deliberately ignores identity: an edge node is not in the set returned by the SDP protocol and can present a fresh identity per connection, so only a rate that ignores identity bounds the work. The identity is blacklisted on a failed verification as well, which an adversary evades by rotating, but which is cheap and stops one that does not. The verification budget both limits imply is now stated, so it can be checked against measured hardware. The observation window was tied to the connection, which the neighbor controls. A neighbor could disconnect and reconnect just before each window completed, and so never accumulate a full window: never classified unhealthy, never eligible for replacement, holding a connection slot indefinitely against a hard cap. The count and the window now accumulate against the authenticated identity for the epoch, so a reconnecting neighbor resumes where it left off and the grace given to a new neighbor is granted once per identity per epoch. The retained set is bounded by the core nodes of the epoch. A failure of the transport was listed as grounds for a ban without saying what one is. It now means a TLS record that fails authentication or a violation of the framing of the stream, and explicitly not the loss of the connection, which any network in between can cause and which is treated as silence. Also: the spread of honest bootstrapping traffic was computed as a Poisson count, but duplication delivers each message twice rather than generating independent arrivals, so the standard deviation is 27 rather than 19 and the maximum sits about 7 deviations above the mean, not 10; the minimum is justified against that spread instead of being asserted; the relay rule now says a message is not returned to the neighbor it came from, which the traffic estimate depends on; and that estimate is stated as the upper bound it is.
…retire the edge ceiling Follow-up to the previous revision, from a second reading of it. Binding the observation window to the neighbor's identity left unsaid what happens to the rounds it was not connected for. Read as a plain trailing window, a neighbor returning from a transient network failure is immediately unhealthy, and so eligible for replacement, which is the opposite of treating a dropped connection as silence rather than misbehavior. The window now advances only while the neighbor is connected: absent rounds are not observations, are not counted as silence, and do not age earlier messages out. The edge connection ceiling and the acceptance rate governed the same thing, and the rate made the ceiling unreachable: with a limit of eight acceptances per round and connections lasting a single round, the ceiling of three hundred could never bind. The ceiling is gone and the concurrency it used to express is now implied by the rate and the timeout. The acceptance limit said it applied to connections accepted per round, which no node can do: core and edge neighbors are told apart by the Neighbor Distinction Process, which reads the authenticated identity once the connection is established. The limit is now stated where it can be applied, matching what the implementation already does. The blacklist had grown two policies under one name. They are now two structures, because the identities they hold are different in kind: the blacklist holds core nodes, named by the SDP set, which is bounded and costly to join, so entries are long lived and need no ceiling; the edge quarantine holds ephemeral identities that are free to replace, so entries expire with the epoch and the structure has a fixed size. Healthiness is likewise defined only for core-to-core connections now, since those are the only ones classified.
… give the quarantine an effect Replacement fired only when the healthy peering degree had fallen below the minimum, which left the slots above the minimum unreclaimable. An adversary holding every slot between the minimum and the maximum delivered nothing, was never banned because silence is not attributable, and was never displaced because the node was not short of healthy connections. Replacement now fires when the node is at the cap and any connection is unhealthy, and only unhealthy connections are eligible, so a healthy one is never displaced. The distinction this rests on is now explicit in the rule above it: an unhealthy connection is never closed as a penalty, but reclaiming the slot it holds is not a penalty. The edge quarantine was given no effect. An identity was added to it and nothing said what that caused; it now refuses further connections from that identity, and such a refusal does not consume the acceptance budget that bounds verification work. Also: the trade the acceptance limit makes is stated, since it converts an unbounded exhaustion of a node's ability to verify into a bounded denial of its edge service, which is why an edge node sends to several core nodes; the work of establishing a connection is called out as not bounded by that limit, being far below the cost of a proof verification; the count of connections with edge nodes is dropped from the notation, having lost its last user with the ceiling it belonged to; and the revision history now describes what this revision does rather than what its first draft did.
…o itself The maximum was a statistical threshold over an observation window, sized so that honest traffic would rarely reach it. That framing was wrong, and the arithmetic it forced showed why: with a threshold placed a few standard deviations above the mean, a node evicts and blacklists honest neighbors at whatever rate the tail of the distribution delivers them, and the margin needed to make that rare collided with what a Raspberry Pi 5 can verify. A limit that is part of the protocol configuration is not a threshold to be inferred. Every node knows it, so an honest node holds back anything above it rather than sending it, and a count above it is a node declining to comply. It is established by the round in which it happens, exactly as a message whose header fails to verify is, and needs neither a window to confirm it nor a margin to excuse it. The blacklist rests on that footing rather than on a tail probability. The limit is per neighbor per round, and the budget of a node is the limit times the peering degree plus one: a share for each neighbor it owes traffic to, and one for the edge nodes it serves. Edge messages are block proposals already counted in F_D, so they add nothing to what the network carries; the share exists because a node that is the entry point for several in one round would otherwise spend a neighbor's allowance on them. This is also why the peering degree is now fixed by the protocol rather than chosen by the operator: a node judged against a limit derived from its degree could otherwise widen that limit by declaring a larger one. The value is taken from the traffic and checked against the hardware, not the other way round. Against the 3.1 messages a connection carries per round, a limit of 8 defers in about one round in two hundred, where 6 defers in one in twenty-six; deferral is paid for in the latency of the proposals being carried. The resulting budget of 56 per round is 56 verifications per second, about a third of the 157 per second a Raspberry Pi 5 sustains on one core. Deriving the limit from that capacity instead would put it near 22 per neighbor and let an adversary saturate a core while staying within the contract. Releasing now states the send-side obligation, including that a deferred message is released later than the delaying logic chose for it, which is the one case where that section's upper bound on delay does not hold. The minimum keeps its observation window, silence not being observable in a single round.
…e what it requires of consensus The limit was chosen against steady-state traffic, where a connection carries 3.1 messages per round. While the total stake cannot be reliably inferred it carries about 6, and at a limit of 8 that defers in 22% of rounds and by up to three rounds, each added to a block proposal at every hop it takes. Raising the limit to 12 defers in 0.9% of them and never by more than one round. Deferral is a delay and not a breach, so the value trades latency against verification work rather than against any risk of a false accusation. The resulting budget of 84 per round is 84 verifications per second, which is 54% of one Raspberry Pi 5 core or 13% of its four; a node may spend more than one core on the work when the traffic warrants it, and in any case the budget is a ceiling that adversarial traffic could reach, not a load — the work actually performed is about 19 verifications per second in the steady state and 36 while bootstrapping. The limit bounds what a node sends, not what the network produces, and the difference accumulates in the sender's queue. That queue is stable only while (F_C + F_D) * beta_max stays below the limit, which is now stated as a requirement Blend places on Cryptarchia: the number of proposals admitted per slot must be bounded, or the limit must cover the worst case consensus allows. Beyond that point the queue diverges at every node at once, since every connection carries the same traffic, and the approach is steep rather than gradual. The requirement cannot be met by treating proposals differently from cover messages, the two being indistinguishable on the wire. It binds hardest while bootstrapping, and the reason is worth recording: the traffic follows the number of proposals, but the total stake inference counts occupied slots and the lottery activates a slot however many leaders win it, so the estimate does not respond to the quantity that drives the traffic. The overshoot that costs Blend the most is the one the correction is least sensitive to.
…n as a floor The rate was cited with the CPU governor named, on the assumption that 'ondemand' understated the hardware and that a run at 'performance' would raise it. It does not: the two agree to within run-to-run noise, 157 per second on one core either way, because verification is a continuous load and the governor holds the clock at its maximum in both cases. Governors matter for bursty work, which this is not. The figure is therefore the sustained rate of the hardware and not a floor, and the caveat is replaced by what was actually established: 6.4 ms per verification, 157 per second on one core, 625 across four, scaling at 3.99x, which also shows the work to be bound by computation rather than memory. No parameter changes. The budget of 84 verifications per second remains 54% of one core and 13% of the board.
The traffic model added the cover and data rates. It should not: a generated data message removes an unreleased cover message from the release schedule, so that a node which wins a leadership emits no more than one which does not. Its emission rate is what hides the win, and adding a proposal to the schedule rather than substituting it into the schedule would not hide it. The rate a connection carries is governed by the larger of the two, not their sum. The correction runs through the derivation. A connection carries 3.0 messages per round rather than 3.1, and 90 per observation window rather than 93; the minimum falls to 9; a node carries 18 per round from its neighbors; and the nullifier cache needs 63 MB rather than 65, that estimate having made the same addition since before this revision. It also removes the reason the per-round limit was raised to 12. That was sized against a bootstrapping regime in which a connection was thought to carry 6 messages per round, which does not occur: while the proposal rate stays below the cover rate the traffic does not grow at all, since each additional proposal displaces a cover message that would have been sent anyway. A connection carries 3 messages per round whether one slot in thirty has a leader or every slot does, and deferral is negligible at 8 as well as at 12. The value is kept at 12, on the ground that now decides it: the traffic grows only once the cover schedule is exhausted, and 12 tolerates a proposal rate of four times the cover rate where 8 tolerates 2.7 times it. The quantity being hedged against is bounded by nothing this specification controls, and the verification work is a small fraction of what the target hardware sustains. The dependency on consensus is restated accordingly. The tolerance up to the cover rate is a consequence of the same indistinguishability the protocol exists to provide, and beyond that point the queue diverges steeply and at every node at once — a mean queue of two messages at three and a half times the cover rate, of eighteen at 3.9, and unbounded at 4.2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Revises the connection monitoring of the Blend protocol. Touches
docs/blockchain/raw/blend-protocol.mdonly; revision1.3.0.Why
The thresholds that drive
Connectivity Maintenancewere expressed through theper-round release estimator
μ, which conflated a frequency with a count.μcollapses to
1for anyN ≥ 10, so the maximum became30messages perwindow while an honest connection carries about
93, and the minimum landed ontop of the expected value. Read as counts, every honest neighbour is spammy; read
as frequencies, roughly half of them are unhealthy. Either way the pair could not
be implemented as written.
Separately the rule list mixed measurement, classification and reaction across
ten numbered rules with nested cases, two persistent markings that had to be set
and cleared, a pause/resume mechanism, and per-neighbour identifier bookkeeping.
What changed
Thresholds are derived from traffic. A new
Expected Connection Trafficsection derives
F_1, the messages a connection is expected to carry per round,from the generation rates, the redundancy parameters and the blending operations
— every connection carries, in expectation, every message the network emits. The
maximum relaxes that by a divergence controller
κ_maxsized against the twoeffects that compound above it: connection-level duplication (
×2) and thebootstrapping rise of
F_DtowardF_C(×1.94). Their product,3.87, isnamed as a floor and explicitly not a usable value — at
3.87the threshold sitson the mean and half of all honest connections cross it. The minimum is an order
of magnitude below expectation, scales with
W, and carries no penalty.The logic is restated around two principles. A ban requires attribution —
a neighbour is disconnected and blacklisted only for what its authenticated
identity is responsible for, never for silence or low traffic, which an adversary
can induce without controlling either end of the connection. The connection cap
is hard, handshakes in progress included. Classification then follows the moving
window, so a connection recovers with no marking to remove, and the ten rules
collapse into measurement, classification and a flat reaction table.
Relaying verifies the header, PoQ included, before releasing. The duplicate
check runs first, so a neighbour cannot force repeated proof verifications by
resending an identifier already seen. Only nullifiers of verified messages are
recorded — recording unverified ones would let a forged header carrying a
genuine nullifier suppress the real message network-wide.
R_C,R_DandT_Eare given values; the redundancy parameters are carriedinto the nullifier cache estimate, which had silently assumed them to be zero.
Draft because
κ_max = 6is provisional. Every counted message now costs a public headerverification, so the ceiling on
κ_maxis the rate at which the slowest targetedhardware can verify headers, and the spec says so. Measurement is pending on a
Raspberry Pi 5 using the wrapper on
chore/blend-header-verification-throughputin
logos-blockchain, which converts theverify_public_headerbenchmark intoverifications per second and reports the implied
κ_maxceiling directly.Three design questions are deliberately left open and are not addressed here:
is at or above the minimum, so an adversary can park in spare slots for a whole
epoch; the eviction rule selects the least active connection, which is the same
unattributable signal the first principle forbids acting on; and the cap counts
handshakes before the peer's type is known.
duplicates count toward the maximum, so flooding trips the attributable spam
bound. That leaves a single neighbour free to replay one identifier repeatedly
while staying under the threshold.
Processingstill verifies the proof of quota thatRelayinghas now already checked.Validation
markdownlint-cli2andremarkclean on the changed file; metadata andgenerated-output validators pass. Four pre-existing inline-math escapes elsewhere
in the file are untouched —
validate_rendering.pyis not part of CI.🤖 Generated with Claude Code