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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file.
- Add `OutboundIcmp` target type (`= 2`) to the geolocation onchain program, enabling ICMP-based probing as an alternative to TWAMP for outbound geolocation targets
- Allow pending users with subs to be deleted
- Geolocation
- Standardize CLI flag naming: probe mutation commands use `--probe` (was `--code`) accepting pubkey or code; rename `--signing-keypair` → `--signing-pubkey` and `--target-pk` → `--target-signing-pubkey`; add `--json-compact` to `get` commands
- geoprobe-target can now store LocationOffset messages in ClickHouse
- Add ICMP pinger to geoprobe-agent for measuring outbound ICMP targets with interleaved batch send/receive, integrated into the existing measurement cycle alongside TWAMP
- Remove `--additional-parent`, `--additional-targets`, `--additional-icmp-targets`, and `--allowed-pubkeys` CLI flags from geoprobe-agent; all configuration now comes from onchain state via parent and target discovery
Expand Down
6 changes: 3 additions & 3 deletions e2e/geoprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func createGeoprobeOnchain(t *testing.T, dn *devnet.Devnet, code, exchangePK, pu
"--exchange", exchangePK,
"--public-ip", publicIP,
"--port", "8923",
"--signing-keypair", signingKeypair,
"--signing-pubkey", signingKeypair,
})
require.NoError(t, err, "probe create failed: %s", string(output))

Expand Down Expand Up @@ -358,7 +358,7 @@ func addGeoprobeParent(t *testing.T, dn *devnet.Devnet, code, devicePK string) {
t.Helper()
output, err := dn.Manager.Exec(t.Context(), []string{
"doublezero-geolocation", "probe", "add-parent",
"--code", code,
"--probe", code,
"--device", devicePK,
})
require.NoError(t, err, "probe add-parent failed: %s", string(output))
Expand Down Expand Up @@ -912,7 +912,7 @@ func addGeolocationInboundTarget(t *testing.T, dn *devnet.Devnet, userCode, targ
"doublezero-geolocation", "user", "add-target",
"--user", userCode,
"--type", "inbound",
"--target-pk", targetPK,
"--target-signing-pubkey", targetPK,
"--probe", probeCode,
})
require.NoError(t, err, "user add-target inbound failed: %s", string(output))
Expand Down
49 changes: 39 additions & 10 deletions smartcontract/cli/src/geolocation/probe/add_parent.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
use crate::{
geoclicommand::GeoCliCommand,
validators::{validate_code, validate_pubkey_or_code},
};
use crate::{geoclicommand::GeoCliCommand, validators::validate_pubkey_or_code};
use clap::Args;
use doublezero_sdk::geolocation::geo_probe::add_parent_device::AddParentDeviceCommand;
use doublezero_sdk::geolocation::geo_probe::{
add_parent_device::AddParentDeviceCommand, get::GetGeoProbeCommand,
};
use std::io::Write;

#[derive(Args, Debug)]
pub struct AddParentGeoProbeCliCommand {
/// Probe code
#[arg(long, value_name = "PROBE_CODE", value_parser = validate_code)]
pub code: String,
/// Probe pubkey or code
#[arg(long, value_parser = validate_pubkey_or_code)]
pub probe: String,
/// Device pubkey or code to add as parent
#[arg(long, value_name = "PARENT_DEVICE", value_parser = validate_pubkey_or_code)]
pub device: String,
}

impl AddParentGeoProbeCliCommand {
pub fn execute<C: GeoCliCommand, W: Write>(self, client: &C, out: &mut W) -> eyre::Result<()> {
let (_, resolved_probe) = client.get_geo_probe(GetGeoProbeCommand {
pubkey_or_code: self.probe,
})?;
let code = resolved_probe.code;

let device_pk = client.resolve_device_pk(self.device)?;
let serviceability_globalstate_pk = client.get_serviceability_globalstate_pk();

let sig = client.add_parent_device(AddParentDeviceCommand {
code: self.code,
code,
device_pk,
serviceability_globalstate_pk,
})?;
Expand All @@ -37,8 +41,10 @@ impl AddParentGeoProbeCliCommand {
mod tests {
use super::*;
use crate::geoclicommand::MockGeoCliCommand;
use doublezero_geolocation::state::{accounttype::AccountType, geo_probe::GeoProbe};
use mockall::predicate;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
use std::net::Ipv4Addr;

#[test]
fn test_cli_geo_probe_add_parent() {
Expand All @@ -53,6 +59,29 @@ mod tests {
100, 221, 20, 137, 4, 5,
]);

client
.expect_get_geo_probe()
.with(predicate::eq(GetGeoProbeCommand {
pubkey_or_code: "ams-probe-01".to_string(),
}))
.returning(move |_| {
Ok((
Pubkey::new_unique(),
GeoProbe {
account_type: AccountType::GeoProbe,
owner: Pubkey::new_unique(),
exchange_pk: Pubkey::new_unique(),
public_ip: Ipv4Addr::new(10, 0, 0, 1),
location_offset_port: 8923,
code: "ams-probe-01".to_string(),
parent_devices: vec![],
metrics_publisher_pk: Pubkey::new_unique(),
reference_count: 0,
target_update_count: 0,
},
))
});

client
.expect_resolve_device_pk()
.with(predicate::eq(device_pk.to_string()))
Expand All @@ -73,7 +102,7 @@ mod tests {

let mut output = Vec::new();
let res = AddParentGeoProbeCliCommand {
code: "ams-probe-01".to_string(),
probe: "ams-probe-01".to_string(),
device: device_pk.to_string(),
}
.execute(&client, &mut output);
Expand Down
8 changes: 4 additions & 4 deletions smartcontract/cli/src/geolocation/probe/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ pub struct CreateGeoProbeCliCommand {
/// UDP listen port for location offsets
#[arg(long, default_value_t = 8923)]
pub port: u16,
/// Signing keypair public key
/// Signing public key
#[arg(long, value_parser = validate_pubkey)]
pub signing_keypair: String,
pub signing_pubkey: String,
}

impl CreateGeoProbeCliCommand {
pub fn execute<C: GeoCliCommand, W: Write>(self, client: &C, out: &mut W) -> eyre::Result<()> {
let exchange_pk = client.resolve_exchange_pk(self.exchange)?;
let metrics_publisher_pk: Pubkey = self.signing_keypair.parse().expect("validated by clap");
let metrics_publisher_pk: Pubkey = self.signing_pubkey.parse().expect("validated by clap");

let serviceability_globalstate_pk = client.get_serviceability_globalstate_pk();

Expand Down Expand Up @@ -97,7 +97,7 @@ mod tests {
exchange: exchange_pk.to_string(),
public_ip: Ipv4Addr::new(10, 0, 0, 1),
port: 8923,
signing_keypair: metrics_pk.to_string(),
signing_pubkey: metrics_pk.to_string(),
}
.execute(&client, &mut output);
assert!(res.is_ok());
Expand Down
48 changes: 40 additions & 8 deletions smartcontract/cli/src/geolocation/probe/delete.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
use crate::{geoclicommand::GeoCliCommand, validators::validate_code};
use crate::{geoclicommand::GeoCliCommand, validators::validate_pubkey_or_code};
use clap::Args;
use doublezero_sdk::geolocation::geo_probe::delete::DeleteGeoProbeCommand;
use doublezero_sdk::geolocation::geo_probe::{
delete::DeleteGeoProbeCommand, get::GetGeoProbeCommand,
};
use std::io::Write;

#[derive(Args, Debug)]
pub struct DeleteGeoProbeCliCommand {
/// Probe code to delete
#[arg(long, value_name = "PROBE_CODE", value_parser = validate_code)]
pub code: String,
/// Probe pubkey or code to delete
#[arg(long, value_parser = validate_pubkey_or_code)]
pub probe: String,
/// Skip confirmation prompt
#[arg(long, default_value_t = false)]
pub yes: bool,
}

impl DeleteGeoProbeCliCommand {
pub fn execute<C: GeoCliCommand, W: Write>(self, client: &C, out: &mut W) -> eyre::Result<()> {
let (_, resolved_probe) = client.get_geo_probe(GetGeoProbeCommand {
pubkey_or_code: self.probe,
})?;
let code = resolved_probe.code;

if !self.yes {
eprint!("Delete probe '{}'? [y/N]: ", self.code);
eprint!("Delete probe '{code}'? [y/N]: ");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().eq_ignore_ascii_case("y") {
Expand All @@ -28,7 +35,7 @@ impl DeleteGeoProbeCliCommand {
let serviceability_globalstate_pk = client.get_serviceability_globalstate_pk();

let sig = client.delete_geo_probe(DeleteGeoProbeCommand {
code: self.code,
code,
serviceability_globalstate_pk,
})?;

Expand All @@ -42,8 +49,10 @@ impl DeleteGeoProbeCliCommand {
mod tests {
use super::*;
use crate::geoclicommand::MockGeoCliCommand;
use doublezero_geolocation::state::{accounttype::AccountType, geo_probe::GeoProbe};
use mockall::predicate;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
use std::net::Ipv4Addr;

#[test]
fn test_cli_geo_probe_delete() {
Expand All @@ -57,6 +66,29 @@ mod tests {
100, 221, 20, 137, 4, 5,
]);

client
.expect_get_geo_probe()
.with(predicate::eq(GetGeoProbeCommand {
pubkey_or_code: "ams-probe-01".to_string(),
}))
.returning(move |_| {
Ok((
Pubkey::new_unique(),
GeoProbe {
account_type: AccountType::GeoProbe,
owner: Pubkey::new_unique(),
exchange_pk: Pubkey::new_unique(),
public_ip: Ipv4Addr::new(10, 0, 0, 1),
location_offset_port: 8923,
code: "ams-probe-01".to_string(),
parent_devices: vec![],
metrics_publisher_pk: Pubkey::new_unique(),
reference_count: 0,
target_update_count: 0,
},
))
});

client
.expect_get_serviceability_globalstate_pk()
.returning(move || svc_gs_pk);
Expand All @@ -71,7 +103,7 @@ mod tests {

let mut output = Vec::new();
let res = DeleteGeoProbeCliCommand {
code: "ams-probe-01".to_string(),
probe: "ams-probe-01".to_string(),
yes: true,
}
.execute(&client, &mut output);
Expand Down
56 changes: 48 additions & 8 deletions smartcontract/cli/src/geolocation/probe/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ pub struct GetGeoProbeCliCommand {
/// Probe pubkey or code to retrieve
#[arg(long, value_parser = validate_pubkey_or_code)]
pub probe: String,
/// Output as JSON
#[arg(long)]
/// Output as pretty JSON
#[arg(long, default_value_t = false, conflicts_with = "json_compact")]
pub json: bool,
/// Output as compact JSON
#[arg(long, default_value_t = false, conflicts_with = "json")]
pub json_compact: bool,
}

#[derive(Tabled, Serialize)]
Expand All @@ -43,7 +46,7 @@ struct GeoProbeGetDisplay {
#[tabled(rename = "parent_devices")]
pub parent_devices_display: String,
#[serde(serialize_with = "serializer::serialize_pubkey_as_string")]
pub signing_keypair: Pubkey,
pub signing_pubkey: Pubkey,
pub reference_count: u32,
}

Expand Down Expand Up @@ -71,12 +74,16 @@ impl GetGeoProbeCliCommand {
port: probe.location_offset_port,
parent_devices: probe.parent_devices,
parent_devices_display,
signing_keypair: probe.metrics_publisher_pk,
signing_pubkey: probe.metrics_publisher_pk,
reference_count: probe.reference_count,
};

if self.json {
let json = serde_json::to_string_pretty(&display)?;
if self.json || self.json_compact {
let json = if self.json_compact {
serde_json::to_string(&display)?
} else {
serde_json::to_string_pretty(&display)?
};
writeln!(out, "{json}")?;
} else {
let headers = GeoProbeGetDisplay::headers();
Expand Down Expand Up @@ -149,6 +156,7 @@ mod tests {
let res = GetGeoProbeCliCommand {
probe: Pubkey::new_unique().to_string(),
json: false,
json_compact: false,
}
.execute(&client, &mut output);
assert!(res.is_err());
Expand All @@ -157,6 +165,7 @@ mod tests {
let res = GetGeoProbeCliCommand {
probe: probe_pk.to_string(),
json: false,
json_compact: false,
}
.execute(&client, &mut output);
assert!(res.is_ok());
Expand All @@ -172,7 +181,7 @@ mod tests {
assert!(has_row("exchange", &exchange_pk.to_string()));
assert!(has_row("public_ip", "10.0.0.1"));
assert!(has_row("port", "8923"));
assert!(has_row("signing_keypair", &metrics_pk.to_string()));
assert!(has_row("signing_pubkey", &metrics_pk.to_string()));
assert!(has_row("reference_count", "0"));
}

Expand All @@ -193,6 +202,7 @@ mod tests {
let res = GetGeoProbeCliCommand {
probe: probe_pk.to_string(),
json: true,
json_compact: false,
}
.execute(&client, &mut output);
assert!(res.is_ok());
Expand All @@ -208,9 +218,39 @@ mod tests {
assert_eq!(parents.len(), 1);
assert_eq!(parents[0].as_str().unwrap(), parent_pk.to_string());
assert_eq!(
json["signing_keypair"].as_str().unwrap(),
json["signing_pubkey"].as_str().unwrap(),
metrics_pk.to_string()
);
assert_eq!(json["reference_count"].as_u64().unwrap(), 0);
}

#[test]
fn test_cli_geo_probe_get_json_compact() {
let (mut client, probe_pk, owner_pk, exchange_pk, metrics_pk) = setup_client();
let parent_pk = Pubkey::from_str_const("AQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcc");
let probe = make_probe(owner_pk, exchange_pk, metrics_pk, vec![parent_pk]);

client
.expect_get_geo_probe()
.with(predicate::eq(GetGeoProbeCommand {
pubkey_or_code: probe_pk.to_string(),
}))
.returning(move |_| Ok((probe_pk, probe.clone())));

let mut output = Vec::new();
let res = GetGeoProbeCliCommand {
probe: probe_pk.to_string(),
json: false,
json_compact: true,
}
.execute(&client, &mut output);
assert!(res.is_ok());
let output_str = String::from_utf8(output).unwrap();
let trimmed = output_str.trim();
assert!(!trimmed.contains('\n'), "compact JSON must be single-line");
let json: serde_json::Value = serde_json::from_str(trimmed).unwrap();
assert_eq!(json["account"].as_str().unwrap(), probe_pk.to_string());
assert_eq!(json["code"].as_str().unwrap(), "ams-probe-01");
assert_eq!(json["parent_devices"].as_array().unwrap().len(), 1);
}
}
Loading
Loading