Skip to content

Commit eea704b

Browse files
authored
Merge pull request #33 from openSVM/copilot/fix-32
[WIP] create one command deploy to all svm networks
2 parents 3ad633c + 5d13b25 commit eea704b

File tree

17 files changed

+1952
-63
lines changed

17 files changed

+1952
-63
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ license = "WTFPL"
66
publish = false
77

88
[dependencies]
9+
bincode = "1.3.3"
910
borsh = "1.5.6"
1011
clap = { version = "4.5.32", features = ["derive", "cargo"] }
1112
lazy_static = "1.5.0"

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ osvm rpc sonic [email protected] --network mainnet
162162

163163
# Deploy multiple SVMs to a single server
164164
osvm user@host --svm sonic,solana,eclipse,soon --node-type validator --network devnet
165+
166+
# Deploy an eBPF binary to all available SVM networks
167+
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
165168
```
166169

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

189+
### eBPF Program Deployment
190+
191+
```bash
192+
# Deploy an eBPF binary to all available SVM networks
193+
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
194+
195+
# Deploy with custom Anchor IDL file
196+
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
197+
198+
# Deploy to a specific network only
199+
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
200+
```
201+
202+
The `deploy` command provides a streamlined way to deploy eBPF programs:
203+
204+
- **Multi-network Deployment**: Deploy to all SVM networks with one command
205+
- **Network Selection**: Choose between mainnet, testnet, devnet, or all networks
206+
- **IDL Publishing**: Option to publish the IDL along with the program
207+
- Auto-generated basic IDL (default)
208+
- Custom Anchor IDL JSON file support via `--idl-file` option
209+
- **Required Files**:
210+
- eBPF binary (.so file)
211+
- **Program ID file**:
212+
- For **new deployments**: Must be a keypair JSON file (contains private key)
213+
- For **upgrades**: Can be either a keypair file or pubkey-only JSON file
214+
- **Program owner keypair**: JSON file containing private key (required for all deployments)
215+
- **Fee payer keypair**: JSON file containing private key (pays for deployment transaction)
216+
217+
### File Format Requirements
218+
219+
**Keypair files** (generated with `solana-keygen new`):
220+
```json
221+
[123,45,67,89,...,234] // Array of 64 bytes containing private key
222+
```
223+
224+
**Pubkey-only files** (for upgrades only):
225+
```json
226+
{"programId": "HN4tEEGheziD9dqcWg4xZd29htcerjXKGoGiQXM5hxiS"}
227+
```
228+
or plain string:
229+
```
230+
HN4tEEGheziD9dqcWg4xZd29htcerjXKGoGiQXM5hxiS
231+
```
232+
186233
## 🔧 Detailed Installation
187234

188235
### Prerequisites

src/clparse.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use {
44
};
55

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

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

1414
/// Construct the cli input model and parse command line
@@ -354,6 +354,77 @@ pub fn parse_command_line() -> clap::ArgMatches {
354354
)
355355
)
356356
)
357+
.subcommand(
358+
Command::new("deploy")
359+
.about("Deploy eBPF binary to all available SVM networks")
360+
.arg(
361+
Arg::new("binary")
362+
.value_name("BINARY_PATH")
363+
.help("Path to the eBPF binary file (.so)")
364+
.required(true)
365+
.index(1)
366+
)
367+
.arg(
368+
Arg::new("program-id")
369+
.long("program-id")
370+
.value_name("PROGRAM_ID_PATH")
371+
.help("Path to program keypair JSON file (for new deployments) or program address JSON file (for upgrades)")
372+
.required(true)
373+
)
374+
.arg(
375+
Arg::new("owner")
376+
.long("owner")
377+
.value_name("OWNER_PATH")
378+
.help("Path to program owner keypair JSON file (must contain private key)")
379+
.required(true)
380+
)
381+
.arg(
382+
Arg::new("fee")
383+
.long("fee")
384+
.value_name("FEE_PAYER_PATH")
385+
.help("Path to deployment fee payer keypair JSON file (must contain private key)")
386+
.required(true)
387+
)
388+
.arg(
389+
Arg::new("publish-idl")
390+
.long("publish-idl")
391+
.action(ArgAction::SetTrue)
392+
.help("Publish IDL alongside the program deployment")
393+
)
394+
.arg(
395+
Arg::new("idl-file")
396+
.long("idl-file")
397+
.value_name("IDL_PATH")
398+
.help("Path to Anchor IDL JSON file (optional, defaults to generated IDL)")
399+
)
400+
.arg(
401+
Arg::new("network")
402+
.long("network")
403+
.value_name("NETWORK")
404+
.value_parser(clap::builder::PossibleValuesParser::new(["mainnet", "testnet", "devnet", "all"]))
405+
.default_value("all")
406+
.help("Network to deploy on (default: deploy to all networks)")
407+
)
408+
.arg(
409+
Arg::new("json")
410+
.long("json")
411+
.action(ArgAction::SetTrue)
412+
.help("Output results in JSON format for machine-readable processing")
413+
)
414+
.arg(
415+
Arg::new("retry-attempts")
416+
.long("retry-attempts")
417+
.value_name("COUNT")
418+
.default_value("3")
419+
.help("Number of retry attempts for failed deployments (default: 3)")
420+
)
421+
.arg(
422+
Arg::new("confirm-large")
423+
.long("confirm-large")
424+
.action(ArgAction::SetTrue)
425+
.help("Require confirmation for deploying large binaries (>1MB)")
426+
)
427+
)
357428
.subcommand(
358429
Command::new("new_feature_command")
359430
.about("New feature for testing")

src/commands/node.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ pub fn execute(args: Args) -> Result<(), Box<dyn Error>> {
1414
// Parse configuration and setup client
1515
let config = Config::default();
1616
println!("OSVM - Node Management");
17-
17+
1818
if args.verbose {
1919
println!("Available SVMs in the chain:");
2020
let svms = get_available_svms(&args.json_rpc_url)?;
2121
for svm in svms {
2222
println!(" - {}: {}", svm.name, svm.token);
2323
}
2424
}
25-
25+
2626
// Process the requested node command
2727
match args.command.as_deref() {
2828
Some("list") => {
@@ -51,7 +51,7 @@ pub fn execute(args: Args) -> Result<(), Box<dyn Error>> {
5151
return Err(format!("Unknown node subcommand: {}", cmd).into());
5252
}
5353
}
54-
54+
5555
Ok(())
5656
}
5757

src/commands/svm.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
pub fn install(args: InstallArgs) -> Result<(), Box<dyn Error>> {
44
println!("Installing SVM: {}", args.name);
5-
5+
66
// Existing implementation
77
let client = RpcClient::new(args.json_rpc_url);
8-
8+
99
// Handle keypair file errors gracefully
1010
let keypair = match read_keypair_file(&*args.keypair_path) {
1111
Ok(kp) => kp,
@@ -25,10 +25,10 @@ pub fn install(args: InstallArgs) -> Result<(), Box<dyn Error>> {
2525

2626
pub fn list(args: ListArgs) -> Result<(), Box<dyn Error>> {
2727
// ...existing code...
28-
28+
2929
println!("Available SVMs in the chain:");
3030
// Continue with existing implementation
31-
31+
3232
// ...existing code...
3333
Ok(())
3434
}

src/main.rs

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use {
2-
crate::utils::{dashboard, examples, nodes, ssh_deploy, svm_info},
2+
crate::utils::{dashboard, ebpf_deploy, examples, nodes, ssh_deploy, svm_info},
33
clparse::parse_command_line,
44
solana_clap_utils::input_validators::normalize_to_url_if_moniker,
55
solana_client::rpc_client::RpcClient,
@@ -16,7 +16,7 @@ fn pubkey_of_checked(matches: &clap::ArgMatches, name: &str) -> Option<solana_sd
1616
}
1717

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

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

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

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

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

9596
Config {
9697
json_rpc_url: normalize_to_url_if_moniker(
@@ -101,7 +102,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
101102
),
102103
default_signer: Box::new(signer),
103104
// Count occurrences of the verbose flag to determine verbosity level
104-
verbose: matches.get_count("verbose") as u8,
105+
verbose: matches.get_count("verbose"),
105106
no_color,
106107
commitment_config: CommitmentConfig::confirmed(),
107108
}
@@ -583,7 +584,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
583584
println!("Solana validator node deployed successfully!");
584585
}
585586
"rpc" => {
586-
let solana_sub_matches = solana_sub_matches;
587587
// Deploy a Solana RPC node with enhanced features
588588
let connection_str = solana_sub_matches
589589
.get_one::<String>("connection")
@@ -899,12 +899,90 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
899899
exit(1);
900900
}
901901
}
902+
"deploy" => {
903+
// Command to deploy eBPF binary to all SVM networks
904+
let binary_path = matches
905+
.get_one::<String>("binary")
906+
.map(|s| s.as_str())
907+
.unwrap();
908+
let program_id_path = matches
909+
.get_one::<String>("program-id")
910+
.map(|s| s.as_str())
911+
.unwrap();
912+
let owner_path = matches
913+
.get_one::<String>("owner")
914+
.map(|s| s.as_str())
915+
.unwrap();
916+
let fee_payer_path = matches
917+
.get_one::<String>("fee")
918+
.map(|s| s.as_str())
919+
.unwrap();
920+
let publish_idl = matches.get_flag("publish-idl");
921+
let idl_file_path = matches.get_one::<String>("idl-file").map(|s| s.to_string());
922+
let network_str = matches
923+
.get_one::<String>("network")
924+
.map(|s| s.as_str())
925+
.unwrap_or("all");
926+
let json_output = matches.get_flag("json");
927+
let retry_attempts = matches
928+
.get_one::<String>("retry-attempts")
929+
.and_then(|s| s.parse().ok())
930+
.unwrap_or(3);
931+
let confirm_large_binaries = matches.get_flag("confirm-large");
932+
933+
// Create deployment configuration
934+
let deploy_config = ebpf_deploy::DeployConfig {
935+
binary_path: binary_path.to_string(),
936+
program_id_path: program_id_path.to_string(),
937+
owner_path: owner_path.to_string(),
938+
fee_payer_path: fee_payer_path.to_string(),
939+
publish_idl,
940+
idl_file_path,
941+
network_selection: network_str.to_string(),
942+
json_output,
943+
retry_attempts,
944+
confirm_large_binaries,
945+
};
946+
947+
println!("🚀 OSVM eBPF Deployment Tool");
948+
println!("============================");
949+
println!("📁 Binary path: {binary_path}");
950+
println!("🆔 Program ID: {program_id_path}");
951+
println!("👤 Owner: {owner_path}");
952+
println!("💰 Fee payer: {fee_payer_path}");
953+
println!("📄 Publish IDL: {}", if publish_idl { "yes" } else { "no" });
954+
println!("🌐 Target network(s): {network_str}");
955+
println!();
956+
957+
// Execute deployment
958+
let results = ebpf_deploy::deploy_to_all_networks(
959+
deploy_config.clone(),
960+
config.commitment_config,
961+
)
962+
.await;
963+
964+
// Display results using the new display function
965+
if let Err(e) =
966+
ebpf_deploy::display_deployment_results(&results, deploy_config.json_output)
967+
{
968+
eprintln!("Error displaying results: {}", e);
969+
}
970+
971+
// Determine exit status
972+
let failure_count = results
973+
.iter()
974+
.filter(|r| r.as_ref().map_or(true, |d| !d.success))
975+
.count();
976+
977+
if failure_count > 0 {
978+
return Err("Some deployments failed".into());
979+
}
980+
}
902981
"new_feature_command" => {
903982
println!("Expected output for new feature");
904983
}
905984
cmd => {
906-
eprintln!("Unknown command: {}", cmd);
907-
exit(1);
985+
return Err(format!("Unknown command: {cmd}").into());
908986
}
909987
};
910988

@@ -919,7 +997,7 @@ mod test {
919997
#[test]
920998
fn test_borsh() {
921999
#[repr(C)]
922-
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
1000+
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
9231001
pub struct UpdateMetadataAccountArgs {
9241002
pub data: Option<String>,
9251003
pub update_authority: Option<Pubkey>,

0 commit comments

Comments
 (0)