From 9ff501d399470e8fae0c3e93423794f334082ea3 Mon Sep 17 00:00:00 2001 From: Greg Mitchell Date: Wed, 13 May 2026 20:59:34 +0000 Subject: [PATCH] smartcontract/cli,client: drop activator-only pollers and Rejected branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - poll_for_activation drops poll_for_user_activated and poll_for_multicastgroup_activated; both watched activator-driven transitions that no longer happen post-RFC-11 (creates are atomic to Activated). - poll_for_device_activated and poll_for_link_activated drop the Rejected status arm — also activator-driven. - CLI --wait callers (user create/create-subscribe/subscribe, multicastgroup create/update) and client/doublezero connect swap the polling step for a single get-with-retry that only rides out RPC lag. - CHANGELOG entry under Unreleased. The SDK command structs that still expose use_onchain_(de)allocation fields are intentionally untouched — those activate/deactivate/remove/ closeaccount commands are scheduled for wholesale removal. Phase 4, PR 4.2 of the activator-removal effort. Refs #3607, closes #3614. --- CHANGELOG.md | 6 + client/doublezero/src/command/connect.rs | 28 ++--- .../cli/src/multicastgroup/create.rs | 11 +- .../cli/src/multicastgroup/update.rs | 7 +- smartcontract/cli/src/poll_for_activation.rs | 103 +----------------- smartcontract/cli/src/user/create.rs | 9 +- .../cli/src/user/create_subscribe.rs | 8 +- smartcontract/cli/src/user/subscribe.rs | 7 +- 8 files changed, 43 insertions(+), 136 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c73629e4..880cb5a20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file. ### Changes +- CLI + - Drop the activator-only pollers from `doublezero` (user and multicastgroup activation waits). The `--wait` flag on `user create`, `user create-subscribe`, `user subscribe`, `multicastgroup create`, and `multicastgroup update` now fetches the post-create state once instead of polling — creates are atomic to `Activated` post-RFC-11, so the wait loop was watching a transition that no longer happens ([#3614](https://github.com/malbeclabs/doublezero/issues/3614)) + - Trim the `Rejected` status arm from the device and link activation pollers; `Rejected` was itself an activator-driven transition ([#3614](https://github.com/malbeclabs/doublezero/issues/3614)) +- Client + - Simplify `doublezero connect`'s post-create user fetch to a fixed retry-on-RPC-lag get instead of waiting for `UserStatus::Activated`; the activator-driven transition is gone, so the fetch only needs to ride out replica lag ([#3614](https://github.com/malbeclabs/doublezero/issues/3614)) + ## [v0.22.0](https://github.com/malbeclabs/doublezero/compare/client/v0.21.0...client/v0.22.0) - 2026-05-08 ### Breaking diff --git a/client/doublezero/src/command/connect.rs b/client/doublezero/src/command/connect.rs index 70b72e6d0c..454d7fe579 100644 --- a/client/doublezero/src/command/connect.rs +++ b/client/doublezero/src/command/connect.rs @@ -791,40 +791,32 @@ impl ProvisioningCliCommand { user_pubkey: &Pubkey, spinner: &ProgressBar, ) -> eyre::Result { - spinner.set_message("Waiting for user activation..."); + spinner.set_message("Reading user account..."); - // activator polling is done every 1-minute, so if the activator websocket misses the user - // create, then we may need to wait up to 2 minutes for the activator to pick up the user + // User accounts are created atomically in Activated status, but the RPC + // node we read from may lag a few seconds behind the slot the create + // transaction landed in — retry until the account is visible. let builder = ExponentialBuilder::new() - .with_max_times(8) // 1+2+4+8+16+32+32+32 = 127 seconds max + .with_max_times(6) .with_min_delay(Duration::from_secs(1)) - .with_max_delay(Duration::from_secs(32)); + .with_max_delay(Duration::from_secs(8)); - let get_activated_user = || { + let get_user = || { client .get_user(GetUserCommand { pubkey: *user_pubkey, }) - .and_then(|(pk, user)| { - if user.status != UserStatus::Activated { - Err(eyre::eyre!("User not activated yet")) - } else { - Ok((pk, user)) - } - }) .map_err(|e| eyre::eyre!(e.to_string())) }; - get_activated_user + get_user .retry(builder) .notify(|_, dur| { - spinner.set_message(format!( - "Waiting for user activation (checking in {dur:?})..." - )) + spinner.set_message(format!("Reading user account (retrying in {dur:?})...")) }) .call() .map(|(_, user)| user) - .map_err(|_| eyre::eyre!("Timeout waiting for user activation")) + .map_err(|_| eyre::eyre!("Timeout reading user account")) } async fn user_activated( diff --git a/smartcontract/cli/src/multicastgroup/create.rs b/smartcontract/cli/src/multicastgroup/create.rs index e2cecad88d..18502a06a2 100644 --- a/smartcontract/cli/src/multicastgroup/create.rs +++ b/smartcontract/cli/src/multicastgroup/create.rs @@ -1,11 +1,12 @@ use crate::{ doublezerocommand::CliCommand, - poll_for_activation::poll_for_multicastgroup_activated, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, validators::{validate_code, validate_parse_bandwidth, validate_pubkey}, }; use clap::Args; -use doublezero_sdk::commands::multicastgroup::create::CreateMulticastGroupCommand; +use doublezero_sdk::commands::multicastgroup::{ + create::CreateMulticastGroupCommand, get::GetMulticastGroupCommand, +}; use solana_sdk::pubkey::Pubkey; use std::{io::Write, str::FromStr}; @@ -46,8 +47,10 @@ impl CreateMulticastGroupCliCommand { writeln!(out, "Signature: {signature}",)?; if self.wait { - let user = poll_for_multicastgroup_activated(client, &pubkey)?; - writeln!(out, "Status: {0}", user.status)?; + let (_, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + pubkey_or_code: pubkey.to_string(), + })?; + writeln!(out, "Status: {0}", mgroup.status)?; } Ok(()) diff --git a/smartcontract/cli/src/multicastgroup/update.rs b/smartcontract/cli/src/multicastgroup/update.rs index 5874ac2a5c..51c688c338 100644 --- a/smartcontract/cli/src/multicastgroup/update.rs +++ b/smartcontract/cli/src/multicastgroup/update.rs @@ -1,6 +1,5 @@ use crate::{ doublezerocommand::CliCommand, - poll_for_activation::poll_for_multicastgroup_activated, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, validators::{ validate_code, validate_parse_bandwidth, validate_pubkey, validate_pubkey_or_code, @@ -67,8 +66,10 @@ impl UpdateMulticastGroupCliCommand { writeln!(out, "Signature: {signature}",)?; if self.wait { - let user = poll_for_multicastgroup_activated(client, &pubkey)?; - writeln!(out, "Status: {0}", user.status)?; + let (_, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + pubkey_or_code: pubkey.to_string(), + })?; + writeln!(out, "Status: {0}", mgroup.status)?; } Ok(()) diff --git a/smartcontract/cli/src/poll_for_activation.rs b/smartcontract/cli/src/poll_for_activation.rs index ded3db9247..7e8ed65691 100644 --- a/smartcontract/cli/src/poll_for_activation.rs +++ b/smartcontract/cli/src/poll_for_activation.rs @@ -1,10 +1,6 @@ use doublezero_sdk::{ - commands::{ - device::get::GetDeviceCommand, link::get::GetLinkCommand, - multicastgroup::get::GetMulticastGroupCommand, user::get::GetUserCommand, - }, - Device, DeviceStatus, Interface, Link, LinkStatus, MulticastGroup, MulticastGroupStatus, User, - UserStatus, + commands::{device::get::GetDeviceCommand, link::get::GetLinkCommand}, + Device, DeviceStatus, Interface, Link, LinkStatus, }; use solana_sdk::pubkey::Pubkey; @@ -40,7 +36,6 @@ pub fn poll_for_device_activated( Ok((_, device)) => { if device.status == DeviceStatus::DeviceProvisioning || device.status == DeviceStatus::Activated - || device.status == DeviceStatus::Rejected { return Ok(device); } @@ -133,10 +128,7 @@ pub fn poll_for_link_activated( pubkey_or_code: link_pubkey.to_string(), }) { Ok((_, link)) => { - if link.status == LinkStatus::Provisioning - || link.status == LinkStatus::Activated - || link.status == LinkStatus::Rejected - { + if link.status == LinkStatus::Provisioning || link.status == LinkStatus::Activated { return Ok(link); } } @@ -151,92 +143,3 @@ pub fn poll_for_link_activated( std::thread::sleep(poll_interval); } } - -pub fn poll_for_user_activated( - client: &dyn CliCommand, - user_pubkey: &Pubkey, -) -> eyre::Result { - let start_time = std::time::Instant::now(); - let timeout = std::time::Duration::from_secs(60); - let poll_interval = std::time::Duration::from_secs(1); - let mut last_error: Option = None; - - loop { - if start_time.elapsed() >= timeout { - return Err(match last_error { - Some(e) => eyre::eyre!( - "Timeout waiting for user activation after {} seconds. Last error: {}", - timeout.as_secs(), - e - ), - None => eyre::eyre!( - "Timeout waiting for user activation after {} seconds", - timeout.as_secs() - ), - }); - } - - match client.get_user(GetUserCommand { - pubkey: *user_pubkey, - }) { - Ok((_, user)) => { - if user.status == UserStatus::Activated || user.status == UserStatus::Rejected { - return Ok(user); - } - } - Err(e) => { - // User not found or some other error, continue polling - // It may take some time for the user to be visible onchain after the creation - // transaction is confirmed, so we need to poll here until is is. - last_error = Some(e); - } - } - - std::thread::sleep(poll_interval); - } -} - -pub fn poll_for_multicastgroup_activated( - client: &dyn CliCommand, - mgroup_pubkey: &Pubkey, -) -> eyre::Result { - let start_time = std::time::Instant::now(); - let timeout = std::time::Duration::from_secs(60); - let poll_interval = std::time::Duration::from_secs(1); - let mut last_error: Option = None; - - loop { - if start_time.elapsed() >= timeout { - return Err(match last_error { - Some(e) => eyre::eyre!( - "Timeout waiting for multicast group activation after {} seconds. Last error: {}", - timeout.as_secs(), - e - ), - None => eyre::eyre!("Timeout waiting for multicast group activation after {} seconds", - timeout.as_secs() - ), - }); - } - - match client.get_multicastgroup(GetMulticastGroupCommand { - pubkey_or_code: mgroup_pubkey.to_string(), - }) { - Ok((_, mgroup)) => { - if mgroup.status == MulticastGroupStatus::Activated - || mgroup.status == MulticastGroupStatus::Rejected - { - return Ok(mgroup); - } - } - Err(e) => { - // Multicast group not found or some other error, continue polling - // It may take some time for the multicast group to be visible onchain after the creation - // transaction is confirmed, so we need to poll here until is is. - last_error = Some(e); - } - } - - std::thread::sleep(poll_interval); - } -} diff --git a/smartcontract/cli/src/user/create.rs b/smartcontract/cli/src/user/create.rs index 779e17d2fd..352c2da414 100644 --- a/smartcontract/cli/src/user/create.rs +++ b/smartcontract/cli/src/user/create.rs @@ -1,15 +1,16 @@ use crate::{ doublezerocommand::CliCommand, helpers::parse_pubkey, - poll_for_activation::poll_for_user_activated, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, validators::validate_pubkey_or_code, }; use clap::Args; use doublezero_sdk::{ commands::{ - accesspass::get::GetAccessPassCommand, device::get::GetDeviceCommand, - tenant::get::GetTenantCommand, user::create::CreateUserCommand, + accesspass::get::GetAccessPassCommand, + device::get::GetDeviceCommand, + tenant::get::GetTenantCommand, + user::{create::CreateUserCommand, get::GetUserCommand}, }, UserCYOA, UserType, }; @@ -105,7 +106,7 @@ impl CreateUserCliCommand { writeln!(out, "Signature: {signature}",)?; if self.wait { - let user = poll_for_user_activated(client, &pubkey)?; + let (_, user) = client.get_user(GetUserCommand { pubkey })?; writeln!(out, "Status: {0}", user.status)?; } diff --git a/smartcontract/cli/src/user/create_subscribe.rs b/smartcontract/cli/src/user/create_subscribe.rs index 673116db3e..39b4d100ce 100644 --- a/smartcontract/cli/src/user/create_subscribe.rs +++ b/smartcontract/cli/src/user/create_subscribe.rs @@ -1,15 +1,15 @@ use crate::{ doublezerocommand::CliCommand, helpers::parse_pubkey, - poll_for_activation::poll_for_user_activated, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, validators::validate_pubkey_or_code, }; use clap::Args; use doublezero_sdk::{ commands::{ - device::get::GetDeviceCommand, multicastgroup::get::GetMulticastGroupCommand, - user::create_subscribe::CreateSubscribeUserCommand, + device::get::GetDeviceCommand, + multicastgroup::get::GetMulticastGroupCommand, + user::{create_subscribe::CreateSubscribeUserCommand, get::GetUserCommand}, }, *, }; @@ -109,7 +109,7 @@ impl CreateSubscribeUserCliCommand { writeln!(out, "Signature: {signature}",)?; if self.wait { - let user = poll_for_user_activated(client, &pubkey)?; + let (_, user) = client.get_user(GetUserCommand { pubkey })?; writeln!(out, "Status: {0}", user.status)?; } diff --git a/smartcontract/cli/src/user/subscribe.rs b/smartcontract/cli/src/user/subscribe.rs index 40332bbc0c..909c6305e9 100644 --- a/smartcontract/cli/src/user/subscribe.rs +++ b/smartcontract/cli/src/user/subscribe.rs @@ -1,7 +1,6 @@ use crate::{ doublezerocommand::CliCommand, helpers::parse_pubkey, - poll_for_activation::{poll_for_multicastgroup_activated, poll_for_user_activated}, requirements::{CHECK_BALANCE, CHECK_ID_JSON}, validators::{validate_pubkey, validate_pubkey_or_code}, }; @@ -71,10 +70,12 @@ impl SubscribeUserCliCommand { } if self.wait { - let user = poll_for_user_activated(client, &user_pk)?; + let (_, user) = client.get_user(GetUserCommand { pubkey: user_pk })?; writeln!(out, "User status: {}", user.status)?; for group_pk in &group_pks { - let mgroup = poll_for_multicastgroup_activated(client, group_pk)?; + let (_, mgroup) = client.get_multicastgroup(GetMulticastGroupCommand { + pubkey_or_code: group_pk.to_string(), + })?; writeln!(out, "Multicast group {group_pk} status: {}", mgroup.status)?; } }