Add deploy method to solana-wallet

This commit is contained in:
Tyera Eulberg 2018-10-22 22:21:33 -06:00 committed by Tyera Eulberg
parent ead7f4287a
commit 21eae981f9
3 changed files with 168 additions and 0 deletions

View File

@ -150,6 +150,18 @@ fn main() -> Result<(), Box<error::Error>> {
.required(true) .required(true)
.help("The transaction signature to confirm"), .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(
SubCommand::with_name("pay") SubCommand::with_name("pay")
.about("Send a payment") .about("Send a payment")

View File

@ -90,6 +90,7 @@ extern crate bytes;
extern crate chrono; extern crate chrono;
extern crate clap; extern crate clap;
extern crate dirs; extern crate dirs;
extern crate elf;
extern crate generic_array; extern crate generic_array;
#[cfg(test)] #[cfg(test)]
#[cfg(any(feature = "chacha", feature = "cuda"))] #[cfg(any(feature = "chacha", feature = "cuda"))]

View File

@ -1,4 +1,5 @@
use bincode::{deserialize, serialize}; use bincode::{deserialize, serialize};
// use bpf_loader;
use bs58; use bs58;
use budget_program::BudgetState; use budget_program::BudgetState;
use budget_transaction::BudgetTransaction; use budget_transaction::BudgetTransaction;
@ -6,8 +7,11 @@ use chrono::prelude::*;
use clap::ArgMatches; use clap::ArgMatches;
use cluster_info::NodeInfo; use cluster_info::NodeInfo;
use drone::DroneRequest; use drone::DroneRequest;
use elf;
use fullnode::Config; use fullnode::Config;
use hash::Hash; use hash::Hash;
use loader_transaction::LoaderTransaction;
use native_loader;
use ring::rand::SystemRandom; use ring::rand::SystemRandom;
use ring::signature::Ed25519KeyPair; use ring::signature::Ed25519KeyPair;
use rpc_request::RpcRequest; use rpc_request::RpcRequest;
@ -26,6 +30,10 @@ use std::{error, fmt, mem};
use system_transaction::SystemTransaction; use system_transaction::SystemTransaction;
use transaction::Transaction; 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)] #[derive(Debug, PartialEq)]
pub enum WalletCommand { pub enum WalletCommand {
Address, Address,
@ -33,6 +41,7 @@ pub enum WalletCommand {
Balance, Balance,
Cancel(Pubkey), Cancel(Pubkey),
Confirm(Signature), Confirm(Signature),
Deploy(String),
// Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable) // Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable)
Pay( Pay(
i64, i64,
@ -52,6 +61,7 @@ pub enum WalletCommand {
pub enum WalletError { pub enum WalletError {
CommandNotRecognized(String), CommandNotRecognized(String),
BadParameter(String), BadParameter(String),
DynamicProgramError(String),
RpcRequestError(String), RpcRequestError(String),
} }
@ -129,6 +139,12 @@ pub fn parse_command(
Err(WalletError::BadParameter("Invalid signature".to_string())) 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)) => { ("pay", Some(pay_matches)) => {
let tokens = pay_matches.value_of("tokens").unwrap().parse()?; let tokens = pay_matches.value_of("tokens").unwrap().parse()?;
let to = if pay_matches.is_present("to") { let to = if pay_matches.is_present("to") {
@ -364,6 +380,133 @@ pub fn process_command(config: &WalletConfig) -> Result<String, Box<error::Error
))?, ))?,
} }
} }
// Deploy a custom program to the chain
WalletCommand::Deploy(ref program_location) => {
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 // If client has positive balance, pay tokens to another address
WalletCommand::Pay(tokens, to, timestamp, timestamp_pubkey, ref witnesses, cancelable) => { WalletCommand::Pay(tokens, to, timestamp, timestamp_pubkey, ref witnesses, cancelable) => {
let last_id = get_last_id(&config)?; let last_id = get_last_id(&config)?;
@ -609,6 +752,18 @@ fn serialize_and_send_tx(
Ok(signature.as_str().unwrap().to_string()) Ok(signature.as_str().unwrap().to_string())
} }
fn confirm_tx(config: &WalletConfig, signature: &str) -> Result<bool, Box<error::Error>> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;