Update lmdb (#755)

* wallet: update lmdb from store crate, migrate single backend from trait to struct, update tests cargo deps and design doc

* backend: do not panic on iter and directory creation

* return  iterator errors

* build: update to last node revision

* fix: height selection for scan, remove unused mutability for wallet

* wallet: remove unused mutability conversion

* tx: store slate state at database

* build: cargo fmt

* fix: do not return error when slate not found on tx slate state update

* lmdb: do not collect all tx log records at iterator, show read errors at log

* fix: save slate state, return error if slate not found

* tx: specify amount of bad txs on slate state update error

---------

Co-authored-by: Joerg <wiesche89@googlemail.com>
This commit is contained in:
ardocrat
2026-06-09 14:54:51 +03:00
committed by GitHub
parent 9570ed4731
commit 06ab92a619
39 changed files with 2704 additions and 2473 deletions
Generated
+1120 -666
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -41,10 +41,10 @@ grin_wallet_util = { path = "./util", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.4.0"
grin_keychain = "5.4.0"
grin_util = "5.4.0"
grin_api = "5.4.0"
#grin_core = "5.4.0"
#grin_keychain = "5.4.0"
#grin_util = "5.4.0"
#grin_api = "5.4.0"
# For beta release
@@ -54,10 +54,10 @@ grin_api = "5.4.0"
# grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_api = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing
# grin_core = { path = "../grin/core"}
+6 -6
View File
@@ -30,9 +30,9 @@ grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.4.0"
grin_keychain = "5.4.0"
grin_util = "5.4.0"
#grin_core = "5.4.0"
#grin_keychain = "5.4.0"
#grin_util = "5.4.0"
# For beta release
@@ -41,9 +41,9 @@ grin_util = "5.4.0"
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing
# grin_core = { path = "../../grin/core"}
+16 -22
View File
@@ -146,7 +146,7 @@ where
/// // by the reference wallet implementation.
/// // These traits can be replaced with alternative implementations if desired
///
/// let mut wallet = Box::new(DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap())
/// let mut wallet = Box::new(DefaultWalletImpl::<HTTPNodeClient>::new(node_client.clone()).unwrap())
/// as Box<dyn WalletInst<'static, DefaultLCProvider<HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>>;
///
/// // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc...
@@ -286,7 +286,7 @@ where
)?;
}
foreign::build_coinbase(
&mut **w,
w,
(&self.keychain_mask).as_ref(),
block_fees,
self.doctest_mode,
@@ -362,7 +362,7 @@ where
)?;
}
let ret_slate = foreign::receive_tx(
&mut **w,
w,
(&self.keychain_mask).as_ref(),
slate,
dest_acct_name,
@@ -380,8 +380,8 @@ where
self.doctest_mode,
);
match res {
Ok(s) => return Ok(s.unwrap()),
Err(_) => return Ok(ret_slate),
Ok(s) => Ok(s.unwrap()),
Err(_) => Ok(ret_slate),
}
}
None => Ok(ret_slate),
@@ -443,12 +443,7 @@ where
true => false,
false => post_automatically,
};
foreign::finalize_tx(
&mut **w,
(&self.keychain_mask).as_ref(),
slate,
post_automatically,
)
foreign::finalize_tx(w, (&self.keychain_mask).as_ref(), slate, post_automatically)
}
}
@@ -496,17 +491,16 @@ macro_rules! doctest_helper_setup_doc_env_foreign {
let node_client =
HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None).unwrap();
let mut wallet = Box::new(
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
)
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let mut wallet =
Box::new(DefaultWalletImpl::<HTTPNodeClient>::new(node_client.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let lc = wallet.lc_provider().unwrap();
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
lc.open_wallet(None, pw, false, false);
+3 -6
View File
@@ -381,7 +381,6 @@ pub fn run_doctest_foreign(
Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client1.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
@@ -416,7 +415,6 @@ pub fn run_doctest_foreign(
Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client2.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
@@ -473,7 +471,7 @@ pub fn run_doctest_foreign(
amount,
..Default::default()
};
api_impl::owner::issue_invoice_tx(&mut **w, (&mask2).as_ref(), args, true).unwrap()
api_impl::owner::issue_invoice_tx(w, (&mask2).as_ref(), args, true).unwrap()
};
slate = {
let mut w_lock = wallet1.lock();
@@ -487,8 +485,7 @@ pub fn run_doctest_foreign(
selection_strategy_is_use_all: true,
..Default::default()
};
api_impl::owner::process_invoice_tx(&mut **w, (&mask1).as_ref(), &slate, args, true)
.unwrap()
api_impl::owner::process_invoice_tx(w, (&mask1).as_ref(), &slate, args, true).unwrap()
};
println!("INIT INVOICE SLATE");
// Spit out slate for input to finalize_tx
@@ -508,7 +505,7 @@ pub fn run_doctest_foreign(
selection_strategy_is_use_all: true,
..Default::default()
};
let slate = api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap();
let slate = api_impl::owner::init_send_tx(w, (&mask1).as_ref(), args, true).unwrap();
println!("INIT SLATE");
// Spit out slate for input to finalize_tx
println!("{}", serde_json::to_string_pretty(&slate).unwrap());
+34 -36
View File
@@ -97,7 +97,7 @@ where
///
/// Each method will call the [`WalletBackend`](../grin_wallet_libwallet/types/trait.WalletBackend.html)'s
/// [`open_with_credentials`](../grin_wallet_libwallet/types/trait.WalletBackend.html#tymethod.open_with_credentials)
/// (initialising a keychain with the master seed,) perform its operation, then close the keychain
/// initializing a keychain with the master seed, perform its operation, then close the keychain
/// with a call to [`close`](../grin_wallet_libwallet/types/trait.WalletBackend.html#tymethod.close)
///
/// # Arguments
@@ -147,11 +147,11 @@ where
/// let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None).unwrap();
///
/// // impls::DefaultWalletImpl is provided for convenience in instantiating the wallet
/// // It contains the LMDBBackend, DefaultLCProvider (lifecycle) and ExtKeychain used
/// // It contains the WalletBackend, DefaultLCProvider (lifecycle) and ExtKeychain used
/// // by the reference wallet implementation.
/// // These traits can be replaced with alternative implementations if desired
///
/// let mut wallet = Box::new(DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap())
/// let mut wallet = Box::new(DefaultWalletImpl::<HTTPNodeClient>::new(node_client.clone()).unwrap())
/// as Box<dyn WalletInst<'static, DefaultLCProvider<HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>>;
///
/// // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc...
@@ -258,7 +258,7 @@ where
let w = w_lock.lc_provider()?.wallet_inst()?;
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
owner::accounts(&mut **w)
owner::accounts(w)
}
/// Creates a new 'account', which is a mapping of a user-specified
@@ -308,7 +308,7 @@ where
) -> Result<Identifier, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::create_account_path(&mut **w, keychain_mask, label)
owner::create_account_path(w, keychain_mask, label)
}
/// Sets the wallet's currently active account. This sets the
@@ -358,7 +358,7 @@ where
let w = w_lock.lc_provider()?.wallet_inst()?;
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
owner::set_active_account(&mut **w, label)
owner::set_active_account(w, label)
}
/// Returns a list of outputs from the active account in the wallet.
@@ -665,7 +665,7 @@ where
let slate = {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::init_send_tx(&mut **w, keychain_mask, args, self.doctest_mode)?
owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?
};
// Helper functionality. If send arguments exist, attempt to send sync and
// finalize
@@ -701,17 +701,17 @@ where
match result {
Ok(_) => {
info!("Tx sent ok",);
return Ok(ret_slate);
Ok(ret_slate)
}
Err(e) => {
error!("Tx sent fail: {}", e);
return Err(e);
Err(e)
}
}
} else {
self.tx_lock_outputs(keychain_mask, &s)?;
let ret_slate = self.finalize_tx(keychain_mask, &s)?;
return Ok(ret_slate);
Ok(ret_slate)
}
}
Ok(None) => Ok(slate),
@@ -724,7 +724,7 @@ where
/// Issues a new invoice transaction slate, essentially a `request for payment`.
/// The slate created by this function will contain the amount, an output for the amount,
/// as well as round 1 of singature creation complete. The slate should then be send
/// as well as round 1 of signature creation complete. The slate should then be sent
/// to the payer, who should add their inputs and signature data and return the slate
/// via the [Foreign API's `finalize_tx`](struct.Foreign.html#method.finalize_tx) method.
///
@@ -764,14 +764,14 @@ where
) -> Result<Slate, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::issue_invoice_tx(&mut **w, keychain_mask, args, self.doctest_mode)
owner::issue_invoice_tx(w, keychain_mask, args, self.doctest_mode)
}
/// Processes an invoice tranaction created by another party, essentially
/// Processes an invoice transaction created by another party, essentially
/// a `request for payment`. The incoming slate should contain a requested
/// amount, an output created by the invoicer convering the amount, and
/// amount, an output created by the invoicer converting the amount, and
/// part 1 of signature creation completed. This function will add inputs
/// equalling the amount + fees, as well as perform round 1 and 2 of signature
/// equaling the amount + fees, as well as perform round 1 and 2 of signature
/// creation.
///
/// Callers should note that no prompting of the user will be done by this function
@@ -837,8 +837,7 @@ where
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let send_args = args.send_args.clone();
let slate =
owner::process_invoice_tx(&mut **w, keychain_mask, slate, args, self.doctest_mode)?;
let slate = owner::process_invoice_tx(w, keychain_mask, slate, args, self.doctest_mode)?;
// Helper functionality. If send arguments exist, attempt to send
match send_args {
Some(sa) => {
@@ -871,7 +870,7 @@ where
/// Locks the outputs associated with the inputs to the transaction in the given
/// [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html),
/// making them unavailable for use in further transactions. This function is called
/// by the sender, (or more generally, all parties who have put inputs into the transaction,)
/// by the sender, (or more generally, all parties who have put inputs into the transaction)
/// and must be called before the corresponding call to [`finalize_tx`](struct.Owner.html#method.finalize_tx)
/// that completes the transaction.
///
@@ -929,7 +928,7 @@ where
) -> Result<(), Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::tx_lock_outputs(&mut **w, keychain_mask, slate)
owner::tx_lock_outputs(w, keychain_mask, slate)
}
/// Finalizes a transaction, after all parties
@@ -995,7 +994,7 @@ where
) -> Result<Slate, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::finalize_tx(&mut **w, keychain_mask, slate)
owner::finalize_tx(w, keychain_mask, slate)
}
/// Posts a completed transaction to the listening node for validation and inclusion in a block
@@ -1186,7 +1185,7 @@ where
let w = w_lock.lc_provider()?.wallet_inst()?;
// Test keychain mask, to keep API consistent
let _ = w.keychain(keychain_mask)?;
owner::get_stored_tx(&**w, tx_id, slate_id)
owner::get_stored_tx(w, tx_id, slate_id)
}
/// Return the rewind hash of the wallet.
@@ -2422,7 +2421,7 @@ where
) -> Result<BuiltOutput, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::build_output(&mut **w, keychain_mask, features, amount)
owner::build_output(w, keychain_mask, features, amount)
}
// MWIXNET
@@ -2481,7 +2480,7 @@ where
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::create_mwixnet_req(
&mut **w,
w,
keychain_mask,
params,
commitment,
@@ -2499,7 +2498,7 @@ pub fn try_slatepack_sync_workflow(
tor_sender: Option<HttpSlateSender>,
send_to_finalize: bool,
test_mode: bool,
) -> Result<Option<Slate>, libwallet::Error> {
) -> Result<Option<Slate>, Error> {
if let Some(tc) = &tor_config {
if tc.skip_send_attempt == Some(true) {
return Ok(None);
@@ -2604,7 +2603,7 @@ macro_rules! doctest_helper_setup_doc_env {
use uuid::Uuid;
// don't run on windows CI, which gives very inconsistent results
// don't run on Windows CI, which gives very inconsistent results
if cfg!(windows) {
return;
}
@@ -2624,17 +2623,16 @@ macro_rules! doctest_helper_setup_doc_env {
let node_client =
HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None).unwrap();
let mut wallet = Box::new(
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
)
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let mut wallet =
Box::new(DefaultWalletImpl::<HTTPNodeClient>::new(node_client.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let lc = wallet.lc_provider().unwrap();
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
lc.open_wallet(None, pw, false, false);
+21 -18
View File
@@ -272,6 +272,7 @@ pub trait OwnerRpc {
"stored_tx": null,
"ttl_cutoff_height": null,
"tx_slate_id": null,
"tx_slate_state": null,
"payment_proof": null,
"reverted_after": null,
"tx_type": "ConfirmedCoinbase"
@@ -294,6 +295,7 @@ pub trait OwnerRpc {
"payment_proof": null,
"reverted_after": null,
"tx_slate_id": null,
"tx_slate_state": null,
"tx_type": "ConfirmedCoinbase"
}
]
@@ -363,6 +365,7 @@ pub trait OwnerRpc {
"stored_tx": null,
"ttl_cutoff_height": null,
"tx_slate_id": null,
"tx_slate_state": null,
"payment_proof": null,
"reverted_after": null,
"tx_type": "ConfirmedCoinbase"
@@ -385,6 +388,7 @@ pub trait OwnerRpc {
"payment_proof": null,
"reverted_after": null,
"tx_slate_id": null,
"tx_slate_state": null,
"tx_type": "ConfirmedCoinbase"
}
]
@@ -2137,6 +2141,14 @@ where
VersionedSlate::into_version(out_slate, version)
}
fn tx_lock_outputs(&self, token: Token, in_slate: VersionedSlate) -> Result<(), Error> {
Owner::tx_lock_outputs(
self,
(&token.keychain_mask).as_ref(),
&Slate::from(in_slate),
)
}
fn finalize_tx(&self, token: Token, in_slate: VersionedSlate) -> Result<VersionedSlate, Error> {
let out_slate = Owner::finalize_tx(
self,
@@ -2147,11 +2159,12 @@ where
VersionedSlate::into_version(out_slate, version)
}
fn tx_lock_outputs(&self, token: Token, in_slate: VersionedSlate) -> Result<(), Error> {
Owner::tx_lock_outputs(
fn post_tx(&self, token: Token, slate: VersionedSlate, fluff: bool) -> Result<(), Error> {
Owner::post_tx(
self,
(&token.keychain_mask).as_ref(),
&Slate::from(in_slate),
&Slate::from(slate),
fluff,
)
}
@@ -2185,15 +2198,6 @@ where
}
}
fn post_tx(&self, token: Token, slate: VersionedSlate, fluff: bool) -> Result<(), Error> {
Owner::post_tx(
self,
(&token.keychain_mask).as_ref(),
&Slate::from(slate),
fluff,
)
}
fn get_rewind_hash(&self, token: Token) -> Result<String, Error> {
Owner::get_rewind_hash(self, (&token.keychain_mask).as_ref())
}
@@ -2625,26 +2629,25 @@ pub fn run_doctest_owner(
payment_proof_recipient_address: proof_address,
..Default::default()
};
let mut slate =
api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap();
let mut slate = api_impl::owner::init_send_tx(w, (&mask1).as_ref(), args, true).unwrap();
println!("INITIAL SLATE");
println!("{}", serde_json::to_string_pretty(&slate).unwrap());
{
let mut w_lock = wallet2.lock();
let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap();
slate = api_impl::foreign::receive_tx(&mut **w2, (&mask2).as_ref(), &slate, None, true)
.unwrap();
slate =
api_impl::foreign::receive_tx(w2, (&mask2).as_ref(), &slate, None, true).unwrap();
w2.close().unwrap();
}
// Spit out slate for input to finalize_tx
if lock_tx {
println!("LOCKING TX");
api_impl::owner::tx_lock_outputs(&mut **w, (&mask1).as_ref(), &slate).unwrap();
api_impl::owner::tx_lock_outputs(w, (&mask1).as_ref(), &slate).unwrap();
}
println!("RECEIPIENT SLATE");
println!("{}", serde_json::to_string_pretty(&slate).unwrap());
if finalize_tx {
slate = api_impl::owner::finalize_tx(&mut **w, (&mask1).as_ref(), &slate).unwrap();
slate = api_impl::owner::finalize_tx(w, (&mask1).as_ref(), &slate).unwrap();
error!("FINALIZED TX SLATE");
println!("{}", serde_json::to_string_pretty(&slate).unwrap());
}
+4 -4
View File
@@ -21,8 +21,8 @@ grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.4.0"
grin_util = "5.4.0"
#grin_core = "5.4.0"
#grin_util = "5.4.0"
# For beta release
@@ -30,8 +30,8 @@ grin_util = "5.4.0"
#grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing
# grin_core = { path = "../../grin/core"}
+12 -26
View File
@@ -39,10 +39,11 @@ grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.4.0"
grin_keychain = "5.4.0"
grin_util = "5.4.0"
grin_api = "5.4.0"
#grin_core = "5.4.0"
#grin_keychain = "5.4.0"
#grin_util = "5.4.0"
#grin_api = "5.4.0"
#grin_chain = "5.4.0"
# For beta release
@@ -50,39 +51,24 @@ grin_api = "5.4.0"
# grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_api = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_chain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing
# grin_core = { path = "../../grin/core"}
# grin_keychain = { path = "../../grin/keychain"}
# grin_util = { path = "../../grin/util"}
# grin_api = { path = "../../grin/api"}
# grin_chain = { path = "../../grin/chain"}
#####
[dev-dependencies]
ed25519-dalek = "1.0.0-pre.4"
remove_dir_all = "0.7"
##### Grin Imports
# For Release
grin_chain = "5.4.0"
# For beta release
# grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# For local testing
# grin_chain = { path = "../../grin/chain"}
#####
+6
View File
@@ -152,6 +152,7 @@ pub fn txs(
table.set_titles(row![
bMG->"Id",
bMG->"Type",
bMG->"State",
bMG->"Shared Transaction Id",
bMG->"Creation Time",
bMG->"TTL Cutoff Height",
@@ -174,6 +175,10 @@ pub fn txs(
Some(m) => format!("{}", m),
None => "None".to_owned(),
};
let slate_state = match t.tx_slate_state.as_ref() {
Some(m) => format!("{}", m),
None => "None".to_owned(),
};
let entry_type = format!("{}", t.tx_type);
let creation_ts = format!("{}", t.creation_ts.format("%Y-%m-%d %H:%M:%S"));
let ttl_cutoff_height = match t.ttl_cutoff_height {
@@ -220,6 +225,7 @@ pub fn txs(
table.add_row(row![
bFC->id,
bFC->entry_type,
bFC->slate_state,
bFC->slate_id,
bFB->creation_ts,
bFB->ttl_cutoff_height,
+5 -5
View File
@@ -98,7 +98,7 @@ pub fn create_wallet_proxy(
test_dir: &str,
) -> WalletProxy<
'_,
DefaultLCProvider<'_, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
> {
@@ -117,7 +117,7 @@ pub fn create_local_wallet(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -129,7 +129,7 @@ pub fn create_local_wallet(
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -156,7 +156,7 @@ pub fn open_local_wallet(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -168,7 +168,7 @@ pub fn open_local_wallet(
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+1 -1
View File
@@ -41,7 +41,7 @@ type Wallet = Arc<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+1 -1
View File
@@ -45,7 +45,7 @@ fn test_wallet_tx_filtering(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+4 -11
View File
@@ -55,24 +55,22 @@ the default client only supports single-user - single recipient, an arbitrary nu
### Wallet Traits
In the current code, a Wallet implementation is just a combination of these three traits. The vast majority of functions within libwallet
In the current code, a Wallet implementation is just a combination of these two traits. The vast majority of functions within libwallet
and libTX have a signature similar to the following:
```rust
pub fn retrieve_outputs<T: ?Sized, C, K>(
!·wallet: &mut T,
!·wallet: &mut WalletBackend<C, K>,
!·show_spent: bool,
!·tx_id: Option<u32>,
) -> Result<Vec<OutputData>, Error>
where
!·T: WalletBackend<C, K>,
!·C: NodeClient,
!·K: Keychain,
{
```
With `T` in this instance being a class that implements the `WalletBackend` trait, which is further parameterized with implementations of
`NodeClient` and `Keychain`.
Provided `WalletBackend` parameterized with implementations of `NodeClient` and `Keychain`.
There is currently only a single implementation of the Keychain trait within the Grin code, in the `keychain` crate exported as `ExtKeyChain`.
The `Keychain` trait makes several assumptions about the underlying implementation, particularly that it will adhere to a
@@ -81,9 +79,4 @@ The `Keychain` trait makes several assumptions about the underlying implementati
There are two implementations of `NodeClient` within the code, the main version being the `HTTPNodeClient` found within `wallet/src/client.rs` and
the seconds a test client that communicates with an in-process instance of a chain. The NodeClient isolates all network calls, so upgrading wallet
communication from the current simple http interaction to a more secure protocol (or allowing for many options) should be a simple
matter of dropping in different `NodeClient` implementations.
There are also two implementations of `WalletBackend` within the code at the base of the `wallet` crate. `LMDBBackend` found within
`wallet/src/lmdb_wallet.rs` is the main implementation, and is now used by all grin wallet commands. The earlier `FileWallet` still exists
within the code, however it is not invoked, and given there are no real advantages to running it over a DB implementation, development on it
has been dropped in favour of the LMDB implementation.
matter of dropping in different `NodeClient` implementations.
+12 -12
View File
@@ -43,12 +43,12 @@ grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.4.0"
grin_keychain = "5.4.0"
grin_chain = "5.4.0"
grin_util = "5.4.0"
grin_api = "5.4.0"
grin_store = "5.4.0"
#grin_core = "5.4.0"
#grin_keychain = "5.4.0"
#grin_chain = "5.4.0"
#grin_util = "5.4.0"
#grin_api = "5.4.0"
#grin_store = "5.4.0"
# For beta release
@@ -60,12 +60,12 @@ grin_store = "5.4.0"
# grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_chain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_api = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_store = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing
# grin_core = { path = "../../grin/core"}
-777
View File
@@ -1,777 +0,0 @@
// Copyright 2021 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.
use std::cell::RefCell;
use std::{fs, path};
// for writing stored transaction files
use std::fs::File;
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::path::Path;
use uuid::Uuid;
use crate::blake2::blake2b::{Blake2b, Blake2bResult};
use crate::keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchCommitmentType};
use crate::store::{self, option_to_not_found, to_key, to_key_u64};
use crate::core::core::Transaction;
use crate::core::ser;
use crate::libwallet::{
AcctPathMapping, Context, Error, NodeClient, OutputData, ScannedBlockInfo, TxLogEntry,
WalletBackend, WalletInitStatus, WalletOutputBatch,
};
use crate::util::secp::constants::SECRET_KEY_SIZE;
use crate::util::secp::key::SecretKey;
use crate::util::{self, secp, ToHex};
use rand::rngs::mock::StepRng;
use rand::thread_rng;
pub const DB_DIR: &str = "db";
pub const TX_SAVE_DIR: &str = "saved_txs";
const OUTPUT_PREFIX: u8 = b'o';
const DERIV_PREFIX: u8 = b'd';
const CONFIRMED_HEIGHT_PREFIX: u8 = b'c';
const PRIVATE_TX_CONTEXT_PREFIX: u8 = b'p';
const TX_LOG_ENTRY_PREFIX: u8 = b't';
const TX_LOG_ID_PREFIX: u8 = b'i';
const ACCOUNT_PATH_MAPPING_PREFIX: u8 = b'a';
const LAST_SCANNED_BLOCK: u8 = b'l';
const LAST_SCANNED_KEY: &str = "LAST_SCANNED_KEY";
const WALLET_INIT_STATUS: u8 = b'w';
const WALLET_INIT_STATUS_KEY: &str = "WALLET_INIT_STATUS";
/// test to see if database files exist in the current directory. If so,
/// use a DB backend for all operations
pub fn wallet_db_exists(data_file_dir: &str) -> bool {
let db_path = path::Path::new(data_file_dir).join(DB_DIR);
db_path.exists()
}
/// Helper to derive XOR keys for storing private transaction keys in the DB
/// (blind_xor_key, nonce_xor_key)
fn private_ctx_xor_keys<K>(
keychain: &K,
slate_id: &[u8],
) -> Result<([u8; SECRET_KEY_SIZE], [u8; SECRET_KEY_SIZE]), Error>
where
K: Keychain,
{
let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?;
// derive XOR values for storing secret values in DB
// h(root_key|slate_id|"blind")
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
hasher.update(&slate_id[..]);
hasher.update(&b"blind"[..]);
let blind_xor_key = hasher.finalize();
let mut ret_blind = [0; SECRET_KEY_SIZE];
ret_blind.copy_from_slice(&blind_xor_key.as_bytes()[0..SECRET_KEY_SIZE]);
// h(root_key|slate_id|"nonce")
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
hasher.update(&slate_id[..]);
hasher.update(&b"nonce"[..]);
let nonce_xor_key = hasher.finalize();
let mut ret_nonce = [0; SECRET_KEY_SIZE];
ret_nonce.copy_from_slice(&nonce_xor_key.as_bytes()[0..SECRET_KEY_SIZE]);
Ok((ret_blind, ret_nonce))
}
pub struct LMDBBackend<'ck, C, K>
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
db: store::Store,
data_file_dir: String,
/// Keychain
pub keychain: Option<K>,
/// Check value for XORed keychain seed
pub master_checksum: Box<Option<Blake2bResult>>,
/// Parent path to use by default for output operations
parent_key_id: Identifier,
/// wallet to node client
w2n_client: C,
///phantom
_phantom: &'ck PhantomData<C>,
}
impl<'ck, C, K> LMDBBackend<'ck, C, K>
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
pub fn new(data_file_dir: &str, n_client: C) -> Result<Self, Error> {
let db_path = path::Path::new(data_file_dir).join(DB_DIR);
fs::create_dir_all(&db_path).expect("Couldn't create wallet backend directory!");
let stored_tx_path = path::Path::new(data_file_dir).join(TX_SAVE_DIR);
fs::create_dir_all(&stored_tx_path)
.expect("Couldn't create wallet backend tx storage directory!");
let store = store::Store::new(db_path.to_str().unwrap(), None, Some(DB_DIR), None)?;
// Make sure default wallet derivation path always exists
// as well as path (so it can be retrieved by batches to know where to store
// completed transactions, for reference
let default_account = AcctPathMapping {
label: "default".to_owned(),
path: LMDBBackend::<C, K>::default_path(),
};
let acct_key = to_key(
ACCOUNT_PATH_MAPPING_PREFIX,
&mut default_account.label.as_bytes().to_vec(),
);
{
let batch = store.batch()?;
batch.put_ser(&acct_key, &default_account)?;
batch.commit()?;
}
let res = LMDBBackend {
db: store,
data_file_dir: data_file_dir.to_owned(),
keychain: None,
master_checksum: Box::new(None),
parent_key_id: LMDBBackend::<C, K>::default_path(),
w2n_client: n_client,
_phantom: &PhantomData,
};
Ok(res)
}
fn default_path() -> Identifier {
// return the default parent wallet path, corresponding to the default account
// in the BIP32 spec. Parent is account 0 at level 2, child output identifiers
// are all at level 3
ExtKeychain::derive_key_id(2, 0, 0, 0, 0)
}
/// Just test to see if database files exist in the current directory. If
/// so, use a DB backend for all operations
pub fn exists(data_file_dir: &str) -> bool {
let db_path = path::Path::new(data_file_dir).join(DB_DIR);
db_path.exists()
}
}
impl<'ck, C, K> WalletBackend<'ck, C, K> for LMDBBackend<'ck, C, K>
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
/// Set the keychain, which should already have been opened
fn set_keychain(
&mut self,
mut k: Box<K>,
mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, Error> {
// store hash of master key, so it can be verified later after unmasking
let root_key = k.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?;
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
self.master_checksum = Box::new(Some(hasher.finalize()));
let mask_value = {
match mask {
true => {
// Random value that must be XORed against the stored wallet seed
// before it is used
let mask_value = match use_test_rng {
true => {
let mut test_rng = StepRng::new(1_234_567_890_u64, 1);
secp::key::SecretKey::new(&k.secp(), &mut test_rng)
}
false => secp::key::SecretKey::new(&k.secp(), &mut thread_rng()),
};
k.mask_master_key(&mask_value)?;
Some(mask_value)
}
false => None,
}
};
self.keychain = Some(*k);
Ok(mask_value)
}
/// Close wallet
fn close(&mut self) -> Result<(), Error> {
self.keychain = None;
Ok(())
}
/// Return the keychain being used, cloned with XORed token value
/// for temporary use
fn keychain(&self, mask: Option<&SecretKey>) -> Result<K, Error> {
match self.keychain.as_ref() {
Some(k) => {
let mut k_masked = k.clone();
if let Some(m) = mask {
k_masked.mask_master_key(m)?;
}
// Check if master seed is what is expected (especially if it's been xored)
let root_key =
k_masked.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?;
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
if *self.master_checksum != Some(hasher.finalize()) {
error!("Supplied keychain mask is invalid");
return Err(Error::InvalidKeychainMask);
}
Ok(k_masked)
}
None => Err(Error::KeychainDoesntExist),
}
}
/// Return the node client being used
fn w2n_client(&mut self) -> &mut C {
&mut self.w2n_client
}
/// return the version of the commit for caching
fn calc_commit_for_cache(
&mut self,
keychain_mask: Option<&SecretKey>,
amount: u64,
id: &Identifier,
) -> Result<Option<String>, Error> {
//TODO: Check if this is really necessary, it's the only thing
//preventing removing the need for config in the wallet backend
/*if self.config.no_commit_cache == Some(true) {
Ok(None)
} else {*/
Ok(Some(
self.keychain(keychain_mask)?
.commit(amount, &id, SwitchCommitmentType::Regular)?
.0
.to_vec()
.to_hex(), // TODO: proper support for different switch commitment schemes
))
/*}*/
}
/// Set parent path by account name
fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> {
let label = label.to_owned();
let res = self.acct_path_iter().find(|l| l.label == label);
if let Some(a) = res {
self.set_parent_key_id(a.path);
Ok(())
} else {
Err(Error::UnknownAccountLabel(label))
}
}
/// set parent path
fn set_parent_key_id(&mut self, id: Identifier) {
self.parent_key_id = id;
}
fn parent_key_id(&mut self) -> Identifier {
self.parent_key_id.clone()
}
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = match mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
None => to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()),
};
option_to_not_found(self.db.get_ser(&key, None), || format!("Key Id: {}", id))
.map_err(|e| e.into())
}
// TODO - fix this awkward conversion between PrefixIterator and our Box<dyn Iterator>
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self.db.iter(&[OUTPUT_PREFIX], move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let iter = prefix_iter.expect("deserialize").into_iter();
Box::new(iter)
}
fn get_tx_log_entry(&self, u: &Uuid) -> Result<Option<TxLogEntry>, Error> {
let key = to_key(TX_LOG_ENTRY_PREFIX, &mut u.as_bytes().to_vec());
self.db.get_ser(&key, None).map_err(|e| e.into())
}
// TODO - fix this awkward conversion between PrefixIterator and our Box<dyn Iterator>
fn tx_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = TxLogEntry> + 'a> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self.db.iter(&[TX_LOG_ENTRY_PREFIX], move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let iter = prefix_iter.expect("deserialize").into_iter();
Box::new(iter)
}
fn get_private_context(
&mut self,
keychain_mask: Option<&SecretKey>,
slate_id: &[u8],
) -> Result<Context, Error> {
let ctx_key = to_key_u64(PRIVATE_TX_CONTEXT_PREFIX, &mut slate_id.to_vec(), 0);
let (blind_xor_key, nonce_xor_key) =
private_ctx_xor_keys(&self.keychain(keychain_mask)?, slate_id)?;
let mut ctx: Context = option_to_not_found(self.db.get_ser(&ctx_key, None), || {
format!("Slate id: {:x?}", slate_id.to_vec())
})?;
for i in 0..SECRET_KEY_SIZE {
ctx.sec_key.0[i] ^= blind_xor_key[i];
ctx.sec_nonce.0[i] ^= nonce_xor_key[i];
}
Ok(ctx)
}
// TODO - fix this awkward conversion between PrefixIterator and our Box<dyn Iterator>
fn acct_path_iter<'a>(&'a self) -> Box<dyn Iterator<Item = AcctPathMapping> + 'a> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self
.db
.iter(&[ACCOUNT_PATH_MAPPING_PREFIX], move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let iter = prefix_iter.expect("deserialize").into_iter();
Box::new(iter)
}
fn get_acct_path(&self, label: String) -> Result<Option<AcctPathMapping>, Error> {
let acct_key = to_key(ACCOUNT_PATH_MAPPING_PREFIX, &mut label.as_bytes().to_vec());
self.db.get_ser(&acct_key, None).map_err(|e| e.into())
}
fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error> {
let filename = format!("{}.grintx", uuid);
let path = path::Path::new(&self.data_file_dir)
.join(TX_SAVE_DIR)
.join(filename);
let path_buf = Path::new(&path).to_path_buf();
let mut stored_tx = File::create(path_buf)?;
let tx_hex = ser::ser_vec(tx, ser::ProtocolVersion(1)).unwrap().to_hex();
stored_tx.write_all(&tx_hex.as_bytes())?;
stored_tx.sync_all()?;
Ok(())
}
fn get_stored_tx(&self, uuid: &str) -> Result<Option<Transaction>, Error> {
let filename = format!("{}.grintx", uuid);
let path = path::Path::new(&self.data_file_dir)
.join(TX_SAVE_DIR)
.join(filename);
let tx_file = Path::new(&path).to_path_buf();
let mut tx_f = File::open(tx_file)?;
let mut content = String::new();
tx_f.read_to_string(&mut content)?;
let tx_bin = util::from_hex(&content).unwrap();
Ok(Some(
ser::deserialize(
&mut &tx_bin[..],
ser::ProtocolVersion(1),
ser::DeserializationMode::default(),
)
.unwrap(),
))
}
fn batch<'a>(
&'a mut self,
keychain_mask: Option<&SecretKey>,
) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error> {
Ok(Box::new(Batch {
_store: self,
db: RefCell::new(Some(self.db.batch()?)),
keychain: Some(self.keychain(keychain_mask)?),
}))
}
fn batch_no_mask<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error> {
Ok(Box::new(Batch {
_store: self,
db: RefCell::new(Some(self.db.batch()?)),
keychain: None,
}))
}
fn current_child_index<'a>(&mut self, parent_key_id: &Identifier) -> Result<u32, Error> {
let index = {
let batch = self.db.batch()?;
let deriv_key = to_key(DERIV_PREFIX, &mut parent_key_id.to_bytes().to_vec());
match batch.get_ser(&deriv_key, None)? {
Some(idx) => idx,
None => 0,
}
};
Ok(index)
}
fn next_child<'a>(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error> {
let parent_key_id = self.parent_key_id.clone();
let mut deriv_idx = {
let batch = self.db.batch()?;
let deriv_key = to_key(DERIV_PREFIX, &mut self.parent_key_id.to_bytes().to_vec());
match batch.get_ser(&deriv_key, None)? {
Some(idx) => idx,
None => 0,
}
};
let mut return_path = self.parent_key_id.to_path();
return_path.depth += 1;
return_path.path[return_path.depth as usize - 1] = ChildNumber::from(deriv_idx);
deriv_idx += 1;
let mut batch = self.batch(keychain_mask)?;
batch.save_child_index(&parent_key_id, deriv_idx)?;
batch.commit()?;
Ok(Identifier::from_path(&return_path))
}
fn last_confirmed_height<'a>(&mut self) -> Result<u64, Error> {
let batch = self.db.batch()?;
let height_key = to_key(
CONFIRMED_HEIGHT_PREFIX,
&mut self.parent_key_id.to_bytes().to_vec(),
);
let last_confirmed_height = match batch.get_ser(&height_key, None)? {
Some(h) => h,
None => 0,
};
Ok(last_confirmed_height)
}
fn last_scanned_block<'a>(&mut self) -> Result<ScannedBlockInfo, Error> {
let batch = self.db.batch()?;
let scanned_block_key = to_key(
LAST_SCANNED_BLOCK,
&mut LAST_SCANNED_KEY.as_bytes().to_vec(),
);
let last_scanned_block = match batch.get_ser(&scanned_block_key, None)? {
Some(b) => b,
None => ScannedBlockInfo {
height: 0,
hash: "".to_owned(),
start_pmmr_index: 0,
last_pmmr_index: 0,
},
};
Ok(last_scanned_block)
}
fn init_status<'a>(&mut self) -> Result<WalletInitStatus, Error> {
let batch = self.db.batch()?;
let init_status_key = to_key(
WALLET_INIT_STATUS,
&mut WALLET_INIT_STATUS_KEY.as_bytes().to_vec(),
);
let status = match batch.get_ser(&init_status_key, None)? {
Some(s) => s,
None => WalletInitStatus::InitComplete,
};
Ok(status)
}
}
/// An atomic batch in which all changes can be committed all at once or
/// discarded on error.
pub struct Batch<'a, C, K>
where
C: NodeClient,
K: Keychain,
{
_store: &'a LMDBBackend<'a, C, K>,
db: RefCell<Option<store::Batch<'a>>>,
/// Keychain
keychain: Option<K>,
}
#[allow(missing_docs)]
impl<'a, C, K> WalletOutputBatch<K> for Batch<'a, C, K>
where
C: NodeClient,
K: Keychain,
{
fn keychain(&mut self) -> &mut K {
self.keychain.as_mut().unwrap()
}
fn save(&mut self, out: OutputData) -> Result<(), Error> {
// Save the output data to the db.
{
let key = match out.mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec(), i),
None => to_key(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec()),
};
self.db.borrow().as_ref().unwrap().put_ser(&key, &out)?;
}
Ok(())
}
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = match mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
None => to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()),
};
option_to_not_found(
self.db.borrow().as_ref().unwrap().get_ser(&key, None),
|| format!("Key ID: {}", id),
)
.map_err(|e| e.into())
}
// TODO - fix this awkward conversion between PrefixIterator and our Box<dyn Iterator>
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>> {
let db = self.db.borrow();
let db = db.as_ref().unwrap();
let protocol_version = db.protocol_version();
let prefix_iter = db.iter(&[OUTPUT_PREFIX], move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let iter = prefix_iter.expect("deserialize").into_iter();
Box::new(iter)
}
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error> {
// Delete the output data.
{
let key = match mmr_index {
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
None => to_key(OUTPUT_PREFIX, &mut id.to_bytes().to_vec()),
};
let _ = self.db.borrow().as_ref().unwrap().delete(&key);
}
Ok(())
}
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error> {
let tx_id_key = to_key(TX_LOG_ID_PREFIX, &mut parent_key_id.to_bytes().to_vec());
let last_tx_log_id = match self
.db
.borrow()
.as_ref()
.unwrap()
.get_ser(&tx_id_key, None)?
{
Some(t) => t,
None => 0,
};
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&tx_id_key, &(last_tx_log_id + 1))?;
Ok(last_tx_log_id)
}
// TODO - fix this awkward conversion between PrefixIterator and our Box<dyn Iterator>
fn tx_log_iter(&self) -> Box<dyn Iterator<Item = TxLogEntry>> {
let db = self.db.borrow();
let db = db.as_ref().unwrap();
let protocol_version = db.protocol_version();
let prefix_iter = db.iter(&[TX_LOG_ENTRY_PREFIX], move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let iter = prefix_iter.expect("deserialize").into_iter();
Box::new(iter)
}
fn save_last_confirmed_height(
&mut self,
parent_key_id: &Identifier,
height: u64,
) -> Result<(), Error> {
let height_key = to_key(
CONFIRMED_HEIGHT_PREFIX,
&mut parent_key_id.to_bytes().to_vec(),
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&height_key, &height)?;
Ok(())
}
fn save_last_scanned_block(&mut self, block_info: ScannedBlockInfo) -> Result<(), Error> {
let pmmr_index_key = to_key(
LAST_SCANNED_BLOCK,
&mut LAST_SCANNED_KEY.as_bytes().to_vec(),
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&pmmr_index_key, &block_info)?;
Ok(())
}
fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error> {
let init_status_key = to_key(
WALLET_INIT_STATUS,
&mut WALLET_INIT_STATUS_KEY.as_bytes().to_vec(),
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&init_status_key, &value)?;
Ok(())
}
fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> {
let deriv_key = to_key(DERIV_PREFIX, &mut parent_id.to_bytes().to_vec());
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&deriv_key, &child_n)?;
Ok(())
}
fn save_tx_log_entry(
&mut self,
tx_in: TxLogEntry,
parent_id: &Identifier,
) -> Result<(), Error> {
let tx_log_key = to_key_u64(
TX_LOG_ENTRY_PREFIX,
&mut parent_id.to_bytes().to_vec(),
tx_in.id as u64,
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&tx_log_key, &tx_in)?;
Ok(())
}
fn delete_tx_log_entry(&mut self, tx_id: u32, parent_id: &Identifier) -> Result<(), Error> {
let tx_log_key = to_key_u64(
TX_LOG_ENTRY_PREFIX,
&mut parent_id.to_bytes().to_vec(),
tx_id as u64,
);
self.db.borrow().as_ref().unwrap().delete(&tx_log_key)?;
Ok(())
}
fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error> {
let acct_key = to_key(
ACCOUNT_PATH_MAPPING_PREFIX,
&mut mapping.label.as_bytes().to_vec(),
);
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&acct_key, &mapping)?;
Ok(())
}
// TODO - fix this awkward conversion between PrefixIterator and our Box<dyn Iterator>
fn acct_path_iter(&self) -> Box<dyn Iterator<Item = AcctPathMapping>> {
let db = self.db.borrow();
let db = db.as_ref().unwrap();
let protocol_version = db.protocol_version();
let prefix_iter = db.iter(&[ACCOUNT_PATH_MAPPING_PREFIX], move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let iter = prefix_iter.expect("deserialize").into_iter();
Box::new(iter)
}
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error> {
out.lock();
self.save(out.clone())
}
fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error> {
let ctx_key = to_key_u64(PRIVATE_TX_CONTEXT_PREFIX, &mut slate_id.to_vec(), 0);
let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?;
let mut s_ctx = ctx.clone();
for i in 0..SECRET_KEY_SIZE {
s_ctx.sec_key.0[i] ^= blind_xor_key[i];
s_ctx.sec_nonce.0[i] ^= nonce_xor_key[i];
}
self.db
.borrow()
.as_ref()
.unwrap()
.put_ser(&ctx_key, &s_ctx)?;
Ok(())
}
fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error> {
let ctx_key = to_key_u64(PRIVATE_TX_CONTEXT_PREFIX, &mut slate_id.to_vec(), 0);
self.db
.borrow()
.as_ref()
.unwrap()
.delete(&ctx_key)
.map_err(|e| e.into())
}
fn commit(&self) -> Result<(), Error> {
let db = self.db.replace(None);
db.unwrap().commit()?;
Ok(())
}
}
-17
View File
@@ -1,17 +0,0 @@
// Copyright 2021 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.
mod lmdb;
pub use self::lmdb::{wallet_db_exists, LMDBBackend};
+9 -16
View File
@@ -28,14 +28,12 @@ use grin_api as api;
use grin_chain as chain;
use grin_core as core;
use grin_keychain as keychain;
use grin_store as store;
use grin_util as util;
use grin_wallet_libwallet as libwallet;
use grin_wallet_config as config;
mod adapters;
mod backends;
mod client_utils;
mod error;
mod lifecycle;
@@ -47,7 +45,6 @@ pub use crate::adapters::{
HttpSlateSender, PathToSlate, PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver,
SlateSender,
};
pub use crate::backends::{wallet_db_exists, LMDBBackend};
pub use crate::error::Error;
pub use crate::lifecycle::DefaultLCProvider;
pub use crate::node_clients::HTTPNodeClient;
@@ -57,35 +54,31 @@ use crate::keychain::{ExtKeychain, Keychain};
use libwallet::{NodeClient, WalletInst, WalletLCProvider};
/// Main wallet instance
pub struct DefaultWalletImpl<'a, C>
pub struct DefaultWalletImpl<C>
where
C: NodeClient + 'a,
C: NodeClient,
{
lc_provider: DefaultLCProvider<'a, C, ExtKeychain>,
lc_provider: DefaultLCProvider<C, ExtKeychain>,
}
impl<'a, C> DefaultWalletImpl<'a, C>
impl<C> DefaultWalletImpl<C>
where
C: NodeClient + 'a,
C: NodeClient,
{
pub fn new(node_client: C) -> Result<Self, Error> {
let lc_provider = DefaultLCProvider::new(node_client);
Ok(DefaultWalletImpl {
lc_provider: lc_provider,
})
Ok(DefaultWalletImpl { lc_provider })
}
}
impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl<'a, C>
impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl<C>
where
DefaultLCProvider<'a, C, ExtKeychain>: WalletLCProvider<'a, C, K>,
DefaultLCProvider<C, ExtKeychain>: WalletLCProvider<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn lc_provider(
&mut self,
) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), libwallet::Error> {
fn lc_provider(&mut self) -> Result<&mut dyn WalletLCProvider<'a, C, K>, libwallet::Error> {
Ok(&mut self.lc_provider)
}
}
+19 -21
View File
@@ -23,13 +23,12 @@ use crate::libwallet::{Error, NodeClient, WalletBackend, WalletInitStatus, Walle
use crate::lifecycle::seed::WalletSeed;
use crate::util::secp::key::SecretKey;
use crate::util::ZeroingString;
use crate::LMDBBackend;
use grin_util::logger::LoggingConfig;
use std::fs;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
// Helper fuction to format paths according to OS, avoids bugs on Linux
/// Helper function to format paths according to OS, avoids bugs on Linux
pub fn fmt_path(path: String) -> String {
let sep = &MAIN_SEPARATOR.to_string();
let path = path.replace("/", &sep);
@@ -37,20 +36,20 @@ pub fn fmt_path(path: String) -> String {
path
}
pub struct DefaultLCProvider<'a, C, K>
pub struct DefaultLCProvider<C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
data_dir: String,
node_client: C,
backend: Option<Box<dyn WalletBackend<'a, C, K> + 'a>>,
backend: Option<WalletBackend<C, K>>,
}
impl<'a, C, K> DefaultLCProvider<'a, C, K>
impl<C, K> DefaultLCProvider<C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
/// Create new provider
pub fn new(node_client: C) -> Self {
@@ -62,7 +61,7 @@ where
}
}
impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider<'a, C, K>
impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider<C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
@@ -217,8 +216,8 @@ where
Error::Lifecycle("Error creating wallet seed (is mnemonic valid?)".to_owned())
})?;
info!("Wallet seed file created");
let mut wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
let mut wallet: WalletBackend<C, K> =
match WalletBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error creating wallet: {}, Data Dir: {}", e, &data_dir_name);
error!("{}", msg);
@@ -247,8 +246,8 @@ where
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = fmt_path(data_dir_name.to_str().unwrap().to_string());
let mut wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
let mut wallet: WalletBackend<C, K> =
match WalletBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error opening wallet: {}, Data Dir: {}", e, &data_dir_name);
return Err(Error::Lifecycle(msg));
@@ -262,8 +261,8 @@ where
.derive_keychain(global::is_testnet())
.map_err(|_| Error::Lifecycle("Error deriving keychain".to_owned()))?;
let mask = wallet.set_keychain(Box::new(keychain), create_mask, use_test_rng)?;
self.backend = Some(Box::new(wallet));
let mask = wallet.set_keychain(keychain, create_mask, use_test_rng)?;
self.backend = Some(wallet);
Ok(mask)
}
@@ -329,8 +328,7 @@ where
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
// get seed for later check
// Get seed for later check
let orig_wallet_seed = WalletSeed::from_file(&data_dir_name, old)
.map_err(|_| Error::Lifecycle("Error opening wallet seed file".into()))?;
let orig_mnemonic = orig_wallet_seed
@@ -366,7 +364,7 @@ where
.to_string();
return Err(Error::Lifecycle(msg));
}
// Removin
// Removing old file
info!("Password change confirmed, removing old seed file.");
fs::remove_file(backup_name).map_err(|e| Error::IO(e.to_string()))?;
@@ -383,13 +381,13 @@ where
Ok(())
}
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error> {
fn wallet_inst(&mut self) -> Result<&mut WalletBackend<C, K>, Error> {
match self.backend.as_mut() {
None => {
let msg = "Wallet has not been opened".into();
Err(Error::Lifecycle(msg))
}
Some(_) => Ok(&mut *self.backend.as_mut().unwrap()),
Some(b) => Ok(b),
}
}
}
+7 -13
View File
@@ -25,6 +25,7 @@ use ring::aead;
use ring::pbkdf2;
use crate::keychain::{mnemonic, Keychain};
use crate::lifecycle::default::fmt_path;
use crate::util::{self, ToHex};
use crate::Error;
@@ -56,13 +57,6 @@ impl WalletSeed {
self.0.to_vec().to_hex()
}
// Helper fuction to format paths according to OS, avoids bugs on Linux
pub fn fmt_path(path: String) -> String {
let sep = &MAIN_SEPARATOR.to_string();
let path = path.replace("/", &sep);
let path = path.replace("\\", &sep);
path
}
pub fn to_mnemonic(&self) -> Result<String, Error> {
let result = mnemonic::from_entropy(&self.0);
match result {
@@ -105,7 +99,7 @@ impl WalletSeed {
pub fn seed_file_exists(data_file_dir: &str) -> Result<bool, Error> {
let seed_file_path = &format!(
"{}{}{}",
Self::fmt_path(data_file_dir.to_string()),
fmt_path(data_file_dir.to_string()),
MAIN_SEPARATOR,
SEED_FILE,
);
@@ -145,7 +139,7 @@ impl WalletSeed {
) -> Result<(), Error> {
let seed_file_path = &format!(
"{}{}{}",
Self::fmt_path(data_file_dir.to_string()),
fmt_path(data_file_dir.to_string()),
MAIN_SEPARATOR,
SEED_FILE,
);
@@ -184,11 +178,11 @@ impl WalletSeed {
let seed_file_path = &format!(
"{}{}{}",
Self::fmt_path(data_file_dir.to_string()),
fmt_path(data_file_dir.to_string()),
MAIN_SEPARATOR,
SEED_FILE,
);
let data_file_dir = Self::fmt_path(data_file_dir.to_string());
let data_file_dir = fmt_path(data_file_dir.to_string());
warn!("Generating wallet seed file at: {}", seed_file_path);
let exists = WalletSeed::seed_file_exists(&data_file_dir)?;
if exists && !test_mode {
@@ -219,7 +213,7 @@ impl WalletSeed {
let seed_file_path = &format!(
"{}{}{}",
Self::fmt_path(data_file_dir.to_string()),
fmt_path(data_file_dir.to_string()),
MAIN_SEPARATOR,
SEED_FILE,
);
@@ -247,7 +241,7 @@ impl WalletSeed {
pub fn delete_seed_file(data_file_dir: &str) -> Result<(), Error> {
let seed_file_path = &format!(
"{}{}{}",
Self::fmt_path(data_file_dir.to_string()),
fmt_path(data_file_dir.to_string()),
MAIN_SEPARATOR,
SEED_FILE,
);
+17 -17
View File
@@ -18,7 +18,6 @@ use crate::chain::Chain;
use crate::core;
use crate::core::core::{Output, Transaction, TxKernel};
use crate::core::{consensus, global, pow};
use crate::keychain;
use crate::libwallet;
use crate::libwallet::api_impl::{foreign, owner};
use crate::libwallet::{
@@ -28,6 +27,7 @@ use crate::util::secp::key::SecretKey;
use crate::util::secp::pedersen;
use crate::util::Mutex;
use chrono::Duration;
use grin_keychain::Keychain;
use std::sync::Arc;
use std::thread;
@@ -36,7 +36,7 @@ mod testclient;
pub use self::{testclient::LocalWalletClient, testclient::WalletProxy};
/// Get an output from the chain locally and present it back as an API output
fn get_output_local(chain: &chain::Chain, commit: pedersen::Commitment) -> Option<api::Output> {
fn get_output_local(chain: &Chain, commit: pedersen::Commitment) -> Option<api::Output> {
if chain.get_unspent(commit).unwrap().is_some() {
let block_height = chain.get_header_for_output(commit).unwrap().height;
let output_pos = chain.get_output_pos(&commit).unwrap_or(0);
@@ -48,7 +48,7 @@ fn get_output_local(chain: &chain::Chain, commit: pedersen::Commitment) -> Optio
/// Get a kernel from the chain locally
fn get_kernel_local(
chain: Arc<chain::Chain>,
chain: Arc<Chain>,
excess: &pedersen::Commitment,
min_height: Option<u64>,
max_height: Option<u64>,
@@ -65,7 +65,7 @@ fn get_kernel_local(
/// get output listing traversing pmmr from local
fn get_outputs_by_pmmr_index_local(
chain: Arc<chain::Chain>,
chain: Arc<Chain>,
start_index: u64,
end_index: Option<u64>,
max: u64,
@@ -86,7 +86,7 @@ fn get_outputs_by_pmmr_index_local(
/// get output listing in a given block range
fn height_range_to_pmmr_indices_local(
chain: Arc<chain::Chain>,
chain: Arc<Chain>,
start_index: u64,
end_index: Option<u64>,
) -> api::OutputListing {
@@ -147,13 +147,13 @@ pub fn create_block_for_wallet<'a, L, C, K>(
chain: &Chain,
prev: core::core::BlockHeader,
txs: &[Transaction],
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
) -> Result<core::core::Block, libwallet::Error>
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
K: Keychain + 'a,
{
// build block fees
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
@@ -166,7 +166,7 @@ where
let coinbase_tx = {
let mut w_lock = wallet.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
foreign::build_coinbase(&mut **w, keychain_mask, &block_fees, false)?
foreign::build_coinbase(w, keychain_mask, &block_fees, false)?
};
let block = create_block_with_reward(chain, prev, txs, coinbase_tx.output, coinbase_tx.kernel);
Ok(block)
@@ -178,13 +178,13 @@ where
pub fn award_block_to_wallet<'a, L, C, K>(
chain: &Chain,
txs: &[Transaction],
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
) -> Result<(), libwallet::Error>
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
K: Keychain + 'a,
{
let prev = chain.head_header().unwrap();
let block = create_block_for_wallet(chain, prev, txs, wallet, keychain_mask)?;
@@ -200,7 +200,7 @@ pub fn process_block(chain: &Chain, block: core::core::Block) {
/// Award a blocks to a wallet directly
pub fn award_blocks_to_wallet<'a, L, C, K>(
chain: &Chain,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
number: usize,
pause_between: bool,
@@ -208,7 +208,7 @@ pub fn award_blocks_to_wallet<'a, L, C, K>(
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
K: Keychain + 'a,
{
for _ in 0..number {
award_block_to_wallet(chain, &[], wallet.clone(), keychain_mask)?;
@@ -231,7 +231,7 @@ pub fn send_to_dest<'a, L, C, K>(
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
K: Keychain + 'a,
{
let slate = {
let mut w_lock = wallet.lock();
@@ -245,10 +245,10 @@ where
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = owner::init_send_tx(&mut **w, keychain_mask, args, test_mode)?;
let slate_i = owner::init_send_tx(w, keychain_mask, args, test_mode)?;
let slate = client.send_tx_slate_direct(dest, &slate_i)?;
owner::tx_lock_outputs(&mut **w, keychain_mask, &slate)?;
owner::finalize_tx(&mut **w, keychain_mask, &slate)?
owner::tx_lock_outputs(w, keychain_mask, &slate)?;
owner::finalize_tx(w, keychain_mask, &slate)?
};
let client = {
let mut w_lock = wallet.lock();
@@ -267,7 +267,7 @@ pub fn wallet_info<'a, L, C, K>(
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
K: Keychain + 'a,
{
let (wallet_refreshed, wallet_info) =
owner::retrieve_summary_info(wallet, keychain_mask, &None, true, 1)?;
+6 -6
View File
@@ -70,7 +70,7 @@ where
String,
(
Sender<WalletProxyMessage>,
Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
Option<SecretKey>,
),
>,
@@ -100,14 +100,15 @@ where
genesis_block,
pow::verify_size,
false,
None,
)
.unwrap();
let (tx, rx) = channel();
WalletProxy {
chain_dir: chain_dir.to_owned(),
chain: Arc::new(c),
tx: tx,
rx: rx,
tx,
rx,
wallets: HashMap::new(),
running: Arc::new(AtomicBool::new(false)),
}
@@ -118,7 +119,7 @@ where
&mut self,
addr: &str,
tx: Sender<WalletProxyMessage>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
) {
self.wallets
@@ -218,8 +219,7 @@ where
let w = w_lock.lc_provider()?.wallet_inst()?;
let mask = wallet.2.clone();
// receive tx
match foreign::receive_tx(&mut **w, (&mask).as_ref(), &Slate::from(slate), None, false)
{
match foreign::receive_tx(w, (&mask).as_ref(), &Slate::from(slate), None, false) {
Err(e) => {
return Ok(WalletProxyMessage {
sender_id: m.dest,
+9 -9
View File
@@ -46,10 +46,10 @@ grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" }
##### Grin Imports
# For Release
grin_core = "5.4.0"
grin_keychain = "5.4.0"
grin_util = "5.4.0"
grin_store = "5.4.0"
#grin_core = "5.4.0"
#grin_keychain = "5.4.0"
#grin_util = "5.4.0"
#grin_store = "5.4.0"
# For beta release
@@ -59,10 +59,10 @@ grin_store = "5.4.0"
# grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
# grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
grin_store = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing
# grin_core = { path = "../../grin/core"}
@@ -70,4 +70,4 @@ grin_store = "5.4.0"
# grin_util = { path = "../../grin/util"}
# grin_store = { path = "../../grin/store"}
#####
#####
+70 -29
View File
@@ -13,9 +13,13 @@
// limitations under the License.
//! Generic implementation of owner API functions
use grin_keychain::Identifier;
use strum::IntoEnumIterator;
use super::owner::tx_lock_outputs;
use crate::api_impl::owner::{check_ttl, post_tx};
use crate::backend::WalletBackend;
use crate::grin_core::core::FeeFields;
use crate::grin_keychain::Keychain;
use crate::grin_util::secp::key::SecretKey;
@@ -23,11 +27,8 @@ use crate::internal::{selection, tx, updater};
use crate::slate_versions::SlateVersion;
use crate::{
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
WalletBackend,
};
use super::owner::tx_lock_outputs;
const FOREIGN_API_VERSION: u16 = 2;
/// Return the version info
@@ -39,32 +40,30 @@ pub fn check_version() -> VersionInfo {
}
/// Build a coinbase transaction
pub fn build_coinbase<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn build_coinbase<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
block_fees: &BlockFees,
test_mode: bool,
) -> Result<CbData, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
updater::build_coinbase(&mut *w, keychain_mask, block_fees, test_mode)
updater::build_coinbase(w, keychain_mask, block_fees, test_mode)
}
/// Receive a tx as recipient
pub fn receive_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn receive_tx<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
dest_acct_name: Option<&str>,
use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut ret_slate = slate.clone();
check_ttl(w, &ret_slate)?;
@@ -80,7 +79,7 @@ where
};
// Don't do this multiple times
let tx = updater::retrieve_txs(
&mut *w,
w,
None,
Some(ret_slate.id),
None,
@@ -99,7 +98,7 @@ where
let keychain = w.keychain(keychain_mask)?;
let context = tx::add_output_to_slate(
&mut *w,
w,
keychain_mask,
&mut ret_slate,
height,
@@ -127,22 +126,23 @@ where
ret_slate.amount = 0;
ret_slate.fee_fields = FeeFields::zero();
ret_slate.remove_other_sigdata(&keychain, &context.sec_nonce, &context.sec_key)?;
ret_slate.state = SlateState::Standard2;
update_tx_slate_state(w, keychain_mask, &parent_key_id, &ret_slate)?;
Ok(ret_slate)
}
/// Receive a tx that this wallet has issued
pub fn finalize_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn finalize_tx<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
post_automatically: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut sl = slate.clone();
let mut context = w.get_private_context(keychain_mask, sl.id.as_bytes())?;
@@ -154,10 +154,10 @@ where
let mut temp_ctx = context.clone();
temp_ctx.sec_key = context.initial_sec_key.clone();
temp_ctx.sec_nonce = context.initial_sec_nonce.clone();
selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &temp_ctx, false)?;
selection::repopulate_tx(w, keychain_mask, &mut sl, &temp_ctx, false)?;
tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?;
tx::update_stored_tx(&mut *w, keychain_mask, &context, &mut sl, true)?;
tx::complete_tx(w, keychain_mask, &mut sl, &context)?;
tx::update_stored_tx(w, keychain_mask, &context, &mut sl, true)?;
{
let mut batch = w.batch(keychain_mask)?;
batch.delete_private_context(sl.id.as_bytes())?;
@@ -165,6 +165,9 @@ where
}
sl.state = SlateState::Invoice3;
sl.amount = 0;
let parent_key_id = w.parent_key_id();
update_tx_slate_state(w, keychain_mask, &parent_key_id, &sl)?;
} else if sl.state == SlateState::Standard2 {
let keychain = w.keychain(keychain_mask)?;
let parent_key_id = w.parent_key_id();
@@ -175,7 +178,7 @@ where
let current_height = w.w2n_client().get_chain_tip()?.0;
let mut temp_sl =
tx::new_tx_slate(&mut *w, context.amount, false, 2, false, args.ttl_blocks)?;
tx::new_tx_slate(w, context.amount, false, 2, false, args.ttl_blocks)?;
let temp_context = selection::build_send_tx(
w,
&keychain,
@@ -211,11 +214,11 @@ where
// Add our contribution to the offset
sl.adjust_offset(&keychain, &context)?;
selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &context, true)?;
selection::repopulate_tx(w, keychain_mask, &mut sl, &context, true)?;
tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?;
tx::verify_slate_payment_proof(&mut *w, keychain_mask, &parent_key_id, &context, &sl)?;
tx::update_stored_tx(&mut *w, keychain_mask, &context, &sl, false)?;
tx::complete_tx(w, keychain_mask, &mut sl, &context)?;
tx::verify_slate_payment_proof(w, keychain_mask, &parent_key_id, &context, &sl)?;
tx::update_stored_tx(w, keychain_mask, &context, &sl, false)?;
{
let mut batch = w.batch(keychain_mask)?;
batch.delete_private_context(sl.id.as_bytes())?;
@@ -223,6 +226,8 @@ where
}
sl.state = SlateState::Standard3;
sl.amount = 0;
update_tx_slate_state(w, keychain_mask, &parent_key_id, &sl)?;
} else {
return Err(Error::SlateState);
}
@@ -231,3 +236,39 @@ where
}
Ok(sl)
}
/// Update transaction slate state.
fn update_tx_slate_state<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
slate: &Slate,
) -> Result<(), Error>
where
C: NodeClient,
K: Keychain,
{
let mut bad_records = 0;
let tx = wallet
.tx_log_iter()?
.filter(|tx| {
if tx.is_err() {
bad_records += 1;
}
tx.is_ok()
})
.map(|tx| tx.unwrap())
.find(|tx| tx.tx_slate_id == Some(slate.id));
if let Some(mut tx) = tx {
let mut batch = wallet.batch(keychain_mask)?;
tx.tx_slate_state = Some(slate.state.clone());
batch.save_tx_log_entry(tx.clone(), parent_key_id)?;
batch.commit()?;
} else {
return Err(Error::Backend(format!(
"Tx log entry with slate id {} not found, there are {} bad tx log records",
slate.id, bad_records
)));
}
Ok(())
}
+95 -126
View File
@@ -31,15 +31,14 @@ use crate::api_impl::owner_updater::StatusMessage;
use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
use crate::internal::{keys, scan, selection, tx, updater};
use crate::slate::{PaymentInfo, Slate, SlateState};
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo};
use crate::Error;
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletInfo};
use crate::{
address,
mwixnet::{create_onion, ComSignature, Hop, MixnetReqCreationParams, SwapReq},
wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
wallet_lock, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, ScannedBlockInfo, Slatepack,
SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus,
WalletInst, WalletLCProvider,
SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletBackend,
WalletInitStatus, WalletInst, WalletLCProvider,
};
use ed25519_dalek::PublicKey as DalekPublicKey;
@@ -52,35 +51,32 @@ use std::sync::mpsc::Sender;
use std::sync::Arc;
/// List of accounts
pub fn accounts<'a, T: ?Sized, C, K>(w: &mut T) -> Result<Vec<AcctPathMapping>, Error>
pub fn accounts<C, K>(w: &mut WalletBackend<C, K>) -> Result<Vec<AcctPathMapping>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
keys::accounts(&mut *w)
keys::accounts(w)
}
/// new account path
pub fn create_account_path<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn create_account_path<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
label: &str,
) -> Result<Identifier, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
keys::new_acct_path(&mut *w, keychain_mask, label)
keys::new_acct_path(w, keychain_mask, label)
}
/// set active account
pub fn set_active_account<'a, T: ?Sized, C, K>(w: &mut T, label: &str) -> Result<(), Error>
pub fn set_active_account<C, K>(w: &mut WalletBackend<C, K>, label: &str) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
w.set_parent_key_id_by_name(label)
}
@@ -196,7 +192,7 @@ where
dec_key: None,
});
let slatepack = packer.deser_slatepack(slatepack.as_bytes(), true)?;
return packer.get_slate(&slatepack);
packer.get_slate(&slatepack)
} else {
for index in secret_indices {
let dec_key = Some(get_slatepack_secret_key(
@@ -218,11 +214,11 @@ where
};
return packer.get_slate(&slatepack);
}
return Err(Error::SlatepackDecryption(
Err(Error::SlatepackDecryption(
"Could not decrypt slatepack with any provided index on the address derivation path"
.to_owned(),
)
.into());
.into())
}
}
@@ -302,13 +298,7 @@ where
Ok((
validated,
updater::retrieve_outputs(
&mut **w,
keychain_mask,
include_spent,
tx_id,
Some(&parent_key_id),
)?,
updater::retrieve_outputs(w, keychain_mask, include_spent, tx_id, Some(&parent_key_id))?,
))
}
@@ -341,7 +331,7 @@ where
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
let txs = updater::retrieve_txs(
&mut **w,
w,
tx_id,
tx_slate_id,
query_args,
@@ -378,7 +368,7 @@ where
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
let wallet_info = updater::retrieve_info(&mut **w, &parent_key_id, minimum_confirmations)?;
let wallet_info = updater::retrieve_info(w, &parent_key_id, minimum_confirmations)?;
Ok((validated, wallet_info))
}
@@ -469,8 +459,8 @@ where
}
};
Ok(PaymentProof {
amount: amount,
excess: excess,
amount,
excess,
recipient_address: SlatepackAddress::new(&proof.receiver_address),
recipient_sig: r_sig,
sender_address: SlatepackAddress::new(&proof.sender_address),
@@ -479,16 +469,15 @@ where
}
/// Initiate tx as sender
pub fn init_send_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn init_send_tx<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
args: InitTxArgs,
use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let parent_key_id = match &args.src_acct_name {
Some(d) => {
@@ -501,14 +490,7 @@ where
None => w.parent_key_id(),
};
let mut slate = tx::new_tx_slate(
&mut *w,
args.amount,
false,
2,
use_test_rng,
args.ttl_blocks,
)?;
let mut slate = tx::new_tx_slate(w, args.amount, false, 2, use_test_rng, args.ttl_blocks)?;
if let Some(v) = args.target_slate_version {
slate.version_info.version = v;
@@ -518,7 +500,7 @@ where
// back
if let Some(true) = args.estimate_only {
let (total, fee) = tx::estimate_send_tx(
&mut *w,
w,
keychain_mask,
args.amount,
args.amount_includes_fee.unwrap_or(false),
@@ -536,7 +518,7 @@ where
let height = w.w2n_client().get_chain_tip()?.0;
let mut context = if args.late_lock.unwrap_or(false) {
tx::create_late_lock_context(
&mut *w,
w,
keychain_mask,
&mut slate,
height,
@@ -546,7 +528,7 @@ where
)?
} else {
tx::add_inputs_to_slate(
&mut *w,
w,
keychain_mask,
&mut slate,
height,
@@ -595,16 +577,15 @@ where
}
/// Initiate a transaction as the recipient (invoicing)
pub fn issue_invoice_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn issue_invoice_tx<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
args: IssueInvoiceTxArgs,
use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let parent_key_id = match args.dest_acct_name {
Some(d) => {
@@ -617,10 +598,10 @@ where
None => w.parent_key_id(),
};
let mut slate = tx::new_tx_slate(&mut *w, args.amount, true, 2, use_test_rng, None)?;
let mut slate = tx::new_tx_slate(w, args.amount, true, 2, use_test_rng, None)?;
let height = w.w2n_client().get_chain_tip()?.0;
let context = tx::add_output_to_slate(
&mut *w,
w,
keychain_mask,
&mut slate,
height,
@@ -648,17 +629,16 @@ where
/// Receive an invoice tx, essentially adding inputs to whatever
/// output was specified
pub fn process_invoice_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn process_invoice_tx<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
args: InitTxArgs,
use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut ret_slate = slate.clone();
check_ttl(w, &ret_slate)?;
@@ -674,7 +654,7 @@ where
};
// Don't do this multiple times
let tx = updater::retrieve_txs(
&mut *w,
w,
None,
Some(ret_slate.id),
None,
@@ -704,7 +684,7 @@ where
let context_res = w.get_private_context(keychain_mask, slate.id.as_bytes());
let mut context = tx::add_inputs_to_slate(
&mut *w,
w,
keychain_mask,
&mut ret_slate,
height,
@@ -750,7 +730,7 @@ where
}
}
selection::repopulate_tx(&mut *w, keychain_mask, &mut ret_slate, &context, false)?;
selection::repopulate_tx(w, keychain_mask, &mut ret_slate, &context, false)?;
// Save the aggsig context in our DB for when we
// recieve the transaction back
@@ -769,15 +749,14 @@ where
}
/// Lock sender outputs
pub fn tx_lock_outputs<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn tx_lock_outputs<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
let mut excess_override = None;
@@ -786,7 +765,7 @@ where
if sl.tx == None {
sl.tx = Some(Slate::empty_transaction());
selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &context, true)?;
selection::repopulate_tx(w, keychain_mask, &mut sl, &context, true)?;
}
if slate.participant_data.len() == 1 {
@@ -795,26 +774,18 @@ where
}
let height = w.w2n_client().get_chain_tip()?.0;
selection::lock_tx_context(
&mut *w,
keychain_mask,
&sl,
height,
&context,
excess_override,
)
selection::lock_tx_context(w, keychain_mask, &sl, height, &context, excess_override)
}
/// Finalize slate
pub fn finalize_tx<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn finalize_tx<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
foreign_finalize(w, keychain_mask, slate, false)
}
@@ -844,24 +815,27 @@ where
}
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, tx_id, tx_slate_id)
tx::cancel_tx(w, keychain_mask, &parent_key_id, tx_id, tx_slate_id)
}
/// get stored tx
/// crashes if stored tx has total fees exceeding 2^40 nanogrin
pub fn get_stored_tx<'a, T: ?Sized, C, K>(
w: &T,
pub fn get_stored_tx<C, K>(
w: &WalletBackend<C, K>,
tx_id: Option<u32>,
slate_id: Option<&Uuid>,
) -> Result<Option<Slate>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut uuid = None;
if let Some(i) = tx_id {
let tx = w.tx_log_iter().find(|t| t.id == i);
let tx = w
.tx_log_iter()?
.filter(|tx| tx.is_ok())
.map(|tx| tx.unwrap())
.find(|t| t.id == i);
if let Some(t) = tx {
uuid = t.tx_slate_id;
}
@@ -896,9 +870,9 @@ where
/// Posts a transaction to the chain
/// take a client impl instead of wallet so as not to have to lock the wallet
pub fn post_tx<'a, C>(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error>
pub fn post_tx<C>(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error>
where
C: NodeClient + 'a,
C: NodeClient,
{
let res = client.post_tx(tx, fluff);
if let Err(e) = res {
@@ -930,8 +904,7 @@ where
let is_hex = rewind_hash.chars().all(|c| c.is_ascii_hexdigit());
let rewind_hash = rewind_hash.to_lowercase();
if !(is_hex && rewind_hash.len() == 64) {
let msg = format!("Invalid Rewind Hash");
return Err(Error::RewindHash(msg));
return Err(Error::RewindHash("Invalid Rewind Hash".to_string()));
}
let tip = {
@@ -939,10 +912,7 @@ where
w.w2n_client().get_chain_tip()?
};
let start_height = match start_height {
Some(h) => h,
None => 1,
};
let start_height = start_height.unwrap_or_else(|| 1);
let info = scan::scan_rewind_hash(
wallet_inst,
@@ -979,7 +949,7 @@ where
// Scan every 10k heights to save data between batches in case of interruption.
let mut total_pmmr_range = None;
for h in (start_height..tip.0).step_by(10001) {
for h in (start_height..tip.0 + 1).step_by(10001) {
let batch_end_height = cmp::min(tip.0, h + 10000);
let (mut info, range) = scan::scan(
wallet_inst.clone(),
@@ -1030,10 +1000,12 @@ where
}),
Err(_) => {
let outputs = retrieve_outputs(wallet_inst, keychain_mask, &None, true, false, None)?;
let height = match outputs.1.iter().map(|m| m.output.height).max() {
Some(height) => height,
None => 0,
};
let height = outputs
.1
.iter()
.map(|m| m.output.height)
.max()
.unwrap_or_else(|| 0);
Ok(NodeHeightResult {
height,
header_hash: "".to_owned(),
@@ -1093,7 +1065,7 @@ where
// Step 2: Update outstanding transactions with no change outputs by kernel
let mut txs = {
wallet_lock!(wallet_inst, w);
updater::retrieve_txs(&mut **w, None, None, None, Some(&parent_key_id), true)?
updater::retrieve_txs(w, None, None, None, Some(&parent_key_id), true)?
};
result = update_txs_via_kernel(wallet_inst.clone(), keychain_mask, &mut txs)?;
if !result {
@@ -1152,7 +1124,7 @@ where
// Scan every 10k heights to save data between batches in case of interruption.
let mut total_pmmr_range = None;
for h in (start_height..tip.0).step_by(10001) {
for h in (start_height..tip.0 + 1).step_by(10001) {
let batch_end_height = cmp::min(tip.0, h + 10000);
let (mut info, range) = scan::scan(
wallet_inst.clone(),
@@ -1186,7 +1158,7 @@ where
if tip.0 >= e {
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, Some(tx.id), None)?;
tx::cancel_tx(w, keychain_mask, &parent_key_id, Some(tx.id), None)?;
}
}
}
@@ -1195,11 +1167,10 @@ where
}
/// Check TTL
pub fn check_ttl<'a, T: ?Sized, C, K>(w: &mut T, slate: &Slate) -> Result<(), Error>
pub fn check_ttl<C, K>(w: &mut WalletBackend<C, K>, slate: &Slate) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// Refuse if TTL is expired
let last_confirmed_height = w.last_confirmed_height()?;
@@ -1265,7 +1236,7 @@ where
return Err(Error::PaymentProof("Invalid sender signature".to_owned()));
};
// for now, simple test as to whether one of the addresses belongs to this wallet
// for now, simple test whether one of the addresses belongs to this wallet
let sec_key = address::address_from_derivation_path(&keychain, &parent_key_id, 0)?;
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
Ok(k) => k,
@@ -1294,7 +1265,7 @@ where
{
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
match updater::refresh_outputs(&mut **w, keychain_mask, &parent_key_id, update_all) {
match updater::refresh_outputs(w, keychain_mask, &parent_key_id, update_all) {
Ok(_) => Ok(true),
Err(e) => {
if let Error::InvalidKeychainMask = e {
@@ -1361,20 +1332,19 @@ where
}
/// Builds an output for the wallet's next available key
pub fn build_output<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn build_output<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
features: OutputFeatures,
amount: u64,
) -> Result<BuiltOutput, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let k = w.keychain(keychain_mask)?;
let key_id = keys::next_available_key(&mut *w, keychain_mask)?;
let key_id = keys::next_available_key(w, keychain_mask)?;
let blind = k.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?;
let commit = k.secp().commit(amount, blind.clone())?;
@@ -1394,14 +1364,14 @@ where
Ok(BuiltOutput {
blind: BlindingFactor::from_secret_key(blind),
key_id: key_id,
output: output,
key_id,
output,
})
}
/// Create MXMixnet request
pub fn create_mwixnet_req<'a, T: ?Sized, C, K>(
w: &mut T,
pub fn create_mwixnet_req<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
params: &MixnetReqCreationParams,
commitment: &Commitment,
@@ -1409,9 +1379,8 @@ pub fn create_mwixnet_req<'a, T: ?Sized, C, K>(
use_test_rng: bool,
) -> Result<SwapReq, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let parent_key_id = w.parent_key_id();
let keychain = w.keychain(keychain_mask)?;
+755
View File
@@ -0,0 +1,755 @@
// Copyright 2021 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.
use byteorder::{BigEndian, WriteBytesExt};
use std::fs;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use uuid::Uuid;
use crate::blake2::blake2b::{Blake2b, Blake2bResult};
use crate::{
AcctPathMapping, Context, Error, NodeClient, OutputData, ScannedBlockInfo, TxLogEntry,
WalletInitStatus,
};
use grin_core::core::Transaction;
use grin_core::ser;
use grin_keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchCommitmentType};
use grin_store::{option_to_not_found, Store};
use grin_util::secp::constants::SECRET_KEY_SIZE;
use grin_util::secp::SecretKey;
use grin_util::ToHex;
use rand::rngs::mock::StepRng;
use rand::thread_rng;
pub const DB_DIR: &str = "db";
pub const TX_SAVE_DIR: &str = "saved_txs";
const OUTPUT_PREFIX: u8 = b'o';
const DERIV_PREFIX: u8 = b'd';
const CONFIRMED_HEIGHT_PREFIX: u8 = b'c';
const PRIVATE_TX_CONTEXT_PREFIX: u8 = b'p';
const TX_LOG_ENTRY_PREFIX: u8 = b't';
const TX_LOG_ID_PREFIX: u8 = b'i';
const ACCOUNT_PATH_MAPPING_PREFIX: u8 = b'a';
const LAST_SCANNED_BLOCK: u8 = b'l';
const LAST_SCANNED_KEY: &str = "LAST_SCANNED_KEY";
const WALLET_INIT_STATUS: u8 = b'w';
const WALLET_INIT_STATUS_KEY: &str = "WALLET_INIT_STATUS";
const DB_PREFIXES: [u8; 9] = [
OUTPUT_PREFIX,
DERIV_PREFIX,
CONFIRMED_HEIGHT_PREFIX,
PRIVATE_TX_CONTEXT_PREFIX,
TX_LOG_ENTRY_PREFIX,
TX_LOG_ID_PREFIX,
ACCOUNT_PATH_MAPPING_PREFIX,
LAST_SCANNED_BLOCK,
WALLET_INIT_STATUS,
];
/// Helper to derive XOR keys for storing private transaction keys in the DB
/// (blind_xor_key, nonce_xor_key)
fn private_ctx_xor_keys<K>(
keychain: &K,
slate_id: &[u8],
) -> Result<([u8; SECRET_KEY_SIZE], [u8; SECRET_KEY_SIZE]), Error>
where
K: Keychain,
{
let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?;
// derive XOR values for storing secret values in DB
// h(root_key|slate_id|"blind")
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
hasher.update(&slate_id[..]);
hasher.update(&b"blind"[..]);
let blind_xor_key = hasher.finalize();
let mut ret_blind = [0; SECRET_KEY_SIZE];
ret_blind.copy_from_slice(&blind_xor_key.as_bytes()[0..SECRET_KEY_SIZE]);
// h(root_key|slate_id|"nonce")
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
hasher.update(&slate_id[..]);
hasher.update(&b"nonce"[..]);
let nonce_xor_key = hasher.finalize();
let mut ret_nonce = [0; SECRET_KEY_SIZE];
ret_nonce.copy_from_slice(&nonce_xor_key.as_bytes()[0..SECRET_KEY_SIZE]);
Ok((ret_blind, ret_nonce))
}
/// Wallet backend. All functions here expect that the wallet instance
/// has instantiated itself or stored whatever credentials it needs.
pub struct WalletBackend<C, K>
where
C: NodeClient,
K: Keychain,
{
db: Store,
data_file_dir: String,
/// Keychain
pub keychain: Option<K>,
/// Check value for XORed keychain seed
pub master_checksum: Box<Option<Blake2bResult>>,
/// Parent path to use by default for output operations
parent_key_id: Identifier,
/// wallet to node client
w2n_client: C,
}
impl<C, K> WalletBackend<C, K>
where
C: NodeClient,
K: Keychain,
{
/// Create new wallet backend.
pub fn new(data_file_dir: &str, n_client: C) -> Result<Self, Error> {
let db_path = Path::new(data_file_dir).join(DB_DIR);
fs::create_dir_all(&db_path)?;
let stored_tx_path = Path::new(data_file_dir).join(TX_SAVE_DIR);
fs::create_dir_all(&stored_tx_path)?;
let store = Store::new(
db_path.to_str().unwrap(),
None,
Some(DB_DIR),
DB_PREFIXES.to_vec(),
None,
None,
)?;
// Make sure default wallet derivation path always exists
// as well as path (so it can be retrieved by batches to know where to store
// completed transactions, for reference
let default_account = AcctPathMapping {
label: "default".to_owned(),
path: WalletBackend::<C, K>::default_path(),
};
{
let mut batch = store.batch()?;
batch.put_ser(
Some(ACCOUNT_PATH_MAPPING_PREFIX),
default_account.label.as_bytes(),
&default_account,
)?;
batch.commit()?;
}
let res = WalletBackend {
db: store,
data_file_dir: data_file_dir.to_owned(),
keychain: None,
master_checksum: Box::new(None),
parent_key_id: WalletBackend::<C, K>::default_path(),
w2n_client: n_client,
};
Ok(res)
}
/// Return the default parent wallet path, corresponding to the default account
/// in the BIP32 spec. Parent is account 0 at level 2, child output identifiers
/// are all at level 3.
pub fn default_path() -> Identifier {
ExtKeychain::derive_key_id(2, 0, 0, 0, 0)
}
/// Just test to see if database files exist in the current directory. If
/// so, use a DB backend for all operations.
pub fn exists(data_file_dir: &str) -> bool {
let db_path = Path::new(data_file_dir).join(DB_DIR);
db_path.exists()
}
/// Set the keychain, which should already be initialized
/// Optionally return a token value used to XOR the stored
/// key value
pub fn set_keychain(
&mut self,
mut k: K,
mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, Error> {
// store hash of master key, so it can be verified later after unmasking
let root_key = k.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?;
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
self.master_checksum = Box::new(Some(hasher.finalize()));
let mask_value = {
match mask {
true => {
// Random value that must be XORed against the stored wallet seed
// before it is used
let mask_value = match use_test_rng {
true => {
let mut test_rng = StepRng::new(1_234_567_890_u64, 1);
SecretKey::new(&k.secp(), &mut test_rng)
}
false => SecretKey::new(&k.secp(), &mut thread_rng()),
};
k.mask_master_key(&mask_value)?;
Some(mask_value)
}
false => None,
}
};
self.keychain = Some(k);
Ok(mask_value)
}
/// Close wallet by removing stored keychain.
pub fn close(&mut self) -> Result<(), Error> {
self.keychain = None;
Ok(())
}
/// Return the keychain being used, cloned with XORed token value
/// for temporary use
/// Can optionally take a mask value
pub fn keychain(&self, mask: Option<&SecretKey>) -> Result<K, Error> {
match self.keychain.as_ref() {
Some(k) => {
let mut k_masked = k.clone();
if let Some(m) = mask {
k_masked.mask_master_key(m)?;
}
// Check if master seed is what is expected (especially if it's been xored)
let root_key =
k_masked.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?;
let mut hasher = Blake2b::new(SECRET_KEY_SIZE);
hasher.update(&root_key.0[..]);
if *self.master_checksum != Some(hasher.finalize()) {
error!("Supplied keychain mask is invalid");
return Err(Error::InvalidKeychainMask);
}
Ok(k_masked)
}
None => Err(Error::KeychainDoesntExist),
}
}
/// Return the client being used to communicate with the node.
pub fn w2n_client(&mut self) -> &mut C {
&mut self.w2n_client
}
/// Return the version of the commit for caching if allowed.
pub fn calc_commit_for_cache(
&mut self,
keychain_mask: Option<&SecretKey>,
amount: u64,
id: &Identifier,
) -> Result<Option<String>, Error> {
//TODO: Check if this is really necessary, it's the only thing
//preventing removing the need for config in the wallet backend
/*if self.config.no_commit_cache == Some(true) {
Ok(None)
} else {*/
Ok(Some(
self.keychain(keychain_mask)?
.commit(amount, &id, SwitchCommitmentType::Regular)?
.0
.to_vec()
.to_hex(), // TODO: proper support for different switch commitment schemes
))
/*}*/
}
/// Set parent key id by stored account name.
pub fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> {
let label = label.to_owned();
let res = self.acct_path_iter()?.find(|l| l.label == label);
if let Some(a) = res {
self.set_parent_key_id(a.path);
Ok(())
} else {
Err(Error::UnknownAccountLabel(label))
}
}
/// The BIP32 path of the parent path to use for all output-related
/// functions, essentially 'accounts' within a wallet.
pub fn set_parent_key_id(&mut self, id: Identifier) {
self.parent_key_id = id;
}
/// Get the parent path.
pub fn parent_key_id(&mut self) -> Identifier {
self.parent_key_id.clone()
}
/// Get output data by id.
pub fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = match mmr_index {
Some(i) => to_key_u64(id.to_bytes(), *i),
None => id.to_bytes().to_vec(),
};
option_to_not_found(self.db.get_ser(Some(OUTPUT_PREFIX), &key, None), || {
format!("Key Id: {}", id)
})
.map_err(|e| e.into())
}
/// Iterate over all output data stored by the backend.
pub fn iter(&self) -> Result<impl Iterator<Item = OutputData>, Error> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self.db.iter(Some(OUTPUT_PREFIX), move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let items: Vec<OutputData> = prefix_iter?.collect::<Result<Vec<_>, _>>()?;
Ok(items.into_iter())
}
/// Get an (Optional) tx log entry by uuid.
pub fn get_tx_log_entry(&self, u: &Uuid) -> Result<Option<TxLogEntry>, Error> {
self.db
.get_ser(Some(TX_LOG_ENTRY_PREFIX), u.as_bytes(), None)
.map_err(|e| e.into())
}
/// Iterate over all tx log data stored by the backend.
pub fn tx_log_iter(
&self,
) -> Result<impl Iterator<Item = Result<TxLogEntry, grin_store::Error>>, Error> {
let protocol_version = self.db.protocol_version();
self.db
.iter(Some(TX_LOG_ENTRY_PREFIX), move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
})
.map_err(From::from)
}
/// Retrieve the private context associated with a given slate id.
pub fn get_private_context(
&mut self,
keychain_mask: Option<&SecretKey>,
slate_id: &[u8],
) -> Result<Context, Error> {
let ctx_key = to_key_u64(slate_id, 0);
let (blind_xor_key, nonce_xor_key) =
private_ctx_xor_keys(&self.keychain(keychain_mask)?, slate_id)?;
let mut ctx: Context = option_to_not_found(
self.db
.get_ser(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key, None),
|| format!("Slate id: {:x?}", slate_id.to_vec()),
)?;
for i in 0..SECRET_KEY_SIZE {
ctx.sec_key.0[i] ^= blind_xor_key[i];
ctx.sec_nonce.0[i] ^= nonce_xor_key[i];
}
Ok(ctx)
}
/// Iterate over all stored account paths.
pub fn acct_path_iter(&self) -> Result<impl Iterator<Item = AcctPathMapping>, Error> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self
.db
.iter(Some(ACCOUNT_PATH_MAPPING_PREFIX), move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let items: Vec<AcctPathMapping> = prefix_iter?.collect::<Result<Vec<_>, _>>()?;
Ok(items.into_iter())
}
/// Gets an account path for a given label.
pub fn get_acct_path(&self, label: String) -> Result<Option<AcctPathMapping>, Error> {
self.db
.get_ser(Some(ACCOUNT_PATH_MAPPING_PREFIX), label.as_bytes(), None)
.map_err(|e| e.into())
}
/// Stores a transaction.
pub fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error> {
let filename = format!("{}.grintx", uuid);
let path = Path::new(&self.data_file_dir)
.join(TX_SAVE_DIR)
.join(filename);
let path_buf = Path::new(&path).to_path_buf();
let mut stored_tx = File::create(path_buf)?;
let tx_hex = ser::ser_vec(tx, ser::ProtocolVersion(1)).unwrap().to_hex();
stored_tx.write_all(&tx_hex.as_bytes())?;
stored_tx.sync_all()?;
Ok(())
}
/// Retrieves a stored transaction.
//TODO: Store content of .grintx file at TxLogEntry?
pub fn get_stored_tx(&self, uuid: &str) -> Result<Option<Transaction>, Error> {
let filename = format!("{}.grintx", uuid);
let path = Path::new(&self.data_file_dir)
.join(TX_SAVE_DIR)
.join(filename);
let tx_file = Path::new(&path).to_path_buf();
let mut tx_f = File::open(tx_file)?;
let mut content = String::new();
tx_f.read_to_string(&mut content)?;
let tx_bin = grin_util::from_hex(&content).unwrap();
Ok(Some(
ser::deserialize(
&mut &tx_bin[..],
ser::ProtocolVersion(1),
ser::DeserializationMode::default(),
)
.unwrap(),
))
}
/// Create a new write batch to update or remove output data.
pub fn batch(
&mut self,
keychain_mask: Option<&SecretKey>,
) -> Result<WalletBatch<'_, K>, Error> {
Ok(WalletBatch {
db: self.db.batch()?,
keychain: Some(self.keychain(keychain_mask)?),
})
}
/// Batch for use when keychain isn't available or required.
pub fn batch_no_mask(&mut self) -> Result<WalletBatch<'_, K>, Error> {
Ok(WalletBatch {
db: self.db.batch()?,
keychain: None,
})
}
/// Return the current child index.
pub fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result<u32, Error> {
let index = {
let batch = self.db.batch()?;
batch
.get_ser(Some(DERIV_PREFIX), &parent_key_id.to_bytes(), None)?
.unwrap_or_else(|| 0)
};
Ok(index)
}
/// Next child ID when we want to create a new output, based on current parent.
pub fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error> {
let parent_key_id = self.parent_key_id.clone();
let mut deriv_idx = {
let batch = self.db.batch()?;
batch
.get_ser(Some(DERIV_PREFIX), &self.parent_key_id.to_bytes(), None)?
.unwrap_or_else(|| 0)
};
let mut return_path = self.parent_key_id.to_path();
return_path.depth += 1;
return_path.path[return_path.depth as usize - 1] = ChildNumber::from(deriv_idx);
deriv_idx += 1;
let mut batch = self.batch(keychain_mask)?;
batch.save_child_index(&parent_key_id, deriv_idx)?;
batch.commit()?;
Ok(Identifier::from_path(&return_path))
}
/// Last verified height of outputs directly descending from the given parent key.
pub fn last_confirmed_height(&mut self) -> Result<u64, Error> {
let batch = self.db.batch()?;
let last_confirmed_height = batch
.get_ser(
Some(CONFIRMED_HEIGHT_PREFIX),
&self.parent_key_id.to_bytes(),
None,
)?
.unwrap_or_else(|| 0);
Ok(last_confirmed_height)
}
/// Last block scanned during scan or restore.
pub fn last_scanned_block(&mut self) -> Result<ScannedBlockInfo, Error> {
let batch = self.db.batch()?;
let last_scanned_block = batch
.get_ser(Some(LAST_SCANNED_BLOCK), LAST_SCANNED_KEY.as_bytes(), None)?
.unwrap_or_else(|| ScannedBlockInfo {
height: 0,
hash: "".to_owned(),
start_pmmr_index: 0,
last_pmmr_index: 0,
});
Ok(last_scanned_block)
}
/// Flag whether the wallet needs a full UTXO scan on next update attempt.
pub fn init_status(&mut self) -> Result<WalletInitStatus, Error> {
let batch = self.db.batch()?;
let status = batch
.get_ser(
Some(WALLET_INIT_STATUS),
WALLET_INIT_STATUS_KEY.as_bytes(),
None,
)?
.unwrap_or_else(|| WalletInitStatus::InitComplete);
Ok(status)
}
}
/// An atomic batch in which all changes can be committed all at once or
/// discarded on error.
pub struct WalletBatch<'a, K>
where
K: Keychain,
{
db: grin_store::Batch<'a>,
/// Keychain
keychain: Option<K>,
}
#[allow(missing_docs)]
impl<'a, K> WalletBatch<'a, K>
where
K: Keychain,
{
/// Return the keychain being used.
pub fn keychain(&mut self) -> &mut K {
self.keychain.as_mut().unwrap()
}
/// Add or update data about an output to the backend.
pub fn save(&mut self, out: OutputData) -> Result<(), Error> {
let key = match out.mmr_index {
Some(i) => to_key_u64(out.key_id.to_bytes(), i),
None => out.key_id.to_bytes().to_vec(),
};
self.db.put_ser(Some(OUTPUT_PREFIX), &key, &out)?;
Ok(())
}
/// Gets output data by id
pub fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
let key = match mmr_index {
Some(i) => to_key_u64(id.to_bytes(), *i),
None => id.to_bytes().to_vec(),
};
option_to_not_found(self.db.get_ser(Some(OUTPUT_PREFIX), &key, None), || {
format!("Key ID: {}", id)
})
.map_err(|e| e.into())
}
/// Iterate over all output data stored by the backend.
pub fn iter(&'a self) -> Result<impl Iterator<Item = OutputData> + 'a, Error> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self.db.iter(Some(OUTPUT_PREFIX), move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let items: Vec<OutputData> = prefix_iter?.collect::<Result<Vec<_>, _>>()?;
Ok(items.into_iter())
}
/// Delete data about an output from the backend.
pub fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error> {
// Delete the output data.
let key = match mmr_index {
Some(i) => to_key_u64(id.to_bytes(), *i),
None => id.to_bytes().to_vec(),
};
self.db.delete(Some(OUTPUT_PREFIX), &key)?;
Ok(())
}
/// Save last stored child index of a given parent.
pub fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> {
self.db
.put_ser(Some(DERIV_PREFIX), &parent_id.to_bytes(), &child_n)?;
Ok(())
}
/// Save last confirmed height of outputs for a given parent.
pub fn save_last_confirmed_height(
&mut self,
parent_key_id: &Identifier,
height: u64,
) -> Result<(), Error> {
self.db.put_ser(
Some(CONFIRMED_HEIGHT_PREFIX),
&parent_key_id.to_bytes(),
&height,
)?;
Ok(())
}
/// Save the last PMMR index that was scanned via a scan operation.
pub fn save_last_scanned_block(&mut self, block_info: ScannedBlockInfo) -> Result<(), Error> {
self.db.put_ser(
Some(LAST_SCANNED_BLOCK),
LAST_SCANNED_KEY.as_bytes(),
&block_info,
)?;
Ok(())
}
/// Save flag indicating whether wallet needs a full UTXO scan.
pub fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error> {
self.db.put_ser(
Some(WALLET_INIT_STATUS),
WALLET_INIT_STATUS_KEY.as_bytes(),
&value,
)?;
Ok(())
}
/// Get next transaction log entry for the parent.
pub fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error> {
let last_tx_log_id = self
.db
.get_ser(Some(TX_LOG_ID_PREFIX), &parent_key_id.to_bytes(), None)?
.unwrap_or_else(|| 0);
self.db.put_ser(
Some(TX_LOG_ID_PREFIX),
&parent_key_id.to_bytes(),
&(last_tx_log_id + 1),
)?;
Ok(last_tx_log_id)
}
/// Iterate over transactions data stored by the backend.
pub fn tx_log_iter(
&'a self,
) -> Result<impl Iterator<Item = Result<TxLogEntry, grin_store::Error>> + 'a, Error> {
let protocol_version = self.db.protocol_version();
self.db
.iter(Some(TX_LOG_ENTRY_PREFIX), move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
})
.map_err(From::from)
}
/// Save a transaction log entry.
pub fn save_tx_log_entry(
&mut self,
tx_in: TxLogEntry,
parent_id: &Identifier,
) -> Result<(), Error> {
let tx_log_key = to_key_u64(parent_id.to_bytes(), tx_in.id as u64);
self.db
.put_ser(Some(TX_LOG_ENTRY_PREFIX), &tx_log_key, &tx_in)?;
Ok(())
}
/// Delete a transaction log entry.
pub fn delete_tx_log_entry(&mut self, tx_id: u32, parent_id: &Identifier) -> Result<(), Error> {
let tx_log_key = to_key_u64(parent_id.to_bytes(), tx_id as u64);
self.db.delete(Some(TX_LOG_ENTRY_PREFIX), &tx_log_key)?;
Ok(())
}
/// Save an account label -> path mapping.
pub fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error> {
self.db.put_ser(
Some(ACCOUNT_PATH_MAPPING_PREFIX),
mapping.label.as_bytes(),
&mapping,
)?;
Ok(())
}
/// Iterate over account names stored in backend.
pub fn acct_path_iter(&'a self) -> Result<impl Iterator<Item = AcctPathMapping> + 'a, Error> {
let protocol_version = self.db.protocol_version();
let prefix_iter = self
.db
.iter(Some(ACCOUNT_PATH_MAPPING_PREFIX), move |_, mut v| {
ser::deserialize(
&mut v,
protocol_version,
ser::DeserializationMode::default(),
)
.map_err(From::from)
});
let items: Vec<AcctPathMapping> = prefix_iter?.collect::<Result<Vec<_>, _>>()?;
Ok(items.into_iter())
}
/// Save an output as locked in the backend.
pub fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error> {
out.lock();
self.save(out.clone())
}
/// Save the private context associated with a slate id.
pub fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error> {
let ctx_key = to_key_u64(slate_id, 0);
let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?;
let mut s_ctx = ctx.clone();
for i in 0..SECRET_KEY_SIZE {
s_ctx.sec_key.0[i] ^= blind_xor_key[i];
s_ctx.sec_nonce.0[i] ^= nonce_xor_key[i];
}
self.db
.put_ser(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key, &s_ctx)?;
Ok(())
}
/// Delete the private context associated with the slate id.
pub fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error> {
let ctx_key = to_key_u64(slate_id, 0);
self.db
.delete(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key)
.map_err(|e| e.into())
}
/// Write the wallet data to backend file.
pub fn commit(self) -> Result<(), Error> {
self.db.commit()?;
Ok(())
}
}
/// Build a db key from a byte vector identifier and numeric identifier
fn to_key_u64<K: AsRef<[u8]>>(k: K, val: u64) -> Vec<u8> {
let mut res = k.as_ref().to_vec();
res.write_u64::<BigEndian>(val).unwrap();
res
}
+27 -31
View File
@@ -16,32 +16,31 @@
use crate::error::Error;
use crate::grin_keychain::{ChildNumber, ExtKeychain, Identifier, Keychain};
use crate::grin_util::secp::key::SecretKey;
use crate::types::{AcctPathMapping, NodeClient, WalletBackend};
use crate::types::{AcctPathMapping, NodeClient};
use crate::WalletBackend;
/// Get next available key in the wallet for a given parent
pub fn next_available_key<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn next_available_key<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
) -> Result<Identifier, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let child = wallet.next_child(keychain_mask)?;
Ok(child)
}
/// Retrieve an existing key from a wallet
pub fn retrieve_existing_key<'a, T: ?Sized, C, K>(
wallet: &T,
pub fn retrieve_existing_key<C, K>(
wallet: &WalletBackend<C, K>,
key_id: Identifier,
mmr_index: Option<u64>,
) -> Result<(Identifier, u32), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let existing = wallet.get(&key_id, &mmr_index)?;
let key_id = existing.key_id.clone();
@@ -50,28 +49,26 @@ where
}
/// Returns a list of account to BIP32 path mappings
pub fn accounts<'a, T: ?Sized, C, K>(wallet: &mut T) -> Result<Vec<AcctPathMapping>, Error>
pub fn accounts<C, K>(wallet: &WalletBackend<C, K>) -> Result<Vec<AcctPathMapping>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
Ok(wallet.acct_path_iter().collect())
Ok(wallet.acct_path_iter()?.collect())
}
/// Adds an new parent account path with a given label
pub fn new_acct_path<'a, T: ?Sized, C, K>(
wallet: &mut T,
/// Adds a new parent account path with a given label
pub fn new_acct_path<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
label: &str,
) -> Result<Identifier, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let label = label.to_owned();
if wallet.acct_path_iter().any(|l| l.label == label) {
if wallet.acct_path_iter()?.any(|l| l.label == label) {
return Err(Error::AccountLabelAlreadyExists(label));
}
@@ -79,7 +76,7 @@ where
// so find the highest of those, then increment (to conform with external/internal
// derivation chains in BIP32 spec)
let highest_entry = wallet.acct_path_iter().max_by(|a, b| {
let highest_entry = wallet.acct_path_iter()?.max_by(|a, b| {
<u32>::from(a.path.to_path().path[0]).cmp(&<u32>::from(b.path.to_path().path[0]))
});
@@ -94,7 +91,7 @@ where
};
let save_path = AcctPathMapping {
label: label,
label,
path: return_id.clone(),
};
@@ -105,20 +102,19 @@ where
}
/// Adds/sets a particular account path with a given label
pub fn set_acct_path<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn set_acct_path<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
label: &str,
path: &Identifier,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let label = label.to_owned();
let save_path = AcctPathMapping {
label: label,
label,
path: path.clone(),
};
+12 -13
View File
@@ -66,14 +66,14 @@ struct RestoredTxStats {
pub num_outputs: usize,
}
fn identify_utxo_outputs<'a, K>(
fn identify_utxo_outputs<K>(
keychain: &K,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
status_send_channel: &Option<Sender<StatusMessage>>,
percentage_complete: u8,
) -> Result<Vec<OutputResult>, Error>
where
K: Keychain + 'a,
K: Keychain,
{
let mut wallet_outputs: Vec<OutputResult> = Vec::new();
@@ -137,7 +137,7 @@ where
n_child: key_id.to_path().last_path_index(),
value: amount,
height: *height,
lock_height: lock_height,
lock_height,
is_coinbase: *is_coinbase,
mmr_index: *mmr_index,
});
@@ -145,7 +145,7 @@ where
Ok(wallet_outputs)
}
fn collect_chain_outputs_rewind_hash<'a, C>(
fn collect_chain_outputs_rewind_hash<C>(
client: C,
rewind_hash: String,
start_index: u64,
@@ -153,13 +153,13 @@ fn collect_chain_outputs_rewind_hash<'a, C>(
status_send_channel: &Option<Sender<StatusMessage>>,
) -> Result<ViewWallet, Error>
where
C: NodeClient + 'a,
C: NodeClient,
{
let batch_size = 1000;
let start_index_stat = start_index;
let mut start_index = start_index;
let mut vw = ViewWallet {
rewind_hash: rewind_hash,
rewind_hash,
output_result: vec![],
total_balance: 0,
last_pmmr_index: 0,
@@ -198,7 +198,7 @@ where
continue;
}
let info = info.unwrap();
let info = info?;
vw.total_balance += info.value;
let lock_height = if *is_coinbase {
*height + global::coinbase_maturity()
@@ -212,7 +212,7 @@ where
height: *height,
mmr_index: *mmr_index,
is_coinbase: *is_coinbase,
lock_height: lock_height,
lock_height,
};
vw.output_result.push(output_info);
@@ -285,7 +285,6 @@ where
Ok((result_vec, last_retrieved_return_index, perc_complete))
}
///
fn restore_missing_output<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
@@ -384,7 +383,7 @@ where
wallet_lock!(wallet_inst, w);
let updated_tx_entry = if output.tx_log_entry.is_some() {
let entries = updater::retrieve_txs(
&mut **w,
w,
output.tx_log_entry,
None,
None,
@@ -536,7 +535,7 @@ where
// Now, get all outputs owned by this wallet (regardless of account)
let wallet_outputs = {
wallet_lock!(wallet_inst, w);
updater::retrieve_outputs(&mut **w, keychain_mask, true, None, None)?
updater::retrieve_outputs(w, keychain_mask, true, None, None)?
};
let mut missing_outs = vec![];
@@ -646,7 +645,7 @@ where
// restore labels, account paths and child derivation indices
wallet_lock!(wallet_inst, w);
let label_base = "account";
let accounts: Vec<Identifier> = w.acct_path_iter().map(|m| m.path).collect();
let accounts: Vec<Identifier> = w.acct_path_iter()?.map(|m| m.path).collect();
let mut acct_index = accounts.len();
for (path, max_child_index) in found_parents.iter() {
// Only restore paths that don't exist
@@ -656,7 +655,7 @@ where
if let Some(ref s) = status_send_channel {
let _ = s.send(StatusMessage::Scanning(msg, perc_complete));
}
keys::set_acct_path(&mut **w, keychain_mask, &label, path)?;
keys::set_acct_path(w, keychain_mask, &label, path)?;
acct_index += 1;
}
let current_child_index = w.current_child_index(&path)?;
+63 -71
View File
@@ -14,7 +14,6 @@
//! Selection of inputs for building transactions
use crate::address;
use crate::error::Error;
use crate::grin_core::core::amount_to_hr_string;
use crate::grin_core::libtx::{
@@ -29,6 +28,7 @@ use crate::internal::keys;
use crate::slate::Slate;
use crate::types::*;
use crate::util::OnionV3Address;
use crate::{address, WalletBackend};
use std::collections::HashMap;
use std::convert::TryInto;
@@ -37,8 +37,8 @@ use std::convert::TryInto;
/// and saves the private wallet identifiers of our selected outputs
/// into our transaction context
pub fn build_send_tx<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn build_send_tx<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain: &K,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
@@ -54,9 +54,8 @@ pub fn build_send_tx<'a, T: ?Sized, C, K>(
amount_includes_fee: bool,
) -> Result<Context, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let (elems, inputs, change_amounts_derivations, fee) = select_send_tx(
wallet,
@@ -73,7 +72,7 @@ where
)?;
if amount_includes_fee {
slate.amount = slate.amount.checked_sub(fee).ok_or(Error::GenericError(
format!("Transaction amount is too small to include fee").into(),
"Transaction amount is too small to include fee".to_string(),
))?;
};
@@ -119,8 +118,8 @@ where
/// Locks all corresponding outputs in the context, creates
/// change outputs and tx log entry
pub fn lock_tx_context<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn lock_tx_context<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
current_height: u64,
@@ -128,9 +127,8 @@ pub fn lock_tx_context<'a, T: ?Sized, C, K>(
excess_override: Option<pedersen::Commitment>,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut output_commits: HashMap<Identifier, (Option<String>, u64)> = HashMap::new();
// Store cached commits before locking wallet
@@ -159,6 +157,7 @@ where
let log_id = batch.next_tx_log_id(&parent_key_id)?;
let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxSent, log_id);
t.tx_slate_id = Some(slate_id);
t.tx_slate_state = Some(slate.state.clone());
let filename = format!("{}.grintx", slate_id);
t.stored_tx = Some(filename);
t.fee = context.fee;
@@ -178,7 +177,7 @@ where
let mut amount_debited = 0;
t.num_inputs = lock_inputs.len();
for id in lock_inputs {
let mut coin = batch.get(&id.0, &id.1).unwrap();
let mut coin = batch.get(&id.0, &id.1)?;
coin.tx_log_entry = Some(log_id);
amount_debited += coin.value;
batch.lock_output(&mut coin)?;
@@ -221,11 +220,11 @@ where
root_key_id: parent_key_id.clone(),
key_id: id.clone(),
n_child: id.to_path().last_path_index(),
commit: commit,
commit,
mmr_index: None,
value: change_amount,
status: OutputStatus::Unconfirmed,
height: height,
height,
lock_height: 0,
is_coinbase: false,
tx_log_entry: Some(log_id),
@@ -245,8 +244,8 @@ where
/// Creates a new output in the wallet for the recipient,
/// returning the key of the fresh output
/// Also creates a new transaction containing the output
pub fn build_recipient_output<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn build_recipient_output<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
current_height: u64,
@@ -255,9 +254,8 @@ pub fn build_recipient_output<'a, T: ?Sized, C, K>(
is_initiator: bool,
) -> Result<(Identifier, Context, TxLogEntry), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// Create a potential output for this transaction
let key_id = keys::next_available_key(wallet, keychain_mask).unwrap();
@@ -284,6 +282,7 @@ where
let log_id = batch.next_tx_log_id(&parent_key_id)?;
let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxReceived, log_id);
t.tx_slate_id = Some(slate_id);
t.tx_slate_state = Some(slate.state.clone());
t.amount_credited = amount;
t.num_outputs = 1;
t.ttl_cutoff_height = match slate.ttl_cutoff_height {
@@ -300,10 +299,10 @@ where
key_id: key_id_inner.clone(),
mmr_index: None,
n_child: key_id_inner.to_path().last_path_index(),
commit: commit,
commit,
value: amount,
status: OutputStatus::Unconfirmed,
height: height,
height,
lock_height: 0,
is_coinbase: false,
tx_log_entry: Some(log_id),
@@ -317,8 +316,8 @@ where
/// Builds a transaction to send to someone from the HD seed associated with the
/// wallet and the amount to send. Handles reading through the wallet data file,
/// selecting outputs to spend and building the change.
pub fn select_send_tx<'a, T: ?Sized, C, K, B>(
wallet: &mut T,
pub fn select_send_tx<C, K, B>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
amount: u64,
amount_includes_fee: bool,
@@ -339,9 +338,8 @@ pub fn select_send_tx<'a, T: ?Sized, C, K, B>(
Error,
>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
B: ProofBuild,
{
let (coins, _total, amount, fee) = select_coins_and_fee(
@@ -371,8 +369,8 @@ where
}
/// Select outputs and calculating fee.
pub fn select_coins_and_fee<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn select_coins_and_fee<C, K>(
wallet: &mut WalletBackend<C, K>,
amount: u64,
amount_includes_fee: bool,
current_height: u64,
@@ -391,9 +389,8 @@ pub fn select_coins_and_fee<'a, T: ?Sized, C, K>(
Error,
>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// select some spendable coins from the wallet
let (max_outputs, mut coins) = select_coins(
@@ -404,10 +401,10 @@ where
max_outputs,
selection_strategy_is_use_all,
parent_key_id,
);
)?;
// sender is responsible for setting the fee on the partial tx
// recipient should double check the fee calculation and not blindly trust the
// recipient should double-check the fee calculation and not blindly trust the
// sender
// First attempt to spend without change
@@ -422,8 +419,8 @@ where
return Err(Error::NotEnoughFunds {
available: 0,
available_disp: amount_to_hr_string(0, false),
needed: amount_with_fee as u64,
needed_disp: amount_to_hr_string(amount_with_fee as u64, false),
needed: amount_with_fee,
needed_disp: amount_to_hr_string(amount_with_fee, false),
});
}
@@ -432,8 +429,8 @@ where
return Err(Error::NotEnoughFunds {
available: total,
available_disp: amount_to_hr_string(total, false),
needed: amount_with_fee as u64,
needed_disp: amount_to_hr_string(amount_with_fee as u64, false),
needed: amount_with_fee,
needed_disp: amount_to_hr_string(amount_with_fee, false),
});
}
@@ -453,10 +450,10 @@ where
// End the loop if we have selected all the outputs and still not enough funds
if coins.len() == max_outputs {
return Err(Error::NotEnoughFunds {
available: total as u64,
available: total,
available_disp: amount_to_hr_string(total, false),
needed: amount_with_fee as u64,
needed_disp: amount_to_hr_string(amount_with_fee as u64, false),
needed: amount_with_fee,
needed_disp: amount_to_hr_string(amount_with_fee, false),
});
}
@@ -469,7 +466,7 @@ where
max_outputs,
selection_strategy_is_use_all,
parent_key_id,
)
)?
.1;
fee = tx_fee(coins.len(), num_outputs, 1);
total = coins.iter().map(|c| c.value).sum();
@@ -483,7 +480,7 @@ where
// be reduced, to accommodate the fee.
let new_amount = match amount_includes_fee {
true => amount.checked_sub(fee).ok_or(Error::GenericError(
format!("Transaction amount is too small to include fee").into(),
"Transaction amount is too small to include fee".to_string(),
))?,
false => amount,
};
@@ -491,9 +488,9 @@ where
}
/// Selects inputs and change for a transaction
pub fn inputs_and_change<'a, T: ?Sized, C, K, B>(
pub fn inputs_and_change<C, K, B>(
coins: &[OutputData],
wallet: &mut T,
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
amount: u64,
fee: u64,
@@ -507,9 +504,8 @@ pub fn inputs_and_change<'a, T: ?Sized, C, K, B>(
Error,
>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
B: ProofBuild,
{
let mut parts = vec![];
@@ -554,7 +550,7 @@ where
part_change
};
let change_key = wallet.next_child(keychain_mask).unwrap();
let change_key = wallet.next_child(keychain_mask)?;
change_amounts_derivations.push((change_amount, change_key.clone(), None));
parts.push(build::output(change_amount, change_key));
@@ -569,26 +565,23 @@ where
/// max_outputs). Alternative strategy is to spend smallest outputs first
/// but only as many as necessary. When we introduce additional strategies
/// we should pass something other than a bool in.
/// TODO: Possibly move this into another trait to be owned by a wallet?
pub fn select_coins<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn select_coins<C, K>(
wallet: &WalletBackend<C, K>,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
parent_key_id: &Identifier,
) -> (usize, Vec<OutputData>)
) -> Result<(usize, Vec<OutputData>), Error>
// max_outputs_available, Outputs
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// first find all eligible outputs based on number of confirmations
let mut eligible = wallet
.iter()
.iter()?
.filter(|out| {
out.root_key_id == *parent_key_id
&& out.eligible_to_spend(current_height, minimum_confirmations)
@@ -612,7 +605,7 @@ where
for window in eligible.windows(max_outputs) {
let windowed_eligibles = window.to_vec();
if let Some(outputs) = select_from(amount, select_all, windowed_eligibles) {
return (max_available, outputs);
return Ok((max_available, outputs));
}
}
// Not exist in any window of which total amount >= amount.
@@ -623,20 +616,20 @@ where
"Extending maximum number of outputs. {} outputs selected.",
outputs.len()
);
return (max_available, outputs);
return Ok((max_available, outputs));
}
} else if let Some(outputs) = select_from(amount, select_all, eligible.clone()) {
return (max_available, outputs);
return Ok((max_available, outputs));
}
// we failed to find a suitable set of outputs to spend,
// so return the largest amount we can so we can provide guidance on what is
// possible
eligible.reverse();
(
Ok((
max_available,
eligible.iter().take(max_outputs).cloned().collect(),
)
))
}
fn select_from(amount: u64, select_all: bool, outputs: Vec<OutputData>) -> Option<Vec<OutputData>> {
@@ -663,21 +656,20 @@ fn select_from(amount: u64, select_all: bool, outputs: Vec<OutputData>) -> Optio
}
}
/// Repopulates output in the slate's tranacstion
/// Repopulates output in the slate's transaction
/// with outputs from the stored context
/// change outputs and tx log entry
/// Remove the explicitly stored excess
pub fn repopulate_tx<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn repopulate_tx<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
context: &Context,
update_fee: bool,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// restore the original amount, fee
slate.amount = context.amount;
@@ -694,7 +686,7 @@ where
let mut parts = vec![];
for (id, _, value) in &context.get_inputs() {
let input = wallet.iter().find(|out| out.key_id == *id);
let input = wallet.iter()?.find(|out| out.key_id == *id);
if let Some(i) = input {
if i.is_coinbase {
parts.push(build::coinbase_input(*value, i.key_id.clone()));
@@ -704,7 +696,7 @@ where
}
}
for (id, _, value) in &context.get_outputs() {
let output = wallet.iter().find(|out| out.key_id == *id);
let output = wallet.iter()?.find(|out| out.key_id == *id);
if let Some(i) = output {
parts.push(build::output(*value, i.key_id.clone()));
}
+39 -51
View File
@@ -26,10 +26,10 @@ use crate::grin_util::secp::pedersen;
use crate::grin_util::Mutex;
use crate::internal::{selection, updater};
use crate::slate::Slate;
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend};
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType};
use crate::util::OnionV3Address;
use crate::InitTxArgs;
use crate::{address, Error};
use crate::{InitTxArgs, WalletBackend};
use ed25519_dalek::Keypair as DalekKeypair;
use ed25519_dalek::PublicKey as DalekPublicKey;
use ed25519_dalek::SecretKey as DalekSecretKey;
@@ -44,8 +44,8 @@ lazy_static! {
/// Creates a new slate for a transaction, can be called by anyone involved in
/// the transaction (sender(s), receiver(s))
pub fn new_tx_slate<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn new_tx_slate<C, K>(
wallet: &mut WalletBackend<C, K>,
amount: u64,
is_invoice: bool,
num_participants: u8,
@@ -53,9 +53,8 @@ pub fn new_tx_slate<'a, T: ?Sized, C, K>(
ttl_blocks: Option<u64>,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let current_height = wallet.w2n_client().get_chain_tip()?.0;
let mut slate = Slate::blank(num_participants, is_invoice);
@@ -92,8 +91,8 @@ where
}
/// Estimates locked amount and fee for the transaction without creating one
pub fn estimate_send_tx<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn estimate_send_tx<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
amount: u64,
amount_includes_fee: bool,
@@ -110,9 +109,8 @@ pub fn estimate_send_tx<'a, T: ?Sized, C, K>(
Error,
>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// Get lock height
let current_height = wallet.w2n_client().get_chain_tip()?.0;
@@ -141,8 +139,8 @@ where
}
/// Add inputs to the slate (effectively becoming the sender)
pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn add_inputs_to_slate<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
current_height: u64,
@@ -156,9 +154,8 @@ pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>(
amount_includes_fee: bool,
) -> Result<Context, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// sender should always refresh outputs
updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?;
@@ -207,8 +204,8 @@ where
}
/// Add receiver output to the slate
pub fn add_output_to_slate<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn add_output_to_slate<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
current_height: u64,
@@ -217,9 +214,8 @@ pub fn add_output_to_slate<'a, T: ?Sized, C, K>(
use_test_rng: bool,
) -> Result<Context, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let keychain = wallet.keychain(keychain_mask)?;
// create an output using the amount in the slate
@@ -252,8 +248,8 @@ where
}
/// Create context, without adding inputs to slate
pub fn create_late_lock_context<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn create_late_lock_context<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
current_height: u64,
@@ -262,9 +258,8 @@ pub fn create_late_lock_context<'a, T: ?Sized, C, K>(
use_test_rng: bool,
) -> Result<Context, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// sender should always refresh outputs
updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?;
@@ -300,16 +295,15 @@ where
}
/// Complete a transaction
pub fn complete_tx<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn complete_tx<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &mut Slate,
context: &Context,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// when self sending invoice tx, use initiator nonce to finalize
let (sec_key, sec_nonce) = {
@@ -333,17 +327,16 @@ where
}
/// Rollback outputs associated with a transaction in the wallet
pub fn cancel_tx<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn cancel_tx<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut tx_id_string = String::new();
if let Some(tx_id) = tx_id {
@@ -384,17 +377,16 @@ where
}
/// Update the stored transaction (this update needs to happen when the TX is finalised)
pub fn update_stored_tx<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn update_stored_tx<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
context: &Context,
slate: &Slate,
is_invoiced: bool,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// finalize command
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, None, false)?;
@@ -421,10 +413,7 @@ where
}
if let Some(ref p) = slate.clone().payment_proof {
let derivation_index = match context.payment_proof_derivation_index {
Some(i) => i,
None => 0,
};
let derivation_index = context.payment_proof_derivation_index.unwrap_or_else(|| 0);
let keychain = wallet.keychain(keychain_mask)?;
let parent_key_id = wallet.parent_key_id();
let excess = slate.calc_excess(keychain.secp())?;
@@ -506,17 +495,16 @@ pub fn create_payment_proof_signature(
}
/// Verify all aspects of a completed payment proof on the current slate
pub fn verify_slate_payment_proof<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn verify_slate_payment_proof<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
context: &Context,
slate: &Slate,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let tx_vec = updater::retrieve_txs(
wallet,
+286 -233
View File
@@ -30,32 +30,29 @@ use crate::grin_util::secp::key::SecretKey;
use crate::grin_util::secp::pedersen;
use crate::grin_util::static_secp_instance;
use crate::internal::keys;
use crate::types::{
NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, WalletBackend, WalletInfo,
};
use crate::types::{NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, WalletInfo};
use crate::{
BlockFees, CbData, OutputCommitMapping, RetrieveTxQueryArgs, RetrieveTxQuerySortField,
RetrieveTxQuerySortOrder,
RetrieveTxQuerySortOrder, WalletBackend,
};
use num_bigint::BigInt;
/// Retrieve all of the outputs (doesn't attempt to update from node)
pub fn retrieve_outputs<'a, T: ?Sized, C, K>(
wallet: &mut T,
/// Retrieve all the outputs (don't attempt to update from node)
pub fn retrieve_outputs<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
show_spent: bool,
tx_id: Option<u32>,
parent_key_id: Option<&Identifier>,
) -> Result<Vec<OutputCommitMapping>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// just read the wallet here, no need for a write lock
let mut outputs = wallet
.iter()
.iter()?
.filter(|out| show_spent || out.status != OutputStatus::Spent)
.collect::<Vec<_>>();
@@ -94,192 +91,200 @@ where
}
/// Apply advanced filtering to resultset from retrieve_txs below
pub fn apply_advanced_tx_list_filtering<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn apply_advanced_tx_list_filtering<C, K>(
wallet: &mut WalletBackend<C, K>,
parent_key_id: Option<&Identifier>,
query_args: &RetrieveTxQueryArgs,
) -> Vec<TxLogEntry>
) -> Result<Vec<TxLogEntry>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// Apply simple bool, GTE or LTE fields
let txs_iter: Box<dyn Iterator<Item = TxLogEntry>> = Box::new(
wallet
.tx_log_iter()
.filter(|tx_entry| match parent_key_id {
Some(k) => tx_entry.parent_key_id == *k,
None => true,
})
.filter(|tx_entry| {
if let Some(v) = query_args.exclude_cancelled {
if v {
tx_entry.tx_type != TxLogEntryType::TxReceivedCancelled
&& tx_entry.tx_type != TxLogEntryType::TxSentCancelled
} else {
true
}
let mut bad_records = 0;
let txs_iter = wallet
.tx_log_iter()?
.filter(|tx| {
if tx.is_err() {
bad_records += 1;
}
tx.is_ok()
})
.map(|tx| tx.unwrap())
.filter(|tx_entry| match parent_key_id {
Some(k) => tx_entry.parent_key_id == *k,
None => true,
})
.filter(|tx_entry| {
if let Some(v) = query_args.exclude_cancelled {
if v {
tx_entry.tx_type != TxLogEntryType::TxReceivedCancelled
&& tx_entry.tx_type != TxLogEntryType::TxSentCancelled
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_outstanding_only {
if v {
!tx_entry.confirmed
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_outstanding_only {
if v {
!tx_entry.confirmed
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_confirmed_only {
if v {
tx_entry.confirmed
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_confirmed_only {
if v {
tx_entry.confirmed
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_sent_only {
if v {
tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_received_only {
if v {
tx_entry.tx_type == TxLogEntryType::TxReceived
|| tx_entry.tx_type == TxLogEntryType::TxReceivedCancelled
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_coinbase_only {
if v {
tx_entry.tx_type == TxLogEntryType::ConfirmedCoinbase
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_reverted_only {
if v {
tx_entry.tx_type == TxLogEntryType::TxReverted
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_id {
tx_entry.id >= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.max_id {
tx_entry.id <= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_amount {
if tx_entry.tx_type == TxLogEntryType::TxSent
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_sent_only {
if v {
tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled
{
BigInt::from(tx_entry.amount_debited)
- BigInt::from(tx_entry.amount_credited)
>= BigInt::from(v)
} else {
BigInt::from(tx_entry.amount_credited)
- BigInt::from(tx_entry.amount_debited)
>= BigInt::from(v)
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.max_amount {
if tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled
{
BigInt::from(tx_entry.amount_debited)
- BigInt::from(tx_entry.amount_credited)
<= BigInt::from(v)
} else {
BigInt::from(tx_entry.amount_credited)
- BigInt::from(tx_entry.amount_debited)
<= BigInt::from(v)
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_received_only {
if v {
tx_entry.tx_type == TxLogEntryType::TxReceived
|| tx_entry.tx_type == TxLogEntryType::TxReceivedCancelled
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_creation_timestamp {
tx_entry.creation_ts >= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_coinbase_only {
if v {
tx_entry.tx_type == TxLogEntryType::ConfirmedCoinbase
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_confirmed_timestamp {
tx_entry.creation_ts <= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.include_reverted_only {
if v {
tx_entry.tx_type == TxLogEntryType::TxReverted
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_confirmed_timestamp {
if let Some(t) = tx_entry.confirmation_ts {
t >= v
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_id {
tx_entry.id >= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.max_id {
tx_entry.id <= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_amount {
if tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled
{
BigInt::from(tx_entry.amount_debited) - BigInt::from(tx_entry.amount_credited)
>= BigInt::from(v)
} else {
BigInt::from(tx_entry.amount_credited) - BigInt::from(tx_entry.amount_debited)
>= BigInt::from(v)
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.max_amount {
if tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled
{
BigInt::from(tx_entry.amount_debited) - BigInt::from(tx_entry.amount_credited)
<= BigInt::from(v)
} else {
BigInt::from(tx_entry.amount_credited) - BigInt::from(tx_entry.amount_debited)
<= BigInt::from(v)
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_creation_timestamp {
tx_entry.creation_ts >= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_confirmed_timestamp {
tx_entry.creation_ts <= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.min_confirmed_timestamp {
if let Some(t) = tx_entry.confirmation_ts {
t >= v
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.max_confirmed_timestamp {
if let Some(t) = tx_entry.confirmation_ts {
t <= v
} else {
true
}
} else {
true
}
})
.filter(|tx_entry| {
if let Some(v) = query_args.max_confirmed_timestamp {
if let Some(t) = tx_entry.confirmation_ts {
t <= v
} else {
true
}
}),
);
} else {
true
}
});
// };
//TODO: apply limit + introduce skip before collecting all records.
// Introduce comparator for LMDB to not request all records with limit.
let mut return_txs: Vec<TxLogEntry> = txs_iter.collect();
if bad_records != 0 {
error!("apply_advanced_tx_list_filtering: tx history is missing {} records, cause db read error", bad_records);
}
// Now apply requested sorting
if let Some(ref s) = query_args.sort_field {
match s {
@@ -326,13 +331,13 @@ where
return_txs = return_txs.into_iter().take(l as usize).collect()
}
return_txs
Ok(return_txs)
}
/// Retrieve all of the transaction entries, or a particular entry
/// Retrieve all the transaction entries, or a particular entry
/// if `parent_key_id` is set, only return entries from that key
pub fn retrieve_txs<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn retrieve_txs<C, K>(
wallet: &mut WalletBackend<C, K>,
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
query_args: Option<RetrieveTxQueryArgs>,
@@ -340,18 +345,25 @@ pub fn retrieve_txs<'a, T: ?Sized, C, K>(
outstanding_only: bool,
) -> Result<Vec<TxLogEntry>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut txs;
// Adding in new transaction list query logic. If `tx_id` or `tx_slate_id`
// is provided, then `query_args` is ignored and old logic is followed.
if query_args.is_some() && tx_id.is_none() && tx_slate_id.is_none() {
txs = apply_advanced_tx_list_filtering(wallet, parent_key_id, &query_args.unwrap())
txs = apply_advanced_tx_list_filtering(wallet, parent_key_id, &query_args.unwrap())?
} else {
let mut bad_records = 0;
txs = wallet
.tx_log_iter()
.tx_log_iter()?
.filter(|tx| {
if tx.is_err() {
bad_records += 1;
}
tx.is_ok()
})
.map(|tx| tx.unwrap())
.filter(|tx_entry| {
let f_pk = match parent_key_id {
Some(k) => tx_entry.parent_key_id == *k,
@@ -377,6 +389,12 @@ where
f_pk && f_tx_id && f_txs && f_outstanding
})
.collect();
if bad_records != 0 {
error!(
"retrieve_txs: tx history is missing {} records, cause db read error",
bad_records
);
}
txs.sort_by_key(|tx| tx.creation_ts);
}
Ok(txs)
@@ -384,16 +402,15 @@ where
/// Refreshes the outputs in a wallet with the latest information
/// from a node
pub fn refresh_outputs<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn refresh_outputs<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
update_all: bool,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let height = wallet.w2n_client().get_chain_tip()?.0;
refresh_output_state(wallet, keychain_mask, height, parent_key_id, update_all)?;
@@ -402,21 +419,20 @@ where
/// build a local map of wallet outputs keyed by commit
/// and a list of outputs we want to query the node for
pub fn map_wallet_outputs<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn map_wallet_outputs<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
update_all: bool,
) -> Result<HashMap<pedersen::Commitment, (Identifier, Option<u64>, Option<u32>, bool)>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut wallet_outputs = HashMap::new();
let keychain = wallet.keychain(keychain_mask)?;
let unspents: Vec<OutputData> = wallet
.iter()
.iter()?
.filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent)
.collect();
@@ -453,17 +469,16 @@ where
}
/// Cancel transaction and associated outputs
pub fn cancel_tx_and_outputs<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn cancel_tx_and_outputs<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
mut tx: TxLogEntry,
outputs: Vec<OutputData>,
parent_key_id: &Identifier,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut batch = wallet.batch(keychain_mask)?;
@@ -490,8 +505,8 @@ where
}
/// Apply refreshed API output data to the wallet
pub fn apply_api_outputs<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn apply_api_outputs<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
wallet_outputs: &HashMap<pedersen::Commitment, (Identifier, Option<u64>, Option<u32>, bool)>,
api_outputs: &HashMap<pedersen::Commitment, (String, u64, u64)>,
@@ -500,9 +515,8 @@ pub fn apply_api_outputs<'a, T: ?Sized, C, K>(
parent_key_id: &Identifier,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
// now for each commit, find the output in the wallet and the corresponding
// api output (if it exists) and refresh it in-place in the wallet.
@@ -556,10 +570,14 @@ where
&& (output.status == OutputStatus::Unconfirmed
|| output.status == OutputStatus::Reverted)
{
let tx = batch.tx_log_iter().find(|t| {
Some(t.id) == output.tx_log_entry
&& t.parent_key_id == *parent_key_id
});
let tx = batch
.tx_log_iter()?
.filter(|t| t.is_ok())
.map(|t| t.unwrap())
.find(|t| {
Some(t.id) == output.tx_log_entry
&& t.parent_key_id == *parent_key_id
});
if let Some(mut t) = tx {
if t.tx_type == TxLogEntryType::TxReverted {
t.tx_type = TxLogEntryType::TxReceived;
@@ -568,6 +586,10 @@ where
t.update_confirmation_ts();
t.confirmed = true;
batch.save_tx_log_entry(t, &parent_key_id)?;
} else {
if let Some(tx_id) = output.tx_log_entry {
error!("apply_api_outputs: tx with id {:?} not found", tx_id);
}
}
}
output.height = o.1;
@@ -590,7 +612,18 @@ where
}
}
for mut tx in batch.tx_log_iter() {
let mut txs_to_save = vec![];
let mut bad_records = 0;
for mut tx in batch
.tx_log_iter()?
.filter(|tx| {
if tx.is_err() {
bad_records += 1;
}
tx.is_ok()
})
.map(|t| t.unwrap())
{
if reverted_kernels.contains(&tx.id) && tx.parent_key_id == *parent_key_id {
tx.tx_type = TxLogEntryType::TxReverted;
tx.reverted_after = tx.confirmation_ts.clone().and_then(|t| {
@@ -598,10 +631,21 @@ where
(now - t).to_std().ok()
});
tx.confirmed = false;
batch.save_tx_log_entry(tx, &parent_key_id)?;
txs_to_save.push(tx);
}
}
if bad_records > 0 {
error!(
"apply_api_outputs: tx history is missing {} records, cause db read error",
bad_records
);
}
for tx in txs_to_save {
batch.save_tx_log_entry(tx, &parent_key_id)?;
}
{
batch.save_last_confirmed_height(parent_key_id, height)?;
}
@@ -612,17 +656,16 @@ where
/// Builds a single api query to retrieve the latest output data from the node.
/// So we can refresh the local wallet outputs.
fn refresh_output_state<'a, T: ?Sized, C, K>(
wallet: &mut T,
fn refresh_output_state<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
height: u64,
parent_key_id: &Identifier,
update_all: bool,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
debug!("Refreshing wallet outputs");
@@ -654,16 +697,15 @@ where
Ok(())
}
fn find_reverted_kernels<'a, T: ?Sized, C, K>(
wallet: &mut T,
fn find_reverted_kernels<C, K>(
wallet: &mut WalletBackend<C, K>,
wallet_outputs: &HashMap<pedersen::Commitment, (Identifier, Option<u64>, Option<u32>, bool)>,
api_outputs: &HashMap<pedersen::Commitment, (String, u64, u64)>,
parent_key_id: &Identifier,
) -> Result<HashSet<u32>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut client = wallet.w2n_client().clone();
let mut ids = HashSet::new();
@@ -678,8 +720,16 @@ where
}
// Get corresponding kernels
let mut bad_records = 0;
let kernels = wallet
.tx_log_iter()
.tx_log_iter()?
.filter(|tx| {
if tx.is_err() {
bad_records += 1;
}
tx.is_ok()
})
.map(|t| t.unwrap())
.filter(|t| {
ids.contains(&t.id)
&& t.parent_key_id == *parent_key_id
@@ -698,24 +748,30 @@ where
}
}
if bad_records > 0 {
error!(
"find_reverted_kernels: tx history is missing {} records, cause db read error",
bad_records
);
}
Ok(reverted)
}
fn clean_old_unconfirmed<'a, T: ?Sized, C, K>(
wallet: &mut T,
fn clean_old_unconfirmed<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
height: u64,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
if height < 50 {
return Ok(());
}
let mut ids_to_del = vec![];
for out in wallet.iter() {
for out in wallet.iter()? {
if out.status == OutputStatus::Unconfirmed
&& out.height > 0
&& out.height < height - 50
@@ -734,19 +790,18 @@ where
/// Retrieve summary info about the wallet
/// caller should refresh first if desired
pub fn retrieve_info<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn retrieve_info<C, K>(
wallet: &mut WalletBackend<C, K>,
parent_key_id: &Identifier,
minimum_confirmations: u64,
) -> Result<WalletInfo, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let current_height = wallet.last_confirmed_height()?;
let outputs = wallet
.iter()
.iter()?
.filter(|out| out.root_key_id == *parent_key_id);
let mut unspent_total = 0;
@@ -800,16 +855,15 @@ where
}
/// Build a coinbase output and insert into wallet
pub fn build_coinbase<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn build_coinbase<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
block_fees: &BlockFees,
test_mode: bool,
) -> Result<CbData, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let (out, kern, block_fees) = receive_coinbase(wallet, keychain_mask, block_fees, test_mode)?;
@@ -822,16 +876,15 @@ where
//TODO: Split up the output creation and the wallet insertion
/// Build a coinbase output and the corresponding kernel
pub fn receive_coinbase<'a, T: ?Sized, C, K>(
wallet: &mut T,
pub fn receive_coinbase<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
block_fees: &BlockFees,
test_mode: bool,
) -> Result<(Output, TxKernel, BlockFees), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let height = block_fees.height;
let lock_height = height + global::coinbase_maturity();
@@ -856,11 +909,11 @@ where
key_id: key_id.clone(),
n_child: key_id.to_path().last_path_index(),
mmr_index: None,
commit: commit,
commit,
value: amount,
status: OutputStatus::Unconfirmed,
height: height,
lock_height: lock_height,
height,
lock_height,
is_coinbase: true,
tx_log_entry: None,
})?;
+3 -2
View File
@@ -45,6 +45,7 @@ extern crate strum_macros;
pub mod address;
pub mod api_impl;
mod backend;
mod error;
mod internal;
pub mod mwixnet;
@@ -69,13 +70,13 @@ pub use api_impl::types::{
NodeHeightResult, OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs,
RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, VersionInfo,
};
pub use backend::{WalletBackend, WalletBatch};
pub use internal::scan::scan;
pub use slate_versions::ser as dalek_ser;
pub use types::{
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper,
ViewWallet, WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider,
WalletOutputBatch,
ViewWallet, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider,
};
/// Helper for taking a lock on the wallet instance
+4 -4
View File
@@ -81,7 +81,7 @@ impl ParticipantData {
}
}
/// A 'Slate' is passed around to all parties to build up all of the public
/// A 'Slate' is passed around to all parties to build up all the public
/// transaction data needed to create a finalized transaction. Callers can pass
/// the slate around by whatever means they choose, (but we can provide some
/// binary or JSON serialization helpers here).
@@ -115,7 +115,7 @@ pub struct Slate {
/// 2: height_locked
/// 3: NRD
pub kernel_features: u8,
/// Offset, needed when posting of transasction is deferred
/// Offset, needed when posting of transaction is deferred
pub offset: BlindingFactor,
/// Participant data, each participant in the transaction will
/// insert their public data here. For now, 0 is sender and 1
@@ -134,7 +134,7 @@ impl fmt::Display for Slate {
}
/// Slate state definition
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum SlateState {
/// Unknown, coming from earlier slate versions
Unknown,
@@ -148,7 +148,7 @@ pub enum SlateState {
Invoice1,
///Invoice flow, return journey
Invoice2,
/// Invoice flow, ready for tranasction posting
/// Invoice flow, ready for transaction posting
Invoice3,
}
+9 -181
View File
@@ -28,7 +28,7 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey};
use crate::grin_util::secp::{self, pedersen, Secp256k1};
use crate::grin_util::{ToHex, ZeroingString};
use crate::slate_versions::ser as dalek_ser;
use crate::InitTxArgs;
use crate::{InitTxArgs, SlateState, WalletBackend};
use chrono::prelude::*;
use ed25519_dalek::PublicKey as DalekPublicKey;
use ed25519_dalek::Signature as DalekSignature;
@@ -49,7 +49,7 @@ where
K: Keychain + 'a,
{
/// Return the stored instance
fn lc_provider(&mut self) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), Error>;
fn lc_provider(&mut self) -> Result<&mut dyn WalletLCProvider<'a, C, K>, Error>;
}
/// Trait for a provider of wallet lifecycle methods
@@ -131,182 +131,7 @@ where
fn delete_wallet(&self, name: Option<&str>) -> Result<(), Error>;
/// return wallet instance
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error>;
}
/// TODO:
/// Wallets should implement this backend for their storage. All functions
/// here expect that the wallet instance has instantiated itself or stored
/// whatever credentials it needs
pub trait WalletBackend<'ck, C, K>: Send + Sync
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
/// Set the keychain, which should already be initialized
/// Optionally return a token value used to XOR the stored
/// key value
fn set_keychain(
&mut self,
k: Box<K>,
mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, Error>;
/// Close wallet and remove any stored credentials (TBD)
fn close(&mut self) -> Result<(), Error>;
/// Return the keychain being used. Ensure a cloned copy so it will be dropped
/// and zeroized by the caller
/// Can optionally take a mask value
fn keychain(&self, mask: Option<&SecretKey>) -> Result<K, Error>;
/// Return the client being used to communicate with the node
fn w2n_client(&mut self) -> &mut C;
/// return the commit for caching if allowed, none otherwise
fn calc_commit_for_cache(
&mut self,
keychain_mask: Option<&SecretKey>,
amount: u64,
id: &Identifier,
) -> Result<Option<String>, Error>;
/// Set parent key id by stored account name
fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>;
/// The BIP32 path of the parent path to use for all output-related
/// functions, (essentially 'accounts' within a wallet.
fn set_parent_key_id(&mut self, _: Identifier);
/// return the parent path
fn parent_key_id(&mut self) -> Identifier;
/// Iterate over all output data stored by the backend
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a>;
/// Get output data by id
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
/// Get an (Optional) tx log entry by uuid
fn get_tx_log_entry(&self, uuid: &Uuid) -> Result<Option<TxLogEntry>, Error>;
/// Retrieves the private context associated with a given slate id
fn get_private_context(
&mut self,
keychain_mask: Option<&SecretKey>,
slate_id: &[u8],
) -> Result<Context, Error>;
/// Iterate over all output data stored by the backend
fn tx_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = TxLogEntry> + 'a>;
/// Iterate over all stored account paths
fn acct_path_iter<'a>(&'a self) -> Box<dyn Iterator<Item = AcctPathMapping> + 'a>;
/// Gets an account path for a given label
fn get_acct_path(&self, label: String) -> Result<Option<AcctPathMapping>, Error>;
/// Stores a transaction
fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error>;
/// Retrieves a stored transaction from a TxLogEntry
fn get_stored_tx(&self, uuid: &str) -> Result<Option<Transaction>, Error>;
/// Create a new write batch to update or remove output data
fn batch<'a>(
&'a mut self,
keychain_mask: Option<&SecretKey>,
) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
/// Batch for use when keychain isn't available or required
fn batch_no_mask<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
/// Return the current child Index
fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
/// Next child ID when we want to create a new output, based on current parent
fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error>;
/// last verified height of outputs directly descending from the given parent key
fn last_confirmed_height(&mut self) -> Result<u64, Error>;
/// last block scanned during scan or restore
fn last_scanned_block(&mut self) -> Result<ScannedBlockInfo, Error>;
/// Flag whether the wallet needs a full UTXO scan on next update attempt
fn init_status(&mut self) -> Result<WalletInitStatus, Error>;
}
/// Batch trait to update the output data backend atomically. Trying to use a
/// batch after commit MAY result in a panic. Due to this being a trait, the
/// commit method can't take ownership.
/// TODO: Should these be split into separate batch objects, for outputs,
/// tx_log entries and meta/details?
pub trait WalletOutputBatch<K>
where
K: Keychain,
{
/// Return the keychain being used
fn keychain(&mut self) -> &mut K;
/// Add or update data about an output to the backend
fn save(&mut self, out: OutputData) -> Result<(), Error>;
/// Gets output data by id
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
/// Iterate over all output data stored by the backend
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>>;
/// Delete data about an output from the backend
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error>;
/// Save last stored child index of a given parent
fn save_child_index(&mut self, parent_key_id: &Identifier, child_n: u32) -> Result<(), Error>;
/// Save last confirmed height of outputs for a given parent
fn save_last_confirmed_height(
&mut self,
parent_key_id: &Identifier,
height: u64,
) -> Result<(), Error>;
/// Save the last PMMR index that was scanned via a scan operation
fn save_last_scanned_block(&mut self, block: ScannedBlockInfo) -> Result<(), Error>;
/// Save flag indicating whether wallet needs a full UTXO scan
fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error>;
/// get next tx log entry for the parent
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
/// Iterate over tx log data stored by the backend
fn tx_log_iter(&self) -> Box<dyn Iterator<Item = TxLogEntry>>;
/// save a tx log entry
fn save_tx_log_entry(&mut self, t: TxLogEntry, parent_id: &Identifier) -> Result<(), Error>;
/// delete a tx log entry
fn delete_tx_log_entry(&mut self, tx_id: u32, parent_id: &Identifier) -> Result<(), Error>;
/// save an account label -> path mapping
fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error>;
/// Iterate over account names stored in backend
fn acct_path_iter(&self) -> Box<dyn Iterator<Item = AcctPathMapping>>;
/// Save an output as locked in the backend
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error>;
/// Saves the private context associated with a slate id
fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error>;
/// Delete the private context associated with the slate id
fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error>;
/// Write the wallet data to backend file
fn commit(&self) -> Result<(), Error>;
fn wallet_inst(&mut self) -> Result<&mut WalletBackend<C, K>, Error>;
}
/// Encapsulate all wallet-node communication functions. No functions within libwallet
@@ -798,6 +623,8 @@ pub struct TxLogEntry {
pub id: u32,
/// Slate transaction this entry is associated with, if any
pub tx_slate_id: Option<Uuid>,
/// Transaction slate state
pub tx_slate_state: Option<SlateState>,
/// Transaction type (as above)
pub tx_type: TxLogEntryType,
/// Time this tx entry was created
@@ -861,10 +688,11 @@ impl TxLogEntry {
/// Return a new blank with TS initialised with next entry
pub fn new(parent_key_id: Identifier, t: TxLogEntryType, id: u32) -> Self {
TxLogEntry {
parent_key_id: parent_key_id,
parent_key_id,
tx_type: t,
id: id,
id,
tx_slate_id: None,
tx_slate_state: None,
creation_ts: Utc::now(),
confirmation_ts: None,
confirmed: false,
@@ -1053,7 +881,7 @@ pub struct ViewWalletOutputResult {
impl ViewWalletOutputResult {
pub fn num_confirmations(&self, tip_height: u64) -> u64 {
if self.height > tip_height {
return 0;
0
} else {
1 + (tip_height - self.height)
}
+1 -1
View File
@@ -115,7 +115,7 @@ pub fn command_loop<L, C, K>(
test_mode: bool,
) -> Result<(), Error>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
+14 -16
View File
@@ -92,7 +92,7 @@ fn prompt_recovery_phrase<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
) -> Result<ZeroingString, ParseError>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
@@ -224,12 +224,12 @@ pub fn inst_wallet<L, C, K>(
node_client: C,
) -> Result<Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>, ParseError>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client.clone()).unwrap())
let mut wallet = Box::new(DefaultWalletImpl::<C>::new(node_client.clone()).unwrap())
as Box<dyn WalletInst<'static, L, C, K>>;
let lc = wallet.lc_provider().unwrap();
let _ = lc.set_top_level_directory(&config.data_file_dir);
@@ -335,7 +335,7 @@ pub fn parse_init_args<L, C, K>(
_test_mode: bool,
) -> Result<command::InitArgs, ParseError>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
@@ -355,16 +355,16 @@ where
println!("Please enter a password for your new wallet");
}
let password = match g_args.password.clone() {
Some(p) => p,
None => prompt_password_confirm(),
};
let password = g_args
.password
.clone()
.unwrap_or_else(|| prompt_password_confirm());
Ok(command::InitArgs {
list_length: list_length,
password: password,
list_length,
password,
config: config.clone(),
recovery_phrase: recovery_phrase,
recovery_phrase,
restore: false,
})
}
@@ -375,9 +375,7 @@ pub fn parse_recover_args(
where
{
let passphrase = prompt_password(&g_args.password);
Ok(command::RecoverArgs {
passphrase: passphrase,
})
Ok(command::RecoverArgs { passphrase })
}
pub fn parse_listen_args(
@@ -977,7 +975,7 @@ where
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, C, keychain::ExtKeychain>,
DefaultLCProvider<C, keychain::ExtKeychain>,
C,
keychain::ExtKeychain,
>,
@@ -1112,7 +1110,7 @@ pub fn parse_and_execute<L, C, K>(
cli_mode: bool,
) -> Result<(), Error>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
+3 -14
View File
@@ -226,7 +226,7 @@ pub fn instantiate_wallet(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -241,7 +241,7 @@ pub fn instantiate_wallet(
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -302,18 +302,7 @@ pub fn execute_command_no_setup<C, F>(
where
C: NodeClient + 'static + Clone,
F: FnOnce(
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, C, ExtKeychain>,
C,
ExtKeychain,
>,
>,
>,
>,
Arc<Mutex<Box<dyn WalletInst<'static, DefaultLCProvider<C, ExtKeychain>, C, ExtKeychain>>>>,
),
{
let args = app.clone().get_matches_from(arg_vec);
+1 -1
View File
@@ -57,7 +57,7 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> {
let wallet_proxy_a: Arc<
Mutex<
WalletProxy<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+2 -2
View File
@@ -21,14 +21,14 @@ thiserror = "1"
##### Grin Imports
# For Release
grin_util = "5.4.0"
#grin_util = "5.4.0"
# For beta release
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
# For bleeding edge
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_util = { git = "https://github.com/mimblewimble/grin", rev = "cf2ed3f3becd62b5e754ec964fd90e332b9a021f" }
# For local testing