mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
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:
parent
aeb3dfc0dd
commit
d26a659a97
5 changed files with 105 additions and 15 deletions
|
@ -96,7 +96,12 @@ 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. 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")
|
.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.")
|
.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")
|
.arg(Arg::with_name("amount")
|
||||||
|
@ -195,7 +200,13 @@ fn wallet_command(wallet_args: &ArgMatches) {
|
||||||
.expect("Error deriving extended key from seed.");
|
.expect("Error deriving extended key from seed.");
|
||||||
|
|
||||||
match wallet_args.subcommand() {
|
match wallet_args.subcommand() {
|
||||||
("receive", _) => {
|
("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...");
|
info!("Starting the Grin wallet receiving daemon...");
|
||||||
let mut apis = api::ApiServer::new("/v1".to_string());
|
let mut apis = api::ApiServer::new("/v1".to_string());
|
||||||
apis.register_endpoint("/receive_coinbase".to_string(),
|
apis.register_endpoint("/receive_coinbase".to_string(),
|
||||||
|
@ -204,6 +215,7 @@ 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)) => {
|
("send", Some(send_args)) => {
|
||||||
let amount = send_args.value_of("amount")
|
let amount = send_args.value_of("amount")
|
||||||
.expect("Amount to send required")
|
.expect("Amount to send required")
|
||||||
|
|
|
@ -36,5 +36,5 @@ mod sender;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use extkey::ExtendedKey;
|
pub use extkey::ExtendedKey;
|
||||||
pub use receiver::WalletReceiver;
|
pub use receiver::{WalletReceiver, receive_json_tx};
|
||||||
pub use sender::issue_send_tx;
|
pub use sender::issue_send_tx;
|
||||||
|
|
|
@ -60,6 +60,17 @@ use extkey::{self, ExtendedKey};
|
||||||
use types::*;
|
use types::*;
|
||||||
use util;
|
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.
|
/// Amount in request to build a coinbase output.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct CbAmount {
|
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.",
|
info!("Using child {} for a new coinbase output.",
|
||||||
coinbase_key.n_child);
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,10 @@ use core::core::{Transaction, build};
|
||||||
use extkey::ExtendedKey;
|
use extkey::ExtendedKey;
|
||||||
use types::*;
|
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> {
|
pub fn issue_send_tx(ext_key: &ExtendedKey, amount: u64, dest: String) -> Result<(), Error> {
|
||||||
checker::refresh_outputs(&WalletConfig::default(), ext_key);
|
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
|
// second, check from our local wallet data for outputs to spend
|
||||||
let mut wallet_data = WalletData::read()?;
|
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 {
|
if change < 0 {
|
||||||
return Err(Error::NotEnoughFunds((-change) as u64));
|
return Err(Error::NotEnoughFunds((-change) as u64));
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,15 @@
|
||||||
// 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.
|
||||||
|
|
||||||
|
use std::convert::From;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::num;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::convert::From;
|
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
use secp::{self, Secp256k1};
|
use secp;
|
||||||
use secp::key::SecretKey;
|
use secp::key::SecretKey;
|
||||||
|
|
||||||
use core::core::Transaction;
|
use core::core::Transaction;
|
||||||
|
@ -37,6 +37,8 @@ pub enum Error {
|
||||||
Crypto(secp::Error),
|
Crypto(secp::Error),
|
||||||
Key(extkey::Error),
|
Key(extkey::Error),
|
||||||
WalletData(String),
|
WalletData(String),
|
||||||
|
/// An error in the format of the JSON structures exchanged by the wallet
|
||||||
|
Format(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<secp::Error> for Error {
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct WalletConfig {
|
pub struct WalletConfig {
|
||||||
pub api_http_addr: String,
|
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()
|
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))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue