Command-line receiving end of wallet

Parse the partial transaction encoded in JSON, adding a new output
and finalizing. Should push the final tx to a grin node for
broadcast once that's fleshed out. Should also add an endpoint for
the http receiver.
This commit is contained in:
Ignotus Peverell 2017-06-07 18:12:15 -07:00
parent aeb3dfc0dd
commit d26a659a97
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
5 changed files with 105 additions and 15 deletions

View file

@ -96,7 +96,12 @@ fn main() {
.help("Wallet passphrase used to generate the private key seed")
.takes_value(true))
.subcommand(SubCommand::with_name("receive")
.about("Run the wallet in receiving mode"))
.about("Run the wallet in receiving mode. If an input file is provided, will process it, otherwise runs in server mode waiting for send requests.")
.arg(Arg::with_name("input")
.help("Partial transaction to receive, expects as a JSON file.")
.short("i")
.long("input")
.takes_value(true)))
.subcommand(SubCommand::with_name("send")
.about("Builds a transaction to send someone some coins. By default, the transaction will just be printed to stdout. If a destination is provided, the command will attempt to contact the receiver at that address and send the transaction directly.")
.arg(Arg::with_name("amount")
@ -195,14 +200,21 @@ fn wallet_command(wallet_args: &ArgMatches) {
.expect("Error deriving extended key from seed.");
match wallet_args.subcommand() {
("receive", _) => {
info!("Starting the Grin wallet receiving daemon...");
let mut apis = api::ApiServer::new("/v1".to_string());
apis.register_endpoint("/receive_coinbase".to_string(),
wallet::WalletReceiver { key: key });
apis.start("127.0.0.1:13416").unwrap_or_else(|e| {
error!("Failed to start Grin wallet receiver: {}.", e);
});
("receive", Some(receive_args)) => {
if let Some(f) = receive_args.value_of("input") {
let mut file = File::open(f).expect("Unable to open transaction file.");
let mut contents = String::new();
file.read_to_string(&mut contents).expect("Unable to read transaction file.");
wallet::receive_json_tx(&key, contents.as_str()).unwrap();
} else {
info!("Starting the Grin wallet receiving daemon...");
let mut apis = api::ApiServer::new("/v1".to_string());
apis.register_endpoint("/receive_coinbase".to_string(),
wallet::WalletReceiver { key: key });
apis.start("127.0.0.1:13416").unwrap_or_else(|e| {
error!("Failed to start Grin wallet receiver: {}.", e);
});
}
}
("send", Some(send_args)) => {
let amount = send_args.value_of("amount")

View file

@ -36,5 +36,5 @@ mod sender;
mod types;
pub use extkey::ExtendedKey;
pub use receiver::WalletReceiver;
pub use receiver::{WalletReceiver, receive_json_tx};
pub use sender::issue_send_tx;

View file

@ -60,6 +60,17 @@ use extkey::{self, ExtendedKey};
use types::*;
use util;
/// Receive an already well formed JSON transaction issuance and finalize the
/// transaction, adding our receiving output, to broadcast to the rest of the
/// network.
pub fn receive_json_tx(ext_key: &ExtendedKey, partial_tx_str: &str) -> Result<(), Error> {
let (amount, blinding, partial_tx) = partial_tx_from_json(partial_tx_str)?;
let final_tx = receive_transaction(ext_key, amount, blinding, partial_tx)?;
// TODO send to a node to broadcast
println!("TX OK!");
Ok(())
}
/// Amount in request to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbAmount {
@ -140,5 +151,37 @@ fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKer
info!("Using child {} for a new coinbase output.",
coinbase_key.n_child);
Block::reward_output(coinbase_key.key, &secp).map_err(&From::from);
Block::reward_output(coinbase_key.key, &secp).map_err(&From::from)
}
/// Builds a full transaction from the partial one sent to us for transfer
fn receive_transaction(ext_key: &ExtendedKey,
amount: u64,
blinding: SecretKey,
partial: Transaction)
-> Result<Transaction, Error> {
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
// derive a new private for the receiving output
let mut wallet_data = WalletData::read_or_create()?;
let next_child = wallet_data.next_child(ext_key.fingerprint);
let out_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?;
let (tx_final, _) = build::transaction(vec![build::initial_tx(partial),
build::with_excess(blinding),
build::output(amount, out_key.key)])?;
// track the new output and return the finalized transaction to broadcast
wallet_data.append_output(OutputData {
fingerprint: out_key.fingerprint,
n_child: out_key.n_child,
value: amount,
status: OutputStatus::Unconfirmed,
});
wallet_data.write()?;
info!("Using child {} for a new coinbase output.", out_key.n_child);
Ok(tx_final)
}

View file

@ -21,6 +21,10 @@ use core::core::{Transaction, build};
use extkey::ExtendedKey;
use types::*;
/// Issue a new transaction to the provided sender by spending some of our
/// wallet
/// UTXOs. The destination can be "stdout" (for command line) or a URL to the
/// recipients wallet receiver (to be implemented).
pub fn issue_send_tx(ext_key: &ExtendedKey, amount: u64, dest: String) -> Result<(), Error> {
checker::refresh_outputs(&WalletConfig::default(), ext_key);
@ -44,7 +48,7 @@ fn build_send_tx(ext_key: &ExtendedKey, amount: u64) -> Result<(Transaction, Sec
// second, check from our local wallet data for outputs to spend
let mut wallet_data = WalletData::read()?;
let (mut coins, change) = wallet_data.select(ext_key.fingerprint, amount);
let (coins, change) = wallet_data.select(ext_key.fingerprint, amount);
if change < 0 {
return Err(Error::NotEnoughFunds((-change) as u64));
}

View file

@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::convert::From;
use std::fs::File;
use std::io::Write;
use std::num;
use std::path::Path;
use std::convert::From;
use serde::{Serialize, Deserialize};
use serde_json;
use secp::{self, Secp256k1};
use secp;
use secp::key::SecretKey;
use core::core::Transaction;
@ -37,6 +37,8 @@ pub enum Error {
Crypto(secp::Error),
Key(extkey::Error),
WalletData(String),
/// An error in the format of the JSON structures exchanged by the wallet
Format(String),
}
impl From<secp::Error> for Error {
@ -51,6 +53,18 @@ impl From<extkey::Error> for Error {
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Error {
Error::Format(e.to_string())
}
}
impl From<num::ParseIntError> for Error {
fn from(e: num::ParseIntError) -> Error {
Error::Format("Invalid hex".to_string())
}
}
#[derive(Debug, Clone)]
pub struct WalletConfig {
pub api_http_addr: String,
@ -191,3 +205,20 @@ pub fn partial_tx_to_json(receive_amount: u64, blind_sum: SecretKey, tx: Transac
};
serde_json::to_string_pretty(&partial_tx).unwrap()
}
/// Reads a partial transaction encoded as JSON into the amount, sum of blinding
/// factors and the transaction itself.
pub fn partial_tx_from_json(json_str: &str) -> Result<(u64, SecretKey, Transaction), Error> {
let partial_tx: JSONPartialTx = serde_json::from_str(json_str)?;
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
let blind_bin = util::from_hex(partial_tx.blind_sum)?;
let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?;
let tx_bin = util::from_hex(partial_tx.tx)?;
let tx =
ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
Error::Format("Could not deserialize transaction, invalid format.".to_string())
})?;
Ok((partial_tx.amount, blinding, tx))
}