Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2235e40
Initial plan for issue
Copilot May 24, 2025
2613498
Implement deploy command for eBPF binaries across SVM networks
Copilot May 24, 2025
79cbea1
Add documentation for eBPF deploy command
Copilot May 24, 2025
e59d969
Enhance eBPF deploy command with actual functionality
Copilot May 24, 2025
888e78a
Fix deploy command network parsing logic for "all" networks
Copilot May 29, 2025
0c7223a
fix: resolve clippy warnings and code quality issues
devloai[bot] Jun 2, 2025
8f9dcbf
Enhance deploy command with improved error handling, validation, and …
Copilot Jun 2, 2025
fc44e91
Fix code formatting and improve code quality
Copilot Jun 2, 2025
3f56f4d
fix: improve code quality and formatting across repository
devloai[bot] Jun 2, 2025
dde96c1
Fix failing test and apply code formatting
devloai[bot] Jun 2, 2025
4d4d052
Fix Rust formatting and resolve merge conflicts
Copilot Jun 3, 2025
204c000
Fix variable naming collision and improve deployment realism
Copilot Jun 8, 2025
95967b3
Implement real deployment logic and improve error handling
Copilot Jun 11, 2025
d2d778d
Implement actual BPF loader instructions for real program deployment
Copilot Jun 11, 2025
641377b
Fix deployment issues: use provided program ID, improve error handlin…
Copilot Jun 11, 2025
819265c
Implement IDL publishing and dynamic fee calculation for eBPF deployment
Copilot Jun 12, 2025
0dbaed2
Fix formatting issues in ebpf_deploy.rs
Copilot Jun 12, 2025
14dd764
Fix CLI and deployment validation: program ID keypair validation, boo…
Copilot Jun 12, 2025
a970853
Enhance IDL publishing: add support for custom Anchor IDL files, impr…
Copilot Jun 12, 2025
3b64d6d
Final formatting fixes for code quality
Copilot Jun 12, 2025
5d13b25
Enhance eBPF deployment: improve error handling, add retries, structu…
Copilot Jun 12, 2025
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ license = "WTFPL"
publish = false

[dependencies]
bincode = "1.3.3"
borsh = "1.5.6"
clap = { version = "4.5.32", features = ["derive", "cargo"] }
lazy_static = "1.5.0"
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ osvm rpc sonic [email protected] --network mainnet

# Deploy multiple SVMs to a single server
osvm user@host --svm sonic,solana,eclipse,soon --node-type validator --network devnet

# Deploy an eBPF binary to all available SVM networks
osvm deploy ./path/to/ebpf.so --program-id ./path/to/program-address.json --owner ./path/to/program-owner.json --fee ./path/to/deployment-fee-payer.json --publish-idl
```

### RPC Node Deployment
Expand All @@ -183,6 +186,50 @@ The `rpc` command provides a streamlined way to deploy specific RPC nodes:
- **Network Selection**: Choose between mainnet, testnet, or devnet environments
- **Automatic Configuration**: Handles all dependencies and configuration automatically

### eBPF Program Deployment

```bash
# Deploy an eBPF binary to all available SVM networks
osvm deploy ./path/to/ebpf.so --program-id ./path/to/program-address.json --owner ./path/to/program-owner.json --fee ./path/to/deployment-fee-payer.json --publish-idl

# Deploy with custom Anchor IDL file
osvm deploy ./path/to/ebpf.so --program-id ./path/to/program-address.json --owner ./path/to/program-owner.json --fee ./path/to/deployment-fee-payer.json --publish-idl --idl-file ./path/to/program.json

# Deploy to a specific network only
osvm deploy ./path/to/ebpf.so --program-id ./path/to/program-address.json --owner ./path/to/program-owner.json --fee ./path/to/deployment-fee-payer.json --network mainnet
```

The `deploy` command provides a streamlined way to deploy eBPF programs:

- **Multi-network Deployment**: Deploy to all SVM networks with one command
- **Network Selection**: Choose between mainnet, testnet, devnet, or all networks
- **IDL Publishing**: Option to publish the IDL along with the program
- Auto-generated basic IDL (default)
- Custom Anchor IDL JSON file support via `--idl-file` option
- **Required Files**:
- eBPF binary (.so file)
- **Program ID file**:
- For **new deployments**: Must be a keypair JSON file (contains private key)
- For **upgrades**: Can be either a keypair file or pubkey-only JSON file
- **Program owner keypair**: JSON file containing private key (required for all deployments)
- **Fee payer keypair**: JSON file containing private key (pays for deployment transaction)

### File Format Requirements

**Keypair files** (generated with `solana-keygen new`):
```json
[123,45,67,89,...,234] // Array of 64 bytes containing private key
```

**Pubkey-only files** (for upgrades only):
```json
{"programId": "HN4tEEGheziD9dqcWg4xZd29htcerjXKGoGiQXM5hxiS"}
```
or plain string:
```
HN4tEEGheziD9dqcWg4xZd29htcerjXKGoGiQXM5hxiS
```

## 🔧 Detailed Installation

### Prerequisites
Expand Down
75 changes: 73 additions & 2 deletions src/clparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use {
};

fn validate_signer(s: &str) -> Result<String, String> {
is_valid_signer(s).map(|_| s.to_string())
is_valid_signer(s).map(|()| s.to_string())
}

fn validate_url_or_moniker(s: &str) -> Result<String, String> {
is_url_or_moniker(s).map(|_| s.to_string())
is_url_or_moniker(s).map(|()| s.to_string())
}

/// Construct the cli input model and parse command line
Expand Down Expand Up @@ -354,6 +354,77 @@ pub fn parse_command_line() -> clap::ArgMatches {
)
)
)
.subcommand(
Command::new("deploy")
.about("Deploy eBPF binary to all available SVM networks")
.arg(
Arg::new("binary")
.value_name("BINARY_PATH")
.help("Path to the eBPF binary file (.so)")
.required(true)
.index(1)
)
.arg(
Arg::new("program-id")
.long("program-id")
.value_name("PROGRAM_ID_PATH")
.help("Path to program keypair JSON file (for new deployments) or program address JSON file (for upgrades)")
.required(true)
)
.arg(
Arg::new("owner")
.long("owner")
.value_name("OWNER_PATH")
.help("Path to program owner keypair JSON file (must contain private key)")
.required(true)
)
.arg(
Arg::new("fee")
.long("fee")
.value_name("FEE_PAYER_PATH")
.help("Path to deployment fee payer keypair JSON file (must contain private key)")
.required(true)
)
.arg(
Arg::new("publish-idl")
.long("publish-idl")
.action(ArgAction::SetTrue)
.help("Publish IDL alongside the program deployment")
)
.arg(
Arg::new("idl-file")
.long("idl-file")
.value_name("IDL_PATH")
.help("Path to Anchor IDL JSON file (optional, defaults to generated IDL)")
)
.arg(
Arg::new("network")
.long("network")
.value_name("NETWORK")
.value_parser(clap::builder::PossibleValuesParser::new(["mainnet", "testnet", "devnet", "all"]))
.default_value("all")
.help("Network to deploy on (default: deploy to all networks)")
)
.arg(
Arg::new("json")
.long("json")
.action(ArgAction::SetTrue)
.help("Output results in JSON format for machine-readable processing")
)
.arg(
Arg::new("retry-attempts")
.long("retry-attempts")
.value_name("COUNT")
.default_value("3")
.help("Number of retry attempts for failed deployments (default: 3)")
)
.arg(
Arg::new("confirm-large")
.long("confirm-large")
.action(ArgAction::SetTrue)
.help("Require confirmation for deploying large binaries (>1MB)")
)
)
.subcommand(
Command::new("new_feature_command")
.about("New feature for testing")
Expand Down
6 changes: 3 additions & 3 deletions src/commands/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ pub fn execute(args: Args) -> Result<(), Box<dyn Error>> {
// Parse configuration and setup client
let config = Config::default();
println!("OSVM - Node Management");

if args.verbose {
println!("Available SVMs in the chain:");
let svms = get_available_svms(&args.json_rpc_url)?;
for svm in svms {
println!(" - {}: {}", svm.name, svm.token);
}
}

// Process the requested node command
match args.command.as_deref() {
Some("list") => {
Expand Down Expand Up @@ -51,7 +51,7 @@ pub fn execute(args: Args) -> Result<(), Box<dyn Error>> {
return Err(format!("Unknown node subcommand: {}", cmd).into());
}
}

Ok(())
}

Expand Down
8 changes: 4 additions & 4 deletions src/commands/svm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

pub fn install(args: InstallArgs) -> Result<(), Box<dyn Error>> {
println!("Installing SVM: {}", args.name);

// Existing implementation
let client = RpcClient::new(args.json_rpc_url);

// Handle keypair file errors gracefully
let keypair = match read_keypair_file(&*args.keypair_path) {
Ok(kp) => kp,
Expand All @@ -25,10 +25,10 @@ pub fn install(args: InstallArgs) -> Result<(), Box<dyn Error>> {

pub fn list(args: ListArgs) -> Result<(), Box<dyn Error>> {
// ...existing code...

println!("Available SVMs in the chain:");
// Continue with existing implementation

// ...existing code...
Ok(())
}
Expand Down
106 changes: 92 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
crate::utils::{dashboard, examples, nodes, ssh_deploy, svm_info},
crate::utils::{dashboard, ebpf_deploy, examples, nodes, ssh_deploy, svm_info},
clparse::parse_command_line,
solana_clap_utils::input_validators::normalize_to_url_if_moniker,
solana_client::rpc_client::RpcClient,
Expand All @@ -16,7 +16,7 @@ fn pubkey_of_checked(matches: &clap::ArgMatches, name: &str) -> Option<solana_sd
}

#[cfg(feature = "remote-wallet")]
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use {solana_remote_wallet::remote_wallet::RemoteWalletManager, std::sync::Arc};
pub mod clparse;
pub mod prelude;
pub mod utils;
Expand Down Expand Up @@ -53,8 +53,7 @@ impl From<webpki::Error> for WebPkiError {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app_matches = parse_command_line();
let Some((sub_command, sub_matches)) = app_matches.subcommand() else {
eprintln!("No subcommand provided");
exit(1);
return Err("No subcommand provided".into());
};
let matches = sub_matches;

Expand All @@ -66,6 +65,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}

#[cfg(feature = "remote-wallet")]
#[allow(unused_variables, unused_mut)]
let mut wallet_manager: Option<Arc<RemoteWalletManager>> = None;

#[cfg(not(feature = "remote-wallet"))]
Expand All @@ -86,11 +86,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.unwrap_or_else(|| cli_config.keypair_path.clone());

// Create a signer directly from the keypair path
let signer =
solana_sdk::signature::read_keypair_file(&keypair_path).unwrap_or_else(|err| {
eprintln!("Error reading keypair file {}: {}", keypair_path, err);
exit(1);
});
let signer = match solana_sdk::signature::read_keypair_file(&keypair_path) {
Ok(signer) => signer,
Err(err) => {
return Err(format!("Error reading keypair file {}: {}", keypair_path, err).into());
}
};

Config {
json_rpc_url: normalize_to_url_if_moniker(
Expand All @@ -101,7 +102,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
),
default_signer: Box::new(signer),
// Count occurrences of the verbose flag to determine verbosity level
verbose: matches.get_count("verbose") as u8,
verbose: matches.get_count("verbose"),
Copy link

Copilot AI Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verbose field previously cast the count to u8; dropping that cast may cause a type mismatch if verbose remains u8. Ensure the field type matches or restore the cast.

Suggested change
verbose: matches.get_count("verbose"),
verbose: matches.get_count("verbose") as u8,

Copilot uses AI. Check for mistakes.
no_color,
commitment_config: CommitmentConfig::confirmed(),
}
Expand Down Expand Up @@ -583,7 +584,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Solana validator node deployed successfully!");
}
"rpc" => {
let solana_sub_matches = solana_sub_matches;
// Deploy a Solana RPC node with enhanced features
let connection_str = solana_sub_matches
.get_one::<String>("connection")
Expand Down Expand Up @@ -899,12 +899,90 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
exit(1);
}
}
"deploy" => {
// Command to deploy eBPF binary to all SVM networks
let binary_path = matches
.get_one::<String>("binary")
.map(|s| s.as_str())
.unwrap();
let program_id_path = matches
.get_one::<String>("program-id")
.map(|s| s.as_str())
.unwrap();
let owner_path = matches
.get_one::<String>("owner")
.map(|s| s.as_str())
.unwrap();
let fee_payer_path = matches
.get_one::<String>("fee")
.map(|s| s.as_str())
.unwrap();
let publish_idl = matches.get_flag("publish-idl");
let idl_file_path = matches.get_one::<String>("idl-file").map(|s| s.to_string());
let network_str = matches
.get_one::<String>("network")
.map(|s| s.as_str())
.unwrap_or("all");
let json_output = matches.get_flag("json");
let retry_attempts = matches
.get_one::<String>("retry-attempts")
.and_then(|s| s.parse().ok())
.unwrap_or(3);
let confirm_large_binaries = matches.get_flag("confirm-large");

// Create deployment configuration
let deploy_config = ebpf_deploy::DeployConfig {
binary_path: binary_path.to_string(),
program_id_path: program_id_path.to_string(),
owner_path: owner_path.to_string(),
fee_payer_path: fee_payer_path.to_string(),
publish_idl,
idl_file_path,
network_selection: network_str.to_string(),
json_output,
retry_attempts,
confirm_large_binaries,
};

println!("🚀 OSVM eBPF Deployment Tool");
println!("============================");
println!("📁 Binary path: {binary_path}");
println!("🆔 Program ID: {program_id_path}");
println!("👤 Owner: {owner_path}");
println!("💰 Fee payer: {fee_payer_path}");
println!("📄 Publish IDL: {}", if publish_idl { "yes" } else { "no" });
println!("🌐 Target network(s): {network_str}");
println!();

// Execute deployment
let results = ebpf_deploy::deploy_to_all_networks(
deploy_config.clone(),
config.commitment_config,
)
.await;

// Display results using the new display function
if let Err(e) =
ebpf_deploy::display_deployment_results(&results, deploy_config.json_output)
{
eprintln!("Error displaying results: {}", e);
}

// Determine exit status
let failure_count = results
.iter()
.filter(|r| r.as_ref().map_or(true, |d| !d.success))
.count();

if failure_count > 0 {
return Err("Some deployments failed".into());
}
}
"new_feature_command" => {
println!("Expected output for new feature");
}
cmd => {
eprintln!("Unknown command: {}", cmd);
exit(1);
return Err(format!("Unknown command: {cmd}").into());
}
};

Expand All @@ -919,7 +997,7 @@ mod test {
#[test]
fn test_borsh() {
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
pub struct UpdateMetadataAccountArgs {
pub data: Option<String>,
pub update_authority: Option<Pubkey>,
Expand Down
Loading
Loading