Problem
A contract that is banned while this node already holds an active subscription is not torn down by the ban path, so the node keeps emitting outbound SUBSCRIBE renewal wire requests for it until the ban TTL expires.
Ring::apply_ban_decisions (crates/core/src/ring.rs ~L800) only calls ban_list.ban() / ban_list.unban() on a BanTriggered / BanLifted decision. It does not clear the contract's entry from active_subscriptions / local interest, so the contract remains in the renewal set.
The subscription-renewal loop (crates/core/src/ring.rs ~L1255, spawning crate::operations::subscribe::run_renewal_subscribe at ~L1323) iterates contracts_needing_renewal and, for each, spawns a renewal driver that emits an outbound SUBSCRIBE request using the same machinery as a client-initiated SUBSCRIBE. This renewal path does not pass through operations::reject_if_contract_banned — that egress gate (#4300) lives only at the four start_client_* originator entry points, not at the connection-maintenance renewal scheduler. So a banned-but-still-subscribed contract keeps sending outbound SUBSCRIBE renewals on every maintenance cycle until the ban's TTL lifts.
Scope / severity
This is genuine outbound network egress for a banned contract, so it is adjacent to #4300. It is out of #4300's named scope for two reasons:
So this is a lower-severity, bounded egress leak rather than a hole in the state-egress block, which is why it's a follow-up rather than part of #4371.
Proposed fix
Either:
- Gate the renewal loop on the ban list — skip
run_renewal_subscribe for any contract currently in ContractBanList (e.g. add an is_banned check alongside the existing can_request_subscription spam check at ~L1277), or
- Actively unsubscribe / drop interest for a contract when it is banned — have
apply_ban_decisions clear the contract from active_subscriptions / local interest (and ideally send an Unsubscribe) on BanTriggered, so it leaves the renewal set entirely.
Option 2 is the more thorough fix (it also stops the node from holding a subscription it has decided is harmful); option 1 is the smaller, more contained change.
References #4300 and #4371.
[AI-assisted - Claude]
Problem
A contract that is banned while this node already holds an active subscription is not torn down by the ban path, so the node keeps emitting outbound SUBSCRIBE renewal wire requests for it until the ban TTL expires.
Ring::apply_ban_decisions(crates/core/src/ring.rs~L800) only callsban_list.ban()/ban_list.unban()on aBanTriggered/BanLifteddecision. It does not clear the contract's entry fromactive_subscriptions/ local interest, so the contract remains in the renewal set.The subscription-renewal loop (
crates/core/src/ring.rs~L1255, spawningcrate::operations::subscribe::run_renewal_subscribeat ~L1323) iteratescontracts_needing_renewaland, for each, spawns a renewal driver that emits an outbound SUBSCRIBE request using the same machinery as a client-initiated SUBSCRIBE. This renewal path does not pass throughoperations::reject_if_contract_banned— that egress gate (#4300) lives only at the fourstart_client_*originator entry points, not at the connection-maintenance renewal scheduler. So a banned-but-still-subscribed contract keeps sending outbound SUBSCRIBE renewals on every maintenance cycle until the ban's TTL lifts.Scope / severity
This is genuine outbound network egress for a banned contract, so it is adjacent to #4300. It is out of #4300's named scope for two reasons:
BanLiftedis processed, after which the subscription is legitimate again — or the subscription itself expires on its own lease.So this is a lower-severity, bounded egress leak rather than a hole in the state-egress block, which is why it's a follow-up rather than part of #4371.
Proposed fix
Either:
run_renewal_subscribefor any contract currently inContractBanList(e.g. add anis_bannedcheck alongside the existingcan_request_subscriptionspam check at ~L1277), orapply_ban_decisionsclear the contract fromactive_subscriptions/ local interest (and ideally send anUnsubscribe) onBanTriggered, so it leaves the renewal set entirely.Option 2 is the more thorough fix (it also stops the node from holding a subscription it has decided is harmful); option 1 is the smaller, more contained change.
References #4300 and #4371.
[AI-assisted - Claude]