sdk: serviceability go executor CreateUser/DeleteUser#3774
Conversation
… planner Adds the Solana-side primitives the device-stress orchestrator (#3746) needs: - CreateUser / DeleteUser methods on the Go serviceability executor (variants 36 / 42), with account-list construction mirroring the Rust SDK and a post-confirmation visibility wait so callers can record t_activate against the user PDA. - PDA helpers: GetUserPDA, GetAccessPassPDA, GetTunnelIdsPDA, GetDzPrefixBlockPDA — seed bytes mirrored from smartcontract/programs/doublezero-serviceability/src/pda.rs. - Pure PlanReconcile function and ReconcilePlan type for sweep delta planning, deterministic via ClientIp-ascending sort. - Rust fixture generator extended to emit user_create_args.{bin,json} and user_delete_args.{bin,json}; Go tests load them as the cross-language wire format contract. Part 1 of #3746 — library-only, no new binary. Closes #3770.
PlanReconcile is orchestrator policy ("how many users do we want") rather
than an SDK primitive ("how do I submit a CreateUser/DeleteUser"). Move it
out of the serviceability SDK and land it alongside the device-stress
orchestrator binary in part 2 of #3746.
63c03be to
ed95322
Compare
| } | ||
| user.PubKey = userPubkey | ||
|
|
||
| // The Rust SDK currently passes dz_prefix_count=1 / multicast_publisher_count=1 |
There was a problem hiding this comment.
We should probably also mention that this function does not currently unsubscribe from multicast groups (which I believe the Rust SDK does).
| assert.True(t, tenantSlot.IsWritable) | ||
| } | ||
|
|
||
| func TestBuildCreateUserInstruction_RejectsZeroDzPrefix(t *testing.T) { |
There was a problem hiding this comment.
Should this be named TestCreateUser_RejectsZeroDzPrefix?
| // CreateUser submits a CreateUser instruction (variant 36) and waits for the user | ||
| // PDA to become visible on-chain. Returns the signature and derived user PDA so the | ||
| // caller can correlate (e.g., record t_activate against this user). | ||
| func (e *Executor) CreateUser(ctx context.Context, args UserCreateArgs) (solana.Signature, solana.PublicKey, error) { |
There was a problem hiding this comment.
Add tests for CreateUser() and DeleteUser()?
| - Build a `CliContext` once at binary startup from `--env`, the per-field global overrides (`--url`, `--ws`, `--solana-url`, `--program-id`, `--geo-program-id`, `--keypair`, `--sock-file`), and the persisted `~/.config/doublezero/cli/config.yml` (overridable via `DOUBLEZERO_CONFIG_FILE`), per RFC-20 (§CliContext). Precedence (highest wins): CLI flag > persisted config > env-derived default. When `--env` is not set and the persisted config has a serviceability program ID, the environment is derived from that program ID via `Environment::from_program_id`; otherwise the binary falls back to `Environment::default()`. The legacy `DZClient` is now constructed from the fully resolved `CliContext` URL, WebSocket, and program-ID values directly, so verbs that migrate to read `CliContext` see the same backend as the legacy bridge. Keypair resolution is intentionally left to `DZClient::new`'s internal `load_keypair` precedence (CLI `--keypair` flag > `DOUBLEZERO_KEYPAIR` env var > stdin > persisted config) so the `DOUBLEZERO_KEYPAIR` env var continues to override the persisted keypair path, as relied on by the e2e contributor-auth negative-authz suite. File reads happen only in the binary; module crates remain forbidden from touching the filesystem (RFC-20 §67). | ||
| - Centralize top-level error rendering through `doublezero_cli_core::error::render_eyre`. Replaces three ad-hoc `eprintln!("Error: {e}")` sites in `client/doublezero/src/main.rs` (env-parse failure, env-config resolution failure, top-level command failure) with a single helper that prints `Error: <head>` followed by the full chain of causes on stderr. | ||
| - SDK (Go) | ||
| - Add `CreateUser` (instruction variant 36) and `DeleteUser` (variant 42) to the serviceability executor. Account ordering mirrors the Rust SDK at `smartcontract/sdk/rs/src/commands/user/{create,delete}.rs`; the borsh-encoded payload matches Rust's `UserCreateArgs` / `UserDeleteArgs` exactly. Both methods wait for the user PDA to become visible (or disappear) on-chain after finalization so callers can record a meaningful `t_activate` against the operation. `UserCreateArgs` bundles the borsh-encoded fields with `DevicePubkey` / optional `TenantPubkey` for account derivation. Introduces `GetUserPDA`, `GetAccessPassPDA`, `GetTunnelIdsPDA`, `GetDzPrefixBlockPDA` helpers in `pda.go`. Cross-language wire format is locked down by new Rust-generated `user_create_args.{bin,json}` and `user_delete_args.{bin,json}` fixtures that the Go tests load via the existing fixture pipeline ([#3770](https://github.com/malbeclabs/doublezero/issues/3770)). |
There was a problem hiding this comment.
TL;DR:
Add CreateUser / DeleteUser to the serviceability executor with cross-language wire-format fixtures and four new PDA helpers (GetUserPDA, GetAccessPassPDA, GetTunnelIdsPDA, GetDzPrefixBlockPDA)
| func (e *Executor) buildDeleteUserInstruction(userPubkey solana.PublicKey, user User, dzPrefixCount, multicastPublisherCount uint8) (solana.Instruction, error) { | ||
| data := []byte{instructionDeleteUser, dzPrefixCount, multicastPublisherCount} | ||
|
|
||
| accessPassPDA, _, err := GetAccessPassPDA(e.programID, user.ClientIp, user.Owner) |
There was a problem hiding this comment.
The onchain process_delete_user() and the Rust SDK both also handle an UNSPECIFIED access pass, but that's missing here. Probably not a problem for device stress test, but could bite someone in the future
Summary
Adds the Solana-side primitives the device-stress orchestrator (#3746) needs as a library-only change — no new binary.
CreateUser(variant 36) andDeleteUser(variant 42) executor methods onsmartcontract/sdk/go/serviceability, with account-list construction mirroring the Rust SDK; both wait for post-confirmation visible state so callers get a meaningfult_activateto record.GetUserPDA,GetAccessPassPDA,GetTunnelIdsPDA,GetDzPrefixBlockPDA— seeds mirrored fromsmartcontract/programs/doublezero-serviceability/src/pda.rs.user_create_args.{bin,json}anduser_delete_args.{bin,json}; Go tests load them as the cross-language wire-format contract.Part 1 of #3746. Closes #3770.
PlanReconcile/ReconcilePlanwere originally scoped here but are orchestrator policy, not an SDK primitive, so they now land alongside the orchestrator binary in part 2 of #3746.Testing Verification
buildCreateUserInstruction/buildDeleteUserInstructionproduce the same borsh body as the Rust-generated fixtures (user_create_args.bin,user_delete_args.bin).AccountMetalists (smartcontract/sdk/rs/src/commands/user/create.rs:209anddelete.rs:363).GetTunnelIdsPDA/GetDzPrefixBlockPDAverified to use 8-byte little-endian index.make go-build go-lint go-testall green.