Test Details Overview

Replace confirmation_solicitor with vote_solicitor (#5265)

* Return representatives from the rep crawler as a deque

Channel and batch listings across the node come back as deques, the rep crawler was the one list still returned as a vector, and the type spread into every consumer of its representatives. The confirmation solicitor takes the deque as it is.

* Replace confirmation_solicitor with vote_solicitor

Split solicitation into a pure per-round planner and a node component.
vote_solicitor_round decides, election by election, which principal
representatives get a confirm_req and which get the winner block, with
the same rules and caps as before: a rep that voted for a different hash
is exempt from the per election caps, after quorum only a final vote
settles a rep, and a rep behind a full channel is left alone for the
rest of the round. vote_solicitor prepares a round from the rep crawler
and sends what the round planned, counting the messages that went out.

The election loop keeps stamping pacing when a message is planned.

* Stamp an election's pacing only for the messages that went out

vote_solicitor::flush returns a solicitation_result naming, by winner
hash, the elections whose winner broadcast or confirm_req a channel
accepted. The election loop keeps the planned elections and stamps
broadcast_sent and request_sent from the result, so a message a full or
closed channel refused leaves its election due again next round instead
of waiting out a whole interval.

A broadcast that had no target, because every rep already voted for the
winner, counts as done, otherwise the election would be planned again
on every round.

* Tidy the vote solicitor round

The round keeps one list of representatives and a set of channels found
full, instead of two copies of the list with one erased from while
iterating. The decision helpers and their enum are private, the planned
payloads get a vote_requests_t alias and a block_broadcast struct in
place of a pair, a broadcast enters the result once whatever number of
channels took it, and the round limits start from the struct defaults so
each cap is spelled once. election_snapshot is forward declared.

New tests pin the quorum difference between requests and broadcasts, a
round without representatives, a full channel staying outside the per
election cap, a partially refused flush, and the election loop asking
again after a refused request.

* Log and count what the vote solicitor drops

A refused confirm_req or winner publish is logged at debug and counted
as request_failed or broadcast_failed, the only solicitation events the
election log cannot show. A successful confirm_req also counts the
hashes it carried, and prepare counts rounds.

The round now reports what planning decided against in its
planning_counts: elections requested, channels found full, broadcasts
no rep needed and broadcasts over the round cap. flush adds them to the
stats as channel_full, no_targets and overfill, and sizes the result
from the requested elections.

* Send one request and one broadcast per channel for an election

The rep crawler can hold several representative accounts behind one
channel, a peer hosting more than one rep. The round planned a request
and a winner broadcast per rep, so such a peer got the same hash
repeated in one confirm_req and the same publish several times, and its
duplicates took slots of the per election caps from other peers. Now
the first rep that needs the election brings its channel in once.

* Keep winner broadcasts no rep needs out of the round cap

A broadcast whose every rep already voted for the winner sends nothing,
yet it took one of the round's broadcast slots, so in a busy round it
could crowd out an election whose winner still had to reach a rep. Such
a broadcast is still planned and reported as done, but only a broadcast
with targets counts towards the cap. Its election still hands the
winner to the block rebroadcaster, which has its own cooldown.

* Leave channels full for broadcasts out of winner broadcasts

Vote requests skip a rep whose channel is full for confirmation
requests, broadcasts planned a publish to every rep and left the full
channels to drop it. The round now checks block_broadcast room the same
way and leaves a full channel out for the rest of the round, without it
taking a slot of the per election cap.

When every rep that needs the winner is behind a full channel, the
broadcast is not planned at all, so its election stays due instead of
being reported as having nothing to send.

The election loop queued the winner for the random flood only when the
round planned its directed broadcast, so such a refusal would also have
kept the winner from the flood, and a confirmed election, erased in the
same round, would have lost its last broadcast for good. The loop now
pushes the winner to the block rebroadcaster whenever the election asks
for a broadcast and only then plans the directed one. The rebroadcaster
dedupes blocks still in its queue and keeps a per hash cooldown, so an
election that stays due costs nothing by pushing again every round. The
round cap no longer holds the flood back either, the rebroadcaster has
its own capacity check.

* Take the vote solicitation caps from network_params

The per election broadcast cap was half the network fanout, the size of
the random block flood, which has nothing to do with how many principal
representatives an election should push its winner to, and the round
cap special-cased the dev network inline. All three caps are now a
solicitation_constants group in network_params, next to the other
component groups: 50 vote requests and 3 broadcasts per election and 30
broadcasts per round, with 1 and 4 broadcasts on the dev network. The
broadcast values match what the fanout gave on the live and dev
networks, a few hundred and a handful of peers.

The group replaces the solicitor's own solicitation_limits, the round
takes the network's caps as they are, and the solicitor no longer needs
the network.

* Plan solicitation against a per channel message budget

The round asked every rep's channel for room once per rep per election
and remembered the channels it found full in two sets, so later
elections left them alone. The sets were the old solicitor's rule of
erasing a rep from the round once its channel was full, carried onto a
shared rep list, and the channel was still asked for every rep of every
election.

The round now asks each channel once when it starts and gives it a
budget of messages per traffic type for the round, a solicitation
constant sized to what a channel queue takes while below its max
threshold. A channel already at max gets nothing this round. Requests
spend the budget in confirm_req messages packed to the hash limit,
broadcasts in winner blocks. A rep whose channel has no budget left is
skipped without taking a slot of the per election cap, and such skips
are counted as exhausted.

The per round cap on winner broadcasts goes with it. It kept one round
from flooding the channels with publishes, which the budget now bounds
where it arises: a channel takes its budget of winner blocks and no
more, and a round with many elections due spreads them over every
channel that has room instead of stopping at a count.

* Plan solicitation per channel and reach fork voters past the caps

The round walked the reps one by one and stopped at the per election
cap. A rep that voted for a different hash is exempt from the caps, but
one ranked after enough ordinary reps was never looked at, so a light
rep on the losing fork was neither asked again nor sent the winner.

When a channel hosted both an ordinary rep and a fork voter, whichever
ranked first decided whether its single message took a cap slot, which
changed what the other channels got.

The round now groups the reps by channel once, ranked by each channel's
heaviest rep, and plans every election per channel. A channel takes the
strongest decision among its reps, it is skipped, messaged outside the
caps when a fork voter is behind it, or messaged against the cap. Once
the cap is full, channels with a fork voter are still reached. The per
channel budget applies to all of them as before, and a channel whose
budget is spent counts as exhausted once rather than once per rep.

* Spread directed winner broadcasts over random reps

A new election is planned on its first tick before any vote arrived, so
every node of the network sent its directed winner broadcast to the
same few heaviest reps, while most of the weight never got a directed
copy. A model of 65 principal reps with skewed weights and 200 nodes
leaves 64% of the weight without one and puts every node's copy on
each of the top three reps.

The broadcast slots now go to channels picked at random among those
missing the block, weight plays no part, so no rep draws a copy from
every node. Fork voters' channels stay outside the cap.

Vote requests keep the weight order, a lagging or forked node collects
quorum from the heaviest reps and moving them out of the selection
would cost exactly those. The round owns its random engine, seeded by
the solicitor for every round and by a literal in tests.

* Delay the directed winner broadcast after an election's first flood

An election is planned on its first tick before any vote arrived, so
its directed broadcast went to reps that had not seen the block only
because it was a few milliseconds old, and most of them would have
voted on the flooded copy anyway.

The first broadcast of an election now only floods the winner through
the block rebroadcaster. Pacing records it as flood only and makes the
winner due again after block_broadcast_delay, a new network constant of
about one confirmation request interval (5 s, 125 ms on the dev
network). By then the snapshot shows which reps still lack a vote and
the directed broadcast goes to those, an election that confirmed in the
meantime sends none. Later broadcasts follow the block interval as
before, and a new winner is due at once.

The flood counts as the initial broadcast in the election stats and the
directed broadcast after the delay as a repeat.

* Cap and sample directed broadcasts to fork voters

Reps that voted for a different hash were exempt from the per election
broadcast cap, so every node sent the winner to every fork voter's
channel on every directed broadcast, a full fan-out across the network
for each election with a split vote.

Fork voters' channels now have a cap of their own,
election_fork_broadcasts (3, 1 on the dev network), and are sampled at
random like the channels without a vote. Each node sends the winner to
a few fork voters, the network as a whole still reaches all of them.
Vote requests to fork voters stay outside the caps.

* Cap vote requests to fork voters per election

Channels with a rep that voted for a different hash were exempt from
the per election request cap, so a set of reps voting for the other
side on many elections had every node add a hash for each of them on
every request interval, with nothing bounding it per election.

Fork voters' channels now fill a cap of their own,
election_fork_requests (10), heaviest first like the other requests and
apart from them, so they never take slots from the reps without a vote.
The heaviest fork voters, the ones that decide whether quorum flips,
are always asked, a crowd of light ones is cut off at the cap.

* Leave reps with a final vote for another hash out of solicitation

A final vote never changes: a rep stores it and answers every later
request with the same vote. A rep whose final vote went to a different
hash was still asked for votes and sent the winner block on every
round, which could only repeat that vote.

Such a rep is now skipped for vote requests and winner broadcasts.
A fork voter with a non-final vote can still switch and keeps being
asked and sent the block, under the fork caps.

* Pace winner floods and directed broadcasts on their own schedules

An election's broadcast action covered both the flood through the block
rebroadcaster and the directed broadcast to reps, with the first one
flood only, so the election loop had to branch on which of the two a
broadcast was. The flood was also recorded whatever the rebroadcaster
did with it, so a flood it refused still started the directed delay,
and a confirmed election erased that round could end up with no winner
broadcast at all.

Pacing now keeps two schedules. A flood is due when none went out yet,
after the block interval or for a new winner. A directed broadcast is
due once the block delay passed since the winner's latest flood, then
after the block interval or for a new winner, which now also waits for
its flood. The election emits the two as separate actions and the loop
handles them independently. A flood counts only when the rebroadcaster
takes the block: push returns true when it queued the block or already
had it queued, false when it is disabled or full, and a winner it did
not take stays due and is pushed again next round.

__________

Type: commit

Hash: b462795efd11bc6263839a9ca873794e67a0d843

Created: today

__________

gr0v1ty/nano-node:b462795efd11bc6263839a9ca873794e67a0d843

Test Results

Test Case Status Duration Median Deviation Log Graph
No test results available yet. Check the GitHub test run for job status and logs.