mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Add file based transaction in owner API (#1484)
* Add file based transaction in owner API * Add finalize tx in owner API * Code cleanup and placed file_receive in correct API * Output an explicit error when http dest seems incorrect * Add doc on send method * Add cancel tx endpoint in owner API * Add dump stored tx endpoint in owner API * Add missing parameters
This commit is contained in:
parent
4030b3d2f1
commit
dd1cef2148
7 changed files with 270 additions and 118 deletions
|
@ -144,6 +144,12 @@ Outputs in your wallet will appear as unconfirmed or locked until the transactio
|
||||||
|
|
||||||
Other flags here are:
|
Other flags here are:
|
||||||
|
|
||||||
|
* `-m` 'Method', which can be 'http' or 'file'. In the first case, the transaction will be sent to the IP address which follows the `-d` flag. In the second case, Grin wallet will generate a partial transaction file under the file name specified in the `-d` flag. This file needs to be signed by the recipient using the `grin wallet receive -i filename` command and finalize by the sender using the `grin wallet finalize -i filename.response` command. To create a partial transaction file, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
[host]$ grin wallet send -d "transaction" -m file 60.00
|
||||||
|
```
|
||||||
|
|
||||||
* `-s` 'Selection strategy', which can be 'all' or 'smallest'. Since it's advantageous for outputs to be removed from the Grin chain,
|
* `-s` 'Selection strategy', which can be 'all' or 'smallest'. Since it's advantageous for outputs to be removed from the Grin chain,
|
||||||
the default strategy for selecting inputs in Step 1 above is to use as many outputs as possible to consolidate your balance into a
|
the default strategy for selecting inputs in Step 1 above is to use as many outputs as possible to consolidate your balance into a
|
||||||
couple of outputs. This also drastically reduces your wallet size, so everyone wins. The downside is that the entire contents of
|
couple of outputs. This also drastically reduces your wallet size, so everyone wins. The downside is that the entire contents of
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
// 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 serde_json as json;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
/// Wallet commands processing
|
/// Wallet commands processing
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
@ -166,7 +169,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
||||||
wallet_config.clone(),
|
wallet_config.clone(),
|
||||||
passphrase,
|
passphrase,
|
||||||
)));
|
)));
|
||||||
let res = controller::owner_single_use(wallet, |api| {
|
let res = controller::owner_single_use(wallet.clone(), |api| {
|
||||||
match wallet_args.subcommand() {
|
match wallet_args.subcommand() {
|
||||||
("send", Some(send_args)) => {
|
("send", Some(send_args)) => {
|
||||||
let amount = send_args
|
let amount = send_args
|
||||||
|
@ -182,6 +185,9 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
||||||
let selection_strategy = send_args
|
let selection_strategy = send_args
|
||||||
.value_of("selection_strategy")
|
.value_of("selection_strategy")
|
||||||
.expect("Selection strategy required");
|
.expect("Selection strategy required");
|
||||||
|
let method = send_args
|
||||||
|
.value_of("method")
|
||||||
|
.expect("Payment method required");
|
||||||
let dest = send_args
|
let dest = send_args
|
||||||
.value_of("dest")
|
.value_of("dest")
|
||||||
.expect("Destination wallet address required");
|
.expect("Destination wallet address required");
|
||||||
|
@ -192,54 +198,63 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
||||||
.expect("Failed to parse number of change outputs.");
|
.expect("Failed to parse number of change outputs.");
|
||||||
let fluff = send_args.is_present("fluff");
|
let fluff = send_args.is_present("fluff");
|
||||||
let max_outputs = 500;
|
let max_outputs = 500;
|
||||||
if dest.starts_with("http") {
|
if method == "http" {
|
||||||
let result = api.issue_send_tx(
|
if dest.starts_with("http://") {
|
||||||
amount,
|
let result = api.issue_send_tx(
|
||||||
minimum_confirmations,
|
amount,
|
||||||
dest,
|
minimum_confirmations,
|
||||||
max_outputs,
|
dest,
|
||||||
change_outputs,
|
max_outputs,
|
||||||
selection_strategy == "all",
|
change_outputs,
|
||||||
);
|
selection_strategy == "all",
|
||||||
let slate = match result {
|
);
|
||||||
Ok(s) => {
|
let slate = match result {
|
||||||
info!(
|
Ok(s) => {
|
||||||
LOGGER,
|
info!(
|
||||||
"Tx created: {} grin to {} (strategy '{}')",
|
LOGGER,
|
||||||
core::amount_to_hr_string(amount, false),
|
"Tx created: {} grin to {} (strategy '{}')",
|
||||||
dest,
|
core::amount_to_hr_string(amount, false),
|
||||||
selection_strategy,
|
dest,
|
||||||
);
|
selection_strategy,
|
||||||
s
|
);
|
||||||
}
|
s
|
||||||
Err(e) => {
|
}
|
||||||
error!(LOGGER, "Tx not created: {:?}", e);
|
Err(e) => {
|
||||||
match e.kind() {
|
error!(LOGGER, "Tx not created: {:?}", e);
|
||||||
// user errors, don't backtrace
|
match e.kind() {
|
||||||
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
// user errors, don't backtrace
|
||||||
libwallet::ErrorKind::FeeDispute { .. } => {}
|
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
||||||
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
|
libwallet::ErrorKind::FeeDispute { .. } => {}
|
||||||
_ => {
|
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
|
||||||
// otherwise give full dump
|
_ => {
|
||||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
// otherwise give full dump
|
||||||
}
|
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||||
};
|
}
|
||||||
panic!();
|
};
|
||||||
}
|
panic!();
|
||||||
};
|
}
|
||||||
let result = api.post_tx(&slate, fluff);
|
};
|
||||||
match result {
|
let result = api.post_tx(&slate, fluff);
|
||||||
Ok(_) => {
|
match result {
|
||||||
info!(LOGGER, "Tx sent",);
|
Ok(_) => {
|
||||||
Ok(())
|
info!(LOGGER, "Tx sent",);
|
||||||
}
|
Ok(())
|
||||||
Err(e) => {
|
}
|
||||||
error!(LOGGER, "Tx not sent: {:?}", e);
|
Err(e) => {
|
||||||
Err(e)
|
error!(LOGGER, "Tx not sent: {:?}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"HTTP Destination should start with http://: {}", dest
|
||||||
|
);
|
||||||
|
panic!();
|
||||||
}
|
}
|
||||||
} else {
|
} else if method == "file" {
|
||||||
api.file_send_tx(
|
api.send_tx(
|
||||||
|
true,
|
||||||
amount,
|
amount,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
dest,
|
dest,
|
||||||
|
@ -248,21 +263,36 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
||||||
selection_strategy == "all",
|
selection_strategy == "all",
|
||||||
).expect("Send failed");
|
).expect("Send failed");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
error!(LOGGER, "unsupported payment method: {}", method);
|
||||||
|
panic!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
("receive", Some(send_args)) => {
|
("receive", Some(send_args)) => {
|
||||||
let tx_file = send_args
|
let mut receive_result: Result<(), grin_wallet::libwallet::Error> = Ok(());
|
||||||
.value_of("input")
|
let res = controller::foreign_single_use(wallet, |api| {
|
||||||
.expect("Transaction file required");
|
let tx_file = send_args
|
||||||
api.file_receive_tx(tx_file).expect("Receive failed");
|
.value_of("input")
|
||||||
Ok(())
|
.expect("Transaction file required");
|
||||||
|
receive_result = api.file_receive_tx(tx_file);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if res.is_err() {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
receive_result
|
||||||
}
|
}
|
||||||
("finalize", Some(send_args)) => {
|
("finalize", Some(send_args)) => {
|
||||||
let fluff = send_args.is_present("fluff");
|
let fluff = send_args.is_present("fluff");
|
||||||
let tx_file = send_args
|
let tx_file = send_args
|
||||||
.value_of("input")
|
.value_of("input")
|
||||||
.expect("Receiver's transaction file required");
|
.expect("Receiver's transaction file required");
|
||||||
let slate = api.file_finalize_tx(tx_file).expect("Finalize failed");
|
let mut pub_tx_f = File::open(tx_file)?;
|
||||||
|
let mut content = String::new();
|
||||||
|
pub_tx_f.read_to_string(&mut content)?;
|
||||||
|
let mut slate: grin_wallet::libtx::slate::Slate = json::from_str(&content)
|
||||||
|
.map_err(|_| grin_wallet::libwallet::ErrorKind::Format)?;
|
||||||
|
let _ = api.finalize_tx(&mut slate).expect("Finalize failed");
|
||||||
|
|
||||||
let result = api.post_tx(&slate, fluff);
|
let result = api.post_tx(&slate, fluff);
|
||||||
match result {
|
match result {
|
||||||
|
@ -377,7 +407,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
let result = api.dump_stored_tx(tx_id, f);
|
let result = api.dump_stored_tx(tx_id, true, f);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
warn!(LOGGER, "Dumped transaction data for tx {} to {}", tx_id, f);
|
warn!(LOGGER, "Dumped transaction data for tx {} to {}", tx_id, f);
|
||||||
|
|
|
@ -213,6 +213,13 @@ fn main() {
|
||||||
.long("change_outputs")
|
.long("change_outputs")
|
||||||
.default_value("1")
|
.default_value("1")
|
||||||
.takes_value(true))
|
.takes_value(true))
|
||||||
|
.arg(Arg::with_name("method")
|
||||||
|
.help("Method for sending this transaction.")
|
||||||
|
.short("m")
|
||||||
|
.long("method")
|
||||||
|
.possible_values(&["http", "file"])
|
||||||
|
.default_value("http")
|
||||||
|
.takes_value(true))
|
||||||
.arg(Arg::with_name("dest")
|
.arg(Arg::with_name("dest")
|
||||||
.help("Send the transaction to the provided server (start with http://) or save as file.")
|
.help("Send the transaction to the provided server (start with http://) or save as file.")
|
||||||
.short("d")
|
.short("d")
|
||||||
|
|
|
@ -185,15 +185,16 @@ where
|
||||||
|
|
||||||
/// Write a transaction to send to file so a user can transmit it to the
|
/// Write a transaction to send to file so a user can transmit it to the
|
||||||
/// receiver in whichever way they see fit (aka carrier pigeon mode).
|
/// receiver in whichever way they see fit (aka carrier pigeon mode).
|
||||||
pub fn file_send_tx(
|
pub fn send_tx(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
write_to_disk: bool,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
minimum_confirmations: u64,
|
minimum_confirmations: u64,
|
||||||
dest: &str,
|
dest: &str,
|
||||||
max_outputs: usize,
|
max_outputs: usize,
|
||||||
num_change_outputs: usize,
|
num_change_outputs: usize,
|
||||||
selection_strategy_is_use_all: bool,
|
selection_strategy_is_use_all: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<Slate, Error> {
|
||||||
let mut w = self.wallet.lock().unwrap();
|
let mut w = self.wallet.lock().unwrap();
|
||||||
w.open_with_credentials()?;
|
w.open_with_credentials()?;
|
||||||
|
|
||||||
|
@ -205,10 +206,11 @@ where
|
||||||
num_change_outputs,
|
num_change_outputs,
|
||||||
selection_strategy_is_use_all,
|
selection_strategy_is_use_all,
|
||||||
)?;
|
)?;
|
||||||
|
if write_to_disk {
|
||||||
let mut pub_tx = File::create(dest)?;
|
let mut pub_tx = File::create(dest)?;
|
||||||
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
||||||
pub_tx.sync_all()?;
|
pub_tx.sync_all()?;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut batch = w.batch()?;
|
let mut batch = w.batch()?;
|
||||||
|
@ -221,60 +223,19 @@ where
|
||||||
// lock our inputs
|
// lock our inputs
|
||||||
lock_fn(&mut **w, &tx_hex)?;
|
lock_fn(&mut **w, &tx_hex)?;
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(())
|
Ok(slate)
|
||||||
}
|
|
||||||
|
|
||||||
/// A sender provided a transaction file with appropriate public keys and
|
|
||||||
/// metadata. Complete the receivers' end of it to generate another file
|
|
||||||
/// to send back.
|
|
||||||
pub fn file_receive_tx(&mut self, source: &str) -> Result<(), Error> {
|
|
||||||
let mut pub_tx_f = File::open(source)?;
|
|
||||||
let mut content = String::new();
|
|
||||||
pub_tx_f.read_to_string(&mut content)?;
|
|
||||||
let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?;
|
|
||||||
|
|
||||||
let mut wallet = self.wallet.lock().unwrap();
|
|
||||||
wallet.open_with_credentials()?;
|
|
||||||
|
|
||||||
// create an output using the amount in the slate
|
|
||||||
let (_, mut context, receiver_create_fn) =
|
|
||||||
selection::build_recipient_output_with_slate(&mut **wallet, &mut slate)?;
|
|
||||||
|
|
||||||
// fill public keys
|
|
||||||
let _ = slate.fill_round_1(
|
|
||||||
wallet.keychain(),
|
|
||||||
&mut context.sec_key,
|
|
||||||
&context.sec_nonce,
|
|
||||||
1,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// perform partial sig
|
|
||||||
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?;
|
|
||||||
|
|
||||||
// save to file
|
|
||||||
let mut pub_tx = File::create(source.to_owned() + ".response")?;
|
|
||||||
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
|
||||||
|
|
||||||
// Save output in wallet
|
|
||||||
let _ = receiver_create_fn(&mut wallet);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sender finalization of the transaction. Takes the file returned by the
|
/// Sender finalization of the transaction. Takes the file returned by the
|
||||||
/// sender as well as the private file generate on the first send step.
|
/// sender as well as the private file generate on the first send step.
|
||||||
/// Builds the complete transaction and sends it to a grin node for
|
/// Builds the complete transaction and sends it to a grin node for
|
||||||
/// propagation.
|
/// propagation.
|
||||||
pub fn file_finalize_tx(&mut self, receiver_file: &str) -> Result<Slate, Error> {
|
pub fn finalize_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||||
let mut pub_tx_f = File::open(receiver_file)?;
|
|
||||||
let mut content = String::new();
|
|
||||||
pub_tx_f.read_to_string(&mut content)?;
|
|
||||||
let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?;
|
|
||||||
|
|
||||||
let mut w = self.wallet.lock().unwrap();
|
let mut w = self.wallet.lock().unwrap();
|
||||||
w.open_with_credentials()?;
|
w.open_with_credentials()?;
|
||||||
|
|
||||||
let context = w.get_private_context(slate.id.as_bytes())?;
|
let context = w.get_private_context(slate.id.as_bytes())?;
|
||||||
tx::complete_tx(&mut **w, &mut slate, &context)?;
|
tx::complete_tx(&mut **w, slate, &context)?;
|
||||||
{
|
{
|
||||||
let mut batch = w.batch()?;
|
let mut batch = w.batch()?;
|
||||||
batch.delete_private_context(slate.id.as_bytes())?;
|
batch.delete_private_context(slate.id.as_bytes())?;
|
||||||
|
@ -282,7 +243,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(slate)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Roll back a transaction and all associated outputs with a given
|
/// Roll back a transaction and all associated outputs with a given
|
||||||
|
@ -342,7 +303,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes stored transaction data to a given file
|
/// Writes stored transaction data to a given file
|
||||||
pub fn dump_stored_tx(&self, tx_id: u32, dest: &str) -> Result<(), Error> {
|
pub fn dump_stored_tx(
|
||||||
|
&self,
|
||||||
|
tx_id: u32,
|
||||||
|
write_to_disk: bool,
|
||||||
|
dest: &str,
|
||||||
|
) -> Result<Transaction, Error> {
|
||||||
let (confirmed, tx_hex) = {
|
let (confirmed, tx_hex) = {
|
||||||
let mut w = self.wallet.lock().unwrap();
|
let mut w = self.wallet.lock().unwrap();
|
||||||
w.open_with_credentials()?;
|
w.open_with_credentials()?;
|
||||||
|
@ -365,10 +331,12 @@ where
|
||||||
}
|
}
|
||||||
let tx_bin = util::from_hex(tx_hex.unwrap()).unwrap();
|
let tx_bin = util::from_hex(tx_hex.unwrap()).unwrap();
|
||||||
let tx = ser::deserialize::<Transaction>(&mut &tx_bin[..])?;
|
let tx = ser::deserialize::<Transaction>(&mut &tx_bin[..])?;
|
||||||
let mut tx_file = File::create(dest)?;
|
if write_to_disk {
|
||||||
tx_file.write_all(json::to_string(&tx).unwrap().as_bytes())?;
|
let mut tx_file = File::create(dest)?;
|
||||||
tx_file.sync_all()?;
|
tx_file.write_all(json::to_string(&tx).unwrap().as_bytes())?;
|
||||||
Ok(())
|
tx_file.sync_all()?;
|
||||||
|
}
|
||||||
|
Ok(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// (Re)Posts a transaction that's already been stored to the chain
|
/// (Re)Posts a transaction that's already been stored to the chain
|
||||||
|
@ -498,6 +466,42 @@ where
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A sender provided a transaction file with appropriate public keys and
|
||||||
|
/// metadata. Complete the receivers' end of it to generate another file
|
||||||
|
/// to send back.
|
||||||
|
pub fn file_receive_tx(&mut self, source: &str) -> Result<(), Error> {
|
||||||
|
let mut pub_tx_f = File::open(source)?;
|
||||||
|
let mut content = String::new();
|
||||||
|
pub_tx_f.read_to_string(&mut content)?;
|
||||||
|
let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?;
|
||||||
|
|
||||||
|
let mut wallet = self.wallet.lock().unwrap();
|
||||||
|
wallet.open_with_credentials()?;
|
||||||
|
|
||||||
|
// create an output using the amount in the slate
|
||||||
|
let (_, mut context, receiver_create_fn) =
|
||||||
|
selection::build_recipient_output_with_slate(&mut **wallet, &mut slate)?;
|
||||||
|
|
||||||
|
// fill public keys
|
||||||
|
let _ = slate.fill_round_1(
|
||||||
|
wallet.keychain(),
|
||||||
|
&mut context.sec_key,
|
||||||
|
&context.sec_nonce,
|
||||||
|
1,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// perform partial sig
|
||||||
|
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?;
|
||||||
|
|
||||||
|
// save to file
|
||||||
|
let mut pub_tx = File::create(source.to_owned() + ".response")?;
|
||||||
|
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
||||||
|
|
||||||
|
// Save output in wallet
|
||||||
|
let _ = receiver_create_fn(&mut wallet);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Receive a transaction from a sender
|
/// Receive a transaction from a sender
|
||||||
pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||||
let mut w = self.wallet.lock().unwrap();
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
|
|
@ -28,6 +28,7 @@ use hyper::{Body, Request, Response, StatusCode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
|
use core::core::Transaction;
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
use libtx::slate::Slate;
|
use libtx::slate::Slate;
|
||||||
use libwallet::api::{APIForeign, APIOwner};
|
use libwallet::api::{APIForeign, APIOwner};
|
||||||
|
@ -229,6 +230,35 @@ where
|
||||||
api.retrieve_txs(update_from_node, id)
|
api.retrieve_txs(update_from_node, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dump_stored_tx(
|
||||||
|
&self,
|
||||||
|
req: &Request<Body>,
|
||||||
|
api: APIOwner<T, C, K>,
|
||||||
|
) -> Result<Transaction, Error> {
|
||||||
|
let params = parse_params(req);
|
||||||
|
if let Some(id_string) = params.get("id") {
|
||||||
|
match id_string[0].parse() {
|
||||||
|
Ok(id) => match api.dump_stored_tx(id, false, "") {
|
||||||
|
Ok(tx) => Ok(tx),
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "dump_stored_tx: failed with error: {}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "dump_stored_tx: could not parse id: {}", e);
|
||||||
|
Err(ErrorKind::TransactionDumpError(
|
||||||
|
"dump_stored_tx: cannot dump transaction. Could not parse id in request.",
|
||||||
|
).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ErrorKind::TransactionDumpError(
|
||||||
|
"dump_stored_tx: Cannot dump transaction. Missing id param in request.",
|
||||||
|
).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn retrieve_summary_info(
|
fn retrieve_summary_info(
|
||||||
&self,
|
&self,
|
||||||
req: &Request<Body>,
|
req: &Request<Body>,
|
||||||
|
@ -260,6 +290,7 @@ where
|
||||||
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)?),
|
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)?),
|
||||||
"node_height" => json_response(&self.node_height(req, api)?),
|
"node_height" => json_response(&self.node_height(req, api)?),
|
||||||
"retrieve_txs" => json_response(&self.retrieve_txs(req, api)?),
|
"retrieve_txs" => json_response(&self.retrieve_txs(req, api)?),
|
||||||
|
"dump_stored_tx" => json_response(&self.dump_stored_tx(req, api)?),
|
||||||
_ => response(StatusCode::BAD_REQUEST, ""),
|
_ => response(StatusCode::BAD_REQUEST, ""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -270,17 +301,77 @@ where
|
||||||
mut api: APIOwner<T, C, K>,
|
mut api: APIOwner<T, C, K>,
|
||||||
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
||||||
Box::new(parse_body(req).and_then(move |args: SendTXArgs| {
|
Box::new(parse_body(req).and_then(move |args: SendTXArgs| {
|
||||||
api.issue_send_tx(
|
if args.method == "http" {
|
||||||
args.amount,
|
api.issue_send_tx(
|
||||||
args.minimum_confirmations,
|
args.amount,
|
||||||
&args.dest,
|
args.minimum_confirmations,
|
||||||
args.max_outputs,
|
&args.dest,
|
||||||
args.num_change_outputs,
|
args.max_outputs,
|
||||||
args.selection_strategy_is_use_all,
|
args.num_change_outputs,
|
||||||
)
|
args.selection_strategy_is_use_all,
|
||||||
|
)
|
||||||
|
} else if args.method == "file" {
|
||||||
|
api.send_tx(
|
||||||
|
false,
|
||||||
|
args.amount,
|
||||||
|
args.minimum_confirmations,
|
||||||
|
&args.dest,
|
||||||
|
args.max_outputs,
|
||||||
|
args.num_change_outputs,
|
||||||
|
args.selection_strategy_is_use_all,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error!(LOGGER, "unsupported payment method: {}", args.method);
|
||||||
|
return Err(ErrorKind::ClientCallback("unsupported payment method"))?;
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize_tx(
|
||||||
|
&self,
|
||||||
|
req: Request<Body>,
|
||||||
|
mut api: APIOwner<T, C, K>,
|
||||||
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
||||||
|
Box::new(
|
||||||
|
parse_body(req).and_then(move |mut slate| match api.finalize_tx(&mut slate) {
|
||||||
|
Ok(_) => ok(slate.clone()),
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "finalize_tx: failed with error: {}", e);
|
||||||
|
err(e)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_tx(
|
||||||
|
&self,
|
||||||
|
req: Request<Body>,
|
||||||
|
mut api: APIOwner<T, C, K>,
|
||||||
|
) -> Box<Future<Item = (), Error = Error> + Send> {
|
||||||
|
let params = parse_params(&req);
|
||||||
|
if let Some(id_string) = params.get("id") {
|
||||||
|
Box::new(match id_string[0].parse() {
|
||||||
|
Ok(id) => match api.cancel_tx(id) {
|
||||||
|
Ok(_) => ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "finalize_tx: failed with error: {}", e);
|
||||||
|
err(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "finalize_tx: could not parse id: {}", e);
|
||||||
|
err(ErrorKind::TransactionCancellationError(
|
||||||
|
"finalize_tx: cannot cancel transaction. Could not parse id in request.",
|
||||||
|
).into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Box::new(err(ErrorKind::TransactionCancellationError(
|
||||||
|
"finalize_tx: Cannot cancel transaction. Missing id param in request.",
|
||||||
|
).into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn issue_burn_tx(
|
fn issue_burn_tx(
|
||||||
&self,
|
&self,
|
||||||
_req: Request<Body>,
|
_req: Request<Body>,
|
||||||
|
@ -307,6 +398,14 @@ where
|
||||||
self.issue_send_tx(req, api)
|
self.issue_send_tx(req, api)
|
||||||
.and_then(|slate| ok(json_response_pretty(&slate))),
|
.and_then(|slate| ok(json_response_pretty(&slate))),
|
||||||
),
|
),
|
||||||
|
"finalize_tx" => Box::new(
|
||||||
|
self.finalize_tx(req, api)
|
||||||
|
.and_then(|slate| ok(json_response_pretty(&slate))),
|
||||||
|
),
|
||||||
|
"cancel_tx" => Box::new(
|
||||||
|
self.cancel_tx(req, api)
|
||||||
|
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||||
|
),
|
||||||
"issue_burn_tx" => Box::new(
|
"issue_burn_tx" => Box::new(
|
||||||
self.issue_burn_tx(req, api)
|
self.issue_burn_tx(req, api)
|
||||||
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||||
|
|
|
@ -152,6 +152,10 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "Cancellation Error: {}", _0)]
|
#[fail(display = "Cancellation Error: {}", _0)]
|
||||||
TransactionCancellationError(&'static str),
|
TransactionCancellationError(&'static str),
|
||||||
|
|
||||||
|
/// Cancellation error
|
||||||
|
#[fail(display = "Tx dump Error: {}", _0)]
|
||||||
|
TransactionDumpError(&'static str),
|
||||||
|
|
||||||
/// Attempt to repost a transaction that's already confirmed
|
/// Attempt to repost a transaction that's already confirmed
|
||||||
#[fail(display = "Transaction already confirmed error")]
|
#[fail(display = "Transaction already confirmed error")]
|
||||||
TransactionAlreadyConfirmed,
|
TransactionAlreadyConfirmed,
|
||||||
|
|
|
@ -642,6 +642,8 @@ pub struct SendTXArgs {
|
||||||
pub amount: u64,
|
pub amount: u64,
|
||||||
/// minimum confirmations
|
/// minimum confirmations
|
||||||
pub minimum_confirmations: u64,
|
pub minimum_confirmations: u64,
|
||||||
|
/// payment method
|
||||||
|
pub method: String,
|
||||||
/// destination url
|
/// destination url
|
||||||
pub dest: String,
|
pub dest: String,
|
||||||
/// Max number of outputs
|
/// Max number of outputs
|
||||||
|
|
Loading…
Reference in a new issue