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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.

### Changes

- Smartcontract
- Add `AccessPassType::EdgeSeat(Pubkey)` variant to associate an access pass with a specific onchain Seat pubkey
- Add `--accesspass-type edge-seat --seat <PUBKEY>` to `access-pass set`
- Add `--edge-seat` and `--seat-pubkey` filters to `access-pass list`
- Client
- Add `--sock-file` global flag (aliases: `--socket`, `--socket-path`) to the `doublezero` CLI to override the default Unix socket path used to communicate with `doublezerod` (`/var/run/doublezerod/doublezerod.sock`)
- Controller
Expand Down
20 changes: 20 additions & 0 deletions smartcontract/cli/src/accesspass/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ pub struct ListAccessPassCliCommand {
/// Solana identity public key
#[arg(long)]
pub solana_identity: Option<Pubkey>,
/// List EdgeSeat access passes
#[arg(long, default_value_t = false)]
pub edge_seat: bool,
/// Seat public key
#[arg(long)]
pub seat_pubkey: Option<Pubkey>,
/// Client IP address
#[arg(long)]
pub client_ip: Option<Ipv4Addr>,
Expand Down Expand Up @@ -101,6 +107,16 @@ impl ListAccessPassCliCommand {
access_pass.accesspass_type == AccessPassType::SolanaValidator(solana_identity)
});
}
// Filter access passes by EdgeSeat type
if self.edge_seat {
access_passes.retain(|(_, access_pass)| {
matches!(access_pass.accesspass_type, AccessPassType::EdgeSeat(_))
});
}
// Filter access passes by seat pubkey
if let Some(seat_pk) = self.seat_pubkey {
access_passes.retain(|(_, ap)| ap.accesspass_type == AccessPassType::EdgeSeat(seat_pk));
}
// Filter access passes by client IP
if let Some(client_ip) = self.client_ip {
access_passes.retain(|(_, access_pass)| access_pass.client_ip == client_ip);
Expand Down Expand Up @@ -359,6 +375,8 @@ mod tests {
tenant: None,
solana_validator: false,
solana_identity: None,
edge_seat: false,
seat_pubkey: None,
multicast_group_publisher: None,
multicast_group_subscriber: None,
not_multicast_group_publisher: None,
Expand All @@ -376,6 +394,8 @@ mod tests {
prepaid: false,
solana_validator: false,
solana_identity: None,
edge_seat: false,
seat_pubkey: None,
tenant: None,
json: false,
json_compact: true,
Expand Down
115 changes: 115 additions & 0 deletions smartcontract/cli/src/accesspass/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum CliAccessPassType {
SolanaValidator,
SolanaRPC,
Others,
EdgeSeat,
}

#[derive(Args, Debug)]
Expand Down Expand Up @@ -45,6 +46,9 @@ pub struct SetAccessPassCliCommand {
/// Specifies the key for other access pass types. Required if accesspass_type is others.
#[arg(long, required_if_eq("accesspass_type", "others"))]
pub others_key: Option<String>,
/// Seat pubkey for EdgeSeat access pass type. Required if accesspass_type is edge-seat.
#[arg(long, required_if_eq("accesspass_type", "edge_seat"))]
pub seat: Option<Pubkey>,
/// Tenant code allowed for this access pass
#[arg(long = "tenant")]
pub tenant: Option<String>,
Expand Down Expand Up @@ -87,6 +91,10 @@ impl SetAccessPassCliCommand {
"Others access pass type requires --others-name <STRING> and --others-key <STRING>"
),
},
CliAccessPassType::EdgeSeat => match self.seat {
Some(seat_pk) => AccessPassType::EdgeSeat(seat_pk),
None => eyre::bail!("EdgeSeat access pass type requires --seat <PUBKEY>"),
},
};

// Convert tenant code to PDA if provided
Expand Down Expand Up @@ -180,6 +188,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -214,6 +223,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -269,6 +279,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -323,6 +334,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -374,6 +386,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -408,6 +421,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -441,6 +455,7 @@ mod tests {
allow_multiple_ip: false,
others_name: Some("custom-name".to_string()),
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -497,6 +512,7 @@ mod tests {
allow_multiple_ip: false,
others_name: Some("custom-name".to_string()),
others_key: Some("custom-key".to_string()),
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -528,6 +544,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -555,6 +572,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -583,6 +601,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: Some(too_long.clone()),
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -628,6 +647,7 @@ mod tests {
allow_multiple_ip: true,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -671,6 +691,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: Some("acme".to_string()),
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -711,6 +732,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -752,6 +774,7 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
Expand Down Expand Up @@ -794,9 +817,101 @@ mod tests {
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
assert!(res.is_ok());
}

#[test]
fn test_cli_accesspass_set_edge_seat_missing_seat() {
let mut client = create_test_client();

let client_ip = [100, 0, 0, 1].into();
let payer = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB");

client.expect_get_epoch().returning(|| Ok(10));
client
.expect_check_requirements()
.with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE))
.returning(|_| Ok(()));

let mut output = Vec::new();
let res = SetAccessPassCliCommand {
accesspass_type: CliAccessPassType::EdgeSeat,
client_ip: Some(client_ip),
user_payer: payer.to_string(),
epochs: "1".into(),
solana_validator: None,
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: None,
tenant: None,
}
.execute(&client, &mut output);
assert!(res.is_err());
assert_eq!(
res.err().unwrap().to_string(),
"EdgeSeat access pass type requires --seat <PUBKEY>"
);
}

#[test]
fn test_cli_accesspass_set_edge_seat_success() {
let mut client = create_test_client();

let client_ip = [100, 0, 0, 1].into();
let payer = Pubkey::from_str_const("1111111FVAiSujNZVgYSc27t6zUTWoKfAGxbRzzPB");

let (_pda_pubkey, _bump_seed) =
get_accesspass_pda(&client.get_program_id(), &client_ip, &payer);
let signature = Signature::from([
120, 138, 162, 185, 59, 209, 241, 157, 71, 157, 74, 131, 4, 87, 54, 28, 38, 180, 222,
82, 64, 62, 61, 62, 22, 46, 17, 203, 187, 136, 62, 43, 11, 38, 235, 17, 239, 82, 240,
139, 130, 217, 227, 214, 9, 242, 141, 223, 94, 29, 184, 110, 62, 32, 87, 137, 63, 139,
100, 221, 20, 137, 4, 5,
]);

let seat_pk = Pubkey::new_unique();

client.expect_get_epoch().returning(|| Ok(10));
client
.expect_check_requirements()
.with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE))
.returning(|_| Ok(()));
client
.expect_set_accesspass()
.with(predicate::eq(SetAccessPassCommand {
accesspass_type: AccessPassType::EdgeSeat(seat_pk),
client_ip,
user_payer: payer,
last_access_epoch: 11,
allow_multiple_ip: false,
tenant: Pubkey::default(),
}))
.returning(move |_| Ok(signature));

let mut output = Vec::new();
let res = SetAccessPassCliCommand {
accesspass_type: CliAccessPassType::EdgeSeat,
client_ip: Some(client_ip),
user_payer: payer.to_string(),
epochs: "1".into(),
solana_validator: None,
allow_multiple_ip: false,
others_name: None,
others_key: None,
seat: Some(seat_pk),
tenant: None,
}
.execute(&client, &mut output);
assert!(res.is_ok());
let output_str = String::from_utf8(output).unwrap();
assert_eq!(
output_str,
"AccessPass PDA: 6pw9fvwzjjkkocGuwxhmv1TwHHnYTFjGvV9GKX6nkFMw\nSignature: 3QnHBSdd4doEF6FgpLCejqEw42UQjfvNhQJwoYDSpoBszpCCqVft4cGoneDCnZ6Ez3ujzavzUu85u6F79WtLhcsv\n"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ pub fn process_set_access_pass(
}
}

if let AccessPassType::EdgeSeat(seat_pk) = value.accesspass_type {
if seat_pk == Pubkey::default() {
msg!("EdgeSeat access pass type requires a seat pubkey");
return Err(DoubleZeroError::InvalidSolanaPubkey.into());
}
}

let clock = Clock::get()?;
let current_epoch = clock.epoch;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ pub enum AccessPassType {
Pubkey,
),
Others(String, String), // (type_name, key)
EdgeSeat(
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "doublezero_program_common::serializer::serialize_pubkey_as_string",
deserialize_with = "doublezero_program_common::serializer::deserialize_pubkey_from_string"
)
)]
Pubkey,
),
}

impl AccessPassType {
Expand All @@ -48,6 +58,7 @@ impl AccessPassType {
AccessPassType::SolanaValidator(_) => "solana_validator".to_string(),
AccessPassType::SolanaRPC(_) => "solana_rpc".to_string(),
AccessPassType::Others(type_name, _) => type_name.clone(),
AccessPassType::EdgeSeat(_) => "edge_seat".to_string(),
}
}
}
Expand All @@ -61,6 +72,7 @@ impl fmt::Display for AccessPassType {
AccessPassType::Others(type_name, key) => {
write!(f, "others: {} ({})", type_name, key)
}
AccessPassType::EdgeSeat(seat_pk) => write!(f, "edge_seat: {seat_pk}"),
}
}
}
Expand Down Expand Up @@ -128,6 +140,13 @@ impl Validate for AccessPassType {
}
Ok(())
}
AccessPassType::EdgeSeat(seat_pk) => {
if *seat_pk == Pubkey::default() {
msg!("Invalid EdgeSeat Pubkey: {}", seat_pk);
return Err(DoubleZeroError::InvalidSolanaPubkey);
}
Ok(())
}
_ => Ok(()),
}
}
Expand Down Expand Up @@ -187,6 +206,7 @@ impl fmt::Display for AccessPass {
AccessPassType::Others(type_name, details) => {
write!(f, "Others: {} ({})", type_name, details)
}
AccessPassType::EdgeSeat(seat_pk) => write!(f, "EdgeSeat: ({seat_pk})"),
}
}
}
Expand Down
Loading