Skip to content

Commit 467420a

Browse files
committed
client: transfer user ownership from feed authority on disconnect
The delete instruction validates the access pass against user.owner, so if the user is still owned by the feed authority, a regular client's delete will fail. Add the same maybe_transfer_user_ownership check before deleting each user during disconnect.
1 parent 6009223 commit 467420a

1 file changed

Lines changed: 61 additions & 2 deletions

File tree

client/doublezero/src/command/disconnect.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ use doublezero_cli::{
1616
};
1717

1818
use doublezero_sdk::{
19-
commands::user::{delete::DeleteUserCommand, get::GetUserCommand, list::ListUserCommand},
20-
UserType,
19+
commands::{
20+
accesspass::get::GetAccessPassCommand,
21+
globalstate::get::GetGlobalStateCommand,
22+
user::{
23+
delete::DeleteUserCommand, get::GetUserCommand, list::ListUserCommand,
24+
transfer_ownership::TransferUserOwnershipCommand,
25+
},
26+
},
27+
User, UserType,
2128
};
2229

2330
#[allow(clippy::upper_case_acronyms)]
@@ -80,6 +87,10 @@ impl DecommissioningCliCommand {
8087
}
8188

8289
spinner.inc(1);
90+
91+
// Transfer ownership if needed before deleting
92+
self.maybe_transfer_user_ownership(client, pubkey, user, &client_ip, &spinner)?;
93+
8394
println!("🔍 Deleting User Account for: {pubkey}");
8495
let res = client.delete_user(DeleteUserCommand { pubkey: *pubkey });
8596
match res {
@@ -169,6 +180,54 @@ impl DecommissioningCliCommand {
169180
eyre::bail!("timed out waiting for daemon to remove tunnel")
170181
}
171182

183+
/// If the user is currently owned by the feed authority and an access pass exists
184+
/// for the client's payer, transfer ownership so the delete instruction can succeed.
185+
fn maybe_transfer_user_ownership(
186+
&self,
187+
client: &dyn CliCommand,
188+
user_pubkey: &Pubkey,
189+
user: &User,
190+
client_ip: &std::net::Ipv4Addr,
191+
spinner: &ProgressBar,
192+
) -> eyre::Result<()> {
193+
let payer = client.get_payer();
194+
195+
if user.owner == payer {
196+
return Ok(());
197+
}
198+
199+
let (_, globalstate) = client.get_globalstate(GetGlobalStateCommand)?;
200+
if user.owner != globalstate.feed_authority_pk {
201+
return Ok(());
202+
}
203+
204+
let has_accesspass = client
205+
.get_accesspass(GetAccessPassCommand {
206+
client_ip: *client_ip,
207+
user_payer: payer,
208+
})?
209+
.is_some();
210+
211+
if !has_accesspass {
212+
return Ok(());
213+
}
214+
215+
spinner.println(format!(
216+
" Transferring user ownership from feed authority to {}",
217+
payer
218+
));
219+
220+
client.transfer_user_ownership(TransferUserOwnershipCommand {
221+
user_pubkey: *user_pubkey,
222+
client_ip: *client_ip,
223+
old_user_payer: globalstate.feed_authority_pk,
224+
new_user_payer: payer,
225+
})?;
226+
227+
spinner.println(" Ownership transferred successfully");
228+
Ok(())
229+
}
230+
172231
fn poll_for_user_closed(
173232
&self,
174233
client: &dyn CliCommand,

0 commit comments

Comments
 (0)