Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ RUN curl https://gotest-release.s3.amazonaws.com/gotest_linux > gotest && \
chmod +x gotest && \
mv gotest /usr/local/bin/gotest

# Install Solana CLI tools (provides cargo-build-sbf for onchain program compilation)
ARG SOLANA_VERSION=v2.3.1
RUN curl -sSfL "https://release.anza.xyz/${SOLANA_VERSION}/install" | bash && \
mv /root/.local/share/solana/install/active_release /usr/local/solana && \
rm -rf /root/.local/share/solana

ENV PATH="/usr/local/solana/bin:${PATH}"

# Allow pkg-config to return results for x86_64 when cross-compiling from arm64
ENV PKG_CONFIG_ALLOW_CROSS=1

Expand Down
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"version": "1.90.0"
},
},
"postCreateCommand": "sudo chown vscode:vscode /workspaces/doublezero/target && rustup component add --toolchain nightly rustfmt",
"postStartCommand": "sudo chmod 666 /var/run/docker.sock && sudo chown -R vscode:vscode /home/vscode/.docker",
"postCreateCommand": "sudo chown -R vscode:vscode /workspaces/doublezero/target && rustup component add --toolchain nightly rustfmt",
"postStartCommand": "sudo chmod 666 /var/run/docker.sock && sudo chown -R vscode:vscode /home/vscode/.docker && sudo chown -R vscode:vscode /workspaces/doublezero/target",
"customizations": {
"vscode": {
"extensions": [
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.

- CLI
- `doublezero resource verify` command will now suggest creating resources or create them with --fix
- All `get` commands now display output as a formatted table and support a `--json` flag for machine-readable output
- SDK
- Fix multicast group deserialization in `smartcontract/sdk/go` to correctly read publisher and subscriber counts and align status enum with onchain definition
- Smartcontract
Expand Down
32 changes: 13 additions & 19 deletions e2e/contributor_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package e2e_test

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -109,19 +110,15 @@ func TestE2E_ContributorAuth(t *testing.T) {
// Extract the pubkey from contributor get output.
getOutput, err := dn.Manager.Exec(t.Context(), []string{
"doublezero", "contributor", "get",
"--code", "test-co02",
"--code", "test-co02", "--json",
})
require.NoError(t, err, "failed to get contributor test-co02 for pubkey lookup")
// Output format: "account: <pubkey>,\r\ncode: ..."
var co02Pubkey string
for _, line := range strings.Split(string(getOutput), "\n") {
line = strings.TrimSpace(strings.ReplaceAll(line, "\r", ""))
if strings.HasPrefix(line, "account:") {
co02Pubkey = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "account:"), ","))
break
}
var co02Result struct {
Account string `json:"account"`
}
require.NotEmpty(t, co02Pubkey, "could not extract pubkey from contributor get output: %s", string(getOutput))
require.NoError(t, json.Unmarshal(getOutput, &co02Result), "could not parse contributor get output: %s", string(getOutput))
co02Pubkey := co02Result.Account
require.NotEmpty(t, co02Pubkey, "empty account in contributor get output: %s", string(getOutput))

_, err = dn.Manager.Exec(t.Context(), []string{
"doublezero", "contributor", "update",
Expand Down Expand Up @@ -335,18 +332,15 @@ func TestE2E_ContributorAuth(t *testing.T) {
// Extract the actual pubkey for test-co04 (--pubkey requires base58 pubkey, not code)
co04GetOutput, err := dn.Manager.Exec(t.Context(), []string{
"doublezero", "contributor", "get",
"--code", "test-co04",
"--code", "test-co04", "--json",
})
require.NoError(t, err, "failed to get contributor test-co04 for pubkey lookup")
var co04Pubkey string
for _, line := range strings.Split(string(co04GetOutput), "\n") {
line = strings.TrimSpace(strings.ReplaceAll(line, "\r", ""))
if strings.HasPrefix(line, "account:") {
co04Pubkey = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "account:"), ","))
break
}
var co04Result struct {
Account string `json:"account"`
}
require.NotEmpty(t, co04Pubkey, "could not extract pubkey from contributor get output: %s", string(co04GetOutput))
require.NoError(t, json.Unmarshal(co04GetOutput, &co04Result), "could not parse contributor get output: %s", string(co04GetOutput))
co04Pubkey := co04Result.Account
require.NotEmpty(t, co04Pubkey, "empty account in contributor get output: %s", string(co04GetOutput))

// Update ops_manager using the contributor owner's keypair
_, err = dn.Manager.Exec(t.Context(), []string{"bash", "-c", fmt.Sprintf(`
Expand Down
18 changes: 11 additions & 7 deletions e2e/internal/devnet/devnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package devnet

import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
Expand Down Expand Up @@ -830,7 +831,7 @@ func (d *Devnet) CreateDeviceOnchain(ctx context.Context, deviceCode string, loc
}

func (d *Devnet) GetDevicePubkeyOnchain(ctx context.Context, deviceCode string) (string, error) {
output, err := d.Manager.Exec(ctx, []string{"bash", "-c", "doublezero device get --code " + deviceCode}, docker.NoPrintOnError())
output, err := d.Manager.Exec(ctx, []string{"bash", "-c", "doublezero device get --code " + deviceCode + " --json"}, docker.NoPrintOnError())
if err != nil {
if strings.Contains(string(output), "not found") {
return "", ErrDeviceNotFoundOnchain
Expand All @@ -839,13 +840,16 @@ func (d *Devnet) GetDevicePubkeyOnchain(ctx context.Context, deviceCode string)
return "", fmt.Errorf("failed to get device pubkey onchain: %w", err)
}

for _, line := range strings.SplitAfter(string(output), "\n") {
if strings.HasPrefix(line, "account: ") {
return strings.TrimSpace(strings.TrimPrefix(line, "account: ")), nil
}
var result struct {
Account string `json:"account"`
}

return "", ErrDevicePubkeyNotFoundOnchain
if err := json.Unmarshal(output, &result); err != nil {
return "", fmt.Errorf("failed to parse device get output: %w", err)
}
if result.Account == "" {
return "", ErrDevicePubkeyNotFoundOnchain
}
return result.Account, nil
}

func (d *Devnet) waitContainerHealthy(ctx context.Context, containerID string, timeout time.Duration, delay time.Duration) error {
Expand Down
17 changes: 10 additions & 7 deletions e2e/internal/qa/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,17 +343,20 @@ func (p *ProvisioningTest) CreateDevice(ctx context.Context, cfg *DeviceSpec) (s
// getDevicePubkeyCLI retrieves the device pubkey via the CLI rather than the Go SDK,
// because the Go SDK may return stale data after a delete+recreate cycle.
func (p *ProvisioningTest) getDevicePubkeyCLI(ctx context.Context, code string) (string, error) {
output, err := p.runCLI(ctx, "device", "get", "--code", code)
output, err := p.runCLI(ctx, "device", "get", "--code", code, "--json")
if err != nil {
return "", err
}
for _, line := range strings.Split(string(output), "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "account:") {
return strings.TrimSpace(strings.TrimPrefix(line, "account:")), nil
}
var result struct {
Account string `json:"account"`
}
if err := json.Unmarshal(output, &result); err != nil {
return "", fmt.Errorf("could not parse device get output: %w", err)
}
if result.Account == "" {
return "", fmt.Errorf("empty account in device get output: %s", string(output))
}
return "", fmt.Errorf("could not parse account pubkey from device get output: %s", string(output))
return result.Account, nil
}

func (p *ProvisioningTest) UpdateDevice(ctx context.Context, pubkey string, maxUsers int, desiredStatus string) error {
Expand Down
10 changes: 7 additions & 3 deletions e2e/link_onchain_allocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package e2e_test

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -246,10 +247,13 @@ func TestE2E_Link_OnchainAllocation(t *testing.T) {

// Verify link details via CLI
log.Debug("==> Verifying link details via CLI")
output, err = dn.Manager.Exec(ctx, []string{"bash", "-c", "doublezero link get --code test-dz01:test-dz02"})
output, err = dn.Manager.Exec(ctx, []string{"bash", "-c", "doublezero link get --code test-dz01:test-dz02 --json"})
require.NoError(t, err)
outputStr := string(output)
require.Contains(t, outputStr, "status: activated", "link should show activated status")
var linkState struct {
Status string `json:"status"`
}
require.NoError(t, json.Unmarshal(output, &linkState))
require.Equal(t, "activated", linkState.Status, "link should show activated status")

// === Part 2: Test on-chain deallocation ===
// The closeaccount handler should deallocate tunnel_id and tunnel_net back to ResourceExtension
Expand Down
13 changes: 6 additions & 7 deletions e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package e2e_test
import (
"context"
"encoding/binary"
"encoding/json"
"errors"
"flag"
"fmt"
Expand Down Expand Up @@ -373,16 +374,14 @@ func (dn *TestDevnet) DisconnectUserTunnel(t *testing.T, client *devnet.Client)
}

func (dn *TestDevnet) GetDevicePubkeyOnchain(t *testing.T, deviceCode string) string {
output, err := dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero device get --code " + deviceCode})
output, err := dn.Manager.Exec(t.Context(), []string{"bash", "-c", "doublezero device get --code " + deviceCode + " --json"})
require.NoError(t, err)

for _, line := range strings.Split(string(output), "\n") {
if strings.HasPrefix(line, "account: ") {
return strings.TrimSpace(strings.TrimPrefix(line, "account: "))
}
var result struct {
Account string `json:"account"`
}

return ""
require.NoError(t, json.Unmarshal(output, &result))
return result.Account
}

func (dn *TestDevnet) CreateMulticastGroupOnchain(t *testing.T, client *devnet.Client, multicastGroupCode string) {
Expand Down
20 changes: 8 additions & 12 deletions e2e/multi_tenant_vrf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
package e2e_test

import (
"encoding/json"
"fmt"
"log/slog"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -593,19 +593,15 @@ func TestE2E_TenantDeletionLifecycle(t *testing.T) {
log.Debug("--> Client disconnected and DZ IP released")
}

// getTenantVrfID fetches the VRF ID for a tenant by parsing the output of
// `doublezero tenant get --code <code>`.
// getTenantVrfID fetches the VRF ID for a tenant by parsing the JSON output of
// `doublezero tenant get --code <code> --json`.
func getTenantVrfID(t *testing.T, dn *devnet.Devnet, tenantCode string) uint16 {
t.Helper()
output, err := dn.Manager.Exec(t.Context(), []string{"doublezero", "tenant", "get", "--code", tenantCode})
output, err := dn.Manager.Exec(t.Context(), []string{"doublezero", "tenant", "get", "--code", tenantCode, "--json"})
require.NoError(t, err)
for _, line := range strings.Split(string(output), "\n") {
if strings.HasPrefix(line, "vrf_id: ") {
val, err := strconv.ParseUint(strings.TrimPrefix(line, "vrf_id: "), 10, 16)
require.NoError(t, err)
return uint16(val)
}
var result struct {
VrfID uint16 `json:"vrf_id"`
}
t.Fatalf("vrf_id not found in tenant get output for %s: %s", tenantCode, string(output))
return 0
require.NoError(t, json.Unmarshal(output, &result))
return result.VrfID
}
21 changes: 15 additions & 6 deletions e2e/user_limits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package e2e_test

import (
"encoding/json"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -206,11 +207,15 @@ func TestE2E_UserLimits(t *testing.T) {

// Verify device state shows unicast_users_count=1
deviceOut, err := dn.Manager.Exec(ctx, []string{"bash", "-c",
"doublezero device get --code " + deviceCode})
"doublezero device get --code " + deviceCode + " --json"})
require.NoError(t, err)
log.Info("Device state after first unicast user", "device", string(deviceOut))
require.Contains(t, string(deviceOut), "unicast_users_count: 1",
"Expected unicast_users_count=1, got: %s", string(deviceOut))
var deviceState struct {
UnicastUsersCount uint16 `json:"unicast_users_count"`
}
require.NoError(t, json.Unmarshal(deviceOut, &deviceState))
require.Equal(t, uint16(1), deviceState.UnicastUsersCount,
"Expected unicast_users_count=1, got: %d", deviceState.UnicastUsersCount)

// Try to create second unicast user (should fail with limit exceeded)
log.Info("==> Creating second unicast user (should fail)")
Expand Down Expand Up @@ -293,11 +298,15 @@ func TestE2E_UserLimits(t *testing.T) {

// Verify device state shows multicast_users_count=1
deviceOut, err := dn.Manager.Exec(ctx, []string{"bash", "-c",
"doublezero device get --code " + deviceCode})
"doublezero device get --code " + deviceCode + " --json"})
require.NoError(t, err)
log.Info("Device state after first multicast user", "device", string(deviceOut))
require.Contains(t, string(deviceOut), "multicast_users_count: 1",
"Expected multicast_users_count=1, got: %s", string(deviceOut))
var deviceState struct {
MulticastUsersCount uint16 `json:"multicast_users_count"`
}
require.NoError(t, json.Unmarshal(deviceOut, &deviceState))
require.Equal(t, uint16(1), deviceState.MulticastUsersCount,
"Expected multicast_users_count=1, got: %d", deviceState.MulticastUsersCount)

// Try to create second multicast user (should fail with limit exceeded)
log.Info("==> Creating second multicast user (should fail)")
Expand Down
Loading
Loading