mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21: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"
|
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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)))?;
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue