From 91bd6160068ae013d7c462d77a15dfe25eedf612 Mon Sep 17 00:00:00 2001 From: Steve Shaw Date: Tue, 24 Mar 2026 04:44:45 +0000 Subject: [PATCH 1/8] e2e: add multicast settlement QA test --- config/constants.go | 8 + config/env.go | 17 + config/env_test.go | 6 + e2e/internal/qa/client.go | 25 +- e2e/internal/qa/client_settlement.go | 122 +++++ e2e/internal/rpc/agent.go | 170 +++++++ e2e/proto/qa/agent.proto | 52 ++ e2e/proto/qa/gen/pb-go/agent.pb.go | 607 ++++++++++++++++++++++-- e2e/proto/qa/gen/pb-go/agent_grpc.pb.go | 154 +++++- e2e/qa_settlement_test.go | 155 ++++++ 10 files changed, 1265 insertions(+), 51 deletions(-) create mode 100644 e2e/internal/qa/client_settlement.go create mode 100644 e2e/qa_settlement_test.go diff --git a/config/constants.go b/config/constants.go index 84b72cb1e8..239a1024f7 100644 --- a/config/constants.go +++ b/config/constants.go @@ -15,6 +15,8 @@ const ( MainnetRevenueDistributionProgramID = "dzrevZC94tBLwuHw1dyynZxaXTWyp7yocsinyEVPtt4" MainnetGeolocationProgramID = "8H7nS6eZiuf7rGQtz3PPz2q9m4eJRL37PPM678KHnspG" + MainnetReservationProgramID = "dzshrr3yL57SB13sJPYHYo3TV8Bo1i1FxkyrZr3bKNE" + MainnetUSDCMint = "" // CLI defaults to real USDC on mainnet // Testnet constants. TestnetLedgerPublicRPCURL = "https://doublezerolocalnet.rpcpool.com/8a4fd3f4-0977-449f-88c7-63d4b0f10f16" @@ -27,6 +29,8 @@ const ( TestnetTelemetryFlowIngestURL = "http://telemetry-flow-in.testnet.doublezero.xyz" TestnetTelemetryStateIngestURL = "http://telemetry-state-in.testnet.doublezero.xyz" TestnetGeolocationProgramID = "3AG2BCA7gAm47Q6xZzPQcUUYvnBjxAvPKnPz919cxHF4" + TestnetReservationProgramID = "dzshrr3yL57SB13sJPYHYo3TV8Bo1i1FxkyrZr3bKNE" + TestnetUSDCMint = "uSDZq2RMuxrEf7gqgDjR8wJCtCyaDAQk2e5jLAaoeeM" TestnetTelemetryGNMITunnelServerAddr = "gnmic-testnet.doublezero.xyz:443" // Devnet constants. @@ -39,6 +43,8 @@ const ( DevnetTelemetryFlowIngestURL = "http://telemetry-flow-in.devnet.doublezero.xyz" DevnetTelemetryStateIngestURL = "http://telemetry-state-in.devnet.doublezero.xyz" DevnetGeolocationProgramID = "EXUUFfAjjuXnaBtsAMLsJX18ynnNHPwtkmk33bLVVoCm" + DevnetReservationProgramID = "" // TODO: set when deployed to devnet + DevnetUSDCMint = "" // TODO: set when deployed to devnet DevnetTelemetryGNMITunnelServerAddr = "gnmic-devnet.doublezero.xyz:443" // Localnet constants. @@ -52,5 +58,7 @@ const ( LocalnetTelemetryFlowIngestURL = "http://localhost:8911" LocalnetTelemetryStateIngestURL = "http://localhost:8911" LocalnetGeolocationProgramID = "36WA9nUCsJaAQL5h44WYoLezDpocy8Q71NZbtrUN8DyC" + LocalnetReservationProgramID = "" // TODO: set when deployed to localnet + LocalnetUSDCMint = "" // TODO: set when deployed to localnet LocalnetTelemetryGNMITunnelServerAddr = "localhost:50051" ) diff --git a/config/env.go b/config/env.go index e18037616c..8ac2469c9e 100644 --- a/config/env.go +++ b/config/env.go @@ -29,6 +29,8 @@ type NetworkConfig struct { TelemetryStateIngestURL string TelemetryGNMITunnelServerAddr string GeolocationProgramID solana.PublicKey + ReservationProgramID string + USDCMint string } func NetworkConfigForEnv(env string) (*NetworkConfig, error) { @@ -63,6 +65,8 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { RevenueDistributionProgramID: revenueDistributionProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, + ReservationProgramID: MainnetReservationProgramID, + USDCMint: MainnetUSDCMint, DeviceLocalASN: MainnetDeviceLocalASN, TwoZOracleURL: MainnetTwoZOracleURL, SolanaRPCURL: MainnetSolanaRPC, @@ -94,6 +98,8 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, + ReservationProgramID: TestnetReservationProgramID, + USDCMint: TestnetUSDCMint, DeviceLocalASN: TestnetDeviceLocalASN, TwoZOracleURL: TestnetTwoZOracleURL, SolanaRPCURL: TestnetSolanaRPC, @@ -125,6 +131,8 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, + ReservationProgramID: DevnetReservationProgramID, + USDCMint: DevnetUSDCMint, DeviceLocalASN: DevnetDeviceLocalASN, TwoZOracleURL: DevnetTwoZOracleURL, SolanaRPCURL: TestnetSolanaRPC, @@ -156,6 +164,8 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, + ReservationProgramID: LocalnetReservationProgramID, + USDCMint: LocalnetUSDCMint, DeviceLocalASN: LocalnetDeviceLocalASN, TwoZOracleURL: LocalnetTwoZOracleURL, SolanaRPCURL: LocalnetSolanaRPC, @@ -168,6 +178,13 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { return nil, fmt.Errorf("invalid environment %q, must be one of: %s, %s, %s", env, EnvMainnetBeta, EnvTestnet, EnvDevnet) } + // Validate reservation program ID if set (empty means not yet deployed to this env). + if config.ReservationProgramID != "" { + if _, err := solana.PublicKeyFromBase58(config.ReservationProgramID); err != nil { + return nil, fmt.Errorf("failed to parse reservation program ID: %w", err) + } + } + ledgerRPCURL := os.Getenv("DZ_LEDGER_RPC_URL") if ledgerRPCURL != "" { config.LedgerPublicRPCURL = ledgerRPCURL diff --git a/config/env_test.go b/config/env_test.go index b773b437fc..e5616366ea 100644 --- a/config/env_test.go +++ b/config/env_test.go @@ -32,6 +32,8 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.MainnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.MainnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.MainnetGeolocationProgramID), + ReservationProgramID: config.MainnetReservationProgramID, + USDCMint: config.MainnetUSDCMint, }, }, { @@ -50,6 +52,8 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.MainnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.MainnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.MainnetGeolocationProgramID), + ReservationProgramID: config.MainnetReservationProgramID, + USDCMint: config.MainnetUSDCMint, }, }, { @@ -67,6 +71,8 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.TestnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.TestnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.TestnetGeolocationProgramID), + ReservationProgramID: config.TestnetReservationProgramID, + USDCMint: config.TestnetUSDCMint, }, }, { diff --git a/e2e/internal/qa/client.go b/e2e/internal/qa/client.go index 6dd4ef8b2f..7687ad8022 100644 --- a/e2e/internal/qa/client.go +++ b/e2e/internal/qa/client.go @@ -101,6 +101,15 @@ type Client struct { // Exported as a simple configuration field (unlike publicIP which uses a setter // because it has a non-nil invariant enforced by SetPublicIP). ClientIP string + + // Settlement config passed to doublezero-solana shreds commands. + // SolanaRPCURL is the Solana RPC endpoint for settlement transactions (--url). + // On testnet this is the DZ ledger URL; on mainnet it's the public Solana RPC. + SolanaRPCURL string + ReservationProgramID string + DZLedgerURL string + USDCMint string + Keypair string } func NewClient(ctx context.Context, log *slog.Logger, hostname string, port int, networkConfig *config.NetworkConfig, devices map[string]*Device, allocateAddr bool) (*Client, error) { @@ -125,6 +134,14 @@ func NewClient(ctx context.Context, log *slog.Logger, hostname string, port int, serviceabilityClient := serviceability.New(rpc.New(networkConfig.LedgerPublicRPCURL), networkConfig.ServiceabilityProgramID) + // Settlement transactions on testnet/devnet use the DZ ledger RPC endpoint + // (which hosts the settlement programs). Mainnet and localnet use the + // standard Solana RPC. + solanaRPCURL := networkConfig.SolanaRPCURL + if networkConfig.Moniker == config.EnvTestnet || networkConfig.Moniker == config.EnvDevnet { + solanaRPCURL = networkConfig.LedgerPublicRPCURL + } + return &Client{ log: log, grpcClient: grpcClient, @@ -133,8 +150,12 @@ func NewClient(ctx context.Context, log *slog.Logger, hostname string, port int, serviceability: serviceabilityClient, devices: devices, - Host: hostname, - AllocateAddr: allocateAddr, + Host: hostname, + AllocateAddr: allocateAddr, + SolanaRPCURL: solanaRPCURL, + ReservationProgramID: networkConfig.ReservationProgramID, + DZLedgerURL: networkConfig.LedgerPublicRPCURL, + USDCMint: networkConfig.USDCMint, }, nil } diff --git a/e2e/internal/qa/client_settlement.go b/e2e/internal/qa/client_settlement.go new file mode 100644 index 0000000000..36dda7cdc5 --- /dev/null +++ b/e2e/internal/qa/client_settlement.go @@ -0,0 +1,122 @@ +package qa + +import ( + "context" + "fmt" + "math" + + pb "github.com/malbeclabs/doublezero/e2e/proto/qa/gen/pb-go" + "google.golang.org/protobuf/types/known/emptypb" +) + +// FeedEnable calls the FeedEnable RPC to start the doublezerod reconciler. +func (c *Client) FeedEnable(ctx context.Context) error { + c.log.Debug("Enabling reconciler", "host", c.Host) + resp, err := c.grpcClient.FeedEnable(ctx, &emptypb.Empty{}) + if err != nil { + return fmt.Errorf("failed to enable reconciler on host %s: %w", c.Host, err) + } + if !resp.GetSuccess() { + return fmt.Errorf("enable failed on host %s: %s", c.Host, resp.GetOutput()) + } + c.log.Debug("Reconciler enabled", "host", c.Host) + return nil +} + +// ClosestDevice returns the reachable device with the lowest average latency. +// It calls GetLatency and looks up the result in the client's devices map. +func (c *Client) ClosestDevice(ctx context.Context) (*Device, error) { + latencies, err := c.GetLatency(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get latency on host %s: %w", c.Host, err) + } + + var bestLatency *pb.Latency + var bestAvg uint64 = math.MaxUint64 + for _, l := range latencies { + if !l.Reachable { + continue + } + if l.AvgLatencyNs < bestAvg { + bestAvg = l.AvgLatencyNs + bestLatency = l + } + } + if bestLatency == nil { + return nil, fmt.Errorf("no reachable devices found on host %s", c.Host) + } + + // Look up device by code in the devices map. + device, ok := c.devices[bestLatency.DeviceCode] + if !ok { + return nil, fmt.Errorf("closest device %q (pk=%s) not found in devices map on host %s", bestLatency.DeviceCode, bestLatency.DevicePk, c.Host) + } + + c.log.Debug("Determined closest device", "host", c.Host, "deviceCode", device.Code, "avgLatencyNs", bestAvg) + return device, nil +} + +// FeedSeatPrice calls the FeedSeatPrice RPC to query device seat prices. +func (c *Client) FeedSeatPrice(ctx context.Context) ([]*pb.DevicePrice, error) { + c.log.Debug("Querying seat prices", "host", c.Host) + resp, err := c.grpcClient.FeedSeatPrice(ctx, &pb.FeedSeatPriceRequest{ + SolanaRpcUrl: c.SolanaRPCURL, + DzLedgerUrl: c.DZLedgerURL, + UsdcMint: c.USDCMint, + Keypair: c.Keypair, + ReservationProgramId: c.ReservationProgramID, + }) + if err != nil { + return nil, fmt.Errorf("failed to get seat prices on host %s: %w", c.Host, err) + } + c.log.Debug("Seat prices retrieved", "host", c.Host, "count", len(resp.GetPrices())) + return resp.GetPrices(), nil +} + +// FeedSeatPay calls the FeedSeatPay RPC to pay for a seat on a device. +// The client's public IP is auto-filled. Instant allocation is the default. +func (c *Client) FeedSeatPay(ctx context.Context, devicePubkey string, amount string) error { + c.log.Debug("Paying for seat", "host", c.Host, "device", devicePubkey, "amount", amount) + resp, err := c.grpcClient.FeedSeatPay(ctx, &pb.FeedSeatPayRequest{ + DevicePubkey: devicePubkey, + ClientIp: c.publicIP.To4().String(), + Amount: amount, + SolanaRpcUrl: c.SolanaRPCURL, + ReservationProgramId: c.ReservationProgramID, + DzLedgerUrl: c.DZLedgerURL, + UsdcMint: c.USDCMint, + Keypair: c.Keypair, + }) + if err != nil { + return fmt.Errorf("failed to pay for seat on host %s: %w", c.Host, err) + } + if !resp.GetSuccess() { + c.log.Error("Seat payment failed", "host", c.Host, "device", devicePubkey, "output", resp.GetOutput()) + return fmt.Errorf("seat payment failed on host %s: %s", c.Host, resp.GetOutput()) + } + c.log.Debug("Seat payment successful", "host", c.Host, "device", devicePubkey) + return nil +} + +// FeedSeatWithdraw calls the FeedSeatWithdraw RPC to withdraw a seat from a device. +// Instant withdrawal is the default. +func (c *Client) FeedSeatWithdraw(ctx context.Context, devicePubkey string) error { + c.log.Debug("Withdrawing seat", "host", c.Host, "device", devicePubkey) + resp, err := c.grpcClient.FeedSeatWithdraw(ctx, &pb.FeedSeatWithdrawRequest{ + DevicePubkey: devicePubkey, + ClientIp: c.publicIP.To4().String(), + SolanaRpcUrl: c.SolanaRPCURL, + ReservationProgramId: c.ReservationProgramID, + DzLedgerUrl: c.DZLedgerURL, + UsdcMint: c.USDCMint, + Keypair: c.Keypair, + }) + if err != nil { + return fmt.Errorf("failed to withdraw seat on host %s: %w", c.Host, err) + } + if !resp.GetSuccess() { + return fmt.Errorf("seat withdrawal failed on host %s: %s", c.Host, resp.GetOutput()) + } + c.log.Debug("Seat withdrawal successful", "host", c.Host, "device", devicePubkey) + return nil +} diff --git a/e2e/internal/rpc/agent.go b/e2e/internal/rpc/agent.go index ce87e7135e..cb7f6ca70e 100644 --- a/e2e/internal/rpc/agent.go +++ b/e2e/internal/rpc/agent.go @@ -10,6 +10,7 @@ import ( "log/slog" "net" "net/http" + "os" "os/exec" "strings" "time" @@ -333,6 +334,175 @@ func (q *QAAgent) Disconnect(ctx context.Context, req *emptypb.Empty) (*pb.Resul return res, nil } +// FeedEnable implements the FeedEnable RPC, which enables the reconciler on doublezerod. +// This is equivalent to running `doublezero enable`. +func (q *QAAgent) FeedEnable(ctx context.Context, req *emptypb.Empty) (*pb.Result, error) { + q.log.Debug("Received FeedEnable request") + cmd := exec.CommandContext(ctx, "doublezero", "enable") + res, err := runCmd(cmd) + if err != nil { + q.log.Error("Failed to enable reconciler", "output", res.GetOutput()) + return res, fmt.Errorf("failed to enable reconciler: %w", err) + } + q.log.Debug("Reconciler enabled", "output", res.GetOutput()) + return res, nil +} + +// FeedSeatPrice implements the FeedSeatPrice RPC, which queries device seat prices. +// This executes `doublezero-solana shreds price --json` and parses the output. +func (q *QAAgent) FeedSeatPrice(ctx context.Context, req *pb.FeedSeatPriceRequest) (*pb.FeedSeatPriceResponse, error) { + q.log.Debug("Received FeedSeatPrice request") + + args := []string{"shreds"} + if req.GetDzLedgerUrl() != "" { + args = append(args, "--dz-ledger-url", req.GetDzLedgerUrl()) + } + args = append(args, "price", "--json") + if req.GetSolanaRpcUrl() != "" { + args = append(args, "--url", req.GetSolanaRpcUrl()) + } + + cmdCtx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + + cmd := exec.CommandContext(cmdCtx, "doublezero-solana", args...) + if req.GetReservationProgramId() != "" { + cmd.Env = append(cmd.Environ(), "RESERVATION_PROGRAM_ID="+req.GetReservationProgramId()) + } + out, err := cmd.CombinedOutput() + if err != nil { + q.log.Error("Failed to get seat prices", "error", err, "output", string(out)) + return nil, fmt.Errorf("failed to get seat prices: %w, output: %s", err, string(out)) + } + + var rawPrices []struct { + DeviceCode string `json:"device_code"` + Device string `json:"device"` + MetroCode string `json:"metro_code"` + MetroName string `json:"metro_name"` + Status string `json:"status"` + SettledSeats uint32 `json:"settled_seats"` + AvailableSeats uint32 `json:"available_seats"` + BasePrice uint64 `json:"base_price"` + Premium uint64 `json:"premium"` + EpochPrice uint64 `json:"epoch_price"` + } + if err := json.Unmarshal(out, &rawPrices); err != nil { + q.log.Error("Failed to parse seat prices", "output", string(out), "error", err) + return nil, fmt.Errorf("failed to parse seat prices: %w", err) + } + + prices := make([]*pb.DevicePrice, 0, len(rawPrices)) + for _, p := range rawPrices { + prices = append(prices, &pb.DevicePrice{ + DeviceCode: p.DeviceCode, + DevicePubkey: p.Device, + MetroCode: p.MetroCode, + MetroName: p.MetroName, + Status: p.Status, + SettledSeats: p.SettledSeats, + AvailableSeats: p.AvailableSeats, + BasePrice: p.BasePrice, + Premium: p.Premium, + EpochPrice: p.EpochPrice, + }) + } + + q.log.Debug("Seat prices retrieved", "count", len(prices)) + return &pb.FeedSeatPriceResponse{Prices: prices}, nil +} + +// FeedSeatPay implements the FeedSeatPay RPC, which pays for a seat on a device. +// This executes `doublezero-solana shreds pay` with the provided parameters. +// Instant allocation is always the default in the CLI (--now was removed). +// --accept-partial-epoch is hardcoded to prevent the interactive prompt from +// hanging in non-interactive QA test runs. +func (q *QAAgent) FeedSeatPay(ctx context.Context, req *pb.FeedSeatPayRequest) (*pb.Result, error) { + if req.GetDevicePubkey() == "" { + return nil, fmt.Errorf("device_pubkey is required") + } + if req.GetClientIp() == "" { + return nil, fmt.Errorf("client_ip is required") + } + if req.GetAmount() == "" { + return nil, fmt.Errorf("amount is required") + } + q.log.Debug("Received SeatPay request", "device", req.GetDevicePubkey(), "clientIP", req.GetClientIp(), "amount", req.GetAmount()) + + args := []string{"shreds"} + if req.GetDzLedgerUrl() != "" { + args = append(args, "--dz-ledger-url", req.GetDzLedgerUrl()) + } + args = append(args, "pay", "--device", req.GetDevicePubkey(), "--client-ip", req.GetClientIp(), "--amount", req.GetAmount(), "--accept-partial-epoch") + if req.GetSolanaRpcUrl() != "" { + args = append(args, "--url", req.GetSolanaRpcUrl()) + } + if req.GetUsdcMint() != "" { + args = append(args, "--usdc-mint", req.GetUsdcMint()) + } + if req.GetKeypair() != "" { + args = append(args, "--keypair", os.ExpandEnv(req.GetKeypair())) + } + + cmdCtx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + + cmd := exec.CommandContext(cmdCtx, "doublezero-solana", args...) + if req.GetReservationProgramId() != "" { + cmd.Env = append(cmd.Environ(), "RESERVATION_PROGRAM_ID="+req.GetReservationProgramId()) + } + res, err := runCmd(cmd) + if err != nil { + q.log.Error("Failed to pay for seat", "device", req.GetDevicePubkey(), "output", res.GetOutput()) + return res, fmt.Errorf("failed to pay for seat on device %s: %w", req.GetDevicePubkey(), err) + } + q.log.Debug("Seat payment successful", "device", req.GetDevicePubkey(), "output", res.GetOutput()) + return res, nil +} + +// FeedSeatWithdraw implements the FeedSeatWithdraw RPC, which withdraws a seat from a device. +// This executes `doublezero-solana shreds withdraw` with the provided parameters. +// Instant withdrawal is always the default in the CLI (--unsafe-now was removed). +func (q *QAAgent) FeedSeatWithdraw(ctx context.Context, req *pb.FeedSeatWithdrawRequest) (*pb.Result, error) { + if req.GetDevicePubkey() == "" { + return nil, fmt.Errorf("device_pubkey is required") + } + if req.GetClientIp() == "" { + return nil, fmt.Errorf("client_ip is required") + } + q.log.Debug("Received SeatWithdraw request", "device", req.GetDevicePubkey(), "clientIP", req.GetClientIp()) + + args := []string{"shreds"} + if req.GetDzLedgerUrl() != "" { + args = append(args, "--dz-ledger-url", req.GetDzLedgerUrl()) + } + args = append(args, "withdraw", "--device", req.GetDevicePubkey(), "--client-ip", req.GetClientIp()) + if req.GetSolanaRpcUrl() != "" { + args = append(args, "--url", req.GetSolanaRpcUrl()) + } + if req.GetUsdcMint() != "" { + args = append(args, "--usdc-mint", req.GetUsdcMint()) + } + if req.GetKeypair() != "" { + args = append(args, "--keypair", os.ExpandEnv(req.GetKeypair())) + } + + cmdCtx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + + cmd := exec.CommandContext(cmdCtx, "doublezero-solana", args...) + if req.GetReservationProgramId() != "" { + cmd.Env = append(cmd.Environ(), "RESERVATION_PROGRAM_ID="+req.GetReservationProgramId()) + } + res, err := runCmd(cmd) + if err != nil { + q.log.Error("Failed to withdraw seat", "device", req.GetDevicePubkey(), "output", res.GetOutput()) + return res, fmt.Errorf("failed to withdraw seat on device %s: %w", req.GetDevicePubkey(), err) + } + q.log.Debug("Seat withdrawal successful", "device", req.GetDevicePubkey(), "output", res.GetOutput()) + return res, nil +} + type StatusResponse struct { Response struct { TunnelName string `json:"tunnel_name"` diff --git a/e2e/proto/qa/agent.proto b/e2e/proto/qa/agent.proto index 41f4df6909..f16baeb8c8 100644 --- a/e2e/proto/qa/agent.proto +++ b/e2e/proto/qa/agent.proto @@ -24,6 +24,10 @@ service QAAgentService { rpc MulticastLeave(google.protobuf.Empty) returns (google.protobuf.Empty); rpc MulticastReport(MulticastReportRequest) returns (MulticastReportResult); rpc MulticastSend(MulticastSendRequest) returns (google.protobuf.Empty); + rpc FeedEnable(google.protobuf.Empty) returns (Result); + rpc FeedSeatPrice(FeedSeatPriceRequest) returns (FeedSeatPriceResponse); + rpc FeedSeatPay(FeedSeatPayRequest) returns (Result); + rpc FeedSeatWithdraw(FeedSeatWithdrawRequest) returns (Result); } message ConnectUnicastRequest { @@ -203,3 +207,51 @@ message MulticastSendRequest { uint32 port = 2; uint32 duration = 3; // in seconds } + +message FeedSeatPayRequest { + string device_pubkey = 1; + string client_ip = 2; + string amount = 3; + bool instant = 4; + string solana_rpc_url = 5; + string reservation_program_id = 6; + string dz_ledger_url = 7; + string usdc_mint = 8; + string keypair = 9; +} + +message FeedSeatWithdrawRequest { + string device_pubkey = 1; + string client_ip = 2; + bool instant = 3; + string solana_rpc_url = 4; + string reservation_program_id = 5; + string dz_ledger_url = 6; + string usdc_mint = 7; + string keypair = 8; +} + +message FeedSeatPriceRequest { + string solana_rpc_url = 1; + string dz_ledger_url = 2; + string usdc_mint = 3; + string keypair = 4; + string reservation_program_id = 5; +} + +message DevicePrice { + string device_code = 1; + string device_pubkey = 2; + string metro_code = 3; + string metro_name = 4; + string status = 5; + uint32 settled_seats = 6; + uint32 available_seats = 7; + uint64 base_price = 8; + uint64 premium = 9; + uint64 epoch_price = 10; +} + +message FeedSeatPriceResponse { + repeated DevicePrice prices = 1; +} diff --git a/e2e/proto/qa/gen/pb-go/agent.pb.go b/e2e/proto/qa/gen/pb-go/agent.pb.go index 93af2a53e2..a5f526f286 100644 --- a/e2e/proto/qa/gen/pb-go/agent.pb.go +++ b/e2e/proto/qa/gen/pb-go/agent.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: agent.proto @@ -1734,6 +1734,450 @@ func (x *MulticastSendRequest) GetDuration() uint32 { return 0 } +type FeedSeatPayRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + DevicePubkey string `protobuf:"bytes,1,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` + ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Instant bool `protobuf:"varint,4,opt,name=instant,proto3" json:"instant,omitempty"` + SolanaRpcUrl string `protobuf:"bytes,5,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` + ReservationProgramId string `protobuf:"bytes,6,opt,name=reservation_program_id,json=reservationProgramId,proto3" json:"reservation_program_id,omitempty"` + DzLedgerUrl string `protobuf:"bytes,7,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` + UsdcMint string `protobuf:"bytes,8,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` + Keypair string `protobuf:"bytes,9,opt,name=keypair,proto3" json:"keypair,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FeedSeatPayRequest) Reset() { + *x = FeedSeatPayRequest{} + mi := &file_agent_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FeedSeatPayRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedSeatPayRequest) ProtoMessage() {} + +func (x *FeedSeatPayRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedSeatPayRequest.ProtoReflect.Descriptor instead. +func (*FeedSeatPayRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{26} +} + +func (x *FeedSeatPayRequest) GetDevicePubkey() string { + if x != nil { + return x.DevicePubkey + } + return "" +} + +func (x *FeedSeatPayRequest) GetClientIp() string { + if x != nil { + return x.ClientIp + } + return "" +} + +func (x *FeedSeatPayRequest) GetAmount() string { + if x != nil { + return x.Amount + } + return "" +} + +func (x *FeedSeatPayRequest) GetInstant() bool { + if x != nil { + return x.Instant + } + return false +} + +func (x *FeedSeatPayRequest) GetSolanaRpcUrl() string { + if x != nil { + return x.SolanaRpcUrl + } + return "" +} + +func (x *FeedSeatPayRequest) GetReservationProgramId() string { + if x != nil { + return x.ReservationProgramId + } + return "" +} + +func (x *FeedSeatPayRequest) GetDzLedgerUrl() string { + if x != nil { + return x.DzLedgerUrl + } + return "" +} + +func (x *FeedSeatPayRequest) GetUsdcMint() string { + if x != nil { + return x.UsdcMint + } + return "" +} + +func (x *FeedSeatPayRequest) GetKeypair() string { + if x != nil { + return x.Keypair + } + return "" +} + +type FeedSeatWithdrawRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + DevicePubkey string `protobuf:"bytes,1,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` + ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + Instant bool `protobuf:"varint,3,opt,name=instant,proto3" json:"instant,omitempty"` + SolanaRpcUrl string `protobuf:"bytes,4,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` + ReservationProgramId string `protobuf:"bytes,5,opt,name=reservation_program_id,json=reservationProgramId,proto3" json:"reservation_program_id,omitempty"` + DzLedgerUrl string `protobuf:"bytes,6,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` + UsdcMint string `protobuf:"bytes,7,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` + Keypair string `protobuf:"bytes,8,opt,name=keypair,proto3" json:"keypair,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FeedSeatWithdrawRequest) Reset() { + *x = FeedSeatWithdrawRequest{} + mi := &file_agent_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FeedSeatWithdrawRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedSeatWithdrawRequest) ProtoMessage() {} + +func (x *FeedSeatWithdrawRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedSeatWithdrawRequest.ProtoReflect.Descriptor instead. +func (*FeedSeatWithdrawRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{27} +} + +func (x *FeedSeatWithdrawRequest) GetDevicePubkey() string { + if x != nil { + return x.DevicePubkey + } + return "" +} + +func (x *FeedSeatWithdrawRequest) GetClientIp() string { + if x != nil { + return x.ClientIp + } + return "" +} + +func (x *FeedSeatWithdrawRequest) GetInstant() bool { + if x != nil { + return x.Instant + } + return false +} + +func (x *FeedSeatWithdrawRequest) GetSolanaRpcUrl() string { + if x != nil { + return x.SolanaRpcUrl + } + return "" +} + +func (x *FeedSeatWithdrawRequest) GetReservationProgramId() string { + if x != nil { + return x.ReservationProgramId + } + return "" +} + +func (x *FeedSeatWithdrawRequest) GetDzLedgerUrl() string { + if x != nil { + return x.DzLedgerUrl + } + return "" +} + +func (x *FeedSeatWithdrawRequest) GetUsdcMint() string { + if x != nil { + return x.UsdcMint + } + return "" +} + +func (x *FeedSeatWithdrawRequest) GetKeypair() string { + if x != nil { + return x.Keypair + } + return "" +} + +type FeedSeatPriceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SolanaRpcUrl string `protobuf:"bytes,1,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` + DzLedgerUrl string `protobuf:"bytes,2,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` + UsdcMint string `protobuf:"bytes,3,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` + Keypair string `protobuf:"bytes,4,opt,name=keypair,proto3" json:"keypair,omitempty"` + ReservationProgramId string `protobuf:"bytes,5,opt,name=reservation_program_id,json=reservationProgramId,proto3" json:"reservation_program_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FeedSeatPriceRequest) Reset() { + *x = FeedSeatPriceRequest{} + mi := &file_agent_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FeedSeatPriceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedSeatPriceRequest) ProtoMessage() {} + +func (x *FeedSeatPriceRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedSeatPriceRequest.ProtoReflect.Descriptor instead. +func (*FeedSeatPriceRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{28} +} + +func (x *FeedSeatPriceRequest) GetSolanaRpcUrl() string { + if x != nil { + return x.SolanaRpcUrl + } + return "" +} + +func (x *FeedSeatPriceRequest) GetDzLedgerUrl() string { + if x != nil { + return x.DzLedgerUrl + } + return "" +} + +func (x *FeedSeatPriceRequest) GetUsdcMint() string { + if x != nil { + return x.UsdcMint + } + return "" +} + +func (x *FeedSeatPriceRequest) GetKeypair() string { + if x != nil { + return x.Keypair + } + return "" +} + +func (x *FeedSeatPriceRequest) GetReservationProgramId() string { + if x != nil { + return x.ReservationProgramId + } + return "" +} + +type DevicePrice struct { + state protoimpl.MessageState `protogen:"open.v1"` + DeviceCode string `protobuf:"bytes,1,opt,name=device_code,json=deviceCode,proto3" json:"device_code,omitempty"` + DevicePubkey string `protobuf:"bytes,2,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` + MetroCode string `protobuf:"bytes,3,opt,name=metro_code,json=metroCode,proto3" json:"metro_code,omitempty"` + MetroName string `protobuf:"bytes,4,opt,name=metro_name,json=metroName,proto3" json:"metro_name,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + SettledSeats uint32 `protobuf:"varint,6,opt,name=settled_seats,json=settledSeats,proto3" json:"settled_seats,omitempty"` + AvailableSeats uint32 `protobuf:"varint,7,opt,name=available_seats,json=availableSeats,proto3" json:"available_seats,omitempty"` + BasePrice uint64 `protobuf:"varint,8,opt,name=base_price,json=basePrice,proto3" json:"base_price,omitempty"` + Premium uint64 `protobuf:"varint,9,opt,name=premium,proto3" json:"premium,omitempty"` + EpochPrice uint64 `protobuf:"varint,10,opt,name=epoch_price,json=epochPrice,proto3" json:"epoch_price,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DevicePrice) Reset() { + *x = DevicePrice{} + mi := &file_agent_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DevicePrice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DevicePrice) ProtoMessage() {} + +func (x *DevicePrice) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DevicePrice.ProtoReflect.Descriptor instead. +func (*DevicePrice) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{29} +} + +func (x *DevicePrice) GetDeviceCode() string { + if x != nil { + return x.DeviceCode + } + return "" +} + +func (x *DevicePrice) GetDevicePubkey() string { + if x != nil { + return x.DevicePubkey + } + return "" +} + +func (x *DevicePrice) GetMetroCode() string { + if x != nil { + return x.MetroCode + } + return "" +} + +func (x *DevicePrice) GetMetroName() string { + if x != nil { + return x.MetroName + } + return "" +} + +func (x *DevicePrice) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DevicePrice) GetSettledSeats() uint32 { + if x != nil { + return x.SettledSeats + } + return 0 +} + +func (x *DevicePrice) GetAvailableSeats() uint32 { + if x != nil { + return x.AvailableSeats + } + return 0 +} + +func (x *DevicePrice) GetBasePrice() uint64 { + if x != nil { + return x.BasePrice + } + return 0 +} + +func (x *DevicePrice) GetPremium() uint64 { + if x != nil { + return x.Premium + } + return 0 +} + +func (x *DevicePrice) GetEpochPrice() uint64 { + if x != nil { + return x.EpochPrice + } + return 0 +} + +type FeedSeatPriceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Prices []*DevicePrice `protobuf:"bytes,1,rep,name=prices,proto3" json:"prices,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FeedSeatPriceResponse) Reset() { + *x = FeedSeatPriceResponse{} + mi := &file_agent_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FeedSeatPriceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeedSeatPriceResponse) ProtoMessage() {} + +func (x *FeedSeatPriceResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeedSeatPriceResponse.ProtoReflect.Descriptor instead. +func (*FeedSeatPriceResponse) Descriptor() ([]byte, []int) { + return file_agent_proto_rawDescGZIP(), []int{30} +} + +func (x *FeedSeatPriceResponse) GetPrices() []*DevicePrice { + if x != nil { + return x.Prices + } + return nil +} + var File_agent_proto protoreflect.FileDescriptor const file_agent_proto_rawDesc = "" + @@ -1865,7 +2309,52 @@ const file_agent_proto_rawDesc = "" + "\x14MulticastSendRequest\x12\x14\n" + "\x05group\x18\x01 \x01(\tR\x05group\x12\x12\n" + "\x04port\x18\x02 \x01(\rR\x04port\x12\x1a\n" + - "\bduration\x18\x03 \x01(\rR\bduration2\xa4\b\n" + + "\bduration\x18\x03 \x01(\rR\bduration\"\xbf\x02\n" + + "\x12FeedSeatPayRequest\x12#\n" + + "\rdevice_pubkey\x18\x01 \x01(\tR\fdevicePubkey\x12\x1b\n" + + "\tclient_ip\x18\x02 \x01(\tR\bclientIp\x12\x16\n" + + "\x06amount\x18\x03 \x01(\tR\x06amount\x12\x18\n" + + "\ainstant\x18\x04 \x01(\bR\ainstant\x12$\n" + + "\x0esolana_rpc_url\x18\x05 \x01(\tR\fsolanaRpcUrl\x124\n" + + "\x16reservation_program_id\x18\x06 \x01(\tR\x14reservationProgramId\x12\"\n" + + "\rdz_ledger_url\x18\a \x01(\tR\vdzLedgerUrl\x12\x1b\n" + + "\tusdc_mint\x18\b \x01(\tR\busdcMint\x12\x18\n" + + "\akeypair\x18\t \x01(\tR\akeypair\"\xac\x02\n" + + "\x17FeedSeatWithdrawRequest\x12#\n" + + "\rdevice_pubkey\x18\x01 \x01(\tR\fdevicePubkey\x12\x1b\n" + + "\tclient_ip\x18\x02 \x01(\tR\bclientIp\x12\x18\n" + + "\ainstant\x18\x03 \x01(\bR\ainstant\x12$\n" + + "\x0esolana_rpc_url\x18\x04 \x01(\tR\fsolanaRpcUrl\x124\n" + + "\x16reservation_program_id\x18\x05 \x01(\tR\x14reservationProgramId\x12\"\n" + + "\rdz_ledger_url\x18\x06 \x01(\tR\vdzLedgerUrl\x12\x1b\n" + + "\tusdc_mint\x18\a \x01(\tR\busdcMint\x12\x18\n" + + "\akeypair\x18\b \x01(\tR\akeypair\"\xcd\x01\n" + + "\x14FeedSeatPriceRequest\x12$\n" + + "\x0esolana_rpc_url\x18\x01 \x01(\tR\fsolanaRpcUrl\x12\"\n" + + "\rdz_ledger_url\x18\x02 \x01(\tR\vdzLedgerUrl\x12\x1b\n" + + "\tusdc_mint\x18\x03 \x01(\tR\busdcMint\x12\x18\n" + + "\akeypair\x18\x04 \x01(\tR\akeypair\x124\n" + + "\x16reservation_program_id\x18\x05 \x01(\tR\x14reservationProgramId\"\xd1\x02\n" + + "\vDevicePrice\x12\x1f\n" + + "\vdevice_code\x18\x01 \x01(\tR\n" + + "deviceCode\x12#\n" + + "\rdevice_pubkey\x18\x02 \x01(\tR\fdevicePubkey\x12\x1d\n" + + "\n" + + "metro_code\x18\x03 \x01(\tR\tmetroCode\x12\x1d\n" + + "\n" + + "metro_name\x18\x04 \x01(\tR\tmetroName\x12\x16\n" + + "\x06status\x18\x05 \x01(\tR\x06status\x12#\n" + + "\rsettled_seats\x18\x06 \x01(\rR\fsettledSeats\x12'\n" + + "\x0favailable_seats\x18\a \x01(\rR\x0eavailableSeats\x12\x1d\n" + + "\n" + + "base_price\x18\b \x01(\x04R\tbasePrice\x12\x18\n" + + "\apremium\x18\t \x01(\x04R\apremium\x12\x1f\n" + + "\vepoch_price\x18\n" + + " \x01(\x04R\n" + + "epochPrice\"@\n" + + "\x15FeedSeatPriceResponse\x12'\n" + + "\x06prices\x18\x01 \x03(\v2\x0f.qa.DevicePriceR\x06prices2\x8c\n" + + "\n" + "\x0eQAAgentService\x127\n" + "\x0eConnectUnicast\x12\x19.qa.ConnectUnicastRequest\x1a\n" + ".qa.Result\x12C\n" + @@ -1893,7 +2382,15 @@ const file_agent_proto_rawDesc = "" + "\rMulticastJoin\x12\x18.qa.MulticastJoinRequest\x1a\x17.qa.MulticastJoinResult\x12@\n" + "\x0eMulticastLeave\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12H\n" + "\x0fMulticastReport\x12\x1a.qa.MulticastReportRequest\x1a\x19.qa.MulticastReportResult\x12A\n" + - "\rMulticastSend\x12\x18.qa.MulticastSendRequest\x1a\x16.google.protobuf.EmptyB/Z-github.com/malbeclabs/doublezero/e2e/proto/qab\x06proto3" + "\rMulticastSend\x12\x18.qa.MulticastSendRequest\x1a\x16.google.protobuf.Empty\x120\n" + + "\n" + + "FeedEnable\x12\x16.google.protobuf.Empty\x1a\n" + + ".qa.Result\x12D\n" + + "\rFeedSeatPrice\x12\x18.qa.FeedSeatPriceRequest\x1a\x19.qa.FeedSeatPriceResponse\x121\n" + + "\vFeedSeatPay\x12\x16.qa.FeedSeatPayRequest\x1a\n" + + ".qa.Result\x12;\n" + + "\x10FeedSeatWithdraw\x12\x1b.qa.FeedSeatWithdrawRequest\x1a\n" + + ".qa.ResultB/Z-github.com/malbeclabs/doublezero/e2e/proto/qab\x06proto3" var ( file_agent_proto_rawDescOnce sync.Once @@ -1908,7 +2405,7 @@ func file_agent_proto_rawDescGZIP() []byte { } var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_agent_proto_goTypes = []any{ (ConnectUnicastRequest_UnicastMode)(0), // 0: qa.ConnectUnicastRequest.UnicastMode (ConnectMulticastRequest_MulticastMode)(0), // 1: qa.ConnectMulticastRequest.MulticastMode @@ -1940,8 +2437,13 @@ var file_agent_proto_goTypes = []any{ (*MulticastReport)(nil), // 27: qa.MulticastReport (*MulticastReportResult)(nil), // 28: qa.MulticastReportResult (*MulticastSendRequest)(nil), // 29: qa.MulticastSendRequest - nil, // 30: qa.MulticastReportResult.ReportsEntry - (*emptypb.Empty)(nil), // 31: google.protobuf.Empty + (*FeedSeatPayRequest)(nil), // 30: qa.FeedSeatPayRequest + (*FeedSeatWithdrawRequest)(nil), // 31: qa.FeedSeatWithdrawRequest + (*FeedSeatPriceRequest)(nil), // 32: qa.FeedSeatPriceRequest + (*DevicePrice)(nil), // 33: qa.DevicePrice + (*FeedSeatPriceResponse)(nil), // 34: qa.FeedSeatPriceResponse + nil, // 35: qa.MulticastReportResult.ReportsEntry + (*emptypb.Empty)(nil), // 36: google.protobuf.Empty } var file_agent_proto_depIdxs = []int32{ 0, // 0: qa.ConnectUnicastRequest.mode:type_name -> qa.ConnectUnicastRequest.UnicastMode @@ -1954,47 +2456,56 @@ var file_agent_proto_depIdxs = []int32{ 21, // 7: qa.TracerouteResult.hops:type_name -> qa.TracerouteHop 22, // 8: qa.MulticastJoinRequest.groups:type_name -> qa.MulticastGroup 22, // 9: qa.MulticastReportRequest.groups:type_name -> qa.MulticastGroup - 30, // 10: qa.MulticastReportResult.reports:type_name -> qa.MulticastReportResult.ReportsEntry - 27, // 11: qa.MulticastReportResult.ReportsEntry.value:type_name -> qa.MulticastReport - 4, // 12: qa.QAAgentService.ConnectUnicast:input_type -> qa.ConnectUnicastRequest - 5, // 13: qa.QAAgentService.CreateMulticastGroup:input_type -> qa.CreateMulticastGroupRequest - 6, // 14: qa.QAAgentService.DeleteMulticastGroup:input_type -> qa.DeleteMulticastGroupRequest - 8, // 15: qa.QAAgentService.MulticastAllowListAdd:input_type -> qa.MulticastAllowListAddRequest - 7, // 16: qa.QAAgentService.ConnectMulticast:input_type -> qa.ConnectMulticastRequest - 31, // 17: qa.QAAgentService.GetStatus:input_type -> google.protobuf.Empty - 31, // 18: qa.QAAgentService.GetLatency:input_type -> google.protobuf.Empty - 31, // 19: qa.QAAgentService.GetPublicIP:input_type -> google.protobuf.Empty - 31, // 20: qa.QAAgentService.GetRoutes:input_type -> google.protobuf.Empty - 31, // 21: qa.QAAgentService.Disconnect:input_type -> google.protobuf.Empty - 17, // 22: qa.QAAgentService.Ping:input_type -> qa.PingRequest - 19, // 23: qa.QAAgentService.Traceroute:input_type -> qa.TracerouteRequest - 19, // 24: qa.QAAgentService.TracerouteRaw:input_type -> qa.TracerouteRequest - 23, // 25: qa.QAAgentService.MulticastJoin:input_type -> qa.MulticastJoinRequest - 31, // 26: qa.QAAgentService.MulticastLeave:input_type -> google.protobuf.Empty - 26, // 27: qa.QAAgentService.MulticastReport:input_type -> qa.MulticastReportRequest - 29, // 28: qa.QAAgentService.MulticastSend:input_type -> qa.MulticastSendRequest - 9, // 29: qa.QAAgentService.ConnectUnicast:output_type -> qa.Result - 9, // 30: qa.QAAgentService.CreateMulticastGroup:output_type -> qa.Result - 9, // 31: qa.QAAgentService.DeleteMulticastGroup:output_type -> qa.Result - 9, // 32: qa.QAAgentService.MulticastAllowListAdd:output_type -> qa.Result - 9, // 33: qa.QAAgentService.ConnectMulticast:output_type -> qa.Result - 11, // 34: qa.QAAgentService.GetStatus:output_type -> qa.StatusResponse - 13, // 35: qa.QAAgentService.GetLatency:output_type -> qa.LatencyResponse - 14, // 36: qa.QAAgentService.GetPublicIP:output_type -> qa.GetPublicIPResponse - 15, // 37: qa.QAAgentService.GetRoutes:output_type -> qa.GetRoutesResponse - 9, // 38: qa.QAAgentService.Disconnect:output_type -> qa.Result - 18, // 39: qa.QAAgentService.Ping:output_type -> qa.PingResult - 20, // 40: qa.QAAgentService.Traceroute:output_type -> qa.TracerouteResult - 9, // 41: qa.QAAgentService.TracerouteRaw:output_type -> qa.Result - 24, // 42: qa.QAAgentService.MulticastJoin:output_type -> qa.MulticastJoinResult - 31, // 43: qa.QAAgentService.MulticastLeave:output_type -> google.protobuf.Empty - 28, // 44: qa.QAAgentService.MulticastReport:output_type -> qa.MulticastReportResult - 31, // 45: qa.QAAgentService.MulticastSend:output_type -> google.protobuf.Empty - 29, // [29:46] is the sub-list for method output_type - 12, // [12:29] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 35, // 10: qa.MulticastReportResult.reports:type_name -> qa.MulticastReportResult.ReportsEntry + 33, // 11: qa.FeedSeatPriceResponse.prices:type_name -> qa.DevicePrice + 27, // 12: qa.MulticastReportResult.ReportsEntry.value:type_name -> qa.MulticastReport + 4, // 13: qa.QAAgentService.ConnectUnicast:input_type -> qa.ConnectUnicastRequest + 5, // 14: qa.QAAgentService.CreateMulticastGroup:input_type -> qa.CreateMulticastGroupRequest + 6, // 15: qa.QAAgentService.DeleteMulticastGroup:input_type -> qa.DeleteMulticastGroupRequest + 8, // 16: qa.QAAgentService.MulticastAllowListAdd:input_type -> qa.MulticastAllowListAddRequest + 7, // 17: qa.QAAgentService.ConnectMulticast:input_type -> qa.ConnectMulticastRequest + 36, // 18: qa.QAAgentService.GetStatus:input_type -> google.protobuf.Empty + 36, // 19: qa.QAAgentService.GetLatency:input_type -> google.protobuf.Empty + 36, // 20: qa.QAAgentService.GetPublicIP:input_type -> google.protobuf.Empty + 36, // 21: qa.QAAgentService.GetRoutes:input_type -> google.protobuf.Empty + 36, // 22: qa.QAAgentService.Disconnect:input_type -> google.protobuf.Empty + 17, // 23: qa.QAAgentService.Ping:input_type -> qa.PingRequest + 19, // 24: qa.QAAgentService.Traceroute:input_type -> qa.TracerouteRequest + 19, // 25: qa.QAAgentService.TracerouteRaw:input_type -> qa.TracerouteRequest + 23, // 26: qa.QAAgentService.MulticastJoin:input_type -> qa.MulticastJoinRequest + 36, // 27: qa.QAAgentService.MulticastLeave:input_type -> google.protobuf.Empty + 26, // 28: qa.QAAgentService.MulticastReport:input_type -> qa.MulticastReportRequest + 29, // 29: qa.QAAgentService.MulticastSend:input_type -> qa.MulticastSendRequest + 36, // 30: qa.QAAgentService.FeedEnable:input_type -> google.protobuf.Empty + 32, // 31: qa.QAAgentService.FeedSeatPrice:input_type -> qa.FeedSeatPriceRequest + 30, // 32: qa.QAAgentService.FeedSeatPay:input_type -> qa.FeedSeatPayRequest + 31, // 33: qa.QAAgentService.FeedSeatWithdraw:input_type -> qa.FeedSeatWithdrawRequest + 9, // 34: qa.QAAgentService.ConnectUnicast:output_type -> qa.Result + 9, // 35: qa.QAAgentService.CreateMulticastGroup:output_type -> qa.Result + 9, // 36: qa.QAAgentService.DeleteMulticastGroup:output_type -> qa.Result + 9, // 37: qa.QAAgentService.MulticastAllowListAdd:output_type -> qa.Result + 9, // 38: qa.QAAgentService.ConnectMulticast:output_type -> qa.Result + 11, // 39: qa.QAAgentService.GetStatus:output_type -> qa.StatusResponse + 13, // 40: qa.QAAgentService.GetLatency:output_type -> qa.LatencyResponse + 14, // 41: qa.QAAgentService.GetPublicIP:output_type -> qa.GetPublicIPResponse + 15, // 42: qa.QAAgentService.GetRoutes:output_type -> qa.GetRoutesResponse + 9, // 43: qa.QAAgentService.Disconnect:output_type -> qa.Result + 18, // 44: qa.QAAgentService.Ping:output_type -> qa.PingResult + 20, // 45: qa.QAAgentService.Traceroute:output_type -> qa.TracerouteResult + 9, // 46: qa.QAAgentService.TracerouteRaw:output_type -> qa.Result + 24, // 47: qa.QAAgentService.MulticastJoin:output_type -> qa.MulticastJoinResult + 36, // 48: qa.QAAgentService.MulticastLeave:output_type -> google.protobuf.Empty + 28, // 49: qa.QAAgentService.MulticastReport:output_type -> qa.MulticastReportResult + 36, // 50: qa.QAAgentService.MulticastSend:output_type -> google.protobuf.Empty + 9, // 51: qa.QAAgentService.FeedEnable:output_type -> qa.Result + 34, // 52: qa.QAAgentService.FeedSeatPrice:output_type -> qa.FeedSeatPriceResponse + 9, // 53: qa.QAAgentService.FeedSeatPay:output_type -> qa.Result + 9, // 54: qa.QAAgentService.FeedSeatWithdraw:output_type -> qa.Result + 34, // [34:55] is the sub-list for method output_type + 13, // [13:34] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_agent_proto_init() } @@ -2008,7 +2519,7 @@ func file_agent_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), NumEnums: 4, - NumMessages: 27, + NumMessages: 32, NumExtensions: 0, NumServices: 1, }, diff --git a/e2e/proto/qa/gen/pb-go/agent_grpc.pb.go b/e2e/proto/qa/gen/pb-go/agent_grpc.pb.go index 50925c35e8..104c5e39a4 100644 --- a/e2e/proto/qa/gen/pb-go/agent_grpc.pb.go +++ b/e2e/proto/qa/gen/pb-go/agent_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.1 +// - protoc-gen-go-grpc v1.6.0 // - protoc (unknown) // source: agent.proto @@ -37,6 +37,10 @@ const ( QAAgentService_MulticastLeave_FullMethodName = "/qa.QAAgentService/MulticastLeave" QAAgentService_MulticastReport_FullMethodName = "/qa.QAAgentService/MulticastReport" QAAgentService_MulticastSend_FullMethodName = "/qa.QAAgentService/MulticastSend" + QAAgentService_FeedEnable_FullMethodName = "/qa.QAAgentService/FeedEnable" + QAAgentService_FeedSeatPrice_FullMethodName = "/qa.QAAgentService/FeedSeatPrice" + QAAgentService_FeedSeatPay_FullMethodName = "/qa.QAAgentService/FeedSeatPay" + QAAgentService_FeedSeatWithdraw_FullMethodName = "/qa.QAAgentService/FeedSeatWithdraw" ) // QAAgentServiceClient is the client API for QAAgentService service. @@ -60,6 +64,10 @@ type QAAgentServiceClient interface { MulticastLeave(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) MulticastReport(ctx context.Context, in *MulticastReportRequest, opts ...grpc.CallOption) (*MulticastReportResult, error) MulticastSend(ctx context.Context, in *MulticastSendRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + FeedEnable(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Result, error) + FeedSeatPrice(ctx context.Context, in *FeedSeatPriceRequest, opts ...grpc.CallOption) (*FeedSeatPriceResponse, error) + FeedSeatPay(ctx context.Context, in *FeedSeatPayRequest, opts ...grpc.CallOption) (*Result, error) + FeedSeatWithdraw(ctx context.Context, in *FeedSeatWithdrawRequest, opts ...grpc.CallOption) (*Result, error) } type qAAgentServiceClient struct { @@ -240,6 +248,46 @@ func (c *qAAgentServiceClient) MulticastSend(ctx context.Context, in *MulticastS return out, nil } +func (c *qAAgentServiceClient) FeedEnable(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Result, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Result) + err := c.cc.Invoke(ctx, QAAgentService_FeedEnable_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *qAAgentServiceClient) FeedSeatPrice(ctx context.Context, in *FeedSeatPriceRequest, opts ...grpc.CallOption) (*FeedSeatPriceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(FeedSeatPriceResponse) + err := c.cc.Invoke(ctx, QAAgentService_FeedSeatPrice_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *qAAgentServiceClient) FeedSeatPay(ctx context.Context, in *FeedSeatPayRequest, opts ...grpc.CallOption) (*Result, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Result) + err := c.cc.Invoke(ctx, QAAgentService_FeedSeatPay_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *qAAgentServiceClient) FeedSeatWithdraw(ctx context.Context, in *FeedSeatWithdrawRequest, opts ...grpc.CallOption) (*Result, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Result) + err := c.cc.Invoke(ctx, QAAgentService_FeedSeatWithdraw_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // QAAgentServiceServer is the server API for QAAgentService service. // All implementations must embed UnimplementedQAAgentServiceServer // for forward compatibility. @@ -261,6 +309,10 @@ type QAAgentServiceServer interface { MulticastLeave(context.Context, *emptypb.Empty) (*emptypb.Empty, error) MulticastReport(context.Context, *MulticastReportRequest) (*MulticastReportResult, error) MulticastSend(context.Context, *MulticastSendRequest) (*emptypb.Empty, error) + FeedEnable(context.Context, *emptypb.Empty) (*Result, error) + FeedSeatPrice(context.Context, *FeedSeatPriceRequest) (*FeedSeatPriceResponse, error) + FeedSeatPay(context.Context, *FeedSeatPayRequest) (*Result, error) + FeedSeatWithdraw(context.Context, *FeedSeatWithdrawRequest) (*Result, error) mustEmbedUnimplementedQAAgentServiceServer() } @@ -322,6 +374,18 @@ func (UnimplementedQAAgentServiceServer) MulticastReport(context.Context, *Multi func (UnimplementedQAAgentServiceServer) MulticastSend(context.Context, *MulticastSendRequest) (*emptypb.Empty, error) { return nil, status.Error(codes.Unimplemented, "method MulticastSend not implemented") } +func (UnimplementedQAAgentServiceServer) FeedEnable(context.Context, *emptypb.Empty) (*Result, error) { + return nil, status.Error(codes.Unimplemented, "method FeedEnable not implemented") +} +func (UnimplementedQAAgentServiceServer) FeedSeatPrice(context.Context, *FeedSeatPriceRequest) (*FeedSeatPriceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method FeedSeatPrice not implemented") +} +func (UnimplementedQAAgentServiceServer) FeedSeatPay(context.Context, *FeedSeatPayRequest) (*Result, error) { + return nil, status.Error(codes.Unimplemented, "method FeedSeatPay not implemented") +} +func (UnimplementedQAAgentServiceServer) FeedSeatWithdraw(context.Context, *FeedSeatWithdrawRequest) (*Result, error) { + return nil, status.Error(codes.Unimplemented, "method FeedSeatWithdraw not implemented") +} func (UnimplementedQAAgentServiceServer) mustEmbedUnimplementedQAAgentServiceServer() {} func (UnimplementedQAAgentServiceServer) testEmbeddedByValue() {} @@ -649,6 +713,78 @@ func _QAAgentService_MulticastSend_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _QAAgentService_FeedEnable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QAAgentServiceServer).FeedEnable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: QAAgentService_FeedEnable_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QAAgentServiceServer).FeedEnable(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _QAAgentService_FeedSeatPrice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FeedSeatPriceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QAAgentServiceServer).FeedSeatPrice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: QAAgentService_FeedSeatPrice_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QAAgentServiceServer).FeedSeatPrice(ctx, req.(*FeedSeatPriceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _QAAgentService_FeedSeatPay_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FeedSeatPayRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QAAgentServiceServer).FeedSeatPay(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: QAAgentService_FeedSeatPay_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QAAgentServiceServer).FeedSeatPay(ctx, req.(*FeedSeatPayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _QAAgentService_FeedSeatWithdraw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FeedSeatWithdrawRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QAAgentServiceServer).FeedSeatWithdraw(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: QAAgentService_FeedSeatWithdraw_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QAAgentServiceServer).FeedSeatWithdraw(ctx, req.(*FeedSeatWithdrawRequest)) + } + return interceptor(ctx, in, info, handler) +} + // QAAgentService_ServiceDesc is the grpc.ServiceDesc for QAAgentService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -724,6 +860,22 @@ var QAAgentService_ServiceDesc = grpc.ServiceDesc{ MethodName: "MulticastSend", Handler: _QAAgentService_MulticastSend_Handler, }, + { + MethodName: "FeedEnable", + Handler: _QAAgentService_FeedEnable_Handler, + }, + { + MethodName: "FeedSeatPrice", + Handler: _QAAgentService_FeedSeatPrice_Handler, + }, + { + MethodName: "FeedSeatPay", + Handler: _QAAgentService_FeedSeatPay_Handler, + }, + { + MethodName: "FeedSeatWithdraw", + Handler: _QAAgentService_FeedSeatWithdraw_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "agent.proto", diff --git a/e2e/qa_settlement_test.go b/e2e/qa_settlement_test.go new file mode 100644 index 0000000000..3942d2f600 --- /dev/null +++ b/e2e/qa_settlement_test.go @@ -0,0 +1,155 @@ +//go:build qa + +package e2e + +import ( + "context" + "flag" + "strconv" + "testing" + + "github.com/malbeclabs/doublezero/e2e/internal/qa" + pb "github.com/malbeclabs/doublezero/e2e/proto/qa/gen/pb-go" + "github.com/stretchr/testify/require" +) + +var ( + enableSettlementTests = flag.Bool("enable-settlement-tests", false, "enable multicast settlement tests") + keypairFlag = flag.String("keypair", "$HOME/.config/doublezero/id.json", "path to keypair file for settlement commands") +) + +func TestQA_MulticastSettlement(t *testing.T) { + if !*enableSettlementTests { + t.Skip("Skipping: --enable-settlement-tests flag not set") + } + + log := newTestLogger(t) + ctx := t.Context() + test, err := qa.NewTest(ctx, log, hostsArg, portArg, networkConfig, nil) + require.NoError(t, err, "failed to create test") + + require.Len(t, hostsArg, 1, "settlement test requires exactly one host via -hosts") + client := test.GetClient(hostsArg[0]) + require.NotNil(t, client, "host %q not found", hostsArg[0]) + if *keypairFlag != "" { + client.Keypair = *keypairFlag + } + log.Info("Selected client", "host", client.Host) + + // Shared state across subtests. + var device *qa.Device + var amount string + seatPaid := false + + t.Cleanup(func() { + if seatPaid && device != nil { + cleanupCtx := context.Background() + if withdrawErr := client.FeedSeatWithdraw(cleanupCtx, device.PubKey); withdrawErr != nil { + log.Info("Cleanup: seat withdraw failed (may already be withdrawn)", "error", withdrawErr) + } + } + if t.Failed() { + client.DumpDiagnostics(nil) + } + }) + + if !t.Run("ensure_multicast_disconnected", func(t *testing.T) { + statuses, err := client.GetUserStatuses(ctx) + if err != nil { + log.Info("No active sessions") + return + } + var mcast *pb.Status + for _, s := range statuses { + if s.UserType == "Multicast" && s.SessionStatus != qa.UserStatusDisconnected { + mcast = s + break + } + } + if mcast == nil { + log.Info("No active multicast session") + return + } + log.Info("Active multicast session found, withdrawing", "device", mcast.CurrentDevice, "status", mcast.SessionStatus) + dev, ok := test.Devices()[mcast.CurrentDevice] + require.True(t, ok, "device %q not found in devices map", mcast.CurrentDevice) + err = client.FeedSeatWithdraw(ctx, dev.PubKey) + require.NoError(t, err, "failed to withdraw existing seat") + err = client.WaitForStatusDisconnected(ctx) + require.NoError(t, err, "existing multicast session did not disconnect") + }) { + return + } + + if !t.Run("enable_reconciler", func(t *testing.T) { + err := client.FeedEnable(ctx) + require.NoError(t, err, "failed to enable reconciler") + }) { + return + } + + if !t.Run("find_closest_device", func(t *testing.T) { + var err error + device, err = client.ClosestDevice(ctx) + require.NoError(t, err, "failed to find closest device") + log.Info("Closest device", "code", device.Code, "pubkey", device.PubKey) + }) { + return + } + + if !t.Run("query_seat_price", func(t *testing.T) { + prices, err := client.FeedSeatPrice(ctx) + require.NoError(t, err, "failed to get seat prices") + + var price *pb.DevicePrice + for _, p := range prices { + if p.DeviceCode == device.Code { + price = p + break + } + } + require.NotNil(t, price, "no price found for device %s", device.Code) + require.NotZero(t, price.EpochPrice, "epoch price is zero for device %s", device.Code) + amount = strconv.FormatUint(price.EpochPrice, 10) + log.Info("Found epoch price", "device", device.Code, "amount", amount) + }) { + return + } + + if !t.Run("pay_for_seat", func(t *testing.T) { + err := client.FeedSeatPay(ctx, device.PubKey, amount) + require.NoError(t, err, "failed to pay for seat") + seatPaid = true + }) { + return + } + + if !t.Run("validate_tunnel_up", func(t *testing.T) { + err := client.WaitForStatusUp(ctx) + require.NoError(t, err, "tunnel did not come up after seat payment") + }) { + return + } + + t.Run("validate_device_assignment", func(t *testing.T) { + statuses, err := client.GetUserStatuses(ctx) + require.NoError(t, err, "failed to get user statuses") + ibrlStatus := qa.FindIBRLStatus(statuses) + require.NotNil(t, ibrlStatus, "no IBRL status found after seat payment") + require.Equal(t, device.Code, ibrlStatus.CurrentDevice, "tunnel connected to wrong device") + log.Info("Tunnel up and device matches", "device", ibrlStatus.CurrentDevice, "dzIP", ibrlStatus.DoubleZeroIp) + }) + + if !t.Run("withdraw_seat", func(t *testing.T) { + err := client.FeedSeatWithdraw(ctx, device.PubKey) + require.NoError(t, err, "failed to withdraw seat") + seatPaid = false + }) { + return + } + + t.Run("validate_tunnel_down", func(t *testing.T) { + err := client.WaitForStatusDisconnected(ctx) + require.NoError(t, err, "tunnel did not come down after seat withdrawal") + }) +} From 6418a5dd2b80b47350416dd8e92c37fe3f9e3c78 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 15:32:09 -0400 Subject: [PATCH 2/8] config, e2e: rename ReservationProgramID to ShredSubscriptionProgramID --- config/constants.go | 8 ++++---- config/env.go | 18 +++++++++--------- config/env_test.go | 6 +++--- e2e/internal/qa/client.go | 4 ++-- e2e/internal/qa/client_settlement.go | 6 +++--- e2e/internal/rpc/agent.go | 12 ++++++------ e2e/proto/qa/agent.proto | 6 +++--- e2e/proto/qa/gen/pb-go/agent.pb.go | 24 ++++++++++++------------ 8 files changed, 42 insertions(+), 42 deletions(-) diff --git a/config/constants.go b/config/constants.go index 239a1024f7..ee98aa5ab1 100644 --- a/config/constants.go +++ b/config/constants.go @@ -15,7 +15,7 @@ const ( MainnetRevenueDistributionProgramID = "dzrevZC94tBLwuHw1dyynZxaXTWyp7yocsinyEVPtt4" MainnetGeolocationProgramID = "8H7nS6eZiuf7rGQtz3PPz2q9m4eJRL37PPM678KHnspG" - MainnetReservationProgramID = "dzshrr3yL57SB13sJPYHYo3TV8Bo1i1FxkyrZr3bKNE" + MainnetShredSubscriptionProgramID = "dzshrr3yL57SB13sJPYHYo3TV8Bo1i1FxkyrZr3bKNE" MainnetUSDCMint = "" // CLI defaults to real USDC on mainnet // Testnet constants. @@ -29,7 +29,7 @@ const ( TestnetTelemetryFlowIngestURL = "http://telemetry-flow-in.testnet.doublezero.xyz" TestnetTelemetryStateIngestURL = "http://telemetry-state-in.testnet.doublezero.xyz" TestnetGeolocationProgramID = "3AG2BCA7gAm47Q6xZzPQcUUYvnBjxAvPKnPz919cxHF4" - TestnetReservationProgramID = "dzshrr3yL57SB13sJPYHYo3TV8Bo1i1FxkyrZr3bKNE" + TestnetShredSubscriptionProgramID = "dzshrr3yL57SB13sJPYHYo3TV8Bo1i1FxkyrZr3bKNE" TestnetUSDCMint = "uSDZq2RMuxrEf7gqgDjR8wJCtCyaDAQk2e5jLAaoeeM" TestnetTelemetryGNMITunnelServerAddr = "gnmic-testnet.doublezero.xyz:443" @@ -43,7 +43,7 @@ const ( DevnetTelemetryFlowIngestURL = "http://telemetry-flow-in.devnet.doublezero.xyz" DevnetTelemetryStateIngestURL = "http://telemetry-state-in.devnet.doublezero.xyz" DevnetGeolocationProgramID = "EXUUFfAjjuXnaBtsAMLsJX18ynnNHPwtkmk33bLVVoCm" - DevnetReservationProgramID = "" // TODO: set when deployed to devnet + DevnetShredSubscriptionProgramID = "" // TODO: set when deployed to devnet DevnetUSDCMint = "" // TODO: set when deployed to devnet DevnetTelemetryGNMITunnelServerAddr = "gnmic-devnet.doublezero.xyz:443" @@ -58,7 +58,7 @@ const ( LocalnetTelemetryFlowIngestURL = "http://localhost:8911" LocalnetTelemetryStateIngestURL = "http://localhost:8911" LocalnetGeolocationProgramID = "36WA9nUCsJaAQL5h44WYoLezDpocy8Q71NZbtrUN8DyC" - LocalnetReservationProgramID = "" // TODO: set when deployed to localnet + LocalnetShredSubscriptionProgramID = "" // TODO: set when deployed to localnet LocalnetUSDCMint = "" // TODO: set when deployed to localnet LocalnetTelemetryGNMITunnelServerAddr = "localhost:50051" ) diff --git a/config/env.go b/config/env.go index 8ac2469c9e..80098a6ee2 100644 --- a/config/env.go +++ b/config/env.go @@ -29,7 +29,7 @@ type NetworkConfig struct { TelemetryStateIngestURL string TelemetryGNMITunnelServerAddr string GeolocationProgramID solana.PublicKey - ReservationProgramID string + ShredSubscriptionProgramID string USDCMint string } @@ -65,7 +65,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { RevenueDistributionProgramID: revenueDistributionProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ReservationProgramID: MainnetReservationProgramID, + ShredSubscriptionProgramID: MainnetShredSubscriptionProgramID, USDCMint: MainnetUSDCMint, DeviceLocalASN: MainnetDeviceLocalASN, TwoZOracleURL: MainnetTwoZOracleURL, @@ -98,7 +98,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ReservationProgramID: TestnetReservationProgramID, + ShredSubscriptionProgramID: TestnetShredSubscriptionProgramID, USDCMint: TestnetUSDCMint, DeviceLocalASN: TestnetDeviceLocalASN, TwoZOracleURL: TestnetTwoZOracleURL, @@ -131,7 +131,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ReservationProgramID: DevnetReservationProgramID, + ShredSubscriptionProgramID: DevnetShredSubscriptionProgramID, USDCMint: DevnetUSDCMint, DeviceLocalASN: DevnetDeviceLocalASN, TwoZOracleURL: DevnetTwoZOracleURL, @@ -164,7 +164,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ReservationProgramID: LocalnetReservationProgramID, + ShredSubscriptionProgramID: LocalnetShredSubscriptionProgramID, USDCMint: LocalnetUSDCMint, DeviceLocalASN: LocalnetDeviceLocalASN, TwoZOracleURL: LocalnetTwoZOracleURL, @@ -178,10 +178,10 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { return nil, fmt.Errorf("invalid environment %q, must be one of: %s, %s, %s", env, EnvMainnetBeta, EnvTestnet, EnvDevnet) } - // Validate reservation program ID if set (empty means not yet deployed to this env). - if config.ReservationProgramID != "" { - if _, err := solana.PublicKeyFromBase58(config.ReservationProgramID); err != nil { - return nil, fmt.Errorf("failed to parse reservation program ID: %w", err) + // Validate shred subscription program ID if set (empty means not yet deployed to this env). + if config.ShredSubscriptionProgramID != "" { + if _, err := solana.PublicKeyFromBase58(config.ShredSubscriptionProgramID); err != nil { + return nil, fmt.Errorf("failed to parse shred subscription program ID: %w", err) } } diff --git a/config/env_test.go b/config/env_test.go index e5616366ea..3d2db64d7d 100644 --- a/config/env_test.go +++ b/config/env_test.go @@ -32,7 +32,7 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.MainnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.MainnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.MainnetGeolocationProgramID), - ReservationProgramID: config.MainnetReservationProgramID, + ShredSubscriptionProgramID: config.MainnetShredSubscriptionProgramID, USDCMint: config.MainnetUSDCMint, }, }, @@ -52,7 +52,7 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.MainnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.MainnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.MainnetGeolocationProgramID), - ReservationProgramID: config.MainnetReservationProgramID, + ShredSubscriptionProgramID: config.MainnetShredSubscriptionProgramID, USDCMint: config.MainnetUSDCMint, }, }, @@ -71,7 +71,7 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.TestnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.TestnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.TestnetGeolocationProgramID), - ReservationProgramID: config.TestnetReservationProgramID, + ShredSubscriptionProgramID: config.TestnetShredSubscriptionProgramID, USDCMint: config.TestnetUSDCMint, }, }, diff --git a/e2e/internal/qa/client.go b/e2e/internal/qa/client.go index 7687ad8022..e79e945fcb 100644 --- a/e2e/internal/qa/client.go +++ b/e2e/internal/qa/client.go @@ -106,7 +106,7 @@ type Client struct { // SolanaRPCURL is the Solana RPC endpoint for settlement transactions (--url). // On testnet this is the DZ ledger URL; on mainnet it's the public Solana RPC. SolanaRPCURL string - ReservationProgramID string + ShredSubscriptionProgramID string DZLedgerURL string USDCMint string Keypair string @@ -153,7 +153,7 @@ func NewClient(ctx context.Context, log *slog.Logger, hostname string, port int, Host: hostname, AllocateAddr: allocateAddr, SolanaRPCURL: solanaRPCURL, - ReservationProgramID: networkConfig.ReservationProgramID, + ShredSubscriptionProgramID: networkConfig.ShredSubscriptionProgramID, DZLedgerURL: networkConfig.LedgerPublicRPCURL, USDCMint: networkConfig.USDCMint, }, nil diff --git a/e2e/internal/qa/client_settlement.go b/e2e/internal/qa/client_settlement.go index 36dda7cdc5..41710557f5 100644 --- a/e2e/internal/qa/client_settlement.go +++ b/e2e/internal/qa/client_settlement.go @@ -64,7 +64,7 @@ func (c *Client) FeedSeatPrice(ctx context.Context) ([]*pb.DevicePrice, error) { DzLedgerUrl: c.DZLedgerURL, UsdcMint: c.USDCMint, Keypair: c.Keypair, - ReservationProgramId: c.ReservationProgramID, + ShredSubscriptionProgramId: c.ShredSubscriptionProgramID, }) if err != nil { return nil, fmt.Errorf("failed to get seat prices on host %s: %w", c.Host, err) @@ -82,7 +82,7 @@ func (c *Client) FeedSeatPay(ctx context.Context, devicePubkey string, amount st ClientIp: c.publicIP.To4().String(), Amount: amount, SolanaRpcUrl: c.SolanaRPCURL, - ReservationProgramId: c.ReservationProgramID, + ShredSubscriptionProgramId: c.ShredSubscriptionProgramID, DzLedgerUrl: c.DZLedgerURL, UsdcMint: c.USDCMint, Keypair: c.Keypair, @@ -106,7 +106,7 @@ func (c *Client) FeedSeatWithdraw(ctx context.Context, devicePubkey string) erro DevicePubkey: devicePubkey, ClientIp: c.publicIP.To4().String(), SolanaRpcUrl: c.SolanaRPCURL, - ReservationProgramId: c.ReservationProgramID, + ShredSubscriptionProgramId: c.ShredSubscriptionProgramID, DzLedgerUrl: c.DZLedgerURL, UsdcMint: c.USDCMint, Keypair: c.Keypair, diff --git a/e2e/internal/rpc/agent.go b/e2e/internal/rpc/agent.go index cb7f6ca70e..98f0ff34ca 100644 --- a/e2e/internal/rpc/agent.go +++ b/e2e/internal/rpc/agent.go @@ -366,8 +366,8 @@ func (q *QAAgent) FeedSeatPrice(ctx context.Context, req *pb.FeedSeatPriceReques defer cancel() cmd := exec.CommandContext(cmdCtx, "doublezero-solana", args...) - if req.GetReservationProgramId() != "" { - cmd.Env = append(cmd.Environ(), "RESERVATION_PROGRAM_ID="+req.GetReservationProgramId()) + if req.GetShredSubscriptionProgramId() != "" { + cmd.Env = append(cmd.Environ(), "SHRED_SUBSCRIPTION_PROGRAM_ID="+req.GetShredSubscriptionProgramId()) } out, err := cmd.CombinedOutput() if err != nil { @@ -448,8 +448,8 @@ func (q *QAAgent) FeedSeatPay(ctx context.Context, req *pb.FeedSeatPayRequest) ( defer cancel() cmd := exec.CommandContext(cmdCtx, "doublezero-solana", args...) - if req.GetReservationProgramId() != "" { - cmd.Env = append(cmd.Environ(), "RESERVATION_PROGRAM_ID="+req.GetReservationProgramId()) + if req.GetShredSubscriptionProgramId() != "" { + cmd.Env = append(cmd.Environ(), "SHRED_SUBSCRIPTION_PROGRAM_ID="+req.GetShredSubscriptionProgramId()) } res, err := runCmd(cmd) if err != nil { @@ -491,8 +491,8 @@ func (q *QAAgent) FeedSeatWithdraw(ctx context.Context, req *pb.FeedSeatWithdraw defer cancel() cmd := exec.CommandContext(cmdCtx, "doublezero-solana", args...) - if req.GetReservationProgramId() != "" { - cmd.Env = append(cmd.Environ(), "RESERVATION_PROGRAM_ID="+req.GetReservationProgramId()) + if req.GetShredSubscriptionProgramId() != "" { + cmd.Env = append(cmd.Environ(), "SHRED_SUBSCRIPTION_PROGRAM_ID="+req.GetShredSubscriptionProgramId()) } res, err := runCmd(cmd) if err != nil { diff --git a/e2e/proto/qa/agent.proto b/e2e/proto/qa/agent.proto index f16baeb8c8..3f02c1e3fe 100644 --- a/e2e/proto/qa/agent.proto +++ b/e2e/proto/qa/agent.proto @@ -214,7 +214,7 @@ message FeedSeatPayRequest { string amount = 3; bool instant = 4; string solana_rpc_url = 5; - string reservation_program_id = 6; + string shred_subscription_program_id = 6; string dz_ledger_url = 7; string usdc_mint = 8; string keypair = 9; @@ -225,7 +225,7 @@ message FeedSeatWithdrawRequest { string client_ip = 2; bool instant = 3; string solana_rpc_url = 4; - string reservation_program_id = 5; + string shred_subscription_program_id = 5; string dz_ledger_url = 6; string usdc_mint = 7; string keypair = 8; @@ -236,7 +236,7 @@ message FeedSeatPriceRequest { string dz_ledger_url = 2; string usdc_mint = 3; string keypair = 4; - string reservation_program_id = 5; + string shred_subscription_program_id = 5; } message DevicePrice { diff --git a/e2e/proto/qa/gen/pb-go/agent.pb.go b/e2e/proto/qa/gen/pb-go/agent.pb.go index a5f526f286..d3e362797b 100644 --- a/e2e/proto/qa/gen/pb-go/agent.pb.go +++ b/e2e/proto/qa/gen/pb-go/agent.pb.go @@ -1741,7 +1741,7 @@ type FeedSeatPayRequest struct { Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` Instant bool `protobuf:"varint,4,opt,name=instant,proto3" json:"instant,omitempty"` SolanaRpcUrl string `protobuf:"bytes,5,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` - ReservationProgramId string `protobuf:"bytes,6,opt,name=reservation_program_id,json=reservationProgramId,proto3" json:"reservation_program_id,omitempty"` + ShredSubscriptionProgramId string `protobuf:"bytes,6,opt,name=shred_subscription_program_id,json=shredSubscriptionProgramId,proto3" json:"shred_subscription_program_id,omitempty"` DzLedgerUrl string `protobuf:"bytes,7,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` UsdcMint string `protobuf:"bytes,8,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` Keypair string `protobuf:"bytes,9,opt,name=keypair,proto3" json:"keypair,omitempty"` @@ -1814,9 +1814,9 @@ func (x *FeedSeatPayRequest) GetSolanaRpcUrl() string { return "" } -func (x *FeedSeatPayRequest) GetReservationProgramId() string { +func (x *FeedSeatPayRequest) GetShredSubscriptionProgramId() string { if x != nil { - return x.ReservationProgramId + return x.ShredSubscriptionProgramId } return "" } @@ -1848,7 +1848,7 @@ type FeedSeatWithdrawRequest struct { ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` Instant bool `protobuf:"varint,3,opt,name=instant,proto3" json:"instant,omitempty"` SolanaRpcUrl string `protobuf:"bytes,4,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` - ReservationProgramId string `protobuf:"bytes,5,opt,name=reservation_program_id,json=reservationProgramId,proto3" json:"reservation_program_id,omitempty"` + ShredSubscriptionProgramId string `protobuf:"bytes,5,opt,name=shred_subscription_program_id,json=shredSubscriptionProgramId,proto3" json:"shred_subscription_program_id,omitempty"` DzLedgerUrl string `protobuf:"bytes,6,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` UsdcMint string `protobuf:"bytes,7,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` Keypair string `protobuf:"bytes,8,opt,name=keypair,proto3" json:"keypair,omitempty"` @@ -1914,9 +1914,9 @@ func (x *FeedSeatWithdrawRequest) GetSolanaRpcUrl() string { return "" } -func (x *FeedSeatWithdrawRequest) GetReservationProgramId() string { +func (x *FeedSeatWithdrawRequest) GetShredSubscriptionProgramId() string { if x != nil { - return x.ReservationProgramId + return x.ShredSubscriptionProgramId } return "" } @@ -1948,7 +1948,7 @@ type FeedSeatPriceRequest struct { DzLedgerUrl string `protobuf:"bytes,2,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` UsdcMint string `protobuf:"bytes,3,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` Keypair string `protobuf:"bytes,4,opt,name=keypair,proto3" json:"keypair,omitempty"` - ReservationProgramId string `protobuf:"bytes,5,opt,name=reservation_program_id,json=reservationProgramId,proto3" json:"reservation_program_id,omitempty"` + ShredSubscriptionProgramId string `protobuf:"bytes,5,opt,name=shred_subscription_program_id,json=shredSubscriptionProgramId,proto3" json:"shred_subscription_program_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2011,9 +2011,9 @@ func (x *FeedSeatPriceRequest) GetKeypair() string { return "" } -func (x *FeedSeatPriceRequest) GetReservationProgramId() string { +func (x *FeedSeatPriceRequest) GetShredSubscriptionProgramId() string { if x != nil { - return x.ReservationProgramId + return x.ShredSubscriptionProgramId } return "" } @@ -2316,7 +2316,7 @@ const file_agent_proto_rawDesc = "" + "\x06amount\x18\x03 \x01(\tR\x06amount\x12\x18\n" + "\ainstant\x18\x04 \x01(\bR\ainstant\x12$\n" + "\x0esolana_rpc_url\x18\x05 \x01(\tR\fsolanaRpcUrl\x124\n" + - "\x16reservation_program_id\x18\x06 \x01(\tR\x14reservationProgramId\x12\"\n" + + "\x16shred_subscription_program_id\x18\x06 \x01(\tR\x14shredSubscriptionProgramId\x12\"\n" + "\rdz_ledger_url\x18\a \x01(\tR\vdzLedgerUrl\x12\x1b\n" + "\tusdc_mint\x18\b \x01(\tR\busdcMint\x12\x18\n" + "\akeypair\x18\t \x01(\tR\akeypair\"\xac\x02\n" + @@ -2325,7 +2325,7 @@ const file_agent_proto_rawDesc = "" + "\tclient_ip\x18\x02 \x01(\tR\bclientIp\x12\x18\n" + "\ainstant\x18\x03 \x01(\bR\ainstant\x12$\n" + "\x0esolana_rpc_url\x18\x04 \x01(\tR\fsolanaRpcUrl\x124\n" + - "\x16reservation_program_id\x18\x05 \x01(\tR\x14reservationProgramId\x12\"\n" + + "\x16shred_subscription_program_id\x18\x05 \x01(\tR\x14shredSubscriptionProgramId\x12\"\n" + "\rdz_ledger_url\x18\x06 \x01(\tR\vdzLedgerUrl\x12\x1b\n" + "\tusdc_mint\x18\a \x01(\tR\busdcMint\x12\x18\n" + "\akeypair\x18\b \x01(\tR\akeypair\"\xcd\x01\n" + @@ -2334,7 +2334,7 @@ const file_agent_proto_rawDesc = "" + "\rdz_ledger_url\x18\x02 \x01(\tR\vdzLedgerUrl\x12\x1b\n" + "\tusdc_mint\x18\x03 \x01(\tR\busdcMint\x12\x18\n" + "\akeypair\x18\x04 \x01(\tR\akeypair\x124\n" + - "\x16reservation_program_id\x18\x05 \x01(\tR\x14reservationProgramId\"\xd1\x02\n" + + "\x16shred_subscription_program_id\x18\x05 \x01(\tR\x14shredSubscriptionProgramId\"\xd1\x02\n" + "\vDevicePrice\x12\x1f\n" + "\vdevice_code\x18\x01 \x01(\tR\n" + "deviceCode\x12#\n" + From 920dd4062fed131cc6f83ba155e5d343a9133340 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 16:42:47 -0400 Subject: [PATCH 3/8] e2e: regenerate qa agent protobuf code for protobuf v1.36.x compat --- e2e/proto/qa/gen/pb-go/agent.pb.go | 74 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/e2e/proto/qa/gen/pb-go/agent.pb.go b/e2e/proto/qa/gen/pb-go/agent.pb.go index d3e362797b..4dddf5eb90 100644 --- a/e2e/proto/qa/gen/pb-go/agent.pb.go +++ b/e2e/proto/qa/gen/pb-go/agent.pb.go @@ -1735,18 +1735,18 @@ func (x *MulticastSendRequest) GetDuration() uint32 { } type FeedSeatPayRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - DevicePubkey string `protobuf:"bytes,1,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` - ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` - Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` - Instant bool `protobuf:"varint,4,opt,name=instant,proto3" json:"instant,omitempty"` - SolanaRpcUrl string `protobuf:"bytes,5,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + DevicePubkey string `protobuf:"bytes,1,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` + ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Instant bool `protobuf:"varint,4,opt,name=instant,proto3" json:"instant,omitempty"` + SolanaRpcUrl string `protobuf:"bytes,5,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` ShredSubscriptionProgramId string `protobuf:"bytes,6,opt,name=shred_subscription_program_id,json=shredSubscriptionProgramId,proto3" json:"shred_subscription_program_id,omitempty"` - DzLedgerUrl string `protobuf:"bytes,7,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` - UsdcMint string `protobuf:"bytes,8,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` - Keypair string `protobuf:"bytes,9,opt,name=keypair,proto3" json:"keypair,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + DzLedgerUrl string `protobuf:"bytes,7,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` + UsdcMint string `protobuf:"bytes,8,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` + Keypair string `protobuf:"bytes,9,opt,name=keypair,proto3" json:"keypair,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FeedSeatPayRequest) Reset() { @@ -1843,17 +1843,17 @@ func (x *FeedSeatPayRequest) GetKeypair() string { } type FeedSeatWithdrawRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - DevicePubkey string `protobuf:"bytes,1,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` - ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` - Instant bool `protobuf:"varint,3,opt,name=instant,proto3" json:"instant,omitempty"` - SolanaRpcUrl string `protobuf:"bytes,4,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + DevicePubkey string `protobuf:"bytes,1,opt,name=device_pubkey,json=devicePubkey,proto3" json:"device_pubkey,omitempty"` + ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + Instant bool `protobuf:"varint,3,opt,name=instant,proto3" json:"instant,omitempty"` + SolanaRpcUrl string `protobuf:"bytes,4,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` ShredSubscriptionProgramId string `protobuf:"bytes,5,opt,name=shred_subscription_program_id,json=shredSubscriptionProgramId,proto3" json:"shred_subscription_program_id,omitempty"` - DzLedgerUrl string `protobuf:"bytes,6,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` - UsdcMint string `protobuf:"bytes,7,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` - Keypair string `protobuf:"bytes,8,opt,name=keypair,proto3" json:"keypair,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + DzLedgerUrl string `protobuf:"bytes,6,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` + UsdcMint string `protobuf:"bytes,7,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` + Keypair string `protobuf:"bytes,8,opt,name=keypair,proto3" json:"keypair,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FeedSeatWithdrawRequest) Reset() { @@ -1943,14 +1943,14 @@ func (x *FeedSeatWithdrawRequest) GetKeypair() string { } type FeedSeatPriceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - SolanaRpcUrl string `protobuf:"bytes,1,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` - DzLedgerUrl string `protobuf:"bytes,2,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` - UsdcMint string `protobuf:"bytes,3,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` - Keypair string `protobuf:"bytes,4,opt,name=keypair,proto3" json:"keypair,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + SolanaRpcUrl string `protobuf:"bytes,1,opt,name=solana_rpc_url,json=solanaRpcUrl,proto3" json:"solana_rpc_url,omitempty"` + DzLedgerUrl string `protobuf:"bytes,2,opt,name=dz_ledger_url,json=dzLedgerUrl,proto3" json:"dz_ledger_url,omitempty"` + UsdcMint string `protobuf:"bytes,3,opt,name=usdc_mint,json=usdcMint,proto3" json:"usdc_mint,omitempty"` + Keypair string `protobuf:"bytes,4,opt,name=keypair,proto3" json:"keypair,omitempty"` ShredSubscriptionProgramId string `protobuf:"bytes,5,opt,name=shred_subscription_program_id,json=shredSubscriptionProgramId,proto3" json:"shred_subscription_program_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FeedSeatPriceRequest) Reset() { @@ -2309,32 +2309,32 @@ const file_agent_proto_rawDesc = "" + "\x14MulticastSendRequest\x12\x14\n" + "\x05group\x18\x01 \x01(\tR\x05group\x12\x12\n" + "\x04port\x18\x02 \x01(\rR\x04port\x12\x1a\n" + - "\bduration\x18\x03 \x01(\rR\bduration\"\xbf\x02\n" + + "\bduration\x18\x03 \x01(\rR\bduration\"\xcc\x02\n" + "\x12FeedSeatPayRequest\x12#\n" + "\rdevice_pubkey\x18\x01 \x01(\tR\fdevicePubkey\x12\x1b\n" + "\tclient_ip\x18\x02 \x01(\tR\bclientIp\x12\x16\n" + "\x06amount\x18\x03 \x01(\tR\x06amount\x12\x18\n" + "\ainstant\x18\x04 \x01(\bR\ainstant\x12$\n" + - "\x0esolana_rpc_url\x18\x05 \x01(\tR\fsolanaRpcUrl\x124\n" + - "\x16shred_subscription_program_id\x18\x06 \x01(\tR\x14shredSubscriptionProgramId\x12\"\n" + + "\x0esolana_rpc_url\x18\x05 \x01(\tR\fsolanaRpcUrl\x12A\n" + + "\x1dshred_subscription_program_id\x18\x06 \x01(\tR\x1ashredSubscriptionProgramId\x12\"\n" + "\rdz_ledger_url\x18\a \x01(\tR\vdzLedgerUrl\x12\x1b\n" + "\tusdc_mint\x18\b \x01(\tR\busdcMint\x12\x18\n" + - "\akeypair\x18\t \x01(\tR\akeypair\"\xac\x02\n" + + "\akeypair\x18\t \x01(\tR\akeypair\"\xb9\x02\n" + "\x17FeedSeatWithdrawRequest\x12#\n" + "\rdevice_pubkey\x18\x01 \x01(\tR\fdevicePubkey\x12\x1b\n" + "\tclient_ip\x18\x02 \x01(\tR\bclientIp\x12\x18\n" + "\ainstant\x18\x03 \x01(\bR\ainstant\x12$\n" + - "\x0esolana_rpc_url\x18\x04 \x01(\tR\fsolanaRpcUrl\x124\n" + - "\x16shred_subscription_program_id\x18\x05 \x01(\tR\x14shredSubscriptionProgramId\x12\"\n" + + "\x0esolana_rpc_url\x18\x04 \x01(\tR\fsolanaRpcUrl\x12A\n" + + "\x1dshred_subscription_program_id\x18\x05 \x01(\tR\x1ashredSubscriptionProgramId\x12\"\n" + "\rdz_ledger_url\x18\x06 \x01(\tR\vdzLedgerUrl\x12\x1b\n" + "\tusdc_mint\x18\a \x01(\tR\busdcMint\x12\x18\n" + - "\akeypair\x18\b \x01(\tR\akeypair\"\xcd\x01\n" + + "\akeypair\x18\b \x01(\tR\akeypair\"\xda\x01\n" + "\x14FeedSeatPriceRequest\x12$\n" + "\x0esolana_rpc_url\x18\x01 \x01(\tR\fsolanaRpcUrl\x12\"\n" + "\rdz_ledger_url\x18\x02 \x01(\tR\vdzLedgerUrl\x12\x1b\n" + "\tusdc_mint\x18\x03 \x01(\tR\busdcMint\x12\x18\n" + - "\akeypair\x18\x04 \x01(\tR\akeypair\x124\n" + - "\x16shred_subscription_program_id\x18\x05 \x01(\tR\x14shredSubscriptionProgramId\"\xd1\x02\n" + + "\akeypair\x18\x04 \x01(\tR\akeypair\x12A\n" + + "\x1dshred_subscription_program_id\x18\x05 \x01(\tR\x1ashredSubscriptionProgramId\"\xd1\x02\n" + "\vDevicePrice\x12\x1f\n" + "\vdevice_code\x18\x01 \x01(\tR\n" + "deviceCode\x12#\n" + From f2f68cdbffbffbcb40dc1c68565f6d81c1c80e92 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 16:55:02 -0400 Subject: [PATCH 4/8] config: remove TODO comments for undeployed devnet/localnet constants --- config/constants.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/constants.go b/config/constants.go index ee98aa5ab1..72f9c36f0a 100644 --- a/config/constants.go +++ b/config/constants.go @@ -43,8 +43,8 @@ const ( DevnetTelemetryFlowIngestURL = "http://telemetry-flow-in.devnet.doublezero.xyz" DevnetTelemetryStateIngestURL = "http://telemetry-state-in.devnet.doublezero.xyz" DevnetGeolocationProgramID = "EXUUFfAjjuXnaBtsAMLsJX18ynnNHPwtkmk33bLVVoCm" - DevnetShredSubscriptionProgramID = "" // TODO: set when deployed to devnet - DevnetUSDCMint = "" // TODO: set when deployed to devnet + DevnetShredSubscriptionProgramID = "" + DevnetUSDCMint = "" DevnetTelemetryGNMITunnelServerAddr = "gnmic-devnet.doublezero.xyz:443" // Localnet constants. @@ -58,7 +58,7 @@ const ( LocalnetTelemetryFlowIngestURL = "http://localhost:8911" LocalnetTelemetryStateIngestURL = "http://localhost:8911" LocalnetGeolocationProgramID = "36WA9nUCsJaAQL5h44WYoLezDpocy8Q71NZbtrUN8DyC" - LocalnetShredSubscriptionProgramID = "" // TODO: set when deployed to localnet - LocalnetUSDCMint = "" // TODO: set when deployed to localnet + LocalnetShredSubscriptionProgramID = "" + LocalnetUSDCMint = "" LocalnetTelemetryGNMITunnelServerAddr = "localhost:50051" ) From f717fc6235c3edec92e4417b53a6415fc1250e36 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 17:16:27 -0400 Subject: [PATCH 5/8] e2e: use random client in settlement test instead of requiring single host --- e2e/qa_settlement_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/e2e/qa_settlement_test.go b/e2e/qa_settlement_test.go index 3942d2f600..90a209ba7e 100644 --- a/e2e/qa_settlement_test.go +++ b/e2e/qa_settlement_test.go @@ -28,9 +28,7 @@ func TestQA_MulticastSettlement(t *testing.T) { test, err := qa.NewTest(ctx, log, hostsArg, portArg, networkConfig, nil) require.NoError(t, err, "failed to create test") - require.Len(t, hostsArg, 1, "settlement test requires exactly one host via -hosts") - client := test.GetClient(hostsArg[0]) - require.NotNil(t, client, "host %q not found", hostsArg[0]) + client := test.RandomClient() if *keypairFlag != "" { client.Keypair = *keypairFlag } From 46cd35fdb36ade935cdfa83709846b5340d10a45 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 17:21:38 -0400 Subject: [PATCH 6/8] e2e: rename settlement test flag to --enable-multicast-settlement-tests --- e2e/qa_settlement_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/qa_settlement_test.go b/e2e/qa_settlement_test.go index 90a209ba7e..dffdfa94c4 100644 --- a/e2e/qa_settlement_test.go +++ b/e2e/qa_settlement_test.go @@ -14,13 +14,13 @@ import ( ) var ( - enableSettlementTests = flag.Bool("enable-settlement-tests", false, "enable multicast settlement tests") + enableSettlementTests = flag.Bool("enable-multicast-settlement-tests", false, "enable multicast settlement tests") keypairFlag = flag.String("keypair", "$HOME/.config/doublezero/id.json", "path to keypair file for settlement commands") ) func TestQA_MulticastSettlement(t *testing.T) { if !*enableSettlementTests { - t.Skip("Skipping: --enable-settlement-tests flag not set") + t.Skip("Skipping: --enable-multicast-settlement-tests flag not set") } log := newTestLogger(t) From 4d095590189163462dd93cf92970cede6d6608fd Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 17:30:07 -0400 Subject: [PATCH 7/8] e2e: fix lint issues in qa agent code --- e2e/internal/qa/client.go | 18 +++++++-------- e2e/internal/qa/client_settlement.go | 34 ++++++++++++++-------------- e2e/internal/qa/provisioning.go | 24 -------------------- e2e/internal/rpc/traceroute.go | 2 ++ 4 files changed, 28 insertions(+), 50 deletions(-) diff --git a/e2e/internal/qa/client.go b/e2e/internal/qa/client.go index e79e945fcb..bc5c59a877 100644 --- a/e2e/internal/qa/client.go +++ b/e2e/internal/qa/client.go @@ -105,11 +105,11 @@ type Client struct { // Settlement config passed to doublezero-solana shreds commands. // SolanaRPCURL is the Solana RPC endpoint for settlement transactions (--url). // On testnet this is the DZ ledger URL; on mainnet it's the public Solana RPC. - SolanaRPCURL string + SolanaRPCURL string ShredSubscriptionProgramID string - DZLedgerURL string - USDCMint string - Keypair string + DZLedgerURL string + USDCMint string + Keypair string } func NewClient(ctx context.Context, log *slog.Logger, hostname string, port int, networkConfig *config.NetworkConfig, devices map[string]*Device, allocateAddr bool) (*Client, error) { @@ -150,12 +150,12 @@ func NewClient(ctx context.Context, log *slog.Logger, hostname string, port int, serviceability: serviceabilityClient, devices: devices, - Host: hostname, - AllocateAddr: allocateAddr, - SolanaRPCURL: solanaRPCURL, + Host: hostname, + AllocateAddr: allocateAddr, + SolanaRPCURL: solanaRPCURL, ShredSubscriptionProgramID: networkConfig.ShredSubscriptionProgramID, - DZLedgerURL: networkConfig.LedgerPublicRPCURL, - USDCMint: networkConfig.USDCMint, + DZLedgerURL: networkConfig.LedgerPublicRPCURL, + USDCMint: networkConfig.USDCMint, }, nil } diff --git a/e2e/internal/qa/client_settlement.go b/e2e/internal/qa/client_settlement.go index 41710557f5..2f703bc110 100644 --- a/e2e/internal/qa/client_settlement.go +++ b/e2e/internal/qa/client_settlement.go @@ -60,10 +60,10 @@ func (c *Client) ClosestDevice(ctx context.Context) (*Device, error) { func (c *Client) FeedSeatPrice(ctx context.Context) ([]*pb.DevicePrice, error) { c.log.Debug("Querying seat prices", "host", c.Host) resp, err := c.grpcClient.FeedSeatPrice(ctx, &pb.FeedSeatPriceRequest{ - SolanaRpcUrl: c.SolanaRPCURL, - DzLedgerUrl: c.DZLedgerURL, - UsdcMint: c.USDCMint, - Keypair: c.Keypair, + SolanaRpcUrl: c.SolanaRPCURL, + DzLedgerUrl: c.DZLedgerURL, + UsdcMint: c.USDCMint, + Keypair: c.Keypair, ShredSubscriptionProgramId: c.ShredSubscriptionProgramID, }) if err != nil { @@ -78,14 +78,14 @@ func (c *Client) FeedSeatPrice(ctx context.Context) ([]*pb.DevicePrice, error) { func (c *Client) FeedSeatPay(ctx context.Context, devicePubkey string, amount string) error { c.log.Debug("Paying for seat", "host", c.Host, "device", devicePubkey, "amount", amount) resp, err := c.grpcClient.FeedSeatPay(ctx, &pb.FeedSeatPayRequest{ - DevicePubkey: devicePubkey, - ClientIp: c.publicIP.To4().String(), - Amount: amount, - SolanaRpcUrl: c.SolanaRPCURL, + DevicePubkey: devicePubkey, + ClientIp: c.publicIP.To4().String(), + Amount: amount, + SolanaRpcUrl: c.SolanaRPCURL, ShredSubscriptionProgramId: c.ShredSubscriptionProgramID, - DzLedgerUrl: c.DZLedgerURL, - UsdcMint: c.USDCMint, - Keypair: c.Keypair, + DzLedgerUrl: c.DZLedgerURL, + UsdcMint: c.USDCMint, + Keypair: c.Keypair, }) if err != nil { return fmt.Errorf("failed to pay for seat on host %s: %w", c.Host, err) @@ -103,13 +103,13 @@ func (c *Client) FeedSeatPay(ctx context.Context, devicePubkey string, amount st func (c *Client) FeedSeatWithdraw(ctx context.Context, devicePubkey string) error { c.log.Debug("Withdrawing seat", "host", c.Host, "device", devicePubkey) resp, err := c.grpcClient.FeedSeatWithdraw(ctx, &pb.FeedSeatWithdrawRequest{ - DevicePubkey: devicePubkey, - ClientIp: c.publicIP.To4().String(), - SolanaRpcUrl: c.SolanaRPCURL, + DevicePubkey: devicePubkey, + ClientIp: c.publicIP.To4().String(), + SolanaRpcUrl: c.SolanaRPCURL, ShredSubscriptionProgramId: c.ShredSubscriptionProgramID, - DzLedgerUrl: c.DZLedgerURL, - UsdcMint: c.USDCMint, - Keypair: c.Keypair, + DzLedgerUrl: c.DZLedgerURL, + UsdcMint: c.USDCMint, + Keypair: c.Keypair, }) if err != nil { return fmt.Errorf("failed to withdraw seat on host %s: %w", c.Host, err) diff --git a/e2e/internal/qa/provisioning.go b/e2e/internal/qa/provisioning.go index dd6d9629a0..b184030edd 100644 --- a/e2e/internal/qa/provisioning.go +++ b/e2e/internal/qa/provisioning.go @@ -601,27 +601,3 @@ func formatBandwidth(bps uint64) string { return fmt.Sprintf("%d bps", bps) } -type CLIDeviceOutput struct { - Account string `json:"account"` - Code string `json:"code"` - ContributorCode string `json:"contributor_code"` - LocationCode string `json:"location_code"` - ExchangeCode string `json:"exchange_code"` - DeviceType string `json:"device_type"` - PublicIP string `json:"public_ip"` - DzPrefixes []string `json:"dz_prefixes"` - Users int `json:"users"` - MaxUsers int `json:"max_users"` - Status string `json:"status"` - Health string `json:"health"` - MgmtVrf string `json:"mgmt_vrf"` - Owner string `json:"owner"` -} - -func parseDeviceListJSON(output []byte) ([]CLIDeviceOutput, error) { - var devices []CLIDeviceOutput - if err := json.Unmarshal(output, &devices); err != nil { - return nil, fmt.Errorf("failed to parse device list JSON: %w", err) - } - return devices, nil -} diff --git a/e2e/internal/rpc/traceroute.go b/e2e/internal/rpc/traceroute.go index 41d3b58389..8dee363062 100644 --- a/e2e/internal/rpc/traceroute.go +++ b/e2e/internal/rpc/traceroute.go @@ -1,3 +1,5 @@ +//go:build linux + package rpc import ( From 72b245cc1848965b69144bb05a951ec3f4bc4208 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Tue, 24 Mar 2026 17:40:09 -0400 Subject: [PATCH 8/8] config: fix gofmt formatting --- config/env.go | 10 +++++----- config/env_test.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/env.go b/config/env.go index 80098a6ee2..5ed78d04bf 100644 --- a/config/env.go +++ b/config/env.go @@ -29,7 +29,7 @@ type NetworkConfig struct { TelemetryStateIngestURL string TelemetryGNMITunnelServerAddr string GeolocationProgramID solana.PublicKey - ShredSubscriptionProgramID string + ShredSubscriptionProgramID string USDCMint string } @@ -65,7 +65,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { RevenueDistributionProgramID: revenueDistributionProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ShredSubscriptionProgramID: MainnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: MainnetShredSubscriptionProgramID, USDCMint: MainnetUSDCMint, DeviceLocalASN: MainnetDeviceLocalASN, TwoZOracleURL: MainnetTwoZOracleURL, @@ -98,7 +98,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ShredSubscriptionProgramID: TestnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: TestnetShredSubscriptionProgramID, USDCMint: TestnetUSDCMint, DeviceLocalASN: TestnetDeviceLocalASN, TwoZOracleURL: TestnetTwoZOracleURL, @@ -131,7 +131,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ShredSubscriptionProgramID: DevnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: DevnetShredSubscriptionProgramID, USDCMint: DevnetUSDCMint, DeviceLocalASN: DevnetDeviceLocalASN, TwoZOracleURL: DevnetTwoZOracleURL, @@ -164,7 +164,7 @@ func NetworkConfigForEnv(env string) (*NetworkConfig, error) { TelemetryProgramID: telemetryProgramID, InternetLatencyCollectorPK: internetLatencyCollectorPK, GeolocationProgramID: geolocationProgramID, - ShredSubscriptionProgramID: LocalnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: LocalnetShredSubscriptionProgramID, USDCMint: LocalnetUSDCMint, DeviceLocalASN: LocalnetDeviceLocalASN, TwoZOracleURL: LocalnetTwoZOracleURL, diff --git a/config/env_test.go b/config/env_test.go index 3d2db64d7d..a6a987b287 100644 --- a/config/env_test.go +++ b/config/env_test.go @@ -32,7 +32,7 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.MainnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.MainnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.MainnetGeolocationProgramID), - ShredSubscriptionProgramID: config.MainnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: config.MainnetShredSubscriptionProgramID, USDCMint: config.MainnetUSDCMint, }, }, @@ -52,7 +52,7 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.MainnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.MainnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.MainnetGeolocationProgramID), - ShredSubscriptionProgramID: config.MainnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: config.MainnetShredSubscriptionProgramID, USDCMint: config.MainnetUSDCMint, }, }, @@ -71,7 +71,7 @@ func TestConfig_NetworkConfigForEnv(t *testing.T) { TelemetryStateIngestURL: config.TestnetTelemetryStateIngestURL, TelemetryGNMITunnelServerAddr: config.TestnetTelemetryGNMITunnelServerAddr, GeolocationProgramID: solana.MustPublicKeyFromBase58(config.TestnetGeolocationProgramID), - ShredSubscriptionProgramID: config.TestnetShredSubscriptionProgramID, + ShredSubscriptionProgramID: config.TestnetShredSubscriptionProgramID, USDCMint: config.TestnetUSDCMint, }, },