Merge pull request #661 from nuttycom/wallet/spend_with_usk
Use unified spending keys for spends & shielding.
This commit is contained in:
commit
f7a3b9bda3
|
@ -35,6 +35,7 @@ and this library adheres to Rust's notion of
|
||||||
- `Recipient`
|
- `Recipient`
|
||||||
- `SentTransactionOutput`
|
- `SentTransactionOutput`
|
||||||
- `WalletRead::get_unified_full_viewing_keys`
|
- `WalletRead::get_unified_full_viewing_keys`
|
||||||
|
- `WalletRead::get_account_for_ufvk`
|
||||||
- `WalletRead::get_current_address`
|
- `WalletRead::get_current_address`
|
||||||
- `WalletRead::get_all_nullifiers`
|
- `WalletRead::get_all_nullifiers`
|
||||||
- `WalletWrite::create_account`
|
- `WalletWrite::create_account`
|
||||||
|
@ -132,6 +133,8 @@ and this library adheres to Rust's notion of
|
||||||
- `decode_extended_spending_key`
|
- `decode_extended_spending_key`
|
||||||
- `decode_extended_full_viewing_key`
|
- `decode_extended_full_viewing_key`
|
||||||
- `decode_payment_address`
|
- `decode_payment_address`
|
||||||
|
- `data_api::wallet::create_spend_to_address` has been modified to use a unified
|
||||||
|
spending key rather than a Sapling extended spending key.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_client_backend::data_api`:
|
- `zcash_client_backend::data_api`:
|
||||||
|
|
|
@ -128,6 +128,13 @@ pub trait WalletRead {
|
||||||
&self,
|
&self,
|
||||||
) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error>;
|
) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error>;
|
||||||
|
|
||||||
|
/// Returns the account id corresponding to a given [`UnifiedFullViewingKey`],
|
||||||
|
/// if any.
|
||||||
|
fn get_account_for_ufvk(
|
||||||
|
&self,
|
||||||
|
ufvk: &UnifiedFullViewingKey,
|
||||||
|
) -> Result<Option<AccountId>, Self::Error>;
|
||||||
|
|
||||||
/// Checks whether the specified extended full viewing key is
|
/// Checks whether the specified extended full viewing key is
|
||||||
/// associated with the account.
|
/// associated with the account.
|
||||||
fn is_valid_account_extfvk(
|
fn is_valid_account_extfvk(
|
||||||
|
@ -484,6 +491,13 @@ pub mod testing {
|
||||||
Ok(HashMap::new())
|
Ok(HashMap::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_account_for_ufvk(
|
||||||
|
&self,
|
||||||
|
_ufvk: &UnifiedFullViewingKey,
|
||||||
|
) -> Result<Option<AccountId>, Self::Error> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_valid_account_extfvk(
|
fn is_valid_account_extfvk(
|
||||||
&self,
|
&self,
|
||||||
_account: AccountId,
|
_account: AccountId,
|
||||||
|
|
|
@ -24,6 +24,9 @@ pub enum ChainInvalid {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<NoteId> {
|
pub enum Error<NoteId> {
|
||||||
|
/// No account could be found corresponding to a provided spending key.
|
||||||
|
KeyNotRecognized,
|
||||||
|
|
||||||
/// No account with the given identifier was found in the wallet.
|
/// No account with the given identifier was found in the wallet.
|
||||||
AccountNotFound(AccountId),
|
AccountNotFound(AccountId),
|
||||||
|
|
||||||
|
@ -90,6 +93,9 @@ impl ChainInvalid {
|
||||||
impl<N: fmt::Display> fmt::Display for Error<N> {
|
impl<N: fmt::Display> fmt::Display for Error<N> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
|
Error::KeyNotRecognized => {
|
||||||
|
write!(f, "Wallet does not contain an account corresponding to the provided spending key")
|
||||||
|
}
|
||||||
Error::AccountNotFound(account) => {
|
Error::AccountNotFound(account) => {
|
||||||
write!(f, "Wallet does not contain account {}", u32::from(*account))
|
write!(f, "Wallet does not contain account {}", u32::from(*account))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,10 @@ use zcash_primitives::{
|
||||||
components::{amount::DEFAULT_FEE, Amount},
|
components::{amount::DEFAULT_FEE, Amount},
|
||||||
Transaction,
|
Transaction,
|
||||||
},
|
},
|
||||||
zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use zcash_primitives::{legacy::keys::IncomingViewingKey, sapling::keys::OutgoingViewingKey};
|
||||||
zcash_address::unified::Typecode,
|
|
||||||
zcash_primitives::{
|
|
||||||
legacy::keys::{self as transparent, IncomingViewingKey},
|
|
||||||
sapling::keys::OutgoingViewingKey,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
address::RecipientAddress,
|
address::RecipientAddress,
|
||||||
|
@ -27,6 +20,7 @@ use crate::{
|
||||||
SentTransactionOutput, WalletWrite,
|
SentTransactionOutput, WalletWrite,
|
||||||
},
|
},
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
|
keys::UnifiedSpendingKey,
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
zip321::{Payment, TransactionRequest},
|
zip321::{Payment, TransactionRequest},
|
||||||
};
|
};
|
||||||
|
@ -129,7 +123,7 @@ where
|
||||||
/// };
|
/// };
|
||||||
/// use zcash_proofs::prover::LocalTxProver;
|
/// use zcash_proofs::prover::LocalTxProver;
|
||||||
/// use zcash_client_backend::{
|
/// use zcash_client_backend::{
|
||||||
/// keys::sapling,
|
/// keys::UnifiedSpendingKey,
|
||||||
/// data_api::{wallet::create_spend_to_address, error::Error, testing},
|
/// data_api::{wallet::create_spend_to_address, error::Error, testing},
|
||||||
/// wallet::OvkPolicy,
|
/// wallet::OvkPolicy,
|
||||||
/// };
|
/// };
|
||||||
|
@ -148,8 +142,8 @@ where
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let account = AccountId::from(0);
|
/// let account = AccountId::from(0);
|
||||||
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, account);
|
/// let usk = UnifiedSpendingKey::from_seed(&Network::TestNetwork, &[0; 32][..], account).unwrap();
|
||||||
/// let to = extsk.default_address().1.into();
|
/// let to = usk.to_unified_full_viewing_key().default_address().0.into();
|
||||||
///
|
///
|
||||||
/// let mut db_read = testing::MockWalletDb {
|
/// let mut db_read = testing::MockWalletDb {
|
||||||
/// network: Network::TestNetwork
|
/// network: Network::TestNetwork
|
||||||
|
@ -159,8 +153,7 @@ where
|
||||||
/// &mut db_read,
|
/// &mut db_read,
|
||||||
/// &Network::TestNetwork,
|
/// &Network::TestNetwork,
|
||||||
/// tx_prover,
|
/// tx_prover,
|
||||||
/// account,
|
/// &usk,
|
||||||
/// &extsk,
|
|
||||||
/// &to,
|
/// &to,
|
||||||
/// Amount::from_u64(1).unwrap(),
|
/// Amount::from_u64(1).unwrap(),
|
||||||
/// None,
|
/// None,
|
||||||
|
@ -176,8 +169,7 @@ pub fn create_spend_to_address<E, N, P, D, R>(
|
||||||
wallet_db: &mut D,
|
wallet_db: &mut D,
|
||||||
params: &P,
|
params: &P,
|
||||||
prover: impl TxProver,
|
prover: impl TxProver,
|
||||||
account: AccountId,
|
usk: &UnifiedSpendingKey,
|
||||||
extsk: &ExtendedSpendingKey,
|
|
||||||
to: &RecipientAddress,
|
to: &RecipientAddress,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
memo: Option<MemoBytes>,
|
memo: Option<MemoBytes>,
|
||||||
|
@ -206,8 +198,7 @@ where
|
||||||
wallet_db,
|
wallet_db,
|
||||||
params,
|
params,
|
||||||
prover,
|
prover,
|
||||||
extsk,
|
usk,
|
||||||
account,
|
|
||||||
&req,
|
&req,
|
||||||
ovk_policy,
|
ovk_policy,
|
||||||
min_confirmations,
|
min_confirmations,
|
||||||
|
@ -248,7 +239,7 @@ where
|
||||||
/// * `wallet_db`: A read/write reference to the wallet database
|
/// * `wallet_db`: A read/write reference to the wallet database
|
||||||
/// * `params`: Consensus parameters
|
/// * `params`: Consensus parameters
|
||||||
/// * `prover`: The TxProver to use in constructing the shielded transaction.
|
/// * `prover`: The TxProver to use in constructing the shielded transaction.
|
||||||
/// * `extsk`: The extended spending key that controls the funds that will be spent
|
/// * `usk`: The unified spending key that controls the funds that will be spent
|
||||||
/// in the resulting transaction.
|
/// in the resulting transaction.
|
||||||
/// * `account`: The ZIP32 account identifier associated with the extended spending
|
/// * `account`: The ZIP32 account identifier associated with the extended spending
|
||||||
/// key that controls the funds to be used in creating this transaction. This
|
/// key that controls the funds to be used in creating this transaction. This
|
||||||
|
@ -265,8 +256,7 @@ pub fn spend<E, N, P, D, R>(
|
||||||
wallet_db: &mut D,
|
wallet_db: &mut D,
|
||||||
params: &P,
|
params: &P,
|
||||||
prover: impl TxProver,
|
prover: impl TxProver,
|
||||||
extsk: &ExtendedSpendingKey,
|
usk: &UnifiedSpendingKey,
|
||||||
account: AccountId,
|
|
||||||
request: &TransactionRequest,
|
request: &TransactionRequest,
|
||||||
ovk_policy: OvkPolicy,
|
ovk_policy: OvkPolicy,
|
||||||
min_confirmations: u32,
|
min_confirmations: u32,
|
||||||
|
@ -277,12 +267,11 @@ where
|
||||||
R: Copy + Debug,
|
R: Copy + Debug,
|
||||||
D: WalletWrite<Error = E, TxRef = R>,
|
D: WalletWrite<Error = E, TxRef = R>,
|
||||||
{
|
{
|
||||||
// Check that the ExtendedSpendingKey we have been given corresponds to the
|
let account = wallet_db
|
||||||
// ExtendedFullViewingKey for the account we are spending from.
|
.get_account_for_ufvk(&usk.to_unified_full_viewing_key())?
|
||||||
let extfvk = ExtendedFullViewingKey::from(extsk);
|
.ok_or(Error::KeyNotRecognized)?;
|
||||||
if !wallet_db.is_valid_account_extfvk(account, &extfvk)? {
|
|
||||||
return Err(E::from(Error::InvalidExtSk(account)));
|
let extfvk = usk.sapling().to_extended_full_viewing_key();
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the outgoing viewing key policy.
|
// Apply the outgoing viewing key policy.
|
||||||
let ovk = match ovk_policy {
|
let ovk = match ovk_policy {
|
||||||
|
@ -335,7 +324,12 @@ where
|
||||||
let merkle_path = selected.witness.path().expect("the tree is not empty");
|
let merkle_path = selected.witness.path().expect("the tree is not empty");
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.add_sapling_spend(extsk.clone(), selected.diversifier, note, merkle_path)
|
.add_sapling_spend(
|
||||||
|
usk.sapling().clone(),
|
||||||
|
selected.diversifier,
|
||||||
|
note,
|
||||||
|
merkle_path,
|
||||||
|
)
|
||||||
.map_err(Error::Builder)?;
|
.map_err(Error::Builder)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,8 +446,7 @@ pub fn shield_transparent_funds<E, N, P, D, R, U>(
|
||||||
wallet_db: &mut D,
|
wallet_db: &mut D,
|
||||||
params: &P,
|
params: &P,
|
||||||
prover: impl TxProver,
|
prover: impl TxProver,
|
||||||
sk: &transparent::AccountPrivKey,
|
usk: &UnifiedSpendingKey,
|
||||||
account: AccountId,
|
|
||||||
memo: &MemoBytes,
|
memo: &MemoBytes,
|
||||||
min_confirmations: u32,
|
min_confirmations: u32,
|
||||||
) -> Result<D::TxRef, E>
|
) -> Result<D::TxRef, E>
|
||||||
|
@ -463,28 +456,20 @@ where
|
||||||
R: Copy + Debug,
|
R: Copy + Debug,
|
||||||
D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>,
|
D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>,
|
||||||
{
|
{
|
||||||
// Obtain the UFVK for the specified account & use its internal change address
|
let account = wallet_db
|
||||||
// as the destination for shielded funds.
|
.get_account_for_ufvk(&usk.to_unified_full_viewing_key())?
|
||||||
let shielding_address = wallet_db
|
.ok_or(Error::KeyNotRecognized)?;
|
||||||
.get_unified_full_viewing_keys()
|
|
||||||
.and_then(|ufvks| {
|
|
||||||
ufvks
|
|
||||||
.get(&account)
|
|
||||||
.ok_or_else(|| E::from(Error::AccountNotFound(account)))
|
|
||||||
.and_then(|ufvk| {
|
|
||||||
// TODO: select the most preferred shielded receiver once we have the ability to
|
|
||||||
// spend Orchard funds.
|
|
||||||
ufvk.sapling()
|
|
||||||
.map(|dfvk| dfvk.change_address().1)
|
|
||||||
.ok_or_else(|| E::from(Error::KeyNotFound(account, Typecode::Sapling)))
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
|
let shielding_address = usk
|
||||||
|
.sapling()
|
||||||
|
.to_diversifiable_full_viewing_key()
|
||||||
|
.change_address()
|
||||||
|
.1;
|
||||||
let (latest_scanned_height, latest_anchor) = wallet_db
|
let (latest_scanned_height, latest_anchor) = wallet_db
|
||||||
.get_target_and_anchor_heights(min_confirmations)
|
.get_target_and_anchor_heights(min_confirmations)
|
||||||
.and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?;
|
.and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?;
|
||||||
|
|
||||||
let account_pubkey = sk.to_account_pubkey();
|
let account_pubkey = usk.transparent().to_account_pubkey();
|
||||||
let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes());
|
let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes());
|
||||||
|
|
||||||
// derive the t-address for the extpubkey at the minimum valid child index
|
// derive the t-address for the extpubkey at the minimum valid child index
|
||||||
|
@ -510,7 +495,10 @@ where
|
||||||
|
|
||||||
let mut builder = Builder::new_with_fee(params.clone(), latest_scanned_height, fee);
|
let mut builder = Builder::new_with_fee(params.clone(), latest_scanned_height, fee);
|
||||||
|
|
||||||
let secret_key = sk.derive_external_secret_key(child_index).unwrap();
|
let secret_key = usk
|
||||||
|
.transparent()
|
||||||
|
.derive_external_secret_key(child_index)
|
||||||
|
.unwrap();
|
||||||
for utxo in &utxos {
|
for utxo in &utxos {
|
||||||
builder
|
builder
|
||||||
.add_transparent_input(secret_key, utxo.outpoint.clone(), utxo.txout.clone())
|
.add_transparent_input(secret_key, utxo.outpoint.clone(), utxo.txout.clone())
|
||||||
|
|
|
@ -163,6 +163,13 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
|
||||||
wallet::get_unified_full_viewing_keys(self)
|
wallet::get_unified_full_viewing_keys(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_account_for_ufvk(
|
||||||
|
&self,
|
||||||
|
ufvk: &UnifiedFullViewingKey,
|
||||||
|
) -> Result<Option<AccountId>, Self::Error> {
|
||||||
|
wallet::get_account_for_ufvk(self, ufvk)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_current_address(
|
fn get_current_address(
|
||||||
&self,
|
&self,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
|
@ -289,6 +296,13 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
|
||||||
self.wallet_db.get_unified_full_viewing_keys()
|
self.wallet_db.get_unified_full_viewing_keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_account_for_ufvk(
|
||||||
|
&self,
|
||||||
|
ufvk: &UnifiedFullViewingKey,
|
||||||
|
) -> Result<Option<AccountId>, Self::Error> {
|
||||||
|
self.wallet_db.get_account_for_ufvk(ufvk)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_current_address(
|
fn get_current_address(
|
||||||
&self,
|
&self,
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
|
|
|
@ -342,6 +342,25 @@ pub(crate) fn get_unified_full_viewing_keys<P: consensus::Parameters>(
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the account id corresponding to a given [`UnifiedFullViewingKey`],
|
||||||
|
/// if any.
|
||||||
|
pub(crate) fn get_account_for_ufvk<P: consensus::Parameters>(
|
||||||
|
wdb: &WalletDb<P>,
|
||||||
|
ufvk: &UnifiedFullViewingKey,
|
||||||
|
) -> Result<Option<AccountId>, SqliteClientError> {
|
||||||
|
wdb.conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT account FROM accounts WHERE ufvk = ?",
|
||||||
|
[&ufvk.encode(&wdb.params)],
|
||||||
|
|row| {
|
||||||
|
let acct: u32 = row.get(0)?;
|
||||||
|
Ok(AccountId::from(acct))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.map_err(SqliteClientError::from)
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks whether the specified [`ExtendedFullViewingKey`] is valid and corresponds to the
|
/// Checks whether the specified [`ExtendedFullViewingKey`] is valid and corresponds to the
|
||||||
/// specified account.
|
/// specified account.
|
||||||
///
|
///
|
||||||
|
|
|
@ -157,28 +157,25 @@ pub fn select_spendable_sapling_notes<P>(
|
||||||
mod tests {
|
mod tests {
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use std::collections::HashMap;
|
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{BlockHeight, BranchId, Parameters},
|
consensus::{BlockHeight, BranchId},
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
||||||
transaction::{components::Amount, Transaction},
|
transaction::{components::Amount, Transaction},
|
||||||
zip32::sapling::{
|
zip32::sapling::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||||
DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
use zcash_primitives::legacy::keys as transparent;
|
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead},
|
data_api::{
|
||||||
keys::{sapling, UnifiedFullViewingKey},
|
self, chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead,
|
||||||
|
WalletWrite,
|
||||||
|
},
|
||||||
|
keys::UnifiedSpendingKey,
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -187,7 +184,7 @@ mod tests {
|
||||||
tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height},
|
tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height},
|
||||||
wallet::{
|
wallet::{
|
||||||
get_balance, get_balance_at,
|
get_balance, get_balance_at,
|
||||||
init::{init_accounts_table, init_blocks_table, init_wallet_db},
|
init::{init_blocks_table, init_wallet_db},
|
||||||
},
|
},
|
||||||
AccountId, BlockDb, DataConnStmtCache, WalletDb,
|
AccountId, BlockDb, DataConnStmtCache, WalletDb,
|
||||||
};
|
};
|
||||||
|
@ -202,132 +199,80 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_incorrect_extsk() {
|
fn create_to_address_fails_on_incorrect_usk() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
||||||
|
|
||||||
let acct0 = AccountId::from(0);
|
// Add an account to the wallet
|
||||||
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
let to = dfvk.default_address().1.into();
|
||||||
|
|
||||||
|
// Create a USK that doesn't exist in the wallet
|
||||||
let acct1 = AccountId::from(1);
|
let acct1 = AccountId::from(1);
|
||||||
|
let usk1 = UnifiedSpendingKey::from_seed(&network(), &[1u8; 32], acct1).unwrap();
|
||||||
|
|
||||||
// Add two accounts to the wallet
|
// Attempting to spend with a USK that is not in the wallet results in an error
|
||||||
let extsk0 = sapling::spending_key(&[0u8; 32], network().coin_type(), acct0);
|
|
||||||
let extsk1 = sapling::spending_key(&[1u8; 32], network().coin_type(), acct1);
|
|
||||||
let dfvk0 = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk0));
|
|
||||||
let dfvk1 = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk1));
|
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
|
||||||
let ufvks = {
|
|
||||||
let tsk0 =
|
|
||||||
transparent::AccountPrivKey::from_seed(&network(), &[0u8; 32], acct0).unwrap();
|
|
||||||
let tsk1 =
|
|
||||||
transparent::AccountPrivKey::from_seed(&network(), &[1u8; 32], acct1).unwrap();
|
|
||||||
HashMap::from([
|
|
||||||
(
|
|
||||||
acct0,
|
|
||||||
UnifiedFullViewingKey::new(Some(tsk0.to_account_pubkey()), Some(dfvk0), None)
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
acct1,
|
|
||||||
UnifiedFullViewingKey::new(Some(tsk1.to_account_pubkey()), Some(dfvk1), None)
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
};
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvks = HashMap::from([
|
|
||||||
(
|
|
||||||
acct0,
|
|
||||||
UnifiedFullViewingKey::new(Some(dfvk0), None).unwrap(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
acct1,
|
|
||||||
UnifiedFullViewingKey::new(Some(dfvk1), None).unwrap(),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
let to = extsk0.default_address().1.into();
|
|
||||||
|
|
||||||
// Invalid extsk for the given account should cause an error
|
|
||||||
let mut db_write = db_data.get_update_ops().unwrap();
|
let mut db_write = db_data.get_update_ops().unwrap();
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk1,
|
&usk1,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(1).unwrap(),
|
Amount::from_u64(1).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Err(crate::SqliteClientError::BackendError(
|
||||||
Err(e) => assert_eq!(e.to_string(), "Incorrect ExtendedSpendingKey for account 0"),
|
data_api::error::Error::KeyNotRecognized
|
||||||
}
|
))
|
||||||
|
));
|
||||||
match create_spend_to_address(
|
|
||||||
&mut db_write,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
AccountId::from(1),
|
|
||||||
&extsk0,
|
|
||||||
&to,
|
|
||||||
Amount::from_u64(1).unwrap(),
|
|
||||||
None,
|
|
||||||
OvkPolicy::Sender,
|
|
||||||
10,
|
|
||||||
) {
|
|
||||||
Ok(_) => panic!("Should have failed"),
|
|
||||||
Err(e) => assert_eq!(e.to_string(), "Incorrect ExtendedSpendingKey for account 1"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_with_no_blocks() {
|
fn create_to_address_fails_with_no_blocks() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let to = dfvk.default_address().1.into();
|
||||||
let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk), None).unwrap();
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(Some(dfvk), None).unwrap();
|
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
let to = extsk.default_address().1.into();
|
|
||||||
|
|
||||||
// We cannot do anything if we aren't synchronised
|
// We cannot do anything if we aren't synchronised
|
||||||
let mut db_write = db_data.get_update_ops().unwrap();
|
let mut db_write = db_data.get_update_ops().unwrap();
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(1).unwrap(),
|
Amount::from_u64(1).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
),
|
||||||
Ok(_) => panic!("Should have failed"),
|
Err(crate::SqliteClientError::BackendError(
|
||||||
Err(e) => assert_eq!(e.to_string(), "Must scan blocks first"),
|
data_api::error::Error::ScanRequired
|
||||||
}
|
))
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_insufficient_balance() {
|
fn create_to_address_fails_on_insufficient_balance() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
init_blocks_table(
|
init_blocks_table(
|
||||||
&db_data,
|
&db_data,
|
||||||
BlockHeight::from(1u32),
|
BlockHeight::from(1u32),
|
||||||
|
@ -338,16 +283,11 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk), None).unwrap();
|
let to = dfvk.default_address().1.into();
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(Some(dfvk), None).unwrap();
|
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
let to = extsk.default_address().1.into();
|
|
||||||
|
|
||||||
// Account balance should be zero
|
// Account balance should be zero
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -357,24 +297,26 @@ mod tests {
|
||||||
|
|
||||||
// We cannot spend anything
|
// We cannot spend anything
|
||||||
let mut db_write = db_data.get_update_ops().unwrap();
|
let mut db_write = db_data.get_update_ops().unwrap();
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(1).unwrap(),
|
Amount::from_u64(1).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
|
||||||
Ok(_) => panic!("Should have failed"),
|
|
||||||
Err(e) => assert_eq!(
|
|
||||||
e.to_string(),
|
|
||||||
"Insufficient balance (have 0, need 1001 including fee)"
|
|
||||||
),
|
),
|
||||||
}
|
Err(crate::SqliteClientError::BackendError(
|
||||||
|
data_api::error::Error::InsufficientBalance(
|
||||||
|
available,
|
||||||
|
required
|
||||||
|
)
|
||||||
|
))
|
||||||
|
if available == Amount::zero() && required == Amount::from_u64(1001).unwrap()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -385,18 +327,13 @@ mod tests {
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap();
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap();
|
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
|
@ -437,24 +374,27 @@ mod tests {
|
||||||
// Spend fails because there are insufficient verified notes
|
// Spend fails because there are insufficient verified notes
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||||
let to = extsk2.default_address().1.into();
|
let to = extsk2.default_address().1.into();
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(70000).unwrap(),
|
Amount::from_u64(70000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
|
||||||
Ok(_) => panic!("Should have failed"),
|
|
||||||
Err(e) => assert_eq!(
|
|
||||||
e.to_string(),
|
|
||||||
"Insufficient balance (have 50000, need 71000 including fee)"
|
|
||||||
),
|
),
|
||||||
}
|
Err(crate::SqliteClientError::BackendError(
|
||||||
|
data_api::error::Error::InsufficientBalance(
|
||||||
|
available,
|
||||||
|
required
|
||||||
|
)
|
||||||
|
))
|
||||||
|
if available == Amount::from_u64(50000).unwrap()
|
||||||
|
&& required == Amount::from_u64(71000).unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
|
||||||
// note is verified
|
// note is verified
|
||||||
|
@ -466,24 +406,27 @@ mod tests {
|
||||||
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
|
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
|
||||||
|
|
||||||
// Second spend still fails
|
// Second spend still fails
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(70000).unwrap(),
|
Amount::from_u64(70000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
|
||||||
Ok(_) => panic!("Should have failed"),
|
|
||||||
Err(e) => assert_eq!(
|
|
||||||
e.to_string(),
|
|
||||||
"Insufficient balance (have 50000, need 71000 including fee)"
|
|
||||||
),
|
),
|
||||||
}
|
Err(crate::SqliteClientError::BackendError(
|
||||||
|
data_api::error::Error::InsufficientBalance(
|
||||||
|
available,
|
||||||
|
required
|
||||||
|
)
|
||||||
|
))
|
||||||
|
if available == Amount::from_u64(50000).unwrap()
|
||||||
|
&& required == Amount::from_u64(71000).unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// Mine block 11 so that the second note becomes verified
|
// Mine block 11 so that the second note becomes verified
|
||||||
let (cb, _) = fake_compact_block(sapling_activation_height() + 10, cb.hash(), &dfvk, value);
|
let (cb, _) = fake_compact_block(sapling_activation_height() + 10, cb.hash(), &dfvk, value);
|
||||||
|
@ -491,19 +434,20 @@ mod tests {
|
||||||
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
|
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
|
||||||
|
|
||||||
// Second spend should now succeed
|
// Second spend should now succeed
|
||||||
create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(70000).unwrap(),
|
Amount::from_u64(70000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
)
|
),
|
||||||
.unwrap();
|
Ok(_)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -517,15 +461,10 @@ mod tests {
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap();
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap();
|
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
|
@ -543,39 +482,42 @@ mod tests {
|
||||||
// Send some of the funds to another address
|
// Send some of the funds to another address
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||||
let to = extsk2.default_address().1.into();
|
let to = extsk2.default_address().1.into();
|
||||||
create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(15000).unwrap(),
|
Amount::from_u64(15000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
)
|
),
|
||||||
.unwrap();
|
Ok(_)
|
||||||
|
));
|
||||||
|
|
||||||
// A second spend fails because there are no usable notes
|
// A second spend fails because there are no usable notes
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(2000).unwrap(),
|
Amount::from_u64(2000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
|
||||||
Ok(_) => panic!("Should have failed"),
|
|
||||||
Err(e) => assert_eq!(
|
|
||||||
e.to_string(),
|
|
||||||
"Insufficient balance (have 0, need 3000 including fee)"
|
|
||||||
),
|
),
|
||||||
}
|
Err(crate::SqliteClientError::BackendError(
|
||||||
|
data_api::error::Error::InsufficientBalance(
|
||||||
|
available,
|
||||||
|
required
|
||||||
|
)
|
||||||
|
))
|
||||||
|
if available == Amount::zero() && required == Amount::from_u64(3000).unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 21 (that don't send us funds)
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 21 (that don't send us funds)
|
||||||
// until just before the first transaction expires
|
// until just before the first transaction expires
|
||||||
|
@ -591,24 +533,26 @@ mod tests {
|
||||||
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
|
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
|
||||||
|
|
||||||
// Second spend still fails
|
// Second spend still fails
|
||||||
match create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(2000).unwrap(),
|
Amount::from_u64(2000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
) {
|
|
||||||
Ok(_) => panic!("Should have failed"),
|
|
||||||
Err(e) => assert_eq!(
|
|
||||||
e.to_string(),
|
|
||||||
"Insufficient balance (have 0, need 3000 including fee)"
|
|
||||||
),
|
),
|
||||||
}
|
Err(crate::SqliteClientError::BackendError(
|
||||||
|
data_api::error::Error::InsufficientBalance(
|
||||||
|
available,
|
||||||
|
required
|
||||||
|
)
|
||||||
|
))
|
||||||
|
if available == Amount::zero() && required == Amount::from_u64(3000).unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
// Mine block SAPLING_ACTIVATION_HEIGHT + 22 so that the first transaction expires
|
// Mine block SAPLING_ACTIVATION_HEIGHT + 22 so that the first transaction expires
|
||||||
let (cb, _) = fake_compact_block(
|
let (cb, _) = fake_compact_block(
|
||||||
|
@ -625,8 +569,7 @@ mod tests {
|
||||||
&mut db_write,
|
&mut db_write,
|
||||||
&tests::network(),
|
&tests::network(),
|
||||||
test_prover(),
|
test_prover(),
|
||||||
AccountId::from(0),
|
&usk,
|
||||||
&extsk,
|
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(2000).unwrap(),
|
Amount::from_u64(2000).unwrap(),
|
||||||
None,
|
None,
|
||||||
|
@ -645,18 +588,13 @@ mod tests {
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
let extsk = sapling::spending_key(&[0u8; 32], network.coin_type(), account_id);
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap();
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap();
|
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
|
@ -680,8 +618,7 @@ mod tests {
|
||||||
db_write,
|
db_write,
|
||||||
&tests::network(),
|
&tests::network(),
|
||||||
test_prover(),
|
test_prover(),
|
||||||
AccountId::from(0),
|
&usk,
|
||||||
&extsk,
|
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(15000).unwrap(),
|
Amount::from_u64(15000).unwrap(),
|
||||||
None,
|
None,
|
||||||
|
@ -757,18 +694,13 @@ mod tests {
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
let mut ops = db_data.get_update_ops().unwrap();
|
||||||
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
|
let seed = Secret::new([0u8; 32].to_vec());
|
||||||
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
|
let (_, usk) = ops.create_account(&seed).unwrap();
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap();
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
|
||||||
let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap();
|
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap();
|
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(51000).unwrap();
|
let value = Amount::from_u64(51000).unwrap();
|
||||||
|
@ -791,18 +723,19 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let to = TransparentAddress::PublicKey([7; 20]).into();
|
let to = TransparentAddress::PublicKey([7; 20]).into();
|
||||||
create_spend_to_address(
|
assert!(matches!(
|
||||||
&mut db_write,
|
create_spend_to_address(
|
||||||
&tests::network(),
|
&mut db_write,
|
||||||
test_prover(),
|
&tests::network(),
|
||||||
AccountId::from(0),
|
test_prover(),
|
||||||
&extsk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(50000).unwrap(),
|
Amount::from_u64(50000).unwrap(),
|
||||||
None,
|
None,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
10,
|
10,
|
||||||
)
|
),
|
||||||
.unwrap();
|
Ok(_)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue