Merge pull request #661 from nuttycom/wallet/spend_with_usk

Use unified spending keys for spends & shielding.
This commit is contained in:
Kris Nuttycombe 2022-10-12 12:15:34 -06:00 committed by GitHub
commit f7a3b9bda3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 314 additions and 337 deletions

View File

@ -35,6 +35,7 @@ and this library adheres to Rust's notion of
- `Recipient`
- `SentTransactionOutput`
- `WalletRead::get_unified_full_viewing_keys`
- `WalletRead::get_account_for_ufvk`
- `WalletRead::get_current_address`
- `WalletRead::get_all_nullifiers`
- `WalletWrite::create_account`
@ -132,6 +133,8 @@ and this library adheres to Rust's notion of
- `decode_extended_spending_key`
- `decode_extended_full_viewing_key`
- `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
- `zcash_client_backend::data_api`:

View File

@ -128,6 +128,13 @@ pub trait WalletRead {
&self,
) -> 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
/// associated with the account.
fn is_valid_account_extfvk(
@ -484,6 +491,13 @@ pub mod testing {
Ok(HashMap::new())
}
fn get_account_for_ufvk(
&self,
_ufvk: &UnifiedFullViewingKey,
) -> Result<Option<AccountId>, Self::Error> {
Ok(None)
}
fn is_valid_account_extfvk(
&self,
_account: AccountId,

View File

@ -24,6 +24,9 @@ pub enum ChainInvalid {
#[derive(Debug)]
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.
AccountNotFound(AccountId),
@ -90,6 +93,9 @@ impl ChainInvalid {
impl<N: fmt::Display> fmt::Display for Error<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Error::KeyNotRecognized => {
write!(f, "Wallet does not contain an account corresponding to the provided spending key")
}
Error::AccountNotFound(account) => {
write!(f, "Wallet does not contain account {}", u32::from(*account))
}

View File

@ -8,17 +8,10 @@ use zcash_primitives::{
components::{amount::DEFAULT_FEE, Amount},
Transaction,
},
zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey},
};
#[cfg(feature = "transparent-inputs")]
use {
zcash_address::unified::Typecode,
zcash_primitives::{
legacy::keys::{self as transparent, IncomingViewingKey},
sapling::keys::OutgoingViewingKey,
},
};
use zcash_primitives::{legacy::keys::IncomingViewingKey, sapling::keys::OutgoingViewingKey};
use crate::{
address::RecipientAddress,
@ -27,6 +20,7 @@ use crate::{
SentTransactionOutput, WalletWrite,
},
decrypt_transaction,
keys::UnifiedSpendingKey,
wallet::OvkPolicy,
zip321::{Payment, TransactionRequest},
};
@ -129,7 +123,7 @@ where
/// };
/// use zcash_proofs::prover::LocalTxProver;
/// use zcash_client_backend::{
/// keys::sapling,
/// keys::UnifiedSpendingKey,
/// data_api::{wallet::create_spend_to_address, error::Error, testing},
/// wallet::OvkPolicy,
/// };
@ -148,8 +142,8 @@ where
/// };
///
/// let account = AccountId::from(0);
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, account);
/// let to = extsk.default_address().1.into();
/// let usk = UnifiedSpendingKey::from_seed(&Network::TestNetwork, &[0; 32][..], account).unwrap();
/// let to = usk.to_unified_full_viewing_key().default_address().0.into();
///
/// let mut db_read = testing::MockWalletDb {
/// network: Network::TestNetwork
@ -159,8 +153,7 @@ where
/// &mut db_read,
/// &Network::TestNetwork,
/// tx_prover,
/// account,
/// &extsk,
/// &usk,
/// &to,
/// Amount::from_u64(1).unwrap(),
/// None,
@ -176,8 +169,7 @@ pub fn create_spend_to_address<E, N, P, D, R>(
wallet_db: &mut D,
params: &P,
prover: impl TxProver,
account: AccountId,
extsk: &ExtendedSpendingKey,
usk: &UnifiedSpendingKey,
to: &RecipientAddress,
amount: Amount,
memo: Option<MemoBytes>,
@ -206,8 +198,7 @@ where
wallet_db,
params,
prover,
extsk,
account,
usk,
&req,
ovk_policy,
min_confirmations,
@ -248,7 +239,7 @@ where
/// * `wallet_db`: A read/write reference to the wallet database
/// * `params`: Consensus parameters
/// * `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.
/// * `account`: The ZIP32 account identifier associated with the extended spending
/// 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,
params: &P,
prover: impl TxProver,
extsk: &ExtendedSpendingKey,
account: AccountId,
usk: &UnifiedSpendingKey,
request: &TransactionRequest,
ovk_policy: OvkPolicy,
min_confirmations: u32,
@ -277,12 +267,11 @@ where
R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R>,
{
// Check that the ExtendedSpendingKey we have been given corresponds to the
// ExtendedFullViewingKey for the account we are spending from.
let extfvk = ExtendedFullViewingKey::from(extsk);
if !wallet_db.is_valid_account_extfvk(account, &extfvk)? {
return Err(E::from(Error::InvalidExtSk(account)));
}
let account = wallet_db
.get_account_for_ufvk(&usk.to_unified_full_viewing_key())?
.ok_or(Error::KeyNotRecognized)?;
let extfvk = usk.sapling().to_extended_full_viewing_key();
// Apply the outgoing viewing key policy.
let ovk = match ovk_policy {
@ -335,7 +324,12 @@ where
let merkle_path = selected.witness.path().expect("the tree is not empty");
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)?;
}
@ -452,8 +446,7 @@ pub fn shield_transparent_funds<E, N, P, D, R, U>(
wallet_db: &mut D,
params: &P,
prover: impl TxProver,
sk: &transparent::AccountPrivKey,
account: AccountId,
usk: &UnifiedSpendingKey,
memo: &MemoBytes,
min_confirmations: u32,
) -> Result<D::TxRef, E>
@ -463,28 +456,20 @@ where
R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>,
{
// Obtain the UFVK for the specified account & use its internal change address
// as the destination for shielded funds.
let shielding_address = wallet_db
.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 account = wallet_db
.get_account_for_ufvk(&usk.to_unified_full_viewing_key())?
.ok_or(Error::KeyNotRecognized)?;
let shielding_address = usk
.sapling()
.to_diversifiable_full_viewing_key()
.change_address()
.1;
let (latest_scanned_height, latest_anchor) = wallet_db
.get_target_and_anchor_heights(min_confirmations)
.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());
// 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 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 {
builder
.add_transparent_input(secret_key, utxo.outpoint.clone(), utxo.txout.clone())

View File

@ -163,6 +163,13 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
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(
&self,
account: AccountId,
@ -289,6 +296,13 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
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(
&self,
account: AccountId,

View File

@ -342,6 +342,25 @@ pub(crate) fn get_unified_full_viewing_keys<P: consensus::Parameters>(
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
/// specified account.
///

View File

@ -157,28 +157,25 @@ pub fn select_spendable_sapling_notes<P>(
mod tests {
use rusqlite::Connection;
use secrecy::Secret;
use std::collections::HashMap;
use tempfile::NamedTempFile;
use zcash_proofs::prover::LocalTxProver;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, BranchId, Parameters},
consensus::{BlockHeight, BranchId},
legacy::TransparentAddress,
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
transaction::{components::Amount, Transaction},
zip32::sapling::{
DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
},
zip32::sapling::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::legacy::keys as transparent;
use zcash_client_backend::{
data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead},
keys::{sapling, UnifiedFullViewingKey},
data_api::{
self, chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead,
WalletWrite,
},
keys::UnifiedSpendingKey,
wallet::OvkPolicy,
};
@ -187,7 +184,7 @@ mod tests {
tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height},
wallet::{
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,
};
@ -202,132 +199,80 @@ mod tests {
}
#[test]
fn create_to_address_fails_on_incorrect_extsk() {
fn create_to_address_fails_on_incorrect_usk() {
let data_file = NamedTempFile::new().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();
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 usk1 = UnifiedSpendingKey::from_seed(&network(), &[1u8; 32], acct1).unwrap();
// Add two accounts to the wallet
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
// Attempting to spend with a USK that is not in the wallet results in an error
let mut db_write = db_data.get_update_ops().unwrap();
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk1,
&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 0"),
}
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"),
}
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk1,
&to,
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
),
Err(crate::SqliteClientError::BackendError(
data_api::error::Error::KeyNotRecognized
))
));
}
#[test]
fn create_to_address_fails_with_no_blocks() {
let data_file = NamedTempFile::new().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
let account_id = AccountId::from(0);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
#[cfg(feature = "transparent-inputs")]
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();
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();
// We cannot do anything if we aren't synchronised
let mut db_write = db_data.get_update_ops().unwrap();
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(e.to_string(), "Must scan blocks first"),
}
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
),
Err(crate::SqliteClientError::BackendError(
data_api::error::Error::ScanRequired
))
));
}
#[test]
fn create_to_address_fails_on_insufficient_balance() {
let data_file = NamedTempFile::new().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(
&db_data,
BlockHeight::from(1u32),
@ -338,16 +283,11 @@ mod tests {
.unwrap();
// Add an account to the wallet
let account_id = AccountId::from(0);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
#[cfg(feature = "transparent-inputs")]
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();
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();
// Account balance should be zero
assert_eq!(
@ -357,24 +297,26 @@ mod tests {
// We cannot spend anything
let mut db_write = db_data.get_update_ops().unwrap();
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
e.to_string(),
"Insufficient balance (have 0, need 1001 including fee)"
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(1).unwrap(),
None,
OvkPolicy::Sender,
10,
),
}
Err(crate::SqliteClientError::BackendError(
data_api::error::Error::InsufficientBalance(
available,
required
)
))
if available == Amount::zero() && required == Amount::from_u64(1001).unwrap()
));
}
#[test]
@ -385,18 +327,13 @@ mod tests {
let data_file = NamedTempFile::new().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
let account_id = AccountId::from(0);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
#[cfg(feature = "transparent-inputs")]
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();
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();
// Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap();
@ -437,24 +374,27 @@ mod tests {
// Spend fails because there are insufficient verified notes
let extsk2 = ExtendedSpendingKey::master(&[]);
let to = extsk2.default_address().1.into();
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
e.to_string(),
"Insufficient balance (have 50000, need 71000 including fee)"
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
}
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
// note is verified
@ -466,24 +406,27 @@ mod tests {
scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap();
// Second spend still fails
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
e.to_string(),
"Insufficient balance (have 50000, need 71000 including fee)"
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
}
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
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();
// Second spend should now succeed
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(70000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
Ok(_)
));
}
#[test]
@ -517,15 +461,10 @@ mod tests {
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
// Add an account to the wallet
let account_id = AccountId::from(0);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
#[cfg(feature = "transparent-inputs")]
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();
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();
// Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap();
@ -543,39 +482,42 @@ mod tests {
// Send some of the funds to another address
let extsk2 = ExtendedSpendingKey::master(&[]);
let to = extsk2.default_address().1.into();
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(15000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(15000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
Ok(_)
));
// A second spend fails because there are no usable notes
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
e.to_string(),
"Insufficient balance (have 0, need 3000 including fee)"
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
}
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)
// 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();
// Second spend still fails
match create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
) {
Ok(_) => panic!("Should have failed"),
Err(e) => assert_eq!(
e.to_string(),
"Insufficient balance (have 0, need 3000 including fee)"
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(2000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
}
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
let (cb, _) = fake_compact_block(
@ -625,8 +569,7 @@ mod tests {
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&usk,
&to,
Amount::from_u64(2000).unwrap(),
None,
@ -645,18 +588,13 @@ mod tests {
let data_file = NamedTempFile::new().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
let account_id = AccountId::from(0);
let extsk = sapling::spending_key(&[0u8; 32], network.coin_type(), account_id);
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
#[cfg(feature = "transparent-inputs")]
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();
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();
// Add funds to the wallet in a single note
let value = Amount::from_u64(50000).unwrap();
@ -680,8 +618,7 @@ mod tests {
db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&usk,
&to,
Amount::from_u64(15000).unwrap(),
None,
@ -757,18 +694,13 @@ mod tests {
let data_file = NamedTempFile::new().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
let account_id = AccountId::from(0);
let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id);
let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk));
#[cfg(feature = "transparent-inputs")]
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();
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();
// Add funds to the wallet in a single note
let value = Amount::from_u64(51000).unwrap();
@ -791,18 +723,19 @@ mod tests {
);
let to = TransparentAddress::PublicKey([7; 20]).into();
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
AccountId::from(0),
&extsk,
&to,
Amount::from_u64(50000).unwrap(),
None,
OvkPolicy::Sender,
10,
)
.unwrap();
assert!(matches!(
create_spend_to_address(
&mut db_write,
&tests::network(),
test_prover(),
&usk,
&to,
Amount::from_u64(50000).unwrap(),
None,
OvkPolicy::Sender,
10,
),
Ok(_)
));
}
}