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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ All notable changes to this project will be documented in this file.
- Serviceability: add feature flags support
- Serviceability: expand `is_global` to reject all BGP martian address ranges (CGNAT 100.64/10, IETF 192.0.0/24, benchmarking 198.18/15, multicast 224/4, reserved 240/4, 0/8)
- Serviceability: allow update and deletion of interfaces even when sibling interfaces have invalid CYOA configuration
- Geolocation: add `doublezero-geolocation` program scaffolding as per rfcs/rfc16-geolocation-verification.md
- SDK
- SetFeatureFlagCommand added to manage on-chain feature flags for conditional behavior rollouts
- Dependencies
- Upgrade Solana SDK workspace dependencies from 2.2.7 to 2.3.x (`solana-sdk`, `solana-client`, `solana-program-test`, and others)
Expand Down
50 changes: 42 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"smartcontract/programs/doublezero-record",
"smartcontract/programs/doublezero-serviceability",
"smartcontract/programs/doublezero-telemetry",
"smartcontract/programs/doublezero-geolocation",
"smartcontract/programs/common",
"e2e/docker/ledger/fork-accounts",
]
Expand Down Expand Up @@ -75,6 +76,8 @@ serde_yaml = "0"
serial_test = "0"
solana-account-decoder = "2.3.1"
solana-client = "2.3.1"
solana-bincode = "2.2.1"
solana-loader-v3-interface = { version = "3.0.0", features = ["serde"] }
solana-program = "2.3.0"
solana-program-test = "2.3.1"
solana-pubsub-client = "2.3.1"
Expand Down Expand Up @@ -109,3 +112,7 @@ features = ["no-entrypoint"]
[workspace.dependencies.doublezero-telemetry]
path = "smartcontract/programs/doublezero-telemetry"
features = ["no-entrypoint"]

[workspace.dependencies.doublezero-geolocation]
path = "smartcontract/programs/doublezero-geolocation"
features = ["no-entrypoint"]
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ rust-build-programs:
.PHONY: rust-lint
rust-lint: rust-fmt-check
@cargo install cargo-hack
cargo hack clippy --workspace --all-targets --exclude doublezero-telemetry --exclude doublezero-serviceability --exclude doublezero-program-common --exclude doublezero-record -- -Dclippy::all -Dwarnings
cargo hack clippy --workspace --all-targets --exclude doublezero-telemetry --exclude doublezero-serviceability --exclude doublezero-program-common --exclude doublezero-record --exclude doublezero-geolocation -- -Dclippy::all -Dwarnings
cd smartcontract && $(MAKE) lint-programs

.PHONY: rust-fmt
Expand All @@ -94,7 +94,7 @@ rust-fmt-check:

.PHONY: rust-test
rust-test:
cargo test --workspace --exclude doublezero-telemetry --exclude doublezero-serviceability --exclude doublezero-program-common --exclude doublezero-record --all-features
cargo test --workspace --exclude doublezero-telemetry --exclude doublezero-serviceability --exclude doublezero-program-common --exclude doublezero-record --exclude doublezero-geolocation --all-features
cd smartcontract && $(MAKE) test-programs
$(MAKE) rust-program-accounts-compat

Expand Down
4 changes: 2 additions & 2 deletions rfcs/rfc16-geolocation-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,10 @@ pub struct GeoProbe {
pub exchange_pk: Pubkey, // Reference to Serviceability Exchange account
pub public_ip: Ipv4Addr, // Where probe listens
pub location_offset_port: u16, // UDP listen port (default 8923)
pub code: String, // e.g., "ams-probe-01" (max 32 bytes)
pub parent_devices: Vec<Pubkey>, // DZDs that measure this probe
pub metrics_publisher_pk: Pubkey, // Signing key for telemetry
pub reference_count: u32, // GeolocationTargets referencing this probe
pub code: String, // e.g., "ams-probe-01" (max 32 bytes)
pub parent_devices: Vec<Pubkey>, // DZDs that measure this probe
}
```
**PDA Seeds:** `["doublezero", "probe", code.as_bytes()]`
Expand Down
5 changes: 4 additions & 1 deletion smartcontract/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ test-programs: test-sbf
-p doublezero-program-common \
-p doublezero-record \
-p doublezero-telemetry \
-p doublezero-serviceability
-p doublezero-serviceability \
-p doublezero-geolocation

.PHONY: test-sbf
test-sbf:
Expand All @@ -44,6 +45,7 @@ lint-programs:
-p doublezero-record \
-p doublezero-telemetry \
-p doublezero-serviceability \
-p doublezero-geolocation \
$(CLIPPY_FLAGS)

.PHONY: build
Expand All @@ -56,3 +58,4 @@ build-programs:
cd programs/doublezero-record && cargo build-sbf
cd programs/doublezero-serviceability && cargo build-sbf
cd programs/doublezero-telemetry && cargo build-sbf --features $(env)
cd programs/doublezero-geolocation && cargo build-sbf --features $(env)
33 changes: 33 additions & 0 deletions smartcontract/programs/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Smartcontract Development Best Practices

### Security

1. **PDA ownership verification**: Always verify the owner of PDA accounts (both internal PDAs and those from other programs like serviceability) to prevent being tricked into reading an account owned by another program. For serviceability accounts, verify the owner is the serviceability program ID. For your own PDAs, verify the owner is `program_id`.

2. **System program validation**: Checks for the system program are unnecessary because the system interface builds instructions using the system program as the program ID. If the wrong program is provided, you'll get a revert automatically.

3. **PDA validation**: When validating PDAs with expected seeds/bumps, you don't need to separately check the account derivation before the PDA validation - the PDA validation itself confirms the derivation is correct.

### Error Handling

1. **Simplify error enum conversions**: Use `#[repr(u32)]` on your error enum and implement `From<YourError> for ProgramError` using `as u32`. This eliminates the need to manually maintain error code mappings when adding new variants. Remove `Custom(u32)` variants unless there's a specific use case.

2. **Clear error messages**: Error messages should clearly state what condition is expected, not just what failed. For example, use "Cannot delete GeoProbe. reference_count of {n} > 0" instead of "ReferenceCountNotZero" so users understand what needs to be true.

### Code Organization

1. **Instruction struct placement**: Place instruction argument structs in the same file where the instruction is implemented, rather than collecting them all in a central `instructions.rs` file. This improves locality and makes it easier to understand what arguments an instruction uses.

2. **Minimize stored data**: Don't store bump seeds unless the account needs to sign for something. Bump seeds are only needed for CPI signing, not for PDA validation.

3. **Avoid redundant instruction arguments**: If you're passing an account, don't also pass that account's pubkey as an instruction argument and then check they match. Just use the account's key directly.

### Serialization

1. **Prefer standard derives**: Use `BorshDeserialize` when possible instead of implementing custom deserialization. Custom `unpack()` methods that manually match on instruction indices often duplicate what Borsh's derive already provides.

2. **Use BorshDeserializeIncremental**: For instruction arguments that may gain new optional fields over time, use `BorshDeserializeIncremental` or derive `BorshDeserialize`.

### Program Upgrades

1. **Use standard interfaces**: Use `solana-loader-v3-interface` to parse `UpgradeableLoaderState` rather than implementing your own parser. The interface crate provides well-tested, maintained implementations.
45 changes: 45 additions & 0 deletions smartcontract/programs/doublezero-geolocation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[package]
name = "doublezero-geolocation"

# clippy ignores rust-version.toml
rust-version = "1.84.1" # cargo build-sbf --version

# Workspace inherited keys
version.workspace = true
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[lib]
crate-type = ["cdylib", "lib"]

[dependencies]
borsh.workspace = true
borsh-incremental.workspace = true
serde = { workspace = true, optional = true }
serde_bytes = { workspace = true, optional = true }
solana-bincode.workspace = true
solana-loader-v3-interface.workspace = true
solana-program.workspace = true
doublezero-program-common.workspace = true
doublezero-serviceability.workspace = true
thiserror.workspace = true

[dev-dependencies]
bincode.workspace = true
solana-sdk.workspace = true
solana-program-test.workspace = true

[build-dependencies]
doublezero-config.workspace = true

[features]
default = []
localnet = []
testnet = []
devnet = []
mainnet-beta = []
no-entrypoint = []
serde = ["dep:serde", "dep:serde_bytes"]
47 changes: 47 additions & 0 deletions smartcontract/programs/doublezero-geolocation/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::{env, fs, path::Path};

use doublezero_config::Environment;

fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest = Path::new(&out_dir).join("build_constants.rs");

// Determine the network environment config with serviceability program ID based on the
// features through cargo environment variables during build, so that we can validate and print
// the values being used at build-time.
// The CARGO_FEATURE_* env variables are set by cargo for build scripts based on the features
// defined in the Cargo.toml file.
let environment: Option<Environment> = if std::env::var("CARGO_FEATURE_MAINNET_BETA").is_ok() {
Some(Environment::MainnetBeta)
} else if std::env::var("CARGO_FEATURE_TESTNET").is_ok() {
Some(Environment::Testnet)
} else if std::env::var("CARGO_FEATURE_DEVNET").is_ok() {
Some(Environment::Devnet)
} else {
None
};

let (env_code, serviceability_program_id) = match environment {
Some(environment) => (
environment.to_string(),
environment
.config()
.unwrap()
.serviceability_program_id
.to_string(),
),
None => (
"localnet".to_string(),
"7CTniUa88iJKUHTrCkB4TjAoG6TD7AMivhQeuqN2LPtX".to_string(),
),
};

println!("cargo:warning=Environment: {env_code}");
println!("cargo:warning=Serviceability Program ID: {serviceability_program_id}");

fs::write(
dest,
format!(r#"pub const SERVICEABILITY_PROGRAM_ID: &str = "{serviceability_program_id}";"#),
)
.unwrap();
}
36 changes: 36 additions & 0 deletions smartcontract/programs/doublezero-geolocation/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::{
instructions::GeolocationInstruction,
processors::program_config::{
init::process_init_program_config, update::process_update_program_config,
},
};

use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
pubkey::Pubkey,
};

#[cfg(not(feature = "no-entrypoint"))]
solana_program::entrypoint!(process_instruction);

pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
let instruction: GeolocationInstruction =
borsh::from_slice(data).map_err(|_| ProgramError::InvalidInstructionData)?;

msg!("Instruction: {:?}", instruction);

match instruction {
GeolocationInstruction::InitProgramConfig(args) => {
process_init_program_config(program_id, accounts, &args)?
}
GeolocationInstruction::UpdateProgramConfig(args) => {
process_update_program_config(program_id, accounts, &args)?
}
};

Ok(())
}
Loading
Loading