mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11: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")
|
||||
.takes_value(true))
|
||||
.subcommand(SubCommand::with_name("receive")
|
||||
.about("Run the wallet in receiving mode"))
|
||||
.about("Run the wallet in receiving mode. If an input file is provided, will process it, otherwise runs in server mode waiting for send requests.")
|
||||
.arg(Arg::with_name("input")
|
||||
.help("Partial transaction to receive, expects as a JSON file.")
|
||||
.short("i")
|
||||
.long("input")
|
||||
.takes_value(true)))
|
||||
.subcommand(SubCommand::with_name("send")
|
||||
.about("Builds a transaction to send someone some coins. By default, the transaction will just be printed to stdout. If a destination is provided, the command will attempt to contact the receiver at that address and send the transaction directly.")
|
||||
.arg(Arg::with_name("amount")
|
||||
|
@ -195,14 +200,21 @@ fn wallet_command(wallet_args: &ArgMatches) {
|
|||
.expect("Error deriving extended key from seed.");
|
||||
|
||||
match wallet_args.subcommand() {
|
||||
("receive", _) => {
|
||||
info!("Starting the Grin wallet receiving daemon...");
|
||||
let mut apis = api::ApiServer::new("/v1".to_string());
|
||||
apis.register_endpoint("/receive_coinbase".to_string(),
|
||||
wallet::WalletReceiver { key: key });
|
||||
apis.start("127.0.0.1:13416").unwrap_or_else(|e| {
|
||||
error!("Failed to start Grin wallet receiver: {}.", e);
|
||||
});
|
||||
("receive", Some(receive_args)) => {
|
||||
if let Some(f) = receive_args.value_of("input") {
|
||||
let mut file = File::open(f).expect("Unable to open transaction file.");
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).expect("Unable to read transaction file.");
|
||||
wallet::receive_json_tx(&key, contents.as_str()).unwrap();
|
||||
} else {
|
||||
info!("Starting the Grin wallet receiving daemon...");
|
||||
let mut apis = api::ApiServer::new("/v1".to_string());
|
||||
apis.register_endpoint("/receive_coinbase".to_string(),
|
||||
wallet::WalletReceiver { key: key });
|
||||
apis.start("127.0.0.1:13416").unwrap_or_else(|e| {
|
||||
error!("Failed to start Grin wallet receiver: {}.", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
("send", Some(send_args)) => {
|
||||
let amount = send_args.value_of("amount")
|
||||
|
|
|
@ -36,5 +36,5 @@ mod sender;
|
|||
mod types;
|
||||
|
||||
pub use extkey::ExtendedKey;
|
||||
pub use receiver::WalletReceiver;
|
||||
pub use receiver::{WalletReceiver, receive_json_tx};
|
||||
pub use sender::issue_send_tx;
|
||||
|
|
|
@ -60,6 +60,17 @@ use extkey::{self, ExtendedKey};
|
|||
use types::*;
|
||||
use util;
|
||||
|
||||
/// Receive an already well formed JSON transaction issuance and finalize the
|
||||
/// transaction, adding our receiving output, to broadcast to the rest of the
|
||||
/// network.
|
||||
pub fn receive_json_tx(ext_key: &ExtendedKey, partial_tx_str: &str) -> Result<(), Error> {
|
||||
let (amount, blinding, partial_tx) = partial_tx_from_json(partial_tx_str)?;
|
||||
let final_tx = receive_transaction(ext_key, amount, blinding, partial_tx)?;
|
||||
// TODO send to a node to broadcast
|
||||
println!("TX OK!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Amount in request to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbAmount {
|
||||
|
@ -140,5 +151,37 @@ fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKer
|
|||
info!("Using child {} for a new coinbase output.",
|
||||
coinbase_key.n_child);
|
||||
|
||||
Block::reward_output(coinbase_key.key, &secp).map_err(&From::from);
|
||||
Block::reward_output(coinbase_key.key, &secp).map_err(&From::from)
|
||||
}
|
||||
|
||||
/// Builds a full transaction from the partial one sent to us for transfer
|
||||
fn receive_transaction(ext_key: &ExtendedKey,
|
||||
amount: u64,
|
||||
blinding: SecretKey,
|
||||
partial: Transaction)
|
||||
-> Result<Transaction, Error> {
|
||||
|
||||
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
|
||||
// derive a new private for the receiving output
|
||||
let mut wallet_data = WalletData::read_or_create()?;
|
||||
let next_child = wallet_data.next_child(ext_key.fingerprint);
|
||||
let out_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?;
|
||||
|
||||
let (tx_final, _) = build::transaction(vec![build::initial_tx(partial),
|
||||
build::with_excess(blinding),
|
||||
build::output(amount, out_key.key)])?;
|
||||
|
||||
// track the new output and return the finalized transaction to broadcast
|
||||
wallet_data.append_output(OutputData {
|
||||
fingerprint: out_key.fingerprint,
|
||||
n_child: out_key.n_child,
|
||||
value: amount,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
});
|
||||
wallet_data.write()?;
|
||||
|
||||
info!("Using child {} for a new coinbase output.", out_key.n_child);
|
||||
|
||||
Ok(tx_final)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ use core::core::{Transaction, build};
|
|||
use extkey::ExtendedKey;
|
||||
use types::*;
|
||||
|
||||
/// Issue a new transaction to the provided sender by spending some of our
|
||||
/// wallet
|
||||
/// UTXOs. The destination can be "stdout" (for command line) or a URL to the
|
||||
/// recipients wallet receiver (to be implemented).
|
||||
pub fn issue_send_tx(ext_key: &ExtendedKey, amount: u64, dest: String) -> Result<(), Error> {
|
||||
checker::refresh_outputs(&WalletConfig::default(), ext_key);
|
||||
|
||||
|
@ -44,7 +48,7 @@ fn build_send_tx(ext_key: &ExtendedKey, amount: u64) -> Result<(Transaction, Sec
|
|||
|
||||
// second, check from our local wallet data for outputs to spend
|
||||
let mut wallet_data = WalletData::read()?;
|
||||
let (mut coins, change) = wallet_data.select(ext_key.fingerprint, amount);
|
||||
let (coins, change) = wallet_data.select(ext_key.fingerprint, amount);
|
||||
if change < 0 {
|
||||
return Err(Error::NotEnoughFunds((-change) as u64));
|
||||
}
|
||||
|
|
|
@ -12,15 +12,15 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::convert::From;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::num;
|
||||
use std::path::Path;
|
||||
use std::convert::From;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde_json;
|
||||
|
||||
use secp::{self, Secp256k1};
|
||||
use secp;
|
||||
use secp::key::SecretKey;
|
||||
|
||||
use core::core::Transaction;
|
||||
|
@ -37,6 +37,8 @@ pub enum Error {
|
|||
Crypto(secp::Error),
|
||||
Key(extkey::Error),
|
||||
WalletData(String),
|
||||
/// An error in the format of the JSON structures exchanged by the wallet
|
||||
Format(String),
|
||||
}
|
||||
|
||||
impl From<secp::Error> for Error {
|
||||
|
@ -51,6 +53,18 @@ impl From<extkey::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(e: serde_json::Error) -> Error {
|
||||
Error::Format(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<num::ParseIntError> for Error {
|
||||
fn from(e: num::ParseIntError) -> Error {
|
||||
Error::Format("Invalid hex".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WalletConfig {
|
||||
pub api_http_addr: String,
|
||||
|
@ -191,3 +205,20 @@ pub fn partial_tx_to_json(receive_amount: u64, blind_sum: SecretKey, tx: Transac
|
|||
};
|
||||
serde_json::to_string_pretty(&partial_tx).unwrap()
|
||||
}
|
||||
|
||||
/// Reads a partial transaction encoded as JSON into the amount, sum of blinding
|
||||
/// factors and the transaction itself.
|
||||
pub fn partial_tx_from_json(json_str: &str) -> Result<(u64, SecretKey, Transaction), Error> {
|
||||
let partial_tx: JSONPartialTx = serde_json::from_str(json_str)?;
|
||||
|
||||
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
let blind_bin = util::from_hex(partial_tx.blind_sum)?;
|
||||
let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?;
|
||||
let tx_bin = util::from_hex(partial_tx.tx)?;
|
||||
let tx =
|
||||
ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
|
||||
Error::Format("Could not deserialize transaction, invalid format.".to_string())
|
||||
})?;
|
||||
|
||||
Ok((partial_tx.amount, blinding, tx))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue