mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
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:
parent
da41120293
commit
f79fb8ef95
22 changed files with 181 additions and 101 deletions
|
@ -16,6 +16,7 @@ clap = "^2.23.3"
|
|||
daemonize = "^0.2.3"
|
||||
env_logger="^0.3.5"
|
||||
log = "^0.3"
|
||||
serde = "~0.9.10"
|
||||
serde_json = "~0.9.9"
|
||||
serde = "~1.0.8"
|
||||
serde_derive = "~1.0.8"
|
||||
serde_json = "~1.0.2"
|
||||
tiny-keccak = "1.1"
|
||||
|
|
|
@ -5,11 +5,14 @@ authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
|
|||
workspace = ".."
|
||||
|
||||
[dependencies]
|
||||
grin_core = { path = "../core" }
|
||||
grin_chain = { path = "../chain" }
|
||||
grin_util = { path = "../util" }
|
||||
secp256k1zkp = { path = "../secp256k1zkp" }
|
||||
|
||||
hyper = "~0.10.6"
|
||||
iron = "~0.5.1"
|
||||
log = "~0.3"
|
||||
router = "~0.5.1"
|
||||
serde = "~0.9.10"
|
||||
serde_json = "~0.9.8"
|
||||
serde = "~1.0.8"
|
||||
serde_json = "~1.0.2"
|
||||
|
|
|
@ -26,8 +26,9 @@ use rest::Error;
|
|||
/// returns a JSON object. Handles request building, JSON deserialization and
|
||||
/// response code checking.
|
||||
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 res = check_error(client.get(url).send())?;
|
||||
serde_json::from_reader(res)
|
||||
|
@ -40,7 +41,7 @@ pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
|
|||
/// checking.
|
||||
pub fn post<'a, IN, OUT>(url: &'a str, input: &IN) -> Result<OUT, Error>
|
||||
where IN: Serialize,
|
||||
OUT: Deserialize
|
||||
for<'de> OUT: Deserialize<'de>
|
||||
{
|
||||
let in_json = serde_json::to_string(input)
|
||||
.map_err(|e| Error::Internal(format!("Could not serialize data to JSON: {}", e)))?;
|
||||
|
|
|
@ -24,8 +24,12 @@
|
|||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use core::core::Output;
|
||||
use core::core::hash::Hash;
|
||||
use chain::{self, Tip};
|
||||
use rest::*;
|
||||
use secp::pedersen::Commitment;
|
||||
use util;
|
||||
|
||||
/// ApiEndpoint implementation for the blockchain. Exposes the current chain
|
||||
/// 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
|
||||
/// instance and runs the corresponding HTTP server.
|
||||
pub fn start_rest_apis(addr: String, chain_store: Arc<chain::ChainStore>) {
|
||||
|
||||
thread::spawn(move || {
|
||||
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| {
|
||||
error!("Failed to start API HTTP server: {}.", e);
|
||||
});
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_util as util;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
extern crate hyper;
|
||||
#[macro_use]
|
||||
|
|
|
@ -31,6 +31,7 @@ use iron::modifiers::Header;
|
|||
use iron::middleware::Handler;
|
||||
use router::Router;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json;
|
||||
|
||||
/// 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.
|
||||
pub trait ApiEndpoint: Clone + Send + Sync + 'static {
|
||||
type ID: ToString + FromStr;
|
||||
type T: Serialize + Deserialize;
|
||||
type OP_IN: Serialize + Deserialize;
|
||||
type OP_OUT: Serialize + Deserialize;
|
||||
type T: Serialize + DeserializeOwned;
|
||||
type OP_IN: Serialize + DeserializeOwned;
|
||||
type OP_OUT: Serialize + DeserializeOwned;
|
||||
|
||||
fn operations(&self) -> Vec<Operation>;
|
||||
|
||||
|
@ -251,7 +252,7 @@ impl ApiServer {
|
|||
};
|
||||
let full_path = format!("{}", root.clone());
|
||||
self.router.route(op.to_method(), full_path.clone(), wrapper, route_name);
|
||||
info!("POST {}", full_path);
|
||||
info!("route: POST {}", full_path);
|
||||
} else {
|
||||
|
||||
// regular REST operations
|
||||
|
@ -264,7 +265,7 @@ impl ApiServer {
|
|||
};
|
||||
let wrapper = ApiWrapper(endpoint.clone());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ workspace = ".."
|
|||
bitflags = "^0.7.0"
|
||||
byteorder = "^0.5"
|
||||
log = "^0.3"
|
||||
serde = "~0.9.10"
|
||||
serde_derive = "~0.9.10"
|
||||
serde = "~1.0.8"
|
||||
serde_derive = "~1.0.8"
|
||||
time = "^0.1"
|
||||
tokio-io = "0.1.1"
|
||||
bytes = "0.4.2"
|
||||
|
|
|
@ -30,7 +30,6 @@ const BLOCK_PREFIX: u8 = 'b' as u8;
|
|||
const HEAD_PREFIX: u8 = 'H' as u8;
|
||||
const HEADER_HEAD_PREFIX: u8 = 'I' 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;
|
||||
|
||||
/// 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
|
||||
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(),
|
||||
&to_key(OUTPUT_PREFIX, &mut out.hash().to_vec())[..],
|
||||
out.clone())?
|
||||
.put_enc(&mut BlockCodec::default(),
|
||||
&to_key(OUTPUT_COMMIT_PREFIX, &mut out.commit.as_ref().to_vec())[..],
|
||||
&to_key(OUTPUT_COMMIT_PREFIX, &mut out_bytes)[..],
|
||||
out.hash().clone())?;
|
||||
}
|
||||
batch.write()
|
||||
|
@ -125,9 +123,9 @@ impl ChainStore for ChainKVStore {
|
|||
&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(),
|
||||
&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> {
|
||||
|
|
|
@ -99,8 +99,8 @@ pub trait ChainStore: Send + Sync {
|
|||
/// Gets the block header at the provided height
|
||||
fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error>;
|
||||
|
||||
/// Gets an output by its hash
|
||||
fn get_output(&self, h: &Hash) -> Result<Output, Error>;
|
||||
/// Gets an output by its commitment
|
||||
fn get_output_by_commit(&self, commit: &Commitment) -> Result<Output, Error>;
|
||||
|
||||
/// Checks whether an output commitment exists and returns the output hash
|
||||
fn has_output_commit(&self, commit: &Commitment) -> Result<Hash, Error>;
|
||||
|
|
|
@ -10,8 +10,8 @@ byteorder = "^0.5"
|
|||
num-bigint = "^0.1.35"
|
||||
rust-crypto = "^0.2"
|
||||
rand = "^0.3"
|
||||
serde = "~0.9.10"
|
||||
serde_derive = "~0.9.10"
|
||||
serde = "~1.0.8"
|
||||
serde_derive = "~1.0.8"
|
||||
time = "^0.1"
|
||||
tiny-keccak = "1.1"
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
//! Primary hash function used in the protocol
|
||||
//!
|
||||
|
||||
use std::cmp::min;
|
||||
use std::{fmt, ops};
|
||||
use tiny_keccak::Keccak;
|
||||
use std::convert::AsRef;
|
||||
|
@ -47,6 +48,16 @@ impl fmt::Display for 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
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.0.to_vec()
|
||||
|
|
|
@ -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>
|
||||
where D: Deserializer
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
deserializer.deserialize_i32(DiffVisitor)
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ impl Deserialize for Difficulty {
|
|||
|
||||
struct DiffVisitor;
|
||||
|
||||
impl de::Visitor for DiffVisitor {
|
||||
impl<'de> de::Visitor<'de> for DiffVisitor {
|
||||
type Value = Difficulty;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
|
|
@ -269,6 +269,7 @@ impl Input {
|
|||
|
||||
bitflags! {
|
||||
/// Options for block validation
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub flags OutputFeatures: u8 {
|
||||
/// No flags
|
||||
const DEFAULT_OUTPUT = 0b00000000,
|
||||
|
@ -279,10 +280,9 @@ bitflags! {
|
|||
|
||||
/// 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
|
||||
/// range
|
||||
/// proof guarantees the commitment includes a positive value without overflow
|
||||
/// and the ownership of the private key.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
/// range proof guarantees the commitment includes a positive value without
|
||||
/// overflow and the ownership of the private key.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
/// Options for an output's structure or use
|
||||
pub features: OutputFeatures,
|
||||
|
|
|
@ -19,8 +19,8 @@ futures-cpupool = "^0.1.3"
|
|||
hyper = { git = "https://github.com/hyperium/hyper" }
|
||||
log = "^0.3"
|
||||
time = "^0.1"
|
||||
serde = "~0.9.10"
|
||||
serde_derive = "~0.9.10"
|
||||
serde = "~1.0.8"
|
||||
serde_derive = "~1.0.8"
|
||||
tokio-core="^0.1.1"
|
||||
tokio-timer="^0.1.0"
|
||||
rand = "^0.3"
|
||||
|
|
|
@ -13,8 +13,8 @@ futures = "^0.1.9"
|
|||
log = "^0.3"
|
||||
net2 = "0.2.0"
|
||||
rand = "^0.3"
|
||||
serde = "~0.9.10"
|
||||
serde_derive = "~0.9.10"
|
||||
serde = "~1.0.8"
|
||||
serde_derive = "~1.0.8"
|
||||
tokio-core="^0.1.1"
|
||||
tokio-timer="^0.1.0"
|
||||
|
||||
|
|
|
@ -96,14 +96,24 @@ 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"))
|
||||
.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();
|
||||
|
||||
match args.subcommand() {
|
||||
// server commands and options
|
||||
("server", Some(server_args)) => {
|
||||
server_command(server_args);
|
||||
},
|
||||
}
|
||||
|
||||
// client commands and options
|
||||
("client", Some(client_args)) => {
|
||||
|
@ -117,12 +127,7 @@ fn main() {
|
|||
|
||||
// client commands and options
|
||||
("wallet", Some(wallet_args)) => {
|
||||
match wallet_args.subcommand() {
|
||||
("receive", _) => {
|
||||
wallet_command(wallet_args);
|
||||
},
|
||||
_ => panic!("Unknown client command, use 'grin help client' for details"),
|
||||
}
|
||||
wallet_command(wallet_args);
|
||||
}
|
||||
|
||||
_ => 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);
|
||||
});
|
||||
}
|
||||
("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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ byteorder = "1"
|
|||
log = "^0.3"
|
||||
rand = "^0.3"
|
||||
rust-crypto = "^0.2"
|
||||
serde = "~0.9.10"
|
||||
serde_derive = "~0.9.10"
|
||||
serde_json = "~0.9.8"
|
||||
serde = "~1.0.8"
|
||||
serde_derive = "~1.0.8"
|
||||
serde_json = "~1.0.2"
|
||||
|
||||
grin_api = { path = "../api" }
|
||||
grin_core = { path = "../core" }
|
||||
|
|
|
@ -63,7 +63,6 @@ impl error::Error for Error {
|
|||
/// 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
|
||||
/// given.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtendedKey {
|
||||
/// Depth of the extended key
|
||||
|
@ -79,7 +78,6 @@ pub struct ExtendedKey {
|
|||
}
|
||||
|
||||
impl ExtendedKey {
|
||||
|
||||
/// Creates a new extended key from a serialized one
|
||||
pub fn from_slice(secp: &Secp256k1, slice: &[u8]) -> Result<ExtendedKey, Error> {
|
||||
// TODO change when ser. ext. size is fixed
|
||||
|
@ -134,9 +132,9 @@ impl ExtendedKey {
|
|||
let mut fingerprint: [u8; 4] = [0; 4];
|
||||
let identifier = ext_key.identifier();
|
||||
(&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
|
||||
|
|
|
@ -29,6 +29,7 @@ extern crate grin_core as core;
|
|||
extern crate grin_util as util;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
mod checker;
|
||||
mod extkey;
|
||||
mod receiver;
|
||||
mod sender;
|
||||
|
@ -36,3 +37,4 @@ mod types;
|
|||
|
||||
pub use extkey::ExtendedKey;
|
||||
pub use receiver::WalletReceiver;
|
||||
pub use sender::issue_send_tx;
|
||||
|
|
|
@ -63,54 +63,60 @@ use util;
|
|||
/// Amount in request to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbAmount {
|
||||
amount: u64,
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
output: String,
|
||||
kernel: String,
|
||||
output: String,
|
||||
kernel: String,
|
||||
}
|
||||
|
||||
/// Component used to receive coins, implements all the receiving end of the
|
||||
/// wallet REST API as well as some of the command-line operations.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletReceiver {
|
||||
pub key: ExtendedKey,
|
||||
pub key: ExtendedKey,
|
||||
}
|
||||
|
||||
impl ApiEndpoint for WalletReceiver {
|
||||
type ID = String;
|
||||
type T = String;
|
||||
type OP_IN = CbAmount;
|
||||
type OP_OUT = CbData;
|
||||
type OP_IN = CbAmount;
|
||||
type OP_OUT = CbData;
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Custom("receive_coinbase".to_string())]
|
||||
}
|
||||
|
||||
fn operation(&self, op: String, input: CbAmount) -> ApiResult<CbData> {
|
||||
debug!("Operation {} with amount {}", op, input.amount);
|
||||
if input.amount == 0 {
|
||||
return Err(api::Error::Argument(format!("Zero amount not allowed.")))
|
||||
}
|
||||
match op.as_str() {
|
||||
"receive_coinbase" => {
|
||||
let (out, kern) = receive_coinbase(&self.key, input.amount).
|
||||
map_err(|e| api::Error::Internal(format!("Error building coinbase: {:?}", e)))?;
|
||||
let out_bin = ser::ser_vec(&out).
|
||||
map_err(|e| api::Error::Internal(format!("Error serializing output: {:?}", e)))?;
|
||||
let kern_bin = ser::ser_vec(&kern).
|
||||
map_err(|e| api::Error::Internal(format!("Error serializing kernel: {:?}", e)))?;
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
})
|
||||
},
|
||||
_ => Err(api::Error::Argument(format!("Unknown operation: {}", op))),
|
||||
}
|
||||
}
|
||||
debug!("Operation {} with amount {}", op, input.amount);
|
||||
if input.amount == 0 {
|
||||
return Err(api::Error::Argument(format!("Zero amount not allowed.")));
|
||||
}
|
||||
match op.as_str() {
|
||||
"receive_coinbase" => {
|
||||
let (out, kern) =
|
||||
receive_coinbase(&self.key, input.amount).map_err(|e| {
|
||||
api::Error::Internal(format!("Error building coinbase: {:?}", e))
|
||||
})?;
|
||||
let out_bin =
|
||||
ser::ser_vec(&out).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing output: {:?}", e))
|
||||
})?;
|
||||
let kern_bin =
|
||||
ser::ser_vec(&kern).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
|
||||
})?;
|
||||
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
|
||||
|
@ -131,7 +137,8 @@ fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKer
|
|||
});
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -16,13 +16,15 @@ use std::convert::From;
|
|||
use secp::{self, Secp256k1};
|
||||
use secp::key::SecretKey;
|
||||
|
||||
use checker;
|
||||
use core::core::{Transaction, build};
|
||||
use extkey::ExtendedKey;
|
||||
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);
|
||||
if dest == "stdout" {
|
||||
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
|
||||
/// wallet and the amount to send. Handles reading through the wallet data file,
|
||||
/// 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
|
||||
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
|
||||
let mut wallet_data = WalletData::read()?;
|
||||
|
|
|
@ -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
|
||||
/// unconfirmed, spent, unspent, or locked (when it's been used to generate
|
||||
/// a transaction but we don't have confirmation that the transaction was
|
||||
|
@ -79,7 +90,7 @@ pub struct OutputData {
|
|||
}
|
||||
|
||||
impl OutputData {
|
||||
/// Lock a given output to avoid conflicting use
|
||||
/// Lock a given output to avoid conflicting use
|
||||
pub fn lock(&mut self) {
|
||||
self.status = OutputStatus::Locked;
|
||||
}
|
||||
|
@ -92,32 +103,32 @@ impl OutputData {
|
|||
///
|
||||
/// TODO optimization so everything isn't O(n) or even O(n^2)
|
||||
/// TODO account for fees
|
||||
/// TODO write locks so files don't get overwritten
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WalletData {
|
||||
outputs: Vec<OutputData>,
|
||||
pub outputs: Vec<OutputData>,
|
||||
}
|
||||
|
||||
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
|
||||
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.
|
||||
/// Read the wallet data from disk.
|
||||
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)))?;
|
||||
serde_json::from_reader(data_file)
|
||||
.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> {
|
||||
let mut data_file = File::create(DAT_FILE)
|
||||
.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)))
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.outputs.push(out);
|
||||
}
|
||||
|
||||
/// Select a subset of unspent outputs to spend in a transaction transferring
|
||||
/// the provided amount.
|
||||
/// Select a subset of unspent outputs to spend in a transaction
|
||||
/// transferring
|
||||
/// the provided amount.
|
||||
pub fn select(&self, fingerprint: [u8; 4], amount: u64) -> (Vec<OutputData>, i64) {
|
||||
let mut to_spend = vec![];
|
||||
let mut input_total = 0;
|
||||
|
@ -148,7 +160,7 @@ impl WalletData {
|
|||
(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 {
|
||||
let mut max_n = 0;
|
||||
for out in &self.outputs {
|
||||
|
|
Loading…
Reference in a new issue