From 21eae981f960f026e11b63b098f100aa1d7e307c Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 22 Oct 2018 22:21:33 -0600 Subject: [PATCH] Add deploy method to solana-wallet --- src/bin/wallet.rs | 12 ++++ src/lib.rs | 1 + src/wallet.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) diff --git a/src/bin/wallet.rs b/src/bin/wallet.rs index 5189106bfc..5c28f47b48 100644 --- a/src/bin/wallet.rs +++ b/src/bin/wallet.rs @@ -150,6 +150,18 @@ fn main() -> Result<(), Box> { .required(true) .help("The transaction signature to confirm"), ), + ).subcommand( + SubCommand::with_name("deploy") + .about("Deploy a program") + .arg( + Arg::with_name("program-location") + .index(1) + .value_name("PATH") + .takes_value(true) + .required(true) + .help("/path/to/program.o"), + ) + // TODO: Add "loader" argument; current default is bpf_loader ).subcommand( SubCommand::with_name("pay") .about("Send a payment") diff --git a/src/lib.rs b/src/lib.rs index 0462b3ab62..e0c140d47f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,7 @@ extern crate bytes; extern crate chrono; extern crate clap; extern crate dirs; +extern crate elf; extern crate generic_array; #[cfg(test)] #[cfg(any(feature = "chacha", feature = "cuda"))] diff --git a/src/wallet.rs b/src/wallet.rs index e6aa2add40..49ced97d79 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,4 +1,5 @@ use bincode::{deserialize, serialize}; +// use bpf_loader; use bs58; use budget_program::BudgetState; use budget_transaction::BudgetTransaction; @@ -6,8 +7,11 @@ use chrono::prelude::*; use clap::ArgMatches; use cluster_info::NodeInfo; use drone::DroneRequest; +use elf; use fullnode::Config; use hash::Hash; +use loader_transaction::LoaderTransaction; +use native_loader; use ring::rand::SystemRandom; use ring::signature::Ed25519KeyPair; use rpc_request::RpcRequest; @@ -26,6 +30,10 @@ use std::{error, fmt, mem}; use system_transaction::SystemTransaction; use transaction::Transaction; +// TODO: put these somewhere more logical +const PLATFORM_SECTION_C: &str = ".text.entrypoint"; +const USERDATA_CHUNK_SIZE: usize = 256; + #[derive(Debug, PartialEq)] pub enum WalletCommand { Address, @@ -33,6 +41,7 @@ pub enum WalletCommand { Balance, Cancel(Pubkey), Confirm(Signature), + Deploy(String), // Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable) Pay( i64, @@ -52,6 +61,7 @@ pub enum WalletCommand { pub enum WalletError { CommandNotRecognized(String), BadParameter(String), + DynamicProgramError(String), RpcRequestError(String), } @@ -129,6 +139,12 @@ pub fn parse_command( Err(WalletError::BadParameter("Invalid signature".to_string())) } } + ("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy( + deploy_matches + .value_of("program-location") + .unwrap() + .to_string(), + )), ("pay", Some(pay_matches)) => { let tokens = pay_matches.value_of("tokens").unwrap().parse()?; let to = if pay_matches.is_present("to") { @@ -364,6 +380,133 @@ pub fn process_command(config: &WalletConfig) -> Result { + let params = json!(format!("{}", config.id.pubkey())); + let balance = RpcRequest::GetBalance + .make_rpc_request(&config.rpc_addr, 1, Some(params))? + .as_i64(); + if let Some(tokens) = balance { + if tokens < 2 { + Err(WalletError::DynamicProgramError( + "Insufficient funds".to_string(), + ))? + } + } + + let last_id = get_last_id(&config)?; + let program = Keypair::new(); + let program_userdata = elf::File::open_path(program_location) + .map_err(|_| { + WalletError::DynamicProgramError("Could not parse program file".to_string()) + })?.get_section(PLATFORM_SECTION_C) + .ok_or_else(|| { + WalletError::DynamicProgramError( + "Could not find entrypoint in program file".to_string(), + ) + })?.data + .clone(); + + // Loader instance for testing; REMOVE + let loader = Keypair::new(); + let tx = Transaction::system_create( + &config.id, + loader.pubkey(), + last_id, + 1, + 56, + native_loader::id(), + 0, + ); + let _signature_str = serialize_and_send_tx(&config, &tx)?; + + let name = String::from("bpf_loader"); + let tx = Transaction::write( + &loader, + native_loader::id(), + 0, + name.as_bytes().to_vec(), + last_id, + 0, + ); + let _signature_str = serialize_and_send_tx(&config, &tx)?; + + let tx = Transaction::finalize(&loader, native_loader::id(), last_id, 0); + let _signature_str = serialize_and_send_tx(&config, &tx)?; + + let tx = Transaction::system_spawn(&loader, last_id, 0); + let _signature_str = serialize_and_send_tx(&config, &tx)?; + // end loader instance + + let tx = Transaction::system_create( + &config.id, + program.pubkey(), + last_id, + 1, + program_userdata.len() as u64, + // bpf_loader::id(), + loader.pubkey(), + 0, + ); + let signature_str = serialize_and_send_tx(&config, &tx)?; + if !confirm_tx(&config, &signature_str)? { + Err(WalletError::DynamicProgramError( + "Program allocate space failed".to_string(), + ))? + } + + let mut offset = 0; + let write_result = program_userdata + .chunks(USERDATA_CHUNK_SIZE) + .map(|chunk| { + let tx = Transaction::write( + &program, + // bpf_loader::id(), + loader.pubkey(), + offset, + chunk.to_vec(), + last_id, + 0, + ); + let signature_str = serialize_and_send_tx(&config, &tx).unwrap(); + offset += USERDATA_CHUNK_SIZE as u32; + signature_str + }).map(|signature| confirm_tx(&config, &signature).unwrap()) + .all(|status| status); + + if write_result { + let last_id = get_last_id(&config)?; + let tx = Transaction::finalize( + &program, + // bpf_loader::id(), + loader.pubkey(), + last_id, + 0, + ); + let signature_str = serialize_and_send_tx(&config, &tx)?; + if !confirm_tx(&config, &signature_str)? { + Err(WalletError::DynamicProgramError( + "Program finalize transaction failed".to_string(), + ))? + } + + let tx = Transaction::system_spawn(&program, last_id, 0); + let signature_str = serialize_and_send_tx(&config, &tx)?; + if !confirm_tx(&config, &signature_str)? { + Err(WalletError::DynamicProgramError( + "Program spawn failed".to_string(), + ))? + } + + Ok(json!({ + "programId": format!("{}", program.pubkey()), + }).to_string()) + } else { + Err(WalletError::DynamicProgramError( + "Program chunks write error".to_string(), + ))? + } + } // If client has positive balance, pay tokens to another address WalletCommand::Pay(tokens, to, timestamp, timestamp_pubkey, ref witnesses, cancelable) => { let last_id = get_last_id(&config)?; @@ -609,6 +752,18 @@ fn serialize_and_send_tx( Ok(signature.as_str().unwrap().to_string()) } +fn confirm_tx(config: &WalletConfig, signature: &str) -> Result> { + let params = json!(signature.to_string()); + let status = + RpcRequest::ConfirmTransaction.make_rpc_request(&config.rpc_addr, 1, Some(params))?; + if status.as_bool().is_none() { + Err(WalletError::RpcRequestError( + "Received result of an unexpected type".to_string(), + ))? + } + Ok(status.as_bool().unwrap()) +} + #[cfg(test)] mod tests { use super::*;