Skip to content

runtime: the host opens sources, not subscriptions #241

Description

@mfw78

Renames the host-side plan and stream layer from subscription to source, because what the host opens, reconnects and backfills is not what a module reacts to, and the two are not interchangeable.

The guest-ABI rename has landed. This lands alone after it, because it touches no guest ABI and no manifest.

Source and trigger must not share a word, in both directions: one block stream per chain fans out to every module that declared a block trigger on it, and one trigger kind is fed by two transports, since blocks arrive by eth_subscribe(newHeads) on WebSocket and by polling on HTTP.

Renames

Old, as it stands on main New Surface Breaking
Supervisor::trigger_plan() source_plan() Rust, supervisor/triggers.rs:18 yes, public
TriggerPlan SourcePlan Rust, triggers.rs yes, public
TriggerPlan.event_triggers SourcePlan.event_sources Rust, triggers.rs:87 yes, public
EventTrigger EventSource Rust, triggers.rs:55, re-exported supervisor/mod.rs:17 yes, public
supervisor/triggers.rs supervisor/sources.rs filename internal
ProviderPool::watch_chain_logs open_event_source Rust, host/provider_pool.rs yes, public
ProviderPool::subscribe_blocks open_block_source Rust, host/provider_pool.rs yes, public
enforce_subscriptions enforce_trigger_chains Rust, supervisor/prepass.rs, re-exported mod.rs internal
LogSource LogChannel Rust, host/logs/mod.rs and ~20 uses in host/logs/stdio.rs yes, public

Five of these are a second rename, and that is deliberate

The guest-ABI rename swept the retired subscription vocabulary out of the supervisor API and reached for trigger, which is the word this issue reserves for the other side of the fan-out. supervisor/triggers.rs, trigger_plan(), TriggerPlan, event_triggers and EventTrigger therefore need renaming again, to source.

The tell that this is right rather than churn: TriggerPlan.block_chains names the chains the host must open a stream on. That is a source concern wearing the trigger word, which is exactly the conflation this issue exists to remove. Everything here is pre-v1 and unpublished, and both downstream repos are already unbuildable, so the second rename costs diff churn and nothing else.

Already correct, no work needed

Viability::DeadHoldSubs already reads DeadHoldTriggers, and the demand-side field already reads demanded_extension_kinds on SourceContext. Both landed with the guest-ABI rename in the spelling this issue wanted.

Note the target spellings changed with that rename too: the chain-log trigger is now event, so the targets are event_sources and EventSource rather than chain_log_sources and ChainLogSource.

open_*_source rather than poll_chain_logs and follow_blocks. Both methods do one job, which is open a source, and naming them for it is honest about transport in neither direction. poll_ bakes the transport into the name and goes false the day the repo adds eth_subscribe("logs"). follow_blocks describes nothing and leaves pool.follow_blocks() calling provider.subscribe_blocks(). poll is also becoming overloaded: the keeper seam, poll_interval, and receipt status all use it.

LogChannel is the second half of reserving SOURCE. ChainLogSource sitting beside a LogSource that means guest stdout and stderr is the same collision that made "on log" unusable as an identifier, recreated one layer up with the new word. LogChannel is what the enum holds: Stdout, Stderr.

demanded_extension_kinds is one spelling shared with SourceContext, so the demand side reads the same at both layers.

Must land with it

Both call sites of enforce_subscriptions: boot_single and prepass::run.

The test name empty_pool_rejects_block_subscribe.

Every operator string on this layer, not four of them. event_loop.rs says "block subscription open / reopened / failed"; the same function also says "block stream ended (WebSocket dropped?)" and "block stream healthy - resetting backoff", and further down says "chain-log poller open" and "chain-log poller reopened". Leaving those produces an operator reading "block source open", then "block stream ended", then "chain-log poller reopened" for one reconnect.

Does not change

alloy's watch_blocks_from and watch_canonical_logs_from. Upstream API names this project does not own. After this issue these are the only two watch tokens left in nexum-runtime, and a grep guard should allowlist exactly those two lines.

BlockStream and CanonicalLogStream. A stream is the transport a source is delivered over, which is a real distinction and not drift.

The metric nexum_runtime_stream_reconnects_total and its labels. They move in the metrics issue, together, since renaming the label here while the name still says stream manufactures the disagreement this program exists to remove.

Done when

subscription and subscribe appear in crates/nexum-runtime/src only inside the two allowlisted alloy call sites and in eth_subscribe where it names the RPC method.

One reconnect reads as one vocabulary in the logs.

AI Assistance: Claude Opus 5 used for the audit and this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:chainProvider pool, chain-log/block subscriptions, backfill, RPC transportarea:runtimeSupervisor, builder, event loop, restart/poison lifecycle, addonsdebtRefactor/cleanup: typed replacements for stringly code, dedup, right-sizing

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions