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`
|
||||
- `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`:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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(_)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue