mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 08:51:08 +03:00
Feature/slate serialization (#2534)
* - Add backwards compatability - Add hex serialization * rustfmt * rustfmt * Windows Compatibility Fixes #1 (#2535) * initial changes for windows build and unit/integration tests * rustfmt * wallet+store tests * rustfmt * fix linux daemonize * better encapsulate file rename * rustfmt * remove daemonize commands * rustfmt * remove server start/stop commands * add ability to drop pmmr backend files explicitly for txhashset unzip * rustfmt * fix pmmr tests * rustfmt * Windows TUI Fix (#2555) * switch pancurses backend to win32 * revert changes to restore test * compatibility fix + debug messages * rustfmt * Add content disposition for OK responses (#2545) * Testing http send and fixing accordingly * add repost method into wallet owner api (#2553) * add repost method into wallet owner api * rustfmt * Add ability to compare selection strategies (#2516) Before tx creation user can estimate fee and locked amount with different selection strategies by providing `-e` flag for `wallet send` command.
This commit is contained in:
parent
0d36acf01b
commit
ee4eed71ea
39 changed files with 2408 additions and 1221 deletions
1144
Cargo.lock
generated
1144
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
@ -25,9 +25,7 @@ chrono = "0.4.4"
|
||||||
clap = { version = "2.31", features = ["yaml"] }
|
clap = { version = "2.31", features = ["yaml"] }
|
||||||
rpassword = "2.0.0"
|
rpassword = "2.0.0"
|
||||||
ctrlc = { version = "3.1", features = ["termination"] }
|
ctrlc = { version = "3.1", features = ["termination"] }
|
||||||
cursive = "0.9.0"
|
|
||||||
humansize = "1.1.0"
|
humansize = "1.1.0"
|
||||||
daemonize = "0.3"
|
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -45,6 +43,14 @@ grin_servers = { path = "./servers", version = "1.0.1" }
|
||||||
grin_util = { path = "./util", version = "1.0.1" }
|
grin_util = { path = "./util", version = "1.0.1" }
|
||||||
grin_wallet = { path = "./wallet", version = "1.0.1" }
|
grin_wallet = { path = "./wallet", version = "1.0.1" }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
cursive = { version = "0.10.0", default-features = false, features = ["pancurses-backend"] }
|
||||||
|
[target.'cfg(windows)'.dependencies.pancurses]
|
||||||
|
version = "0.16.0"
|
||||||
|
features = ["win32"]
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
cursive = "0.9.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.3"
|
built = "0.3"
|
||||||
reqwest = "0.9"
|
reqwest = "0.9"
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub struct ChainValidationHandler {
|
||||||
impl Handler for ChainValidationHandler {
|
impl Handler for ChainValidationHandler {
|
||||||
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
fn get(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
match w(&self.chain).validate(true) {
|
match w(&self.chain).validate(true) {
|
||||||
Ok(_) => response(StatusCode::OK, ""),
|
Ok(_) => response(StatusCode::OK, "{}"),
|
||||||
Err(e) => response(
|
Err(e) => response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("validate failed: {}", e),
|
format!("validate failed: {}", e),
|
||||||
|
@ -75,7 +75,7 @@ pub struct ChainCompactHandler {
|
||||||
impl Handler for ChainCompactHandler {
|
impl Handler for ChainCompactHandler {
|
||||||
fn post(&self, _req: Request<Body>) -> ResponseFuture {
|
fn post(&self, _req: Request<Body>) -> ResponseFuture {
|
||||||
match w(&self.chain).compact() {
|
match w(&self.chain).compact() {
|
||||||
Ok(_) => response(StatusCode::OK, ""),
|
Ok(_) => response(StatusCode::OK, "{}"),
|
||||||
Err(e) => response(
|
Err(e) => response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("compact failed: {}", e),
|
format!("compact failed: {}", e),
|
||||||
|
|
|
@ -97,6 +97,6 @@ impl Handler for PeerHandler {
|
||||||
_ => return response(StatusCode::BAD_REQUEST, "invalid command"),
|
_ => return response(StatusCode::BAD_REQUEST, "invalid command"),
|
||||||
};
|
};
|
||||||
|
|
||||||
response(StatusCode::OK, "")
|
response(StatusCode::OK, "{}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -857,6 +857,14 @@ impl Chain {
|
||||||
}
|
}
|
||||||
|
|
||||||
let header = self.get_block_header(&h)?;
|
let header = self.get_block_header(&h)?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut txhashset_ref = self.txhashset.write();
|
||||||
|
// Drop file handles in underlying txhashset
|
||||||
|
txhashset_ref.release_backend_files();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite hashset
|
||||||
txhashset::zip_write(self.db_root.clone(), txhashset_data, &header)?;
|
txhashset::zip_write(self.db_root.clone(), txhashset_data, &header)?;
|
||||||
|
|
||||||
let mut txhashset =
|
let mut txhashset =
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
use crate::core::core::committed::Committed;
|
use crate::core::core::committed::Committed;
|
||||||
use crate::core::core::hash::{Hash, Hashed};
|
use crate::core::core::hash::{Hash, Hashed};
|
||||||
use crate::core::core::merkle_proof::MerkleProof;
|
use crate::core::core::merkle_proof::MerkleProof;
|
||||||
use crate::core::core::pmmr::{self, ReadonlyPMMR, RewindablePMMR, PMMR};
|
use crate::core::core::pmmr::{self, Backend, ReadonlyPMMR, RewindablePMMR, PMMR};
|
||||||
use crate::core::core::{
|
use crate::core::core::{
|
||||||
Block, BlockHeader, Input, Output, OutputIdentifier, TxKernel, TxKernelEntry,
|
Block, BlockHeader, Input, Output, OutputIdentifier, TxKernel, TxKernelEntry,
|
||||||
};
|
};
|
||||||
|
@ -153,6 +153,15 @@ impl TxHashSet {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close all backend file handles
|
||||||
|
pub fn release_backend_files(&mut self) {
|
||||||
|
self.header_pmmr_h.backend.release_files();
|
||||||
|
self.sync_pmmr_h.backend.release_files();
|
||||||
|
self.output_pmmr_h.backend.release_files();
|
||||||
|
self.rproof_pmmr_h.backend.release_files();
|
||||||
|
self.kernel_pmmr_h.backend.release_files();
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if an output is unspent.
|
/// Check if an output is unspent.
|
||||||
/// We look in the index to find the output MMR pos.
|
/// We look in the index to find the output MMR pos.
|
||||||
/// Then we check the entry in the output MMR and confirm the hash matches.
|
/// Then we check the entry in the output MMR and confirm the hash matches.
|
||||||
|
|
|
@ -12,7 +12,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
blake2-rfc = "0.2"
|
blake2-rfc = "0.2"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
croaring = "=0.3"
|
croaring = "=0.3.8"
|
||||||
enum_primitive = "0.1"
|
enum_primitive = "0.1"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
failure_derive = "0.1"
|
failure_derive = "0.1"
|
||||||
|
|
|
@ -62,6 +62,9 @@ pub trait Backend<T: PMMRable> {
|
||||||
/// fastest way to to be able to allow direct access to the file
|
/// fastest way to to be able to allow direct access to the file
|
||||||
fn get_data_file_path(&self) -> &Path;
|
fn get_data_file_path(&self) -> &Path;
|
||||||
|
|
||||||
|
/// Release underlying datafiles and locks
|
||||||
|
fn release_files(&mut self);
|
||||||
|
|
||||||
/// Also a bit of a hack...
|
/// Also a bit of a hack...
|
||||||
/// Saves a snapshot of the rewound utxo file with the block hash as
|
/// Saves a snapshot of the rewound utxo file with the block hash as
|
||||||
/// filename suffix. We need this when sending a txhashset zip file to a
|
/// filename suffix. We need this when sending a txhashset zip file to a
|
||||||
|
|
|
@ -18,6 +18,7 @@ use crate::core::hash::Hashed;
|
||||||
use crate::core::verifier_cache::VerifierCache;
|
use crate::core::verifier_cache::VerifierCache;
|
||||||
use crate::core::{committed, Committed};
|
use crate::core::{committed, Committed};
|
||||||
use crate::keychain::{self, BlindingFactor};
|
use crate::keychain::{self, BlindingFactor};
|
||||||
|
use crate::libtx::secp_ser;
|
||||||
use crate::ser::{
|
use crate::ser::{
|
||||||
self, read_multi, FixedLength, PMMRable, Readable, Reader, VerifySortedAndUnique, Writeable,
|
self, read_multi, FixedLength, PMMRable, Readable, Reader, VerifySortedAndUnique, Writeable,
|
||||||
Writer,
|
Writer,
|
||||||
|
@ -164,9 +165,14 @@ pub struct TxKernel {
|
||||||
/// Remainder of the sum of all transaction commitments. If the transaction
|
/// Remainder of the sum of all transaction commitments. If the transaction
|
||||||
/// is well formed, amounts components should sum to zero and the excess
|
/// is well formed, amounts components should sum to zero and the excess
|
||||||
/// is hence a valid public key.
|
/// is hence a valid public key.
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::commitment_from_hex"
|
||||||
|
)]
|
||||||
pub excess: Commitment,
|
pub excess: Commitment,
|
||||||
/// The signature proving the excess is a valid public key, which signs
|
/// The signature proving the excess is a valid public key, which signs
|
||||||
/// the transaction fee.
|
/// the transaction fee.
|
||||||
|
#[serde(with = "secp_ser::sig_serde")]
|
||||||
pub excess_sig: secp::Signature,
|
pub excess_sig: secp::Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -756,6 +762,10 @@ impl TransactionBody {
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
/// The kernel "offset" k2
|
/// The kernel "offset" k2
|
||||||
/// excess is k1G after splitting the key k = k1 + k2
|
/// excess is k1G after splitting the key k = k1 + k2
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::blind_from_hex"
|
||||||
|
)]
|
||||||
pub offset: BlindingFactor,
|
pub offset: BlindingFactor,
|
||||||
/// The transaction body - inputs/outputs/kernels
|
/// The transaction body - inputs/outputs/kernels
|
||||||
body: TransactionBody,
|
body: TransactionBody,
|
||||||
|
@ -1111,6 +1121,10 @@ pub struct Input {
|
||||||
/// We will check maturity for coinbase output.
|
/// We will check maturity for coinbase output.
|
||||||
pub features: OutputFeatures,
|
pub features: OutputFeatures,
|
||||||
/// The commit referencing the output being spent.
|
/// The commit referencing the output being spent.
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::commitment_from_hex"
|
||||||
|
)]
|
||||||
pub commit: Commitment,
|
pub commit: Commitment,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1214,8 +1228,16 @@ 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,
|
||||||
/// The homomorphic commitment representing the output amount
|
/// The homomorphic commitment representing the output amount
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::commitment_from_hex"
|
||||||
|
)]
|
||||||
pub commit: Commitment,
|
pub commit: Commitment,
|
||||||
/// A proof that the commitment is in the right range
|
/// A proof that the commitment is in the right range
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::rangeproof_from_hex"
|
||||||
|
)]
|
||||||
pub proof: RangeProof,
|
pub proof: RangeProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,8 @@ impl<T: PMMRable> Backend<T> for VecBackend<T> {
|
||||||
Path::new("")
|
Path::new("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn release_files(&mut self) {}
|
||||||
|
|
||||||
fn dump_stats(&self) {}
|
fn dump_stats(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
1. [POST Finalize Tx](#post-finalize-tx)
|
1. [POST Finalize Tx](#post-finalize-tx)
|
||||||
1. [POST Cancel Tx](#post-cancel-tx)
|
1. [POST Cancel Tx](#post-cancel-tx)
|
||||||
1. [POST Post Tx](#post-post-tx)
|
1. [POST Post Tx](#post-post-tx)
|
||||||
|
1. [POST Repost Tx](#post-repost-tx)
|
||||||
1. [POST Issue Burn Tx](#post-issue-burn-tx)
|
1. [POST Issue Burn Tx](#post-issue-burn-tx)
|
||||||
1. [Adding Foreign API Endpoints](#add-foreign-api-endpoints)
|
1. [Adding Foreign API Endpoints](#add-foreign-api-endpoints)
|
||||||
|
|
||||||
|
@ -641,6 +642,50 @@ Push new transaction to the connected node transaction pool. Add `?fluff` at the
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
### POST Repost Tx
|
||||||
|
|
||||||
|
Repost a `sending` transaction to the connected node transaction pool with a given transaction id. Add `?fluff` at the end of the URL to bypass Dandelion relay . This could be used for retry posting when a `sending` transaction is created but somehow failed on posting.
|
||||||
|
|
||||||
|
* **URL**
|
||||||
|
|
||||||
|
* /v1/wallet/owner/repost?id=x
|
||||||
|
* /v1/wallet/owner/repost?tx_id=x
|
||||||
|
* /v1/wallet/owner/repost?fluff&tx_id=x
|
||||||
|
|
||||||
|
* **Method:**
|
||||||
|
|
||||||
|
`POST`
|
||||||
|
|
||||||
|
* **URL Params**
|
||||||
|
|
||||||
|
**Required:**
|
||||||
|
* `id=[number]` the transaction id
|
||||||
|
* `tx_id=[string]`the transaction slate id
|
||||||
|
|
||||||
|
* **Data Params**
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
* **Success Response:**
|
||||||
|
|
||||||
|
* **Code:** 200
|
||||||
|
|
||||||
|
* **Error Response:**
|
||||||
|
|
||||||
|
* **Code:** 400
|
||||||
|
|
||||||
|
* **Sample Call:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
$.ajax({
|
||||||
|
url: "/v1/wallet/owner/repost?id=3",
|
||||||
|
dataType: "json",
|
||||||
|
type : "POST",
|
||||||
|
success : function(r) {
|
||||||
|
console.log(r);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### POST Issue Burn Tx
|
### POST Issue Burn Tx
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
/// Grin server commands processing
|
/// Grin server commands processing
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -22,7 +23,6 @@ use std::time::Duration;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use ctrlc;
|
use ctrlc;
|
||||||
use daemonize::Daemonize;
|
|
||||||
|
|
||||||
use crate::config::GlobalConfig;
|
use crate::config::GlobalConfig;
|
||||||
use crate::core::global;
|
use crate::core::global;
|
||||||
|
@ -31,7 +31,7 @@ use crate::servers;
|
||||||
use crate::tui::ui;
|
use crate::tui::ui;
|
||||||
|
|
||||||
/// wrap below to allow UI to clean up on stop
|
/// wrap below to allow UI to clean up on stop
|
||||||
fn start_server(config: servers::ServerConfig) {
|
pub fn start_server(config: servers::ServerConfig) {
|
||||||
start_server_tui(config);
|
start_server_tui(config);
|
||||||
// Just kill process for now, otherwise the process
|
// Just kill process for now, otherwise the process
|
||||||
// hangs around until sigint because the API server
|
// hangs around until sigint because the API server
|
||||||
|
@ -157,29 +157,11 @@ pub fn server_command(
|
||||||
});
|
});
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
// start the server in the different run modes (interactive or daemon)
|
|
||||||
if let Some(a) = server_args {
|
if let Some(a) = server_args {
|
||||||
match a.subcommand() {
|
match a.subcommand() {
|
||||||
("run", _) => {
|
("run", _) => {
|
||||||
start_server(server_config);
|
start_server(server_config);
|
||||||
}
|
}
|
||||||
("start", _) => {
|
|
||||||
let daemonize = Daemonize::new()
|
|
||||||
.pid_file("/tmp/grin.pid")
|
|
||||||
.chown_pid_file(true)
|
|
||||||
.working_directory(current_dir().unwrap())
|
|
||||||
.privileged_action(move || {
|
|
||||||
start_server(server_config.clone());
|
|
||||||
loop {
|
|
||||||
thread::sleep(Duration::from_secs(60));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
match daemonize.start() {
|
|
||||||
Ok(_) => info!("Grin server successfully started."),
|
|
||||||
Err(e) => error!("Error starting: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("stop", _) => println!("TODO. Just 'kill $pid' for now. Maybe /tmp/grin.pid is $pid"),
|
|
||||||
("", _) => {
|
("", _) => {
|
||||||
println!("Subcommand required, use 'grin help server' for details");
|
println!("Subcommand required, use 'grin help server' for details");
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,6 +349,9 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||||
// selection_strategy
|
// selection_strategy
|
||||||
let selection_strategy = parse_required(args, "selection_strategy")?;
|
let selection_strategy = parse_required(args, "selection_strategy")?;
|
||||||
|
|
||||||
|
// estimate_selection_strategies
|
||||||
|
let estimate_selection_strategies = args.is_present("estimate_selection_strategies");
|
||||||
|
|
||||||
// method
|
// method
|
||||||
let method = parse_required(args, "method")?;
|
let method = parse_required(args, "method")?;
|
||||||
|
|
||||||
|
@ -360,10 +363,18 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||||
None => "default",
|
None => "default",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parse_required(args, "dest")?
|
if !estimate_selection_strategies {
|
||||||
|
parse_required(args, "dest")?
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if method == "http" && !dest.starts_with("http://") && !dest.starts_with("https://") {
|
if !estimate_selection_strategies
|
||||||
|
&& method == "http"
|
||||||
|
&& !dest.starts_with("http://")
|
||||||
|
&& !dest.starts_with("https://")
|
||||||
|
{
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"HTTP Destination should start with http://: or https://: {}",
|
"HTTP Destination should start with http://: or https://: {}",
|
||||||
dest,
|
dest,
|
||||||
|
@ -386,6 +397,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||||
message: message,
|
message: message,
|
||||||
minimum_confirmations: min_c,
|
minimum_confirmations: min_c,
|
||||||
selection_strategy: selection_strategy.to_owned(),
|
selection_strategy: selection_strategy.to_owned(),
|
||||||
|
estimate_selection_strategies,
|
||||||
method: method.to_owned(),
|
method: method.to_owned(),
|
||||||
dest: dest.to_owned(),
|
dest: dest.to_owned(),
|
||||||
change_outputs: change_outputs,
|
change_outputs: change_outputs,
|
||||||
|
@ -562,7 +574,11 @@ pub fn wallet_command(
|
||||||
}
|
}
|
||||||
("send", Some(args)) => {
|
("send", Some(args)) => {
|
||||||
let a = arg_parse!(parse_send_args(&args));
|
let a = arg_parse!(parse_send_args(&args));
|
||||||
command::send(inst_wallet(), a)
|
command::send(
|
||||||
|
inst_wallet(),
|
||||||
|
a,
|
||||||
|
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
("receive", Some(args)) => {
|
("receive", Some(args)) => {
|
||||||
let a = arg_parse!(parse_receive_args(&args));
|
let a = arg_parse!(parse_receive_args(&args));
|
||||||
|
|
|
@ -44,10 +44,6 @@ subcommands:
|
||||||
subcommands:
|
subcommands:
|
||||||
- config:
|
- config:
|
||||||
about: Generate a configuration grin-server.toml file in the current directory
|
about: Generate a configuration grin-server.toml file in the current directory
|
||||||
- start:
|
|
||||||
about: Start the Grin server as a daemon
|
|
||||||
- stop:
|
|
||||||
about: Stop the Grin server daemon
|
|
||||||
- run:
|
- run:
|
||||||
about: Run the Grin server in this console
|
about: Run the Grin server in this console
|
||||||
- client:
|
- client:
|
||||||
|
@ -161,6 +157,10 @@ subcommands:
|
||||||
- smallest
|
- smallest
|
||||||
default_value: all
|
default_value: all
|
||||||
takes_value: true
|
takes_value: true
|
||||||
|
- estimate_selection_strategies:
|
||||||
|
help: Estimates all possible Coin/Output selection strategies.
|
||||||
|
short: e
|
||||||
|
long: estimate-selection
|
||||||
- change_outputs:
|
- change_outputs:
|
||||||
help: Number of change outputs to generate (mainly for testing)
|
help: Number of change outputs to generate (mainly for testing)
|
||||||
short: o
|
short: o
|
||||||
|
|
|
@ -11,7 +11,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
croaring = "=0.3"
|
croaring = "=0.3.8"
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
|
|
|
@ -73,11 +73,19 @@ pub fn new_named_env(path: String, name: String, max_readers: Option<u32>) -> lm
|
||||||
env_builder.set_maxdbs(8).unwrap();
|
env_builder.set_maxdbs(8).unwrap();
|
||||||
// half a TB should give us plenty room, will be an issue on 32 bits
|
// half a TB should give us plenty room, will be an issue on 32 bits
|
||||||
// (which we don't support anyway)
|
// (which we don't support anyway)
|
||||||
env_builder
|
|
||||||
.set_mapsize(549_755_813_888)
|
#[cfg(not(target_os = "windows"))]
|
||||||
.unwrap_or_else(|e| {
|
env_builder.set_mapsize(5_368_709_120).unwrap_or_else(|e| {
|
||||||
panic!("Unable to allocate LMDB space: {:?}", e);
|
panic!("Unable to allocate LMDB space: {:?}", e);
|
||||||
});
|
});
|
||||||
|
//TODO: This is temporary to support (beta) windows support
|
||||||
|
//Windows allocates the entire file at once, so this needs to
|
||||||
|
//be changed to allocate as little as possible and increase as needed
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
env_builder.set_mapsize(524_288_000).unwrap_or_else(|e| {
|
||||||
|
panic!("Unable to allocate LMDB space: {:?}", e);
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(max_readers) = max_readers {
|
if let Some(max_readers) = max_readers {
|
||||||
env_builder
|
env_builder
|
||||||
.set_maxreaders(max_readers)
|
.set_maxreaders(max_readers)
|
||||||
|
|
|
@ -152,6 +152,12 @@ impl<T: PMMRable> Backend<T> for PMMRBackend<T> {
|
||||||
self.data_file.path()
|
self.data_file.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Release underlying data files
|
||||||
|
fn release_files(&mut self) {
|
||||||
|
self.data_file.release();
|
||||||
|
self.hash_file.release();
|
||||||
|
}
|
||||||
|
|
||||||
fn snapshot(&self, header: &BlockHeader) -> Result<(), String> {
|
fn snapshot(&self, header: &BlockHeader) -> Result<(), String> {
|
||||||
self.leaf_set
|
self.leaf_set
|
||||||
.snapshot(header)
|
.snapshot(header)
|
||||||
|
@ -208,8 +214,8 @@ impl<T: PMMRable> PMMRBackend<T> {
|
||||||
Ok(PMMRBackend {
|
Ok(PMMRBackend {
|
||||||
data_dir: data_dir.to_path_buf(),
|
data_dir: data_dir.to_path_buf(),
|
||||||
prunable,
|
prunable,
|
||||||
hash_file,
|
hash_file: hash_file,
|
||||||
data_file,
|
data_file: data_file,
|
||||||
leaf_set,
|
leaf_set,
|
||||||
prune_list,
|
prune_list,
|
||||||
})
|
})
|
||||||
|
@ -334,20 +340,11 @@ impl<T: PMMRable> PMMRBackend<T> {
|
||||||
}
|
}
|
||||||
self.prune_list.flush()?;
|
self.prune_list.flush()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Rename the compact copy of hash file and reopen it.
|
// 4. Rename the compact copy of hash file and reopen it.
|
||||||
fs::rename(
|
self.hash_file.replace(Path::new(&tmp_prune_file_hash))?;
|
||||||
tmp_prune_file_hash.clone(),
|
|
||||||
self.data_dir.join(PMMR_HASH_FILE),
|
|
||||||
)?;
|
|
||||||
self.hash_file = DataFile::open(self.data_dir.join(PMMR_HASH_FILE))?;
|
|
||||||
|
|
||||||
// 5. Rename the compact copy of the data file and reopen it.
|
// 5. Rename the compact copy of the data file and reopen it.
|
||||||
fs::rename(
|
self.data_file.replace(Path::new(&tmp_prune_file_data))?;
|
||||||
tmp_prune_file_data.clone(),
|
|
||||||
self.data_dir.join(PMMR_DATA_FILE),
|
|
||||||
)?;
|
|
||||||
self.data_file = DataFile::open(self.data_dir.join(PMMR_DATA_FILE))?;
|
|
||||||
|
|
||||||
// 6. Write the leaf_set to disk.
|
// 6. Write the leaf_set to disk.
|
||||||
// Optimize the bitmap storage in the process.
|
// Optimize the bitmap storage in the process.
|
||||||
|
|
|
@ -101,6 +101,17 @@ where
|
||||||
self.file.path()
|
self.file.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace underlying file with another, deleting original
|
||||||
|
pub fn replace(&mut self, with: &Path) -> io::Result<()> {
|
||||||
|
self.file.replace(with)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drop underlying file handles
|
||||||
|
pub fn release(&mut self) {
|
||||||
|
self.file.release();
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the file out to disk, pruning removed elements.
|
/// Write the file out to disk, pruning removed elements.
|
||||||
pub fn save_prune<F>(&self, target: &str, prune_offs: &[u64], prune_cb: F) -> io::Result<()>
|
pub fn save_prune<F>(&self, target: &str, prune_offs: &[u64], prune_cb: F) -> io::Result<()>
|
||||||
where
|
where
|
||||||
|
@ -125,7 +136,7 @@ where
|
||||||
/// latter by truncating the underlying file and re-creating the mmap.
|
/// latter by truncating the underlying file and re-creating the mmap.
|
||||||
pub struct AppendOnlyFile {
|
pub struct AppendOnlyFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
file: File,
|
file: Option<File>,
|
||||||
mmap: Option<memmap::Mmap>,
|
mmap: Option<memmap::Mmap>,
|
||||||
buffer_start: usize,
|
buffer_start: usize,
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
|
@ -135,28 +146,36 @@ pub struct AppendOnlyFile {
|
||||||
impl AppendOnlyFile {
|
impl AppendOnlyFile {
|
||||||
/// Open a file (existing or not) as append-only, backed by a mmap.
|
/// Open a file (existing or not) as append-only, backed by a mmap.
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<AppendOnlyFile> {
|
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<AppendOnlyFile> {
|
||||||
let file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.append(true)
|
|
||||||
.create(true)
|
|
||||||
.open(&path)?;
|
|
||||||
let mut aof = AppendOnlyFile {
|
let mut aof = AppendOnlyFile {
|
||||||
file,
|
file: None,
|
||||||
path: path.as_ref().to_path_buf(),
|
path: path.as_ref().to_path_buf(),
|
||||||
mmap: None,
|
mmap: None,
|
||||||
buffer_start: 0,
|
buffer_start: 0,
|
||||||
buffer: vec![],
|
buffer: vec![],
|
||||||
buffer_start_bak: 0,
|
buffer_start_bak: 0,
|
||||||
};
|
};
|
||||||
// If we have a non-empty file then mmap it.
|
aof.init()?;
|
||||||
let sz = aof.size();
|
|
||||||
if sz > 0 {
|
|
||||||
aof.buffer_start = sz as usize;
|
|
||||||
aof.mmap = Some(unsafe { memmap::Mmap::map(&aof.file)? });
|
|
||||||
}
|
|
||||||
Ok(aof)
|
Ok(aof)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (Re)init an underlying file and its associated memmap
|
||||||
|
pub fn init(&mut self) -> io::Result<()> {
|
||||||
|
self.file = Some(
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(self.path.clone())?,
|
||||||
|
);
|
||||||
|
// If we have a non-empty file then mmap it.
|
||||||
|
let sz = self.size();
|
||||||
|
if sz > 0 {
|
||||||
|
self.buffer_start = sz as usize;
|
||||||
|
self.mmap = Some(unsafe { memmap::Mmap::map(&self.file.as_ref().unwrap())? });
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Append data to the file. Until the append-only file is synced, data is
|
/// Append data to the file. Until the append-only file is synced, data is
|
||||||
/// only written to memory.
|
/// only written to memory.
|
||||||
pub fn append(&mut self, bytes: &mut [u8]) {
|
pub fn append(&mut self, bytes: &mut [u8]) {
|
||||||
|
@ -193,21 +212,37 @@ impl AppendOnlyFile {
|
||||||
pub fn flush(&mut self) -> io::Result<()> {
|
pub fn flush(&mut self) -> io::Result<()> {
|
||||||
if self.buffer_start_bak > 0 {
|
if self.buffer_start_bak > 0 {
|
||||||
// Flushing a rewound state, we need to truncate via set_len() before applying.
|
// Flushing a rewound state, we need to truncate via set_len() before applying.
|
||||||
self.file.set_len(self.buffer_start as u64)?;
|
// Drop and recreate, or windows throws an access error
|
||||||
|
self.mmap = None;
|
||||||
|
self.file = None;
|
||||||
|
{
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&self.path)?;
|
||||||
|
file.set_len(self.buffer_start as u64)?;
|
||||||
|
}
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&self.path)?;
|
||||||
|
self.file = Some(file);
|
||||||
self.buffer_start_bak = 0;
|
self.buffer_start_bak = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buffer_start += self.buffer.len();
|
self.buffer_start += self.buffer.len();
|
||||||
self.file.write_all(&self.buffer[..])?;
|
self.file.as_mut().unwrap().write_all(&self.buffer[..])?;
|
||||||
self.file.sync_all()?;
|
self.file.as_mut().unwrap().sync_all()?;
|
||||||
|
|
||||||
self.buffer = vec![];
|
self.buffer = vec![];
|
||||||
|
|
||||||
// Note: file must be non-empty to memory map it
|
// Note: file must be non-empty to memory map it
|
||||||
if self.file.metadata()?.len() == 0 {
|
if self.file.as_ref().unwrap().metadata()?.len() == 0 {
|
||||||
self.mmap = None;
|
self.mmap = None;
|
||||||
} else {
|
} else {
|
||||||
self.mmap = Some(unsafe { memmap::Mmap::map(&self.file)? });
|
self.mmap = Some(unsafe { memmap::Mmap::map(&self.file.as_ref().unwrap())? });
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -313,6 +348,23 @@ impl AppendOnlyFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace the underlying file with another file
|
||||||
|
/// deleting the original
|
||||||
|
pub fn replace(&mut self, with: &Path) -> io::Result<()> {
|
||||||
|
self.mmap = None;
|
||||||
|
self.file = None;
|
||||||
|
fs::remove_file(&self.path)?;
|
||||||
|
fs::rename(with, &self.path)?;
|
||||||
|
self.init()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release underlying file handles
|
||||||
|
pub fn release(&mut self) {
|
||||||
|
self.mmap = None;
|
||||||
|
self.file = None;
|
||||||
|
}
|
||||||
|
|
||||||
/// Current size of the file in bytes.
|
/// Current size of the file in bytes.
|
||||||
pub fn size(&self) -> u64 {
|
pub fn size(&self) -> u64 {
|
||||||
fs::metadata(&self.path).map(|md| md.len()).unwrap_or(0)
|
fs::metadata(&self.path).map(|md| md.len()).unwrap_or(0)
|
||||||
|
|
1168
store/tests/pmmr.rs
1168
store/tests/pmmr.rs
File diff suppressed because it is too large
Load diff
|
@ -80,7 +80,15 @@ where
|
||||||
fs::create_dir_all(&p)?;
|
fs::create_dir_all(&p)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut outfile = fs::File::create(&file_path)?;
|
//let mut outfile = fs::File::create(&file_path)?;
|
||||||
|
let res = fs::File::create(&file_path);
|
||||||
|
let mut outfile = match res {
|
||||||
|
Err(e) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
return Err(zip::result::ZipError::Io(e));
|
||||||
|
}
|
||||||
|
Ok(r) => r,
|
||||||
|
};
|
||||||
io::copy(&mut file, &mut outfile)?;
|
io::copy(&mut file, &mut outfile)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use crate::adapters::util::{deserialize_slate, serialize_slate};
|
||||||
use crate::libwallet::slate::Slate;
|
use crate::libwallet::slate::Slate;
|
||||||
use crate::libwallet::{Error, ErrorKind};
|
use crate::libwallet::Error;
|
||||||
use crate::{WalletCommAdapter, WalletConfig};
|
use crate::{WalletCommAdapter, WalletConfig};
|
||||||
use serde_json as json;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -43,7 +43,8 @@ impl WalletCommAdapter for FileWalletCommAdapter {
|
||||||
|
|
||||||
fn send_tx_async(&self, dest: &str, slate: &Slate) -> Result<(), Error> {
|
fn send_tx_async(&self, dest: &str, slate: &Slate) -> Result<(), Error> {
|
||||||
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())?;
|
let slate_string = serialize_slate(slate);
|
||||||
|
pub_tx.write_all(slate_string.as_bytes())?;
|
||||||
pub_tx.sync_all()?;
|
pub_tx.sync_all()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -52,7 +53,7 @@ impl WalletCommAdapter for FileWalletCommAdapter {
|
||||||
let mut pub_tx_f = File::open(params)?;
|
let mut pub_tx_f = File::open(params)?;
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
pub_tx_f.read_to_string(&mut content)?;
|
pub_tx_f.read_to_string(&mut content)?;
|
||||||
Ok(json::from_str(&content).map_err(|err| ErrorKind::Format(err.to_string()))?)
|
Ok(deserialize_slate(&content))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listen(
|
fn listen(
|
||||||
|
|
|
@ -12,9 +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.
|
||||||
|
|
||||||
|
use crate::adapters::util::get_versioned_slate;
|
||||||
use crate::api;
|
use crate::api;
|
||||||
use crate::controller;
|
use crate::controller;
|
||||||
use crate::libwallet::slate::Slate;
|
use crate::libwallet::slate::{Slate, VersionedSlate};
|
||||||
use crate::libwallet::{Error, ErrorKind};
|
use crate::libwallet::{Error, ErrorKind};
|
||||||
use crate::{instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
|
use crate::{instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
|
||||||
/// HTTP Wallet 'plugin' implementation
|
/// HTTP Wallet 'plugin' implementation
|
||||||
|
@ -47,15 +48,15 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
|
||||||
}
|
}
|
||||||
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
||||||
debug!("Posting transaction slate to {}", url);
|
debug!("Posting transaction slate to {}", url);
|
||||||
|
let slate = get_versioned_slate(slate);
|
||||||
let res = api::client::post(url.as_str(), None, slate);
|
let res: Result<VersionedSlate, _> = api::client::post(url.as_str(), None, &slate);
|
||||||
match res {
|
match res {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let report = format!("Posting transaction slate (is recipient listening?): {}", e);
|
let report = format!("Posting transaction slate (is recipient listening?): {}", e);
|
||||||
error!("{}", report);
|
error!("{}", report);
|
||||||
Err(ErrorKind::ClientCallback(report).into())
|
Err(ErrorKind::ClientCallback(report).into())
|
||||||
}
|
}
|
||||||
Ok(r) => Ok(r),
|
Ok(r) => Ok(r.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
// Keybase Wallet Plugin
|
// Keybase Wallet Plugin
|
||||||
|
|
||||||
use crate::controller;
|
use crate::controller;
|
||||||
use crate::libwallet::slate::Slate;
|
use crate::libwallet::slate::{Slate, VersionedSlate};
|
||||||
|
use crate::libwallet::slate_versions::v0::SlateV0;
|
||||||
use crate::libwallet::{Error, ErrorKind};
|
use crate::libwallet::{Error, ErrorKind};
|
||||||
use crate::{instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
|
use crate::{instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
@ -192,6 +193,7 @@ fn get_unread(topic: &str) -> Result<HashMap<String, String>, Error> {
|
||||||
/// Send a message to a keybase channel that self-destructs after ttl seconds.
|
/// Send a message to a keybase channel that self-destructs after ttl seconds.
|
||||||
fn send<T: Serialize>(message: T, channel: &str, topic: &str, ttl: u16) -> bool {
|
fn send<T: Serialize>(message: T, channel: &str, topic: &str, ttl: u16) -> bool {
|
||||||
let seconds = format!("{}s", ttl);
|
let seconds = format!("{}s", ttl);
|
||||||
|
let serialized = to_string(&message).unwrap();
|
||||||
let payload = to_string(&json!({
|
let payload = to_string(&json!({
|
||||||
"method": "send",
|
"method": "send",
|
||||||
"params": {
|
"params": {
|
||||||
|
@ -200,7 +202,7 @@ fn send<T: Serialize>(message: T, channel: &str, topic: &str, ttl: u16) -> bool
|
||||||
"name": channel, "topic_name": topic, "topic_type": "dev"
|
"name": channel, "topic_name": topic, "topic_type": "dev"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"body": to_string(&message).unwrap()
|
"body": serialized
|
||||||
},
|
},
|
||||||
"exploding_lifetime": seconds
|
"exploding_lifetime": seconds
|
||||||
}
|
}
|
||||||
|
@ -210,7 +212,10 @@ fn send<T: Serialize>(message: T, channel: &str, topic: &str, ttl: u16) -> bool
|
||||||
let response = api_send(&payload);
|
let response = api_send(&payload);
|
||||||
if let Ok(res) = response {
|
if let Ok(res) = response {
|
||||||
match res["result"]["message"].as_str() {
|
match res["result"]["message"].as_str() {
|
||||||
Some("message sent") => true,
|
Some("message sent") => {
|
||||||
|
debug!("Message sent to {}: {}", channel, serialized);
|
||||||
|
true
|
||||||
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -254,9 +259,10 @@ fn poll(nseconds: u64, channel: &str) -> Option<Slate> {
|
||||||
while start.elapsed().as_secs() < nseconds {
|
while start.elapsed().as_secs() < nseconds {
|
||||||
let unread = read_from_channel(channel, SLATE_SIGNED);
|
let unread = read_from_channel(channel, SLATE_SIGNED);
|
||||||
for msg in unread.unwrap().iter() {
|
for msg in unread.unwrap().iter() {
|
||||||
let blob = from_str::<Slate>(msg);
|
let blob = from_str::<VersionedSlate>(msg);
|
||||||
match blob {
|
match blob {
|
||||||
Ok(slate) => {
|
Ok(slate) => {
|
||||||
|
let slate: Slate = slate.into();
|
||||||
info!(
|
info!(
|
||||||
"keybase response message received from @{}, tx uuid: {}",
|
"keybase response message received from @{}, tx uuid: {}",
|
||||||
channel, slate.id,
|
channel, slate.id,
|
||||||
|
@ -288,8 +294,10 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
|
||||||
return Err(ErrorKind::GenericError("Tx rejected".to_owned()))?;
|
return Err(ErrorKind::GenericError("Tx rejected".to_owned()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let id = slate.id;
|
||||||
|
|
||||||
// Send original slate to recipient with the SLATE_NEW topic
|
// Send original slate to recipient with the SLATE_NEW topic
|
||||||
match send(slate, addr, SLATE_NEW, TTL) {
|
match send(&slate, addr, SLATE_NEW, TTL) {
|
||||||
true => (),
|
true => (),
|
||||||
false => {
|
false => {
|
||||||
return Err(ErrorKind::ClientCallback(
|
return Err(ErrorKind::ClientCallback(
|
||||||
|
@ -297,10 +305,7 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
|
||||||
))?
|
))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!(
|
info!("tx request has been sent to @{}, tx uuid: {}", addr, id);
|
||||||
"tx request has been sent to @{}, tx uuid: {}",
|
|
||||||
addr, slate.id
|
|
||||||
);
|
|
||||||
// Wait for response from recipient with SLATE_SIGNED topic
|
// Wait for response from recipient with SLATE_SIGNED topic
|
||||||
match poll(TTL as u64, addr) {
|
match poll(TTL as u64, addr) {
|
||||||
Some(slate) => return Ok(slate),
|
Some(slate) => return Ok(slate),
|
||||||
|
@ -345,9 +350,10 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (msg, channel) in &unread.unwrap() {
|
for (msg, channel) in &unread.unwrap() {
|
||||||
let blob = from_str::<Slate>(msg);
|
let blob = from_str::<VersionedSlate>(msg);
|
||||||
match blob {
|
match blob {
|
||||||
Ok(mut slate) => {
|
Ok(message) => {
|
||||||
|
let mut slate: Slate = message.clone().into();
|
||||||
let tx_uuid = slate.id;
|
let tx_uuid = slate.id;
|
||||||
|
|
||||||
// Reject multiple recipients channel for safety
|
// Reject multiple recipients channel for safety
|
||||||
|
@ -378,19 +384,29 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}) {
|
}) {
|
||||||
// Reply to the same channel with topic SLATE_SIGNED
|
// Reply to the same channel with topic SLATE_SIGNED
|
||||||
Ok(_) => match send(slate, channel, SLATE_SIGNED, TTL) {
|
Ok(_) => {
|
||||||
true => {
|
let success = match message {
|
||||||
|
// Send the same version of slate that was sent to us
|
||||||
|
VersionedSlate::V0(_) => {
|
||||||
|
send(SlateV0::from(slate), channel, SLATE_SIGNED, TTL)
|
||||||
|
}
|
||||||
|
VersionedSlate::V1(_) => {
|
||||||
|
send(slate, channel, SLATE_SIGNED, TTL)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if success {
|
||||||
notify_on_receive(
|
notify_on_receive(
|
||||||
config.keybase_notify_ttl.unwrap_or(1440),
|
config.keybase_notify_ttl.unwrap_or(1440),
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
tx_uuid.to_string(),
|
tx_uuid.to_string(),
|
||||||
);
|
);
|
||||||
debug!("Returned slate to @{} via keybase", channel);
|
debug!("Returned slate to @{} via keybase", channel);
|
||||||
}
|
} else {
|
||||||
false => {
|
|
||||||
error!("Failed to return slate to @{} via keybase. Incoming tx failed", channel);
|
error!("Failed to return slate to @{} via keybase. Incoming tx failed", channel);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"Error on receiving tx via keybase: {}. Incoming tx failed",
|
"Error on receiving tx via keybase: {}. Incoming tx failed",
|
||||||
|
@ -399,7 +415,7 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => (),
|
Err(_) => debug!("Failed to deserialize keybase message: {}", msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sleep(LISTEN_SLEEP_DURATION);
|
sleep(LISTEN_SLEEP_DURATION);
|
||||||
|
|
|
@ -16,6 +16,7 @@ mod file;
|
||||||
mod http;
|
mod http;
|
||||||
mod keybase;
|
mod keybase;
|
||||||
mod null;
|
mod null;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
pub use self::file::FileWalletCommAdapter;
|
pub use self::file::FileWalletCommAdapter;
|
||||||
pub use self::http::HTTPWalletCommAdapter;
|
pub use self::http::HTTPWalletCommAdapter;
|
||||||
|
|
23
wallet/src/adapters/util.rs
Normal file
23
wallet/src/adapters/util.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::libwallet::slate::{Slate, VersionedSlate};
|
||||||
|
use crate::libwallet::slate_versions::v0::SlateV0;
|
||||||
|
use crate::libwallet::ErrorKind;
|
||||||
|
use serde_json as json;
|
||||||
|
|
||||||
|
pub fn get_versioned_slate(slate: &Slate) -> VersionedSlate {
|
||||||
|
let slate = slate.clone();
|
||||||
|
match slate.version {
|
||||||
|
0 => VersionedSlate::V0(SlateV0::from(slate)),
|
||||||
|
_ => VersionedSlate::V1(slate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize_slate(slate: &Slate) -> String {
|
||||||
|
json::to_string(&get_versioned_slate(slate)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_slate(raw_slate: &str) -> Slate {
|
||||||
|
let versioned_slate: VersionedSlate = json::from_str(&raw_slate)
|
||||||
|
.map_err(|err| ErrorKind::Format(err.to_string()))
|
||||||
|
.unwrap();
|
||||||
|
versioned_slate.into()
|
||||||
|
}
|
|
@ -200,6 +200,7 @@ pub struct SendArgs {
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
pub minimum_confirmations: u64,
|
pub minimum_confirmations: u64,
|
||||||
pub selection_strategy: String,
|
pub selection_strategy: String,
|
||||||
|
pub estimate_selection_strategies: bool,
|
||||||
pub method: String,
|
pub method: String,
|
||||||
pub dest: String,
|
pub dest: String,
|
||||||
pub change_outputs: usize,
|
pub change_outputs: usize,
|
||||||
|
@ -210,68 +211,89 @@ pub struct SendArgs {
|
||||||
pub fn send(
|
pub fn send(
|
||||||
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
||||||
args: SendArgs,
|
args: SendArgs,
|
||||||
|
dark_scheme: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
controller::owner_single_use(wallet.clone(), |api| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
let result = api.initiate_tx(
|
if args.estimate_selection_strategies {
|
||||||
None,
|
let strategies = vec!["smallest", "all"]
|
||||||
args.amount,
|
.into_iter()
|
||||||
args.minimum_confirmations,
|
.map(|strategy| {
|
||||||
args.max_outputs,
|
let (total, fee) = api
|
||||||
args.change_outputs,
|
.estimate_initiate_tx(
|
||||||
args.selection_strategy == "all",
|
None,
|
||||||
args.message.clone(),
|
args.amount,
|
||||||
);
|
args.minimum_confirmations,
|
||||||
let (mut slate, lock_fn) = match result {
|
args.max_outputs,
|
||||||
Ok(s) => {
|
args.change_outputs,
|
||||||
info!(
|
strategy == "all",
|
||||||
"Tx created: {} grin to {} (strategy '{}')",
|
)
|
||||||
core::amount_to_hr_string(args.amount, false),
|
.unwrap();
|
||||||
args.dest,
|
(strategy, total, fee)
|
||||||
args.selection_strategy,
|
})
|
||||||
);
|
.collect();
|
||||||
s
|
display::estimate(args.amount, strategies, dark_scheme);
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
info!("Tx not created: {}", e);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let adapter = match args.method.as_str() {
|
|
||||||
"http" => HTTPWalletCommAdapter::new(),
|
|
||||||
"file" => FileWalletCommAdapter::new(),
|
|
||||||
"keybase" => KeybaseWalletCommAdapter::new(),
|
|
||||||
"self" => NullWalletCommAdapter::new(),
|
|
||||||
_ => NullWalletCommAdapter::new(),
|
|
||||||
};
|
|
||||||
if adapter.supports_sync() {
|
|
||||||
slate = adapter.send_tx_sync(&args.dest, &slate)?;
|
|
||||||
api.tx_lock_outputs(&slate, lock_fn)?;
|
|
||||||
if args.method == "self" {
|
|
||||||
controller::foreign_single_use(wallet, |api| {
|
|
||||||
api.receive_tx(&mut slate, Some(&args.dest), None)?;
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
if let Err(e) = api.verify_slate_messages(&slate) {
|
|
||||||
error!("Error validating participant messages: {}", e);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
api.finalize_tx(&mut slate)?;
|
|
||||||
} else {
|
} else {
|
||||||
adapter.send_tx_async(&args.dest, &slate)?;
|
let result = api.initiate_tx(
|
||||||
api.tx_lock_outputs(&slate, lock_fn)?;
|
None,
|
||||||
}
|
args.amount,
|
||||||
if adapter.supports_sync() {
|
args.minimum_confirmations,
|
||||||
let result = api.post_tx(&slate.tx, args.fluff);
|
args.max_outputs,
|
||||||
match result {
|
args.change_outputs,
|
||||||
Ok(_) => {
|
args.selection_strategy == "all",
|
||||||
info!("Tx sent ok",);
|
args.message.clone(),
|
||||||
return Ok(());
|
);
|
||||||
|
let (mut slate, lock_fn) = match result {
|
||||||
|
Ok(s) => {
|
||||||
|
info!(
|
||||||
|
"Tx created: {} grin to {} (strategy '{}')",
|
||||||
|
core::amount_to_hr_string(args.amount, false),
|
||||||
|
args.dest,
|
||||||
|
args.selection_strategy,
|
||||||
|
);
|
||||||
|
s
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Tx sent fail: {}", e);
|
info!("Tx not created: {}", e);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let adapter = match args.method.as_str() {
|
||||||
|
"http" => HTTPWalletCommAdapter::new(),
|
||||||
|
"file" => FileWalletCommAdapter::new(),
|
||||||
|
"keybase" => KeybaseWalletCommAdapter::new(),
|
||||||
|
"self" => NullWalletCommAdapter::new(),
|
||||||
|
_ => NullWalletCommAdapter::new(),
|
||||||
|
};
|
||||||
|
if adapter.supports_sync() {
|
||||||
|
slate = adapter.send_tx_sync(&args.dest, &slate)?;
|
||||||
|
api.tx_lock_outputs(&slate, lock_fn)?;
|
||||||
|
if args.method == "self" {
|
||||||
|
controller::foreign_single_use(wallet, |api| {
|
||||||
|
api.receive_tx(&mut slate, Some(&args.dest), None)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
if let Err(e) = api.verify_slate_messages(&slate) {
|
||||||
|
error!("Error validating participant messages: {}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
api.finalize_tx(&mut slate)?;
|
||||||
|
} else {
|
||||||
|
adapter.send_tx_async(&args.dest, &slate)?;
|
||||||
|
api.tx_lock_outputs(&slate, lock_fn)?;
|
||||||
|
}
|
||||||
|
if adapter.supports_sync() {
|
||||||
|
let result = api.post_tx(&slate.tx, args.fluff);
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("Tx sent ok",);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Tx sent fail: {}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
//! Controller for wallet.. instantiates and handles listeners (or single-run
|
//! Controller for wallet.. instantiates and handles listeners (or single-run
|
||||||
//! invocations) as needed.
|
//! invocations) as needed.
|
||||||
//! Still experimental
|
//! Still experimental
|
||||||
|
use crate::adapters::util::get_versioned_slate;
|
||||||
use crate::adapters::{FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter};
|
use crate::adapters::{FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter};
|
||||||
use crate::api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router, TLSConfig};
|
use crate::api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router, TLSConfig};
|
||||||
use crate::core::core;
|
use crate::core::core;
|
||||||
use crate::core::core::Transaction;
|
use crate::core::core::Transaction;
|
||||||
use crate::keychain::Keychain;
|
use crate::keychain::Keychain;
|
||||||
use crate::libwallet::api::{APIForeign, APIOwner};
|
use crate::libwallet::api::{APIForeign, APIOwner};
|
||||||
use crate::libwallet::slate::Slate;
|
use crate::libwallet::slate::{Slate, VersionedSlate};
|
||||||
use crate::libwallet::types::{
|
use crate::libwallet::types::{
|
||||||
CbData, NodeClient, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletInfo,
|
CbData, NodeClient, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletInfo,
|
||||||
};
|
};
|
||||||
|
@ -40,6 +41,7 @@ use std::marker::PhantomData;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// Instantiate wallet Owner API for a single-use (command line) call
|
/// Instantiate wallet Owner API for a single-use (command line) call
|
||||||
/// Return a function containing a loaded API context to call
|
/// Return a function containing a loaded API context to call
|
||||||
|
@ -467,6 +469,87 @@ where
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn repost(
|
||||||
|
&self,
|
||||||
|
req: Request<Body>,
|
||||||
|
api: APIOwner<T, C, K>,
|
||||||
|
) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
||||||
|
let params = parse_params(&req);
|
||||||
|
let mut id_int: Option<u32> = None;
|
||||||
|
let mut tx_uuid: Option<Uuid> = None;
|
||||||
|
|
||||||
|
if let Some(id_string) = params.get("id") {
|
||||||
|
match id_string[0].parse() {
|
||||||
|
Ok(id) => id_int = Some(id),
|
||||||
|
Err(e) => {
|
||||||
|
error!("repost: could not parse id: {}", e);
|
||||||
|
return Box::new(err(ErrorKind::GenericError(
|
||||||
|
"repost: cannot repost transaction. Could not parse id in request."
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(tx_id_string) = params.get("tx_id") {
|
||||||
|
match tx_id_string[0].parse() {
|
||||||
|
Ok(tx_id) => tx_uuid = Some(tx_id),
|
||||||
|
Err(e) => {
|
||||||
|
error!("repost: could not parse tx_id: {}", e);
|
||||||
|
return Box::new(err(ErrorKind::GenericError(
|
||||||
|
"repost: cannot repost transaction. Could not parse tx_id in request."
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Box::new(err(ErrorKind::GenericError(
|
||||||
|
"repost: Cannot repost transaction. Missing id or tx_id param in request."
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = api.retrieve_txs(true, id_int, tx_uuid);
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Box::new(err(ErrorKind::GenericError(format!(
|
||||||
|
"repost: cannot repost transaction. retrieve_txs failed, err: {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
.into()));
|
||||||
|
}
|
||||||
|
let (_, txs) = res.unwrap();
|
||||||
|
let res = api.get_stored_tx(&txs[0]);
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Box::new(err(ErrorKind::GenericError(format!(
|
||||||
|
"repost: cannot repost transaction. get_stored_tx failed, err: {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
.into()));
|
||||||
|
}
|
||||||
|
let stored_tx = res.unwrap();
|
||||||
|
if stored_tx.is_none() {
|
||||||
|
error!(
|
||||||
|
"Transaction with id {:?}/{:?} does not have transaction data. Not reposting.",
|
||||||
|
id_int, tx_uuid,
|
||||||
|
);
|
||||||
|
return Box::new(err(ErrorKind::GenericError(
|
||||||
|
"repost: Cannot repost transaction. Missing id or tx_id param in request."
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fluff = params.get("fluff").is_some();
|
||||||
|
Box::new(match api.post_tx(&stored_tx.unwrap(), fluff) {
|
||||||
|
Ok(_) => ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
error!("repost: failed with error: {}", e);
|
||||||
|
err(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||||
let api = APIOwner::new(self.wallet.clone());
|
let api = APIOwner::new(self.wallet.clone());
|
||||||
match req
|
match req
|
||||||
|
@ -487,10 +570,14 @@ where
|
||||||
),
|
),
|
||||||
"cancel_tx" => Box::new(
|
"cancel_tx" => Box::new(
|
||||||
self.cancel_tx(req, api)
|
self.cancel_tx(req, api)
|
||||||
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
.and_then(|_| ok(response(StatusCode::OK, "{}"))),
|
||||||
),
|
),
|
||||||
"post_tx" => Box::new(
|
"post_tx" => Box::new(
|
||||||
self.post_tx(req, api)
|
self.post_tx(req, api)
|
||||||
|
.and_then(|_| ok(response(StatusCode::OK, "{}"))),
|
||||||
|
),
|
||||||
|
"repost" => Box::new(
|
||||||
|
self.repost(req, api)
|
||||||
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||||
),
|
),
|
||||||
_ => Box::new(err(ErrorKind::GenericError(
|
_ => Box::new(err(ErrorKind::GenericError(
|
||||||
|
@ -574,16 +661,17 @@ where
|
||||||
&self,
|
&self,
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
mut api: APIForeign<T, C, K>,
|
mut api: APIForeign<T, C, K>,
|
||||||
) -> Box<dyn Future<Item = Slate, Error = Error> + Send> {
|
) -> Box<dyn Future<Item = VersionedSlate, Error = Error> + Send> {
|
||||||
Box::new(parse_body(req).and_then(
|
Box::new(parse_body(req).and_then(
|
||||||
//TODO: No way to insert a message from the params
|
//TODO: No way to insert a message from the params
|
||||||
move |mut slate| {
|
move |slate: VersionedSlate| {
|
||||||
|
let mut slate: Slate = slate.into();
|
||||||
if let Err(e) = api.verify_slate_messages(&slate) {
|
if let Err(e) = api.verify_slate_messages(&slate) {
|
||||||
error!("Error validating participant messages: {}", e);
|
error!("Error validating participant messages: {}", e);
|
||||||
err(e)
|
err(e)
|
||||||
} else {
|
} else {
|
||||||
match api.receive_tx(&mut slate, None, None) {
|
match api.receive_tx(&mut slate, None, None) {
|
||||||
Ok(_) => ok(slate.clone()),
|
Ok(_) => ok(get_versioned_slate(&slate.clone())),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("receive_tx: failed with error: {}", e);
|
error!("receive_tx: failed with error: {}", e);
|
||||||
err(e)
|
err(e)
|
||||||
|
@ -677,20 +765,31 @@ fn create_ok_response(json: &str) -> Response<Body> {
|
||||||
"access-control-allow-headers",
|
"access-control-allow-headers",
|
||||||
"Content-Type, Authorization",
|
"Content-Type, Authorization",
|
||||||
)
|
)
|
||||||
|
.header(hyper::header::CONTENT_TYPE, "application/json")
|
||||||
.body(json.to_string().into())
|
.body(json.to_string().into())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a new hyper Response with the status code and body provided.
|
||||||
|
///
|
||||||
|
/// Whenever the status code is `StatusCode::OK` the text parameter should be
|
||||||
|
/// valid JSON as the content type header will be set to `application/json'
|
||||||
fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
|
fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
|
||||||
Response::builder()
|
let mut builder = &mut Response::builder();
|
||||||
|
|
||||||
|
builder = builder
|
||||||
.status(status)
|
.status(status)
|
||||||
.header("access-control-allow-origin", "*")
|
.header("access-control-allow-origin", "*")
|
||||||
.header(
|
.header(
|
||||||
"access-control-allow-headers",
|
"access-control-allow-headers",
|
||||||
"Content-Type, Authorization",
|
"Content-Type, Authorization",
|
||||||
)
|
);
|
||||||
.body(text.into())
|
|
||||||
.unwrap()
|
if status == StatusCode::OK {
|
||||||
|
builder = builder.header(hyper::header::CONTENT_TYPE, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.body(text.into()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_params(req: &Request<Body>) -> HashMap<String, Vec<String>> {
|
fn parse_params(req: &Request<Body>) -> HashMap<String, Vec<String>> {
|
||||||
|
|
|
@ -338,6 +338,49 @@ pub fn info(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display summary info in a pretty way
|
||||||
|
pub fn estimate(
|
||||||
|
amount: u64,
|
||||||
|
strategies: Vec<(
|
||||||
|
&str, // strategy
|
||||||
|
u64, // total amount to be locked
|
||||||
|
u64, // fee
|
||||||
|
)>,
|
||||||
|
dark_background_color_scheme: bool,
|
||||||
|
) {
|
||||||
|
println!(
|
||||||
|
"\nEstimation for sending {}:\n",
|
||||||
|
amount_to_hr_string(amount, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut table = table!();
|
||||||
|
|
||||||
|
table.set_titles(row![
|
||||||
|
bMG->"Selection strategy",
|
||||||
|
bMG->"Fee",
|
||||||
|
bMG->"Will be locked",
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (strategy, total, fee) in strategies {
|
||||||
|
if dark_background_color_scheme {
|
||||||
|
table.add_row(row![
|
||||||
|
bFC->strategy,
|
||||||
|
FR->amount_to_hr_string(fee, false),
|
||||||
|
FY->amount_to_hr_string(total, false),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
table.add_row(row![
|
||||||
|
bFD->strategy,
|
||||||
|
FR->amount_to_hr_string(fee, false),
|
||||||
|
FY->amount_to_hr_string(total, false),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.printstd();
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
/// Display list of wallet accounts in a pretty way
|
/// Display list of wallet accounts in a pretty way
|
||||||
pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
|
pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
|
||||||
println!("\n____ Wallet Accounts ____\n",);
|
println!("\n____ Wallet Accounts ____\n",);
|
||||||
|
|
|
@ -25,7 +25,8 @@ extern crate serde_derive;
|
||||||
extern crate log;
|
extern crate log;
|
||||||
use failure;
|
use failure;
|
||||||
use grin_api as api;
|
use grin_api as api;
|
||||||
use grin_core as core;
|
#[macro_use]
|
||||||
|
extern crate grin_core as core;
|
||||||
use grin_keychain as keychain;
|
use grin_keychain as keychain;
|
||||||
use grin_store as store;
|
use grin_store as store;
|
||||||
use grin_util as util;
|
use grin_util as util;
|
||||||
|
|
|
@ -671,6 +671,74 @@ where
|
||||||
Ok((slate, lock_fn))
|
Ok((slate, lock_fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Estimates the amount to be locked and fee for the transaction without creating one
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `src_acct_name` - The human readable account name from which to draw outputs
|
||||||
|
/// for the transaction, overriding whatever the active account is as set via the
|
||||||
|
/// [`set_active_account`](struct.APIOwner.html#method.set_active_account) method.
|
||||||
|
/// If None, the transaction will use the active account.
|
||||||
|
/// * `amount` - The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`)
|
||||||
|
/// * `minimum_confirmations` - The minimum number of confirmations an output
|
||||||
|
/// should have in order to be included in the transaction.
|
||||||
|
/// * `max_outputs` - By default, the wallet selects as many inputs as possible in a
|
||||||
|
/// transaction, to reduce the Output set and the fees. The wallet will attempt to spend
|
||||||
|
/// include up to `max_outputs` in a transaction, however if this is not enough to cover
|
||||||
|
/// the whole amount, the wallet will include more outputs. This parameter should be considered
|
||||||
|
/// a soft limit.
|
||||||
|
/// * `num_change_outputs` - The target number of change outputs to create in the transaction.
|
||||||
|
/// The actual number created will be `num_change_outputs` + whatever remainder is needed.
|
||||||
|
/// * `selection_strategy_is_use_all` - If `true`, attempt to use up as many outputs as
|
||||||
|
/// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps
|
||||||
|
/// to reduce the size of the UTXO set and the amount of data stored in the wallet, and
|
||||||
|
/// minimizes fees. This will generally result in many inputs and a large change output(s),
|
||||||
|
/// usually much larger than the amount being sent. If `false`, the transaction will include
|
||||||
|
/// as many outputs as are needed to meet the amount, (and no more) starting with the smallest
|
||||||
|
/// value outputs.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * a result containing:
|
||||||
|
/// * (total, fee) - A tuple:
|
||||||
|
/// * Total amount to be locked.
|
||||||
|
/// * Transaction fee
|
||||||
|
pub fn estimate_initiate_tx(
|
||||||
|
&mut self,
|
||||||
|
src_acct_name: Option<&str>,
|
||||||
|
amount: u64,
|
||||||
|
minimum_confirmations: u64,
|
||||||
|
max_outputs: usize,
|
||||||
|
num_change_outputs: usize,
|
||||||
|
selection_strategy_is_use_all: bool,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
u64, // total
|
||||||
|
u64, // fee
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let mut w = self.wallet.lock();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
let parent_key_id = match src_acct_name {
|
||||||
|
Some(d) => {
|
||||||
|
let pm = w.get_acct_path(d.to_owned())?;
|
||||||
|
match pm {
|
||||||
|
Some(p) => p.path,
|
||||||
|
None => w.parent_key_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => w.parent_key_id(),
|
||||||
|
};
|
||||||
|
tx::estimate_send_tx(
|
||||||
|
&mut *w,
|
||||||
|
amount,
|
||||||
|
minimum_confirmations,
|
||||||
|
max_outputs,
|
||||||
|
num_change_outputs,
|
||||||
|
selection_strategy_is_use_all,
|
||||||
|
&parent_key_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Lock outputs associated with a given slate/transaction
|
/// Lock outputs associated with a given slate/transaction
|
||||||
pub fn tx_lock_outputs(
|
pub fn tx_lock_outputs(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -242,6 +242,52 @@ pub fn select_send_tx<T: ?Sized, C, K>(
|
||||||
),
|
),
|
||||||
Error,
|
Error,
|
||||||
>
|
>
|
||||||
|
where
|
||||||
|
T: WalletBackend<C, K>,
|
||||||
|
C: NodeClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
let (coins, _total, amount, fee) = select_coins_and_fee(
|
||||||
|
wallet,
|
||||||
|
amount,
|
||||||
|
current_height,
|
||||||
|
minimum_confirmations,
|
||||||
|
max_outputs,
|
||||||
|
change_outputs,
|
||||||
|
selection_strategy_is_use_all,
|
||||||
|
&parent_key_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// build transaction skeleton with inputs and change
|
||||||
|
let (mut parts, change_amounts_derivations) =
|
||||||
|
inputs_and_change(&coins, wallet, amount, fee, change_outputs)?;
|
||||||
|
|
||||||
|
// This is more proof of concept than anything but here we set lock_height
|
||||||
|
// on tx being sent (based on current chain height via api).
|
||||||
|
parts.push(build::with_lock_height(lock_height));
|
||||||
|
|
||||||
|
Ok((parts, coins, change_amounts_derivations, fee))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select outputs and calculating fee.
|
||||||
|
pub fn select_coins_and_fee<T: ?Sized, C, K>(
|
||||||
|
wallet: &mut T,
|
||||||
|
amount: u64,
|
||||||
|
current_height: u64,
|
||||||
|
minimum_confirmations: u64,
|
||||||
|
max_outputs: usize,
|
||||||
|
change_outputs: usize,
|
||||||
|
selection_strategy_is_use_all: bool,
|
||||||
|
parent_key_id: &Identifier,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
Vec<OutputData>,
|
||||||
|
u64, // total
|
||||||
|
u64, // amount
|
||||||
|
u64, // fee
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K>,
|
||||||
C: NodeClient,
|
C: NodeClient,
|
||||||
|
@ -325,16 +371,7 @@ where
|
||||||
amount_with_fee = amount + fee;
|
amount_with_fee = amount + fee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok((coins, total, amount, fee))
|
||||||
// build transaction skeleton with inputs and change
|
|
||||||
let (mut parts, change_amounts_derivations) =
|
|
||||||
inputs_and_change(&coins, wallet, amount, fee, change_outputs)?;
|
|
||||||
|
|
||||||
// This is more proof of concept than anything but here we set lock_height
|
|
||||||
// on tx being sent (based on current chain height via api).
|
|
||||||
parts.push(build::with_lock_height(lock_height));
|
|
||||||
|
|
||||||
Ok((parts, coins, change_amounts_derivations, fee))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects inputs and change for a transaction
|
/// Selects inputs and change for a transaction
|
||||||
|
|
|
@ -42,6 +42,52 @@ where
|
||||||
Ok(slate)
|
Ok(slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Estimates locked amount and fee for the transaction without creating one
|
||||||
|
pub fn estimate_send_tx<T: ?Sized, C, K>(
|
||||||
|
wallet: &mut T,
|
||||||
|
amount: u64,
|
||||||
|
minimum_confirmations: u64,
|
||||||
|
max_outputs: usize,
|
||||||
|
num_change_outputs: usize,
|
||||||
|
selection_strategy_is_use_all: bool,
|
||||||
|
parent_key_id: &Identifier,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
u64, // total
|
||||||
|
u64, // fee
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
T: WalletBackend<C, K>,
|
||||||
|
C: NodeClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
// Get lock height
|
||||||
|
let current_height = wallet.w2n_client().get_chain_height()?;
|
||||||
|
// ensure outputs we're selecting are up to date
|
||||||
|
updater::refresh_outputs(wallet, parent_key_id, false)?;
|
||||||
|
|
||||||
|
// Sender selects outputs into a new slate and save our corresponding keys in
|
||||||
|
// a transaction context. The secret key in our transaction context will be
|
||||||
|
// randomly selected. This returns the public slate, and a closure that locks
|
||||||
|
// our inputs and outputs once we're convinced the transaction exchange went
|
||||||
|
// according to plan
|
||||||
|
// This function is just a big helper to do all of that, in theory
|
||||||
|
// this process can be split up in any way
|
||||||
|
let (_coins, total, _amount, fee) = selection::select_coins_and_fee(
|
||||||
|
wallet,
|
||||||
|
amount,
|
||||||
|
current_height,
|
||||||
|
minimum_confirmations,
|
||||||
|
max_outputs,
|
||||||
|
num_change_outputs,
|
||||||
|
selection_strategy_is_use_all,
|
||||||
|
parent_key_id,
|
||||||
|
)?;
|
||||||
|
Ok((total, fee))
|
||||||
|
}
|
||||||
|
|
||||||
/// Add inputs to the slate (effectively becoming the sender)
|
/// Add inputs to the slate (effectively becoming the sender)
|
||||||
pub fn add_inputs_to_slate<T: ?Sized, C, K>(
|
pub fn add_inputs_to_slate<T: ?Sized, C, K>(
|
||||||
wallet: &mut T,
|
wallet: &mut T,
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub mod api;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod internal;
|
pub mod internal;
|
||||||
pub mod slate;
|
pub mod slate;
|
||||||
|
pub mod slate_versions;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use crate::libwallet::error::{Error, ErrorKind};
|
pub use crate::libwallet::error::{Error, ErrorKind};
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
use crate::blake2::blake2b::blake2b;
|
use crate::blake2::blake2b::blake2b;
|
||||||
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
||||||
use crate::libwallet::error::{Error, ErrorKind};
|
use crate::libwallet::error::{Error, ErrorKind};
|
||||||
|
use crate::libwallet::slate_versions::v0::SlateV0;
|
||||||
use crate::util::secp;
|
use crate::util::secp;
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::secp::Signature;
|
use crate::util::secp::Signature;
|
||||||
|
@ -33,6 +34,25 @@ use uuid::Uuid;
|
||||||
|
|
||||||
const CURRENT_SLATE_VERSION: u64 = 1;
|
const CURRENT_SLATE_VERSION: u64 = 1;
|
||||||
|
|
||||||
|
/// A wrapper around slates the enables support for versioning
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum VersionedSlate {
|
||||||
|
/// Pre versioning version
|
||||||
|
V0(SlateV0),
|
||||||
|
/// Version 1 with versioning and hex serialization - current
|
||||||
|
V1(Slate),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VersionedSlate> for Slate {
|
||||||
|
fn from(ver: VersionedSlate) -> Self {
|
||||||
|
match ver {
|
||||||
|
VersionedSlate::V0(slate_v0) => Slate::from(slate_v0),
|
||||||
|
VersionedSlate::V1(slate) => slate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Public data for each participant in the slate
|
/// Public data for each participant in the slate
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -40,14 +60,18 @@ pub struct ParticipantData {
|
||||||
/// Id of participant in the transaction. (For now, 0=sender, 1=rec)
|
/// Id of participant in the transaction. (For now, 0=sender, 1=rec)
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
/// Public key corresponding to private blinding factor
|
/// Public key corresponding to private blinding factor
|
||||||
|
#[serde(with = "secp_ser::pubkey_serde")]
|
||||||
pub public_blind_excess: PublicKey,
|
pub public_blind_excess: PublicKey,
|
||||||
/// Public key corresponding to private nonce
|
/// Public key corresponding to private nonce
|
||||||
|
#[serde(with = "secp_ser::pubkey_serde")]
|
||||||
pub public_nonce: PublicKey,
|
pub public_nonce: PublicKey,
|
||||||
/// Public partial signature
|
/// Public partial signature
|
||||||
|
#[serde(with = "secp_ser::option_sig_serde")]
|
||||||
pub part_sig: Option<Signature>,
|
pub part_sig: Option<Signature>,
|
||||||
/// A message for other participants
|
/// A message for other participants
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
/// Signature, created with private key corresponding to 'public_blind_excess'
|
/// Signature, created with private key corresponding to 'public_blind_excess'
|
||||||
|
#[serde(with = "secp_ser::option_sig_serde")]
|
||||||
pub message_sig: Option<Signature>,
|
pub message_sig: Option<Signature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
wallet/src/libwallet/slate_versions/mod.rs
Normal file
18
wallet/src/libwallet/slate_versions/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2019 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! This module contains old slate versions and conversions to the newest slate version
|
||||||
|
//! Used for serialization and deserialization of slates in a backwards compatible way.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod v0;
|
370
wallet/src/libwallet/slate_versions/v0.rs
Normal file
370
wallet/src/libwallet/slate_versions/v0.rs
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
// Copyright 2018 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Contains V0 of the slate
|
||||||
|
use crate::core::core::transaction::{
|
||||||
|
Input, KernelFeatures, Output, OutputFeatures, Transaction, TransactionBody, TxKernel,
|
||||||
|
};
|
||||||
|
use crate::keychain::BlindingFactor;
|
||||||
|
use crate::libwallet::slate::{ParticipantData, Slate};
|
||||||
|
use crate::util::secp;
|
||||||
|
use crate::util::secp::key::PublicKey;
|
||||||
|
use crate::util::secp::pedersen::{Commitment, RangeProof};
|
||||||
|
use crate::util::secp::Signature;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct SlateV0 {
|
||||||
|
/// The number of participants intended to take part in this transaction
|
||||||
|
pub num_participants: usize,
|
||||||
|
/// Unique transaction ID, selected by sender
|
||||||
|
pub id: Uuid,
|
||||||
|
/// The core transaction data:
|
||||||
|
/// inputs, outputs, kernels, kernel offset
|
||||||
|
pub tx: TransactionV0,
|
||||||
|
/// base amount (excluding fee)
|
||||||
|
pub amount: u64,
|
||||||
|
/// fee amount
|
||||||
|
pub fee: u64,
|
||||||
|
/// Block height for the transaction
|
||||||
|
pub height: u64,
|
||||||
|
/// Lock height
|
||||||
|
pub lock_height: u64,
|
||||||
|
/// Participant data, each participant in the transaction will
|
||||||
|
/// insert their public data here. For now, 0 is sender and 1
|
||||||
|
/// is receiver, though this will change for multi-party
|
||||||
|
pub participant_data: Vec<ParticipantDataV0>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ParticipantDataV0 {
|
||||||
|
/// Id of participant in the transaction. (For now, 0=sender, 1=rec)
|
||||||
|
pub id: u64,
|
||||||
|
/// Public key corresponding to private blinding factor
|
||||||
|
pub public_blind_excess: PublicKey,
|
||||||
|
/// Public key corresponding to private nonce
|
||||||
|
pub public_nonce: PublicKey,
|
||||||
|
/// Public partial signature
|
||||||
|
pub part_sig: Option<Signature>,
|
||||||
|
/// A message for other participants
|
||||||
|
pub message: Option<String>,
|
||||||
|
/// Signature, created with private key corresponding to 'public_blind_excess'
|
||||||
|
pub message_sig: Option<Signature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A transaction
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct TransactionV0 {
|
||||||
|
/// The kernel "offset" k2
|
||||||
|
/// excess is k1G after splitting the key k = k1 + k2
|
||||||
|
pub offset: BlindingFactor,
|
||||||
|
/// The transaction body - inputs/outputs/kernels
|
||||||
|
pub body: TransactionBodyV0,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TransactionBody is a common abstraction for transaction and block
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct TransactionBodyV0 {
|
||||||
|
/// List of inputs spent by the transaction.
|
||||||
|
pub inputs: Vec<InputV0>,
|
||||||
|
/// List of outputs the transaction produces.
|
||||||
|
pub outputs: Vec<OutputV0>,
|
||||||
|
/// List of kernels that make up this transaction (usually a single kernel).
|
||||||
|
pub kernels: Vec<TxKernelV0>,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct InputV0 {
|
||||||
|
/// The features of the output being spent.
|
||||||
|
/// We will check maturity for coinbase output.
|
||||||
|
pub features: OutputFeatures,
|
||||||
|
/// The commit referencing the output being spent.
|
||||||
|
pub commit: Commitment,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct OutputV0 {
|
||||||
|
/// Options for an output's structure or use
|
||||||
|
pub features: OutputFeatures,
|
||||||
|
/// The homomorphic commitment representing the output amount
|
||||||
|
pub commit: Commitment,
|
||||||
|
/// A proof that the commitment is in the right range
|
||||||
|
pub proof: RangeProof,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct TxKernelV0 {
|
||||||
|
/// Options for a kernel's structure or use
|
||||||
|
pub features: KernelFeatures,
|
||||||
|
/// Fee originally included in the transaction this proof is for.
|
||||||
|
pub fee: u64,
|
||||||
|
/// This kernel is not valid earlier than lock_height blocks
|
||||||
|
/// The max lock_height of all *inputs* to this transaction
|
||||||
|
pub lock_height: u64,
|
||||||
|
/// Remainder of the sum of all transaction commitments. If the transaction
|
||||||
|
/// is well formed, amounts components should sum to zero and the excess
|
||||||
|
/// is hence a valid public key.
|
||||||
|
pub excess: Commitment,
|
||||||
|
/// The signature proving the excess is a valid public key, which signs
|
||||||
|
/// the transaction fee.
|
||||||
|
pub excess_sig: secp::Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SlateV0> for Slate {
|
||||||
|
fn from(slate: SlateV0) -> Slate {
|
||||||
|
let SlateV0 {
|
||||||
|
num_participants,
|
||||||
|
id,
|
||||||
|
tx,
|
||||||
|
amount,
|
||||||
|
fee,
|
||||||
|
height,
|
||||||
|
lock_height,
|
||||||
|
participant_data,
|
||||||
|
} = slate;
|
||||||
|
let tx = Transaction::from(tx);
|
||||||
|
let participant_data = map_vec!(participant_data, |data| ParticipantData::from(data));
|
||||||
|
let version = 0;
|
||||||
|
Slate {
|
||||||
|
num_participants,
|
||||||
|
id,
|
||||||
|
tx,
|
||||||
|
amount,
|
||||||
|
fee,
|
||||||
|
height,
|
||||||
|
lock_height,
|
||||||
|
participant_data,
|
||||||
|
version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ParticipantDataV0> for ParticipantData {
|
||||||
|
fn from(data: &ParticipantDataV0) -> ParticipantData {
|
||||||
|
let ParticipantDataV0 {
|
||||||
|
id,
|
||||||
|
public_blind_excess,
|
||||||
|
public_nonce,
|
||||||
|
part_sig,
|
||||||
|
message,
|
||||||
|
message_sig,
|
||||||
|
} = data;
|
||||||
|
let id = *id;
|
||||||
|
let public_blind_excess = *public_blind_excess;
|
||||||
|
let public_nonce = *public_nonce;
|
||||||
|
let part_sig = *part_sig;
|
||||||
|
let message: Option<String> = message.as_ref().map(|t| String::from(&**t));
|
||||||
|
let message_sig = *message_sig;
|
||||||
|
ParticipantData {
|
||||||
|
id,
|
||||||
|
public_blind_excess,
|
||||||
|
public_nonce,
|
||||||
|
part_sig,
|
||||||
|
message,
|
||||||
|
message_sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TransactionV0> for Transaction {
|
||||||
|
fn from(tx: TransactionV0) -> Transaction {
|
||||||
|
let TransactionV0 { offset, body } = tx;
|
||||||
|
let body = TransactionBody::from(&body);
|
||||||
|
let transaction = Transaction::new(body.inputs, body.outputs, body.kernels);
|
||||||
|
transaction.with_offset(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TransactionBodyV0> for TransactionBody {
|
||||||
|
fn from(body: &TransactionBodyV0) -> Self {
|
||||||
|
let TransactionBodyV0 {
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
kernels,
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
let inputs = map_vec!(inputs, |inp| Input::from(inp));
|
||||||
|
let outputs = map_vec!(outputs, |out| Output::from(out));
|
||||||
|
let kernels = map_vec!(kernels, |kern| TxKernel::from(kern));
|
||||||
|
TransactionBody {
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
kernels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&InputV0> for Input {
|
||||||
|
fn from(input: &InputV0) -> Input {
|
||||||
|
let InputV0 { features, commit } = *input;
|
||||||
|
Input { features, commit }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&OutputV0> for Output {
|
||||||
|
fn from(output: &OutputV0) -> Output {
|
||||||
|
let OutputV0 {
|
||||||
|
features,
|
||||||
|
commit,
|
||||||
|
proof,
|
||||||
|
} = *output;
|
||||||
|
Output {
|
||||||
|
features,
|
||||||
|
commit,
|
||||||
|
proof,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TxKernelV0> for TxKernel {
|
||||||
|
fn from(kernel: &TxKernelV0) -> TxKernel {
|
||||||
|
let TxKernelV0 {
|
||||||
|
features,
|
||||||
|
fee,
|
||||||
|
lock_height,
|
||||||
|
excess,
|
||||||
|
excess_sig,
|
||||||
|
} = *kernel;
|
||||||
|
TxKernel {
|
||||||
|
features,
|
||||||
|
fee,
|
||||||
|
lock_height,
|
||||||
|
excess,
|
||||||
|
excess_sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Slate> for SlateV0 {
|
||||||
|
fn from(slate: Slate) -> SlateV0 {
|
||||||
|
let Slate {
|
||||||
|
num_participants,
|
||||||
|
id,
|
||||||
|
tx,
|
||||||
|
amount,
|
||||||
|
fee,
|
||||||
|
height,
|
||||||
|
lock_height,
|
||||||
|
participant_data,
|
||||||
|
version: _,
|
||||||
|
} = slate;
|
||||||
|
let tx = TransactionV0::from(tx);
|
||||||
|
let participant_data = map_vec!(participant_data, |data| ParticipantDataV0::from(data));
|
||||||
|
SlateV0 {
|
||||||
|
num_participants,
|
||||||
|
id,
|
||||||
|
tx,
|
||||||
|
amount,
|
||||||
|
fee,
|
||||||
|
height,
|
||||||
|
lock_height,
|
||||||
|
participant_data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ParticipantData> for ParticipantDataV0 {
|
||||||
|
fn from(data: &ParticipantData) -> ParticipantDataV0 {
|
||||||
|
let ParticipantData {
|
||||||
|
id,
|
||||||
|
public_blind_excess,
|
||||||
|
public_nonce,
|
||||||
|
part_sig,
|
||||||
|
message,
|
||||||
|
message_sig,
|
||||||
|
} = data;
|
||||||
|
let id = *id;
|
||||||
|
let public_blind_excess = *public_blind_excess;
|
||||||
|
let public_nonce = *public_nonce;
|
||||||
|
let part_sig = *part_sig;
|
||||||
|
let message: Option<String> = message.as_ref().map(|t| String::from(&**t));
|
||||||
|
let message_sig = *message_sig;
|
||||||
|
ParticipantDataV0 {
|
||||||
|
id,
|
||||||
|
public_blind_excess,
|
||||||
|
public_nonce,
|
||||||
|
part_sig,
|
||||||
|
message,
|
||||||
|
message_sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Transaction> for TransactionV0 {
|
||||||
|
fn from(tx: Transaction) -> TransactionV0 {
|
||||||
|
let offset = tx.offset;
|
||||||
|
let body: TransactionBody = tx.into();
|
||||||
|
let body = TransactionBodyV0::from(&body);
|
||||||
|
TransactionV0 { offset, body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TransactionBody> for TransactionBodyV0 {
|
||||||
|
fn from(body: &TransactionBody) -> Self {
|
||||||
|
let TransactionBody {
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
kernels,
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
let inputs = map_vec!(inputs, |inp| InputV0::from(inp));
|
||||||
|
let outputs = map_vec!(outputs, |out| OutputV0::from(out));
|
||||||
|
let kernels = map_vec!(kernels, |kern| TxKernelV0::from(kern));
|
||||||
|
TransactionBodyV0 {
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
kernels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Input> for InputV0 {
|
||||||
|
fn from(input: &Input) -> Self {
|
||||||
|
let Input { features, commit } = *input;
|
||||||
|
InputV0 { features, commit }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Output> for OutputV0 {
|
||||||
|
fn from(output: &Output) -> Self {
|
||||||
|
let Output {
|
||||||
|
features,
|
||||||
|
commit,
|
||||||
|
proof,
|
||||||
|
} = *output;
|
||||||
|
OutputV0 {
|
||||||
|
features,
|
||||||
|
commit,
|
||||||
|
proof,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TxKernel> for TxKernelV0 {
|
||||||
|
fn from(kernel: &TxKernel) -> Self {
|
||||||
|
let TxKernel {
|
||||||
|
features,
|
||||||
|
fee,
|
||||||
|
lock_height,
|
||||||
|
excess,
|
||||||
|
excess_sig,
|
||||||
|
} = *kernel;
|
||||||
|
TxKernelV0 {
|
||||||
|
features,
|
||||||
|
fee,
|
||||||
|
lock_height,
|
||||||
|
excess,
|
||||||
|
excess_sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -137,6 +137,10 @@ where
|
||||||
self.wallets.insert(addr.to_owned(), (tx, wallet));
|
self.wallets.insert(addr.to_owned(), (tx, wallet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.running.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
/// Run the incoming message queue and respond more or less
|
/// Run the incoming message queue and respond more or less
|
||||||
/// synchronously
|
/// synchronously
|
||||||
pub fn run(&mut self) -> Result<(), libwallet::Error> {
|
pub fn run(&mut self) -> Result<(), libwallet::Error> {
|
||||||
|
|
|
@ -26,6 +26,7 @@ use grin_keychain as keychain;
|
||||||
use grin_util as util;
|
use grin_util as util;
|
||||||
use grin_wallet as wallet;
|
use grin_wallet as wallet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err
|
||||||
let dest_dir = format!("{}/{}_restore", base_dir, wallet_dir);
|
let dest_dir = format!("{}/{}_restore", base_dir, wallet_dir);
|
||||||
fs::create_dir_all(dest_dir.clone())?;
|
fs::create_dir_all(dest_dir.clone())?;
|
||||||
let dest_seed = format!("{}/wallet.seed", dest_dir);
|
let dest_seed = format!("{}/wallet.seed", dest_dir);
|
||||||
|
println!("Source: {}, Dest: {}", source_seed, dest_seed);
|
||||||
fs::copy(source_seed, dest_seed)?;
|
fs::copy(source_seed, dest_seed)?;
|
||||||
|
|
||||||
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
|
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> = WalletProxy::new(base_dir);
|
||||||
|
@ -54,6 +56,7 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err
|
||||||
wallet_proxy.add_wallet(wallet_dir, client.get_send_instance(), wallet.clone());
|
wallet_proxy.add_wallet(wallet_dir, client.get_send_instance(), wallet.clone());
|
||||||
|
|
||||||
// Set the wallet proxy listener running
|
// Set the wallet proxy listener running
|
||||||
|
let wp_running = wallet_proxy.running.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
if let Err(e) = wallet_proxy.run() {
|
if let Err(e) = wallet_proxy.run() {
|
||||||
error!("Wallet Proxy error: {}", e);
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
@ -67,6 +70,9 @@ fn restore_wallet(base_dir: &str, wallet_dir: &str) -> Result<(), libwallet::Err
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
wp_running.store(false, Ordering::Relaxed);
|
||||||
|
//thread::sleep(Duration::from_millis(1000));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +114,7 @@ fn compare_wallet_restore(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the wallet proxy listener running
|
// Set the wallet proxy listener running
|
||||||
|
let wp_running = wallet_proxy.running.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
if let Err(e) = wallet_proxy.run() {
|
if let Err(e) = wallet_proxy.run() {
|
||||||
error!("Wallet Proxy error: {}", e);
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
@ -164,6 +171,9 @@ fn compare_wallet_restore(
|
||||||
dest_accts.as_ref().unwrap().len()
|
dest_accts.as_ref().unwrap().len()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
wp_running.store(false, Ordering::Relaxed);
|
||||||
|
//thread::sleep(Duration::from_millis(1000));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,6 +218,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
|
wallet_proxy.add_wallet("wallet3", client3.get_send_instance(), wallet3.clone());
|
||||||
|
|
||||||
// Set the wallet proxy listener running
|
// Set the wallet proxy listener running
|
||||||
|
let wp_running = wallet_proxy.running.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
if let Err(e) = wallet_proxy.run() {
|
if let Err(e) = wallet_proxy.run() {
|
||||||
error!("Wallet Proxy error: {}", e);
|
error!("Wallet Proxy error: {}", e);
|
||||||
|
@ -327,6 +338,8 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
wp_running.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,33 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Estimate fee and locked amount for a transaction
|
||||||
|
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
|
||||||
|
let (total, fee) = sender_api.estimate_initiate_tx(
|
||||||
|
None,
|
||||||
|
amount * 2, // amount
|
||||||
|
2, // minimum confirmations
|
||||||
|
500, // max outputs
|
||||||
|
1, // num change outputs
|
||||||
|
true, // select all outputs
|
||||||
|
)?;
|
||||||
|
assert_eq!(total, 600_000_000_000);
|
||||||
|
assert_eq!(fee, 4_000_000);
|
||||||
|
|
||||||
|
let (total, fee) = sender_api.estimate_initiate_tx(
|
||||||
|
None,
|
||||||
|
amount * 2, // amount
|
||||||
|
2, // minimum confirmations
|
||||||
|
500, // max outputs
|
||||||
|
1, // num change outputs
|
||||||
|
false, // select the smallest amount of outputs
|
||||||
|
)?;
|
||||||
|
assert_eq!(total, 180_000_000_000);
|
||||||
|
assert_eq!(fee, 6_000_000);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
// Send another transaction, but don't post to chain immediately and use
|
// Send another transaction, but don't post to chain immediately and use
|
||||||
// the stored transaction instead
|
// the stored transaction instead
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
|
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
|
||||||
|
|
Loading…
Reference in a new issue