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)
.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")

View File

@ -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"))]

View File

@ -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<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
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<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)]
mod tests {
use super::*;