Sending end of the wallet

Most of the logic to build a transaction that sends coin to
another party. Still requires more debugging and clean up.
Main changes and additions are:

* Update to serde 1.0
* API endpoint to retrieve an Output
* Output is now Serialize and Deserialize
* Wallet configuration
* Command line for the send operation
* Wallet data checker to update created outputs into confirmed
* Wallet-specific configuration
This commit is contained in:
Ignotus Peverell 2017-05-28 20:21:29 -07:00
parent da41120293
commit f79fb8ef95
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
22 changed files with 181 additions and 101 deletions

View file

@ -16,6 +16,7 @@ clap = "^2.23.3"
daemonize = "^0.2.3" daemonize = "^0.2.3"
env_logger="^0.3.5" env_logger="^0.3.5"
log = "^0.3" log = "^0.3"
serde = "~0.9.10" serde = "~1.0.8"
serde_json = "~0.9.9" serde_derive = "~1.0.8"
serde_json = "~1.0.2"
tiny-keccak = "1.1" tiny-keccak = "1.1"

View file

@ -5,11 +5,14 @@ authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
workspace = ".." workspace = ".."
[dependencies] [dependencies]
grin_core = { path = "../core" }
grin_chain = { path = "../chain" } grin_chain = { path = "../chain" }
grin_util = { path = "../util" }
secp256k1zkp = { path = "../secp256k1zkp" }
hyper = "~0.10.6" hyper = "~0.10.6"
iron = "~0.5.1" iron = "~0.5.1"
log = "~0.3" log = "~0.3"
router = "~0.5.1" router = "~0.5.1"
serde = "~0.9.10" serde = "~1.0.8"
serde_json = "~0.9.8" serde_json = "~1.0.2"

View file

@ -26,8 +26,9 @@ use rest::Error;
/// returns a JSON object. Handles request building, JSON deserialization and /// returns a JSON object. Handles request building, JSON deserialization and
/// response code checking. /// response code checking.
pub fn get<'a, T>(url: &'a str) -> Result<T, Error> pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
where T: Deserialize where for<'de> T: Deserialize<'de>
{ {
println!("get {}", url);
let client = hyper::Client::new(); let client = hyper::Client::new();
let res = check_error(client.get(url).send())?; let res = check_error(client.get(url).send())?;
serde_json::from_reader(res) serde_json::from_reader(res)
@ -40,7 +41,7 @@ pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
/// checking. /// checking.
pub fn post<'a, IN, OUT>(url: &'a str, input: &IN) -> Result<OUT, Error> pub fn post<'a, IN, OUT>(url: &'a str, input: &IN) -> Result<OUT, Error>
where IN: Serialize, where IN: Serialize,
OUT: Deserialize for<'de> OUT: Deserialize<'de>
{ {
let in_json = serde_json::to_string(input) let in_json = serde_json::to_string(input)
.map_err(|e| Error::Internal(format!("Could not serialize data to JSON: {}", e)))?; .map_err(|e| Error::Internal(format!("Could not serialize data to JSON: {}", e)))?;

View file

@ -24,8 +24,12 @@
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use core::core::Output;
use core::core::hash::Hash;
use chain::{self, Tip}; use chain::{self, Tip};
use rest::*; use rest::*;
use secp::pedersen::Commitment;
use util;
/// ApiEndpoint implementation for the blockchain. Exposes the current chain /// ApiEndpoint implementation for the blockchain. Exposes the current chain
/// state as a simple JSON object. /// state as a simple JSON object.
@ -50,13 +54,38 @@ impl ApiEndpoint for ChainApi {
} }
} }
/// ApiEndpoint implementation for outputs that have been included in the chain.
#[derive(Clone)]
pub struct OutputApi {
/// data store access
chain_store: Arc<chain::ChainStore>,
}
impl ApiEndpoint for OutputApi {
type ID = String;
type T = Output;
type OP_IN = ();
type OP_OUT = ();
fn operations(&self) -> Vec<Operation> {
vec![Operation::Get]
}
fn get(&self, id: String) -> ApiResult<Output> {
debug!("GET output {}", id);
let c = util::from_hex(id.clone()).map_err(|e| Error::Argument(format!("Not a valid commitment: {}", id)))?;
self.chain_store.get_output_by_commit(&Commitment::from_vec(c)).map_err(|e| Error::Internal(e.to_string()))
}
}
/// Start all server REST APIs. Just register all of them on a ApiServer /// Start all server REST APIs. Just register all of them on a ApiServer
/// instance and runs the corresponding HTTP server. /// instance and runs the corresponding HTTP server.
pub fn start_rest_apis(addr: String, chain_store: Arc<chain::ChainStore>) { pub fn start_rest_apis(addr: String, chain_store: Arc<chain::ChainStore>) {
thread::spawn(move || { thread::spawn(move || {
let mut apis = ApiServer::new("/v1".to_string()); let mut apis = ApiServer::new("/v1".to_string());
apis.register_endpoint("/chain".to_string(), ChainApi { chain_store: chain_store }); apis.register_endpoint("/chain".to_string(), ChainApi { chain_store: chain_store.clone() });
apis.register_endpoint("/chain/output".to_string(), OutputApi { chain_store: chain_store.clone() });
apis.start(&addr[..]).unwrap_or_else(|e| { apis.start(&addr[..]).unwrap_or_else(|e| {
error!("Failed to start API HTTP server: {}.", e); error!("Failed to start API HTTP server: {}.", e);
}); });

View file

@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
extern crate grin_core as core;
extern crate grin_chain as chain; extern crate grin_chain as chain;
extern crate grin_util as util;
extern crate secp256k1zkp as secp;
extern crate hyper; extern crate hyper;
#[macro_use] #[macro_use]

View file

@ -31,6 +31,7 @@ use iron::modifiers::Header;
use iron::middleware::Handler; use iron::middleware::Handler;
use router::Router; use router::Router;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned;
use serde_json; use serde_json;
/// Errors that can be returned by an ApiEndpoint implementation. /// Errors that can be returned by an ApiEndpoint implementation.
@ -109,9 +110,9 @@ pub type ApiResult<T> = ::std::result::Result<T, Error>;
/// create and accepted by all other methods must have a string representation. /// create and accepted by all other methods must have a string representation.
pub trait ApiEndpoint: Clone + Send + Sync + 'static { pub trait ApiEndpoint: Clone + Send + Sync + 'static {
type ID: ToString + FromStr; type ID: ToString + FromStr;
type T: Serialize + Deserialize; type T: Serialize + DeserializeOwned;
type OP_IN: Serialize + Deserialize; type OP_IN: Serialize + DeserializeOwned;
type OP_OUT: Serialize + Deserialize; type OP_OUT: Serialize + DeserializeOwned;
fn operations(&self) -> Vec<Operation>; fn operations(&self) -> Vec<Operation>;
@ -251,7 +252,7 @@ impl ApiServer {
}; };
let full_path = format!("{}", root.clone()); let full_path = format!("{}", root.clone());
self.router.route(op.to_method(), full_path.clone(), wrapper, route_name); self.router.route(op.to_method(), full_path.clone(), wrapper, route_name);
info!("POST {}", full_path); info!("route: POST {}", full_path);
} else { } else {
// regular REST operations // regular REST operations
@ -264,7 +265,7 @@ impl ApiServer {
}; };
let wrapper = ApiWrapper(endpoint.clone()); let wrapper = ApiWrapper(endpoint.clone());
self.router.route(op.to_method(), full_path.clone(), wrapper, route_name); self.router.route(op.to_method(), full_path.clone(), wrapper, route_name);
info!("{} {}", op.to_method(), full_path); info!("route: {} {}", op.to_method(), full_path);
} }
} }

View file

@ -8,8 +8,8 @@ workspace = ".."
bitflags = "^0.7.0" bitflags = "^0.7.0"
byteorder = "^0.5" byteorder = "^0.5"
log = "^0.3" log = "^0.3"
serde = "~0.9.10" serde = "~1.0.8"
serde_derive = "~0.9.10" serde_derive = "~1.0.8"
time = "^0.1" time = "^0.1"
tokio-io = "0.1.1" tokio-io = "0.1.1"
bytes = "0.4.2" bytes = "0.4.2"

View file

@ -30,7 +30,6 @@ const BLOCK_PREFIX: u8 = 'b' as u8;
const HEAD_PREFIX: u8 = 'H' as u8; const HEAD_PREFIX: u8 = 'H' as u8;
const HEADER_HEAD_PREFIX: u8 = 'I' as u8; const HEADER_HEAD_PREFIX: u8 = 'I' as u8;
const HEADER_HEIGHT_PREFIX: u8 = '8' as u8; const HEADER_HEIGHT_PREFIX: u8 = '8' as u8;
const OUTPUT_PREFIX: u8 = 'O' as u8;
const OUTPUT_COMMIT_PREFIX: u8 = 'o' as u8; const OUTPUT_COMMIT_PREFIX: u8 = 'o' as u8;
/// An implementation of the ChainStore trait backed by a simple key-value /// An implementation of the ChainStore trait backed by a simple key-value
@ -104,11 +103,10 @@ impl ChainStore for ChainKVStore {
// saving the full output under its hash, as well as a commitment to hash index // saving the full output under its hash, as well as a commitment to hash index
for out in &b.outputs { for out in &b.outputs {
let mut out_bytes = out.commit.as_ref().to_vec();
println!("OUTSAVE: {:?}", out_bytes);
batch = batch.put_enc(&mut BlockCodec::default(), batch = batch.put_enc(&mut BlockCodec::default(),
&to_key(OUTPUT_PREFIX, &mut out.hash().to_vec())[..], &to_key(OUTPUT_COMMIT_PREFIX, &mut out_bytes)[..],
out.clone())?
.put_enc(&mut BlockCodec::default(),
&to_key(OUTPUT_COMMIT_PREFIX, &mut out.commit.as_ref().to_vec())[..],
out.hash().clone())?; out.hash().clone())?;
} }
batch.write() batch.write()
@ -125,9 +123,9 @@ impl ChainStore for ChainKVStore {
&u64_to_key(HEADER_HEIGHT_PREFIX, height))) &u64_to_key(HEADER_HEIGHT_PREFIX, height)))
} }
fn get_output(&self, h: &Hash) -> Result<Output, Error> { fn get_output_by_commit(&self, commit: &Commitment) -> Result<Output, Error> {
option_to_not_found(self.db.get_dec(&mut BlockCodec::default(), option_to_not_found(self.db.get_dec(&mut BlockCodec::default(),
&to_key(OUTPUT_PREFIX, &mut h.to_vec()))) &to_key(OUTPUT_COMMIT_PREFIX, &mut commit.as_ref().to_vec())))
} }
fn has_output_commit(&self, commit: &Commitment) -> Result<Hash, Error> { fn has_output_commit(&self, commit: &Commitment) -> Result<Hash, Error> {

View file

@ -99,8 +99,8 @@ pub trait ChainStore: Send + Sync {
/// Gets the block header at the provided height /// Gets the block header at the provided height
fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error>; fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error>;
/// Gets an output by its hash /// Gets an output by its commitment
fn get_output(&self, h: &Hash) -> Result<Output, Error>; fn get_output_by_commit(&self, commit: &Commitment) -> Result<Output, Error>;
/// Checks whether an output commitment exists and returns the output hash /// Checks whether an output commitment exists and returns the output hash
fn has_output_commit(&self, commit: &Commitment) -> Result<Hash, Error>; fn has_output_commit(&self, commit: &Commitment) -> Result<Hash, Error>;

View file

@ -10,8 +10,8 @@ byteorder = "^0.5"
num-bigint = "^0.1.35" num-bigint = "^0.1.35"
rust-crypto = "^0.2" rust-crypto = "^0.2"
rand = "^0.3" rand = "^0.3"
serde = "~0.9.10" serde = "~1.0.8"
serde_derive = "~0.9.10" serde_derive = "~1.0.8"
time = "^0.1" time = "^0.1"
tiny-keccak = "1.1" tiny-keccak = "1.1"

View file

@ -17,6 +17,7 @@
//! Primary hash function used in the protocol //! Primary hash function used in the protocol
//! //!
use std::cmp::min;
use std::{fmt, ops}; use std::{fmt, ops};
use tiny_keccak::Keccak; use tiny_keccak::Keccak;
use std::convert::AsRef; use std::convert::AsRef;
@ -47,6 +48,16 @@ impl fmt::Display for Hash {
} }
impl Hash { impl Hash {
/// Builds a Hash from a byte vector. If the vector is too short, it will be
/// completed by zeroes. If it's too long, it will be truncated.
pub fn from_vec(v: Vec<u8>) -> Hash {
let mut h = [0; 32];
for i in 0..min(v.len(), 32) {
h[i] = v[i];
}
Hash(h)
}
/// Converts the hash to a byte vector /// Converts the hash to a byte vector
pub fn to_vec(&self) -> Vec<u8> { pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec() self.0.to_vec()

View file

@ -105,9 +105,9 @@ impl Serialize for Difficulty {
} }
} }
impl Deserialize for Difficulty { impl<'de> Deserialize<'de> for Difficulty {
fn deserialize<D>(deserializer: D) -> Result<Difficulty, D::Error> fn deserialize<D>(deserializer: D) -> Result<Difficulty, D::Error>
where D: Deserializer where D: Deserializer<'de>
{ {
deserializer.deserialize_i32(DiffVisitor) deserializer.deserialize_i32(DiffVisitor)
} }
@ -115,7 +115,7 @@ impl Deserialize for Difficulty {
struct DiffVisitor; struct DiffVisitor;
impl de::Visitor for DiffVisitor { impl<'de> de::Visitor<'de> for DiffVisitor {
type Value = Difficulty; type Value = Difficulty;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {

View file

@ -269,6 +269,7 @@ impl Input {
bitflags! { bitflags! {
/// Options for block validation /// Options for block validation
#[derive(Serialize, Deserialize)]
pub flags OutputFeatures: u8 { pub flags OutputFeatures: u8 {
/// No flags /// No flags
const DEFAULT_OUTPUT = 0b00000000, const DEFAULT_OUTPUT = 0b00000000,
@ -279,10 +280,9 @@ bitflags! {
/// Output for a transaction, defining the new ownership of coins that are being /// Output for a transaction, defining the new ownership of coins that are being
/// transferred. The commitment is a blinded value for the output while the /// transferred. The commitment is a blinded value for the output while the
/// range /// range proof guarantees the commitment includes a positive value without
/// proof guarantees the commitment includes a positive value without overflow /// overflow and the ownership of the private key.
/// and the ownership of the private key. #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Output { pub struct Output {
/// Options for an output's structure or use /// Options for an output's structure or use
pub features: OutputFeatures, pub features: OutputFeatures,

View file

@ -19,8 +19,8 @@ futures-cpupool = "^0.1.3"
hyper = { git = "https://github.com/hyperium/hyper" } hyper = { git = "https://github.com/hyperium/hyper" }
log = "^0.3" log = "^0.3"
time = "^0.1" time = "^0.1"
serde = "~0.9.10" serde = "~1.0.8"
serde_derive = "~0.9.10" serde_derive = "~1.0.8"
tokio-core="^0.1.1" tokio-core="^0.1.1"
tokio-timer="^0.1.0" tokio-timer="^0.1.0"
rand = "^0.3" rand = "^0.3"

View file

@ -13,8 +13,8 @@ futures = "^0.1.9"
log = "^0.3" log = "^0.3"
net2 = "0.2.0" net2 = "0.2.0"
rand = "^0.3" rand = "^0.3"
serde = "~0.9.10" serde = "~1.0.8"
serde_derive = "~0.9.10" serde_derive = "~1.0.8"
tokio-core="^0.1.1" tokio-core="^0.1.1"
tokio-timer="^0.1.0" tokio-timer="^0.1.0"

View file

@ -96,14 +96,24 @@ fn main() {
.help("Wallet passphrase used to generate the private key seed") .help("Wallet passphrase used to generate the private key seed")
.takes_value(true)) .takes_value(true))
.subcommand(SubCommand::with_name("receive") .subcommand(SubCommand::with_name("receive")
.about("Run the wallet in receiving mode"))) .about("Run the wallet in receiving mode"))
.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")
.help("Amount to send in the smallest denomination")
.index(1))
.arg(Arg::with_name("dest")
.help("Send the transaction to the provided server")
.short("d")
.long("dest")
.takes_value(true))))
.get_matches(); .get_matches();
match args.subcommand() { match args.subcommand() {
// server commands and options // server commands and options
("server", Some(server_args)) => { ("server", Some(server_args)) => {
server_command(server_args); server_command(server_args);
}, }
// client commands and options // client commands and options
("client", Some(client_args)) => { ("client", Some(client_args)) => {
@ -117,12 +127,7 @@ fn main() {
// client commands and options // client commands and options
("wallet", Some(wallet_args)) => { ("wallet", Some(wallet_args)) => {
match wallet_args.subcommand() { wallet_command(wallet_args);
("receive", _) => {
wallet_command(wallet_args);
},
_ => panic!("Unknown client command, use 'grin help client' for details"),
}
} }
_ => println!("Unknown command, use 'grin help' for a list of all commands"), _ => println!("Unknown command, use 'grin help' for a list of all commands"),
@ -198,6 +203,14 @@ fn wallet_command(wallet_args: &ArgMatches) {
error!("Failed to start Grin wallet receiver: {}.", e); error!("Failed to start Grin wallet receiver: {}.", e);
}); });
} }
("send", Some(send_args)) => {
let amount = send_args.value_of("amount").expect("Amount to send required").parse().expect("Could not parse amount as a whole number.");
let mut dest = "stdout";
if let Some(d) = send_args.value_of("dest") {
dest = d;
}
wallet::issue_send_tx(&key, amount, dest.to_string()).unwrap();
}
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"), _ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
} }
} }

View file

@ -9,9 +9,9 @@ byteorder = "1"
log = "^0.3" log = "^0.3"
rand = "^0.3" rand = "^0.3"
rust-crypto = "^0.2" rust-crypto = "^0.2"
serde = "~0.9.10" serde = "~1.0.8"
serde_derive = "~0.9.10" serde_derive = "~1.0.8"
serde_json = "~0.9.8" serde_json = "~1.0.2"
grin_api = { path = "../api" } grin_api = { path = "../api" }
grin_core = { path = "../core" } grin_core = { path = "../core" }

View file

@ -63,7 +63,6 @@ impl error::Error for Error {
/// To be usable, a secret key should have an amount assigned to it, /// To be usable, a secret key should have an amount assigned to it,
/// but when the key is derived, the amount is not known and must be /// but when the key is derived, the amount is not known and must be
/// given. /// given.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExtendedKey { pub struct ExtendedKey {
/// Depth of the extended key /// Depth of the extended key
@ -79,7 +78,6 @@ pub struct ExtendedKey {
} }
impl ExtendedKey { impl ExtendedKey {
/// Creates a new extended key from a serialized one /// Creates a new extended key from a serialized one
pub fn from_slice(secp: &Secp256k1, slice: &[u8]) -> Result<ExtendedKey, Error> { pub fn from_slice(secp: &Secp256k1, slice: &[u8]) -> Result<ExtendedKey, Error> {
// TODO change when ser. ext. size is fixed // TODO change when ser. ext. size is fixed
@ -134,9 +132,9 @@ impl ExtendedKey {
let mut fingerprint: [u8; 4] = [0; 4]; let mut fingerprint: [u8; 4] = [0; 4];
let identifier = ext_key.identifier(); let identifier = ext_key.identifier();
(&mut fingerprint).clone_from_slice(&identifier[0..4]); (&mut fingerprint).clone_from_slice(&identifier[0..4]);
ext_key.fingerprint = fingerprint; ext_key.fingerprint = fingerprint;
Ok(ext_key) Ok(ext_key)
} }
/// Return the identifier of the key, which is the /// Return the identifier of the key, which is the

View file

@ -29,6 +29,7 @@ extern crate grin_core as core;
extern crate grin_util as util; extern crate grin_util as util;
extern crate secp256k1zkp as secp; extern crate secp256k1zkp as secp;
mod checker;
mod extkey; mod extkey;
mod receiver; mod receiver;
mod sender; mod sender;
@ -36,3 +37,4 @@ mod types;
pub use extkey::ExtendedKey; pub use extkey::ExtendedKey;
pub use receiver::WalletReceiver; pub use receiver::WalletReceiver;
pub use sender::issue_send_tx;

View file

@ -63,54 +63,60 @@ use util;
/// Amount in request to build a coinbase output. /// Amount in request to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbAmount { pub struct CbAmount {
amount: u64, amount: u64,
} }
/// Response to build a coinbase output. /// Response to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbData { pub struct CbData {
output: String, output: String,
kernel: String, kernel: String,
} }
/// Component used to receive coins, implements all the receiving end of the /// Component used to receive coins, implements all the receiving end of the
/// wallet REST API as well as some of the command-line operations. /// wallet REST API as well as some of the command-line operations.
#[derive(Clone)] #[derive(Clone)]
pub struct WalletReceiver { pub struct WalletReceiver {
pub key: ExtendedKey, pub key: ExtendedKey,
} }
impl ApiEndpoint for WalletReceiver { impl ApiEndpoint for WalletReceiver {
type ID = String; type ID = String;
type T = String; type T = String;
type OP_IN = CbAmount; type OP_IN = CbAmount;
type OP_OUT = CbData; type OP_OUT = CbData;
fn operations(&self) -> Vec<Operation> { fn operations(&self) -> Vec<Operation> {
vec![Operation::Custom("receive_coinbase".to_string())] vec![Operation::Custom("receive_coinbase".to_string())]
} }
fn operation(&self, op: String, input: CbAmount) -> ApiResult<CbData> { fn operation(&self, op: String, input: CbAmount) -> ApiResult<CbData> {
debug!("Operation {} with amount {}", op, input.amount); debug!("Operation {} with amount {}", op, input.amount);
if input.amount == 0 { if input.amount == 0 {
return Err(api::Error::Argument(format!("Zero amount not allowed."))) return Err(api::Error::Argument(format!("Zero amount not allowed.")));
} }
match op.as_str() { match op.as_str() {
"receive_coinbase" => { "receive_coinbase" => {
let (out, kern) = receive_coinbase(&self.key, input.amount). let (out, kern) =
map_err(|e| api::Error::Internal(format!("Error building coinbase: {:?}", e)))?; receive_coinbase(&self.key, input.amount).map_err(|e| {
let out_bin = ser::ser_vec(&out). api::Error::Internal(format!("Error building coinbase: {:?}", e))
map_err(|e| api::Error::Internal(format!("Error serializing output: {:?}", e)))?; })?;
let kern_bin = ser::ser_vec(&kern). let out_bin =
map_err(|e| api::Error::Internal(format!("Error serializing kernel: {:?}", e)))?; ser::ser_vec(&out).map_err(|e| {
Ok(CbData { api::Error::Internal(format!("Error serializing output: {:?}", e))
output: util::to_hex(out_bin), })?;
kernel: util::to_hex(kern_bin), let kern_bin =
}) ser::ser_vec(&kern).map_err(|e| {
}, api::Error::Internal(format!("Error serializing kernel: {:?}", e))
_ => Err(api::Error::Argument(format!("Unknown operation: {}", op))), })?;
} Ok(CbData {
} output: util::to_hex(out_bin),
kernel: util::to_hex(kern_bin),
})
}
_ => Err(api::Error::Argument(format!("Unknown operation: {}", op))),
}
}
} }
/// Build a coinbase output and the corresponding kernel /// Build a coinbase output and the corresponding kernel
@ -131,7 +137,8 @@ fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKer
}); });
wallet_data.write()?; wallet_data.write()?;
info!("Using child {} for a new coinbase output.", coinbase_key.n_child); info!("Using child {} for a new coinbase output.",
coinbase_key.n_child);
Block::reward_output(ext_key.key, &secp).map_err(&From::from) Block::reward_output(ext_key.key, &secp).map_err(&From::from)
} }

View file

@ -16,13 +16,15 @@ use std::convert::From;
use secp::{self, Secp256k1}; use secp::{self, Secp256k1};
use secp::key::SecretKey; use secp::key::SecretKey;
use checker;
use core::core::{Transaction, build}; use core::core::{Transaction, build};
use extkey::ExtendedKey; use extkey::ExtendedKey;
use types::*; use types::*;
fn issue_send_tx(hd_seed: &[u8], amount: u64, dest: String) -> Result<(), Error> { pub fn issue_send_tx(ext_key: &ExtendedKey, amount: u64, dest: String) -> Result<(), Error> {
checker::refresh_outputs(&WalletConfig::default(), ext_key);
let (tx, blind_sum) = build_send_tx(hd_seed, amount)?; let (tx, blind_sum) = build_send_tx(ext_key, amount)?;
let json_tx = partial_tx_to_json(amount, blind_sum, tx); let json_tx = partial_tx_to_json(amount, blind_sum, tx);
if dest == "stdout" { if dest == "stdout" {
println!("{}", dest); println!("{}", dest);
@ -36,10 +38,9 @@ fn issue_send_tx(hd_seed: &[u8], amount: u64, dest: String) -> Result<(), Error>
/// Builds a transaction to send to someone from the HD seed associated with the /// Builds a transaction to send to someone from the HD seed associated with the
/// wallet and the amount to send. Handles reading through the wallet data file, /// wallet and the amount to send. Handles reading through the wallet data file,
/// selecting outputs to spend and building the change. /// selecting outputs to spend and building the change.
fn build_send_tx(hd_seed: &[u8], amount: u64) -> Result<(Transaction, SecretKey), Error> { fn build_send_tx(ext_key: &ExtendedKey, amount: u64) -> Result<(Transaction, SecretKey), Error> {
// first, rebuild the private key from the seed // first, rebuild the private key from the seed
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
let ext_key = ExtendedKey::from_seed(&secp, hd_seed).map_err(|e| Error::Key(e))?;
// second, check from our local wallet data for outputs to spend // second, check from our local wallet data for outputs to spend
let mut wallet_data = WalletData::read()?; let mut wallet_data = WalletData::read()?;

View file

@ -51,6 +51,17 @@ impl From<extkey::Error> for Error {
} }
} }
#[derive(Debug, Clone)]
pub struct WalletConfig {
pub api_http_addr: String,
}
impl Default for WalletConfig {
fn default() -> WalletConfig {
WalletConfig { api_http_addr: "http://127.0.0.1:13415".to_string() }
}
}
/// Status of an output that's being tracked by the wallet. Can either be /// Status of an output that's being tracked by the wallet. Can either be
/// unconfirmed, spent, unspent, or locked (when it's been used to generate /// unconfirmed, spent, unspent, or locked (when it's been used to generate
/// a transaction but we don't have confirmation that the transaction was /// a transaction but we don't have confirmation that the transaction was
@ -79,7 +90,7 @@ pub struct OutputData {
} }
impl OutputData { impl OutputData {
/// Lock a given output to avoid conflicting use /// Lock a given output to avoid conflicting use
pub fn lock(&mut self) { pub fn lock(&mut self) {
self.status = OutputStatus::Locked; self.status = OutputStatus::Locked;
} }
@ -92,32 +103,32 @@ impl OutputData {
/// ///
/// TODO optimization so everything isn't O(n) or even O(n^2) /// TODO optimization so everything isn't O(n) or even O(n^2)
/// TODO account for fees /// TODO account for fees
/// TODO write locks so files don't get overwritten
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletData { pub struct WalletData {
outputs: Vec<OutputData>, pub outputs: Vec<OutputData>,
} }
impl WalletData { impl WalletData {
/// Read the wallet data or created a brand new one if it doesn't exist yet
pub fn read_or_create() -> Result<WalletData, Error> {
if Path::new(DAT_FILE).exists() {
WalletData::read()
} else {
// just create a new instance, it will get written afterward
Ok(WalletData { outputs: vec![] })
}
}
/// Read the wallet data or created a brand new one if it doesn't exist yet /// Read the wallet data from disk.
pub fn read_or_create() -> Result<WalletData, Error> {
if Path::new(DAT_FILE).exists() {
WalletData::read()
} else {
// just create a new instance, it will get written afterward
Ok(WalletData { outputs: vec![] })
}
}
/// Read the wallet data from disk.
pub fn read() -> Result<WalletData, Error> { pub fn read() -> Result<WalletData, Error> {
let mut data_file = File::open(DAT_FILE) let data_file = File::open(DAT_FILE)
.map_err(|e| Error::WalletData(format!("Could not open {}: {}", DAT_FILE, e)))?; .map_err(|e| Error::WalletData(format!("Could not open {}: {}", DAT_FILE, e)))?;
serde_json::from_reader(data_file) serde_json::from_reader(data_file)
.map_err(|e| Error::WalletData(format!("Error reading {}: {}", DAT_FILE, e))) .map_err(|e| Error::WalletData(format!("Error reading {}: {}", DAT_FILE, e)))
} }
/// Write the wallet data to disk. /// Write the wallet data to disk.
pub fn write(&self) -> Result<(), Error> { pub fn write(&self) -> Result<(), Error> {
let mut data_file = File::create(DAT_FILE) let mut data_file = File::create(DAT_FILE)
.map_err(|e| Error::WalletData(format!("Could not create {}: {}", DAT_FILE, e)))?; .map_err(|e| Error::WalletData(format!("Could not create {}: {}", DAT_FILE, e)))?;
@ -127,13 +138,14 @@ impl WalletData {
.map_err(|e| Error::WalletData(format!("Error writing {}: {}", DAT_FILE, e))) .map_err(|e| Error::WalletData(format!("Error writing {}: {}", DAT_FILE, e)))
} }
/// Append a new output information to the wallet data. /// Append a new output information to the wallet data.
pub fn append_output(&mut self, out: OutputData) { pub fn append_output(&mut self, out: OutputData) {
self.outputs.push(out); self.outputs.push(out);
} }
/// Select a subset of unspent outputs to spend in a transaction transferring /// Select a subset of unspent outputs to spend in a transaction
/// the provided amount. /// transferring
/// the provided amount.
pub fn select(&self, fingerprint: [u8; 4], amount: u64) -> (Vec<OutputData>, i64) { pub fn select(&self, fingerprint: [u8; 4], amount: u64) -> (Vec<OutputData>, i64) {
let mut to_spend = vec![]; let mut to_spend = vec![];
let mut input_total = 0; let mut input_total = 0;
@ -148,7 +160,7 @@ impl WalletData {
(to_spend, (input_total as i64) - (amount as i64)) (to_spend, (input_total as i64) - (amount as i64))
} }
/// Next child index when we want to create a new output. /// Next child index when we want to create a new output.
pub fn next_child(&self, fingerprint: [u8; 4]) -> u32 { pub fn next_child(&self, fingerprint: [u8; 4]) -> u32 {
let mut max_n = 0; let mut max_n = 0;
for out in &self.outputs { for out in &self.outputs {