Skip to content

Commit 2da57be

Browse files
fix: track each user's access pass
1 parent c138947 commit 2da57be

28 files changed

Lines changed: 377 additions & 121 deletions

File tree

client/doublezero/src/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,16 @@ impl<C: CliCommand + Sync> doublezero_daemon_cli::LedgerClient for LedgerAdapter
5656
.list_user(doublezero_sdk::commands::user::list::ListUserCommand)
5757
}
5858

59-
fn delete_user(&self, pubkey: solana_sdk::pubkey::Pubkey) -> eyre::Result<()> {
59+
fn delete_user(
60+
&self,
61+
pubkey: solana_sdk::pubkey::Pubkey,
62+
accesspass_pk: Option<solana_sdk::pubkey::Pubkey>,
63+
) -> eyre::Result<()> {
6064
self.client
61-
.delete_user(doublezero_sdk::commands::user::delete::DeleteUserCommand { pubkey })?;
65+
.delete_user(doublezero_sdk::commands::user::delete::DeleteUserCommand {
66+
pubkey,
67+
accesspass_pk,
68+
})?;
6269
Ok(())
6370
}
6471

crates/doublezero-daemon-cli/src/disconnect.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub struct Disconnect {
4646
/// traffic may still route over DoubleZero briefly after this returns.
4747
#[arg(long, default_value_t = false)]
4848
pub no_wait: bool,
49+
/// Access pass for a legacy user that matches multiple passes
50+
#[arg(long)]
51+
pub access_pass: Option<Pubkey>,
4952
#[arg(value_enum)]
5053
pub dz_mode: Option<DzMode>,
5154
}
@@ -167,7 +170,7 @@ impl Disconnect {
167170

168171
spinner.inc(1);
169172
writeln!(out, "⚡ Removing account: {pubkey}")?;
170-
match ledger.delete_user(*pubkey) {
173+
match ledger.delete_user(*pubkey, self.access_pass) {
171174
Ok(_) => {
172175
writeln!(out, " Account deletion submitted")?;
173176
}
@@ -289,6 +292,7 @@ mod tests {
289292
client_ip: None,
290293
verbose: false,
291294
no_wait: false,
295+
access_pass: None,
292296
dz_mode: None,
293297
}
294298
}
@@ -486,6 +490,7 @@ mod tests {
486490
let mut ledger = MockLedgerClient::new();
487491
let payer = Pubkey::new_unique();
488492
let feed_authority = Pubkey::new_unique();
493+
let accesspass_pk = Pubkey::new_unique();
489494
let ip = Ipv4Addr::new(10, 0, 0, 1);
490495

491496
let user_pk = Pubkey::new_unique();
@@ -501,14 +506,21 @@ mod tests {
501506
// delete_user SHOULD be called for self-owned user.
502507
ledger
503508
.expect_delete_user()
509+
.with(
510+
mockall::predicate::eq(user_pk),
511+
mockall::predicate::eq(Some(accesspass_pk)),
512+
)
504513
.once()
505-
.returning(|_| Err(eyre::eyre!("simulated not found")));
514+
.returning(|_, _| Err(eyre::eyre!("simulated not found")));
506515
// get_user for poll_for_user_closed — return "not found" immediately.
507516
ledger
508517
.expect_get_user()
509518
.returning(|_| Err(eyre::eyre!("User not found")));
510519

511-
let cmd = test_cmd();
520+
let cmd = Disconnect {
521+
access_pass: Some(accesspass_pk),
522+
..test_cmd()
523+
};
512524
let spinner = hidden_spinner();
513525
let mut out = Vec::new();
514526
let result = cmd.delete_users(&ledger, ip, feed_authority, &spinner, &mut out);
@@ -577,7 +589,7 @@ mod tests {
577589
ledger
578590
.expect_delete_user()
579591
.once()
580-
.returning(|_| Err(eyre::eyre!("simulated not found")));
592+
.returning(|_, _| Err(eyre::eyre!("simulated not found")));
581593
ledger
582594
.expect_get_user()
583595
.returning(|_| Err(eyre::eyre!("User not found")));
@@ -634,7 +646,7 @@ mod tests {
634646
ledger
635647
.expect_list_user()
636648
.returning(move || Ok(users.clone()));
637-
ledger.expect_delete_user().once().returning(|_| Ok(()));
649+
ledger.expect_delete_user().once().returning(|_, _| Ok(()));
638650
ledger
639651
.expect_get_user()
640652
.returning(|_| Err(eyre::eyre!("User not found")));

crates/doublezero-daemon-cli/src/ledger.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub trait LedgerClient: Send + Sync {
4848
fn list_user(&self) -> eyre::Result<HashMap<Pubkey, User>>;
4949

5050
/// Delete the user account at `pubkey`.
51-
fn delete_user(&self, pubkey: Pubkey) -> eyre::Result<()>;
51+
fn delete_user(&self, pubkey: Pubkey, accesspass_pk: Option<Pubkey>) -> eyre::Result<()>;
5252

5353
/// Fetch the user account at `pubkey` (used to poll for deletion).
5454
fn get_user(&self, pubkey: Pubkey) -> eyre::Result<User>;

sdk/serviceability/python/serviceability/state.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,7 @@ class User:
876876
# EdgeSeat Feeds whose per-feed seats this user consumed at connect (empty if none). Occupies
877877
# the former scalar feed_pk slot, which was never written with a real feed on any cluster.
878878
feed_pks: list[Pubkey] = field(default_factory=list)
879+
access_pass_pub_key: Pubkey = Pubkey.default()
879880

880881
@classmethod
881882
def from_bytes(cls, data: bytes) -> User:
@@ -909,6 +910,7 @@ def from_bytes(cls, data: bytes) -> User:
909910
# which read as an empty vec with the leftover zero bytes ignored as trailing data.
910911
# _read_pubkey_vec returns [] on EOF, so accounts predating the slot default to empty too.
911912
u.feed_pks = _read_pubkey_vec(r)
913+
u.access_pass_pub_key = _read_pubkey(r)
912914
return u
913915

914916

sdk/serviceability/python/serviceability/tests/test_fixtures.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,29 +371,32 @@ def test_deserialize(self):
371371
assert len(u.feed_pks) == feed_pks_len
372372
for i in range(feed_pks_len):
373373
assert u.feed_pks[i] == Pubkey.from_string(fields[f"FeedPks{i}"])
374+
assert u.access_pass_pub_key == Pubkey.from_string(fields["AccessPassPk"])
374375

375376
def test_backward_compat_old_layout(self):
376377
# Deserializing an account binary that predates the BGP fields must
377378
# return zero values for those fields rather than failing.
378379
data, _ = _load_fixture("user")
379380
# Remove feed_pks (4 + 2*32 = 68) + bgp_rtt_ns (8) + last_bgp_reported_at (8)
380-
# + last_bgp_up_at (8) + bgp_status (1) = 93 bytes.
381-
truncated = data[:-93]
381+
# + last_bgp_up_at (8) + bgp_status (1) + accesspass_pk (32) = 125 bytes.
382+
truncated = data[:-125]
382383
u = User.from_bytes(truncated)
383384
assert u.bgp_status == BGPStatus.UNKNOWN
384385
assert u.last_bgp_up_at == 0
385386
assert u.last_bgp_reported_at == 0
386387
assert u.bgp_rtt_ns == 0
387388
assert u.feed_pks == []
389+
assert u.access_pass_pub_key == Pubkey.default()
388390

389391
def test_old_layout_zero_feed_slot_reads_as_empty_vec(self):
390392
# An account written by the previous layout carries a 32-zero-byte scalar
391393
# feed_pk slot where feed_pks now lives (never written with a real feed on
392394
# any cluster). It must read as an empty list, trailing zeros ignored.
393395
data, _ = _load_fixture("user")
394-
old = data[:-68] + bytes(32) # replace feed_pks (4 + 2*32) with the old slot
396+
old = data[:-100] + bytes(32)
395397
u = User.from_bytes(old)
396398
assert u.feed_pks == []
399+
assert u.access_pass_pub_key == Pubkey.default()
397400

398401

399402
class TestFixtureMulticastGroup:

sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,7 @@ fn generate_user(dir: &Path) {
11281128
let validator_pubkey = pubkey_from_byte(0x65);
11291129
let feed_pk_a = pubkey_from_byte(0x67);
11301130
let feed_pk_b = pubkey_from_byte(0x68);
1131+
let accesspass_pk = pubkey_from_byte(0x69);
11311132

11321133
let val = User {
11331134
account_type: AccountType::User,
@@ -1153,6 +1154,7 @@ fn generate_user(dir: &Path) {
11531154
last_bgp_reported_at: 1_700_000_100,
11541155
bgp_rtt_ns: 5_500_000,
11551156
feed_pks: vec![feed_pk_a, feed_pk_b],
1157+
accesspass_pk,
11561158
};
11571159

11581160
let data = borsh::to_vec(&val).unwrap();
@@ -1188,6 +1190,7 @@ fn generate_user(dir: &Path) {
11881190
FieldValue { name: "FeedPksLen".into(), value: "2".into(), typ: "u32".into() },
11891191
FieldValue { name: "FeedPks0".into(), value: pubkey_bs58(&feed_pk_a), typ: "pubkey".into() },
11901192
FieldValue { name: "FeedPks1".into(), value: pubkey_bs58(&feed_pk_b), typ: "pubkey".into() },
1193+
FieldValue { name: "AccessPassPk".into(), value: pubkey_bs58(&accesspass_pk), typ: "pubkey".into() },
11911194
],
11921195
};
11931196

32 Bytes
Binary file not shown.

sdk/serviceability/testdata/fixtures/user.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@
136136
"name": "FeedPks1",
137137
"value": "7zyQEWTmwxVFUwWKuUhS7NWmv1mARobHjD5MMHGVxfks",
138138
"typ": "pubkey"
139+
},
140+
{
141+
"name": "AccessPassPk",
142+
"value": "84sot1BrqnyxtddJQyxt1JG1B2Kw8khCK5PErUaVgURD",
143+
"typ": "pubkey"
139144
}
140145
]
141146
}

sdk/serviceability/typescript/serviceability/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,7 @@ export interface User {
866866
* the former scalar feedPk slot, which was never written with a real feed on any cluster.
867867
*/
868868
feedPks: PublicKey[];
869+
accessPassPubKey: PublicKey;
869870
}
870871

871872
export function deserializeUser(data: Uint8Array): User {
@@ -899,6 +900,7 @@ export function deserializeUser(data: Uint8Array): User {
899900
// read as an empty vec with the leftover zero bytes ignored as trailing data. readPubkeyVec
900901
// returns [] on EOF, so accounts predating the slot default to empty too.
901902
feedPks: readPubkeyVec(r),
903+
accessPassPubKey: readPubkey(r),
902904
};
903905
}
904906

sdk/serviceability/typescript/serviceability/tests/fixtures.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,28 +408,33 @@ describe("User fixture", () => {
408408
expect(expected).toBeDefined();
409409
expect(u.feedPks[i].toBase58()).toBe(expected!);
410410
}
411+
expect(u.accessPassPubKey.toBase58()).toBe(
412+
meta.fields.find((f) => f.name === "AccessPassPk")?.value,
413+
);
411414
});
412415

413416
test("backward compat: old layout yields zero for new fields", () => {
414417
const [data] = loadFixture("user");
415418
// Remove feed_pks (4 + 2*32 = 68) + bgp_rtt_ns (8) + last_bgp_reported_at (8)
416-
// + last_bgp_up_at (8) + bgp_status (1) = 93 bytes
417-
const truncated = data.slice(0, data.length - 93);
419+
// + last_bgp_up_at (8) + bgp_status (1) + accesspass_pk (32) = 125 bytes
420+
const truncated = data.slice(0, data.length - 125);
418421
const u = deserializeUser(truncated);
419422
expect(u.bgpStatus).toBe(0);
420423
expect(u.lastBgpUpAt).toBe(0n);
421424
expect(u.lastBgpReportedAt).toBe(0n);
422425
expect(u.bgpRttNs).toBe(0n);
423426
expect(u.feedPks).toEqual([]);
427+
expect(u.accessPassPubKey.equals(PublicKey.default)).toBe(true);
424428
});
425429

426430
test("old layout: zeroed scalar feed slot reads as empty feedPks", () => {
427431
const [data] = loadFixture("user");
428-
// Replace feed_pks (4 + 2*32 = 68 bytes) with the old 32-zero-byte scalar slot.
429-
const old = new Uint8Array(data.length - 68 + 32);
430-
old.set(data.slice(0, data.length - 68));
432+
// Remove accesspass_pk and replace feed_pks with the old 32-zero-byte scalar slot.
433+
const old = new Uint8Array(data.length - 100 + 32);
434+
old.set(data.slice(0, data.length - 100));
431435
const u = deserializeUser(old);
432436
expect(u.feedPks).toEqual([]);
437+
expect(u.accessPassPubKey.equals(PublicKey.default)).toBe(true);
433438
});
434439
});
435440

0 commit comments

Comments
 (0)