Make init_accounts_table only permit sequential account identifiers.
Also, check to ensure that account creation does not exceed the maximum account identifier value.
This commit is contained in:
parent
75eb082203
commit
d086c57f2f
|
@ -1,6 +1,5 @@
|
||||||
//! Interfaces for wallet data persistence & low-level wallet utilities.
|
//! Interfaces for wallet data persistence & low-level wallet utilities.
|
||||||
|
|
||||||
use secrecy::SecretVec;
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -8,6 +7,7 @@ use std::fmt::Debug;
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use secrecy::SecretVec;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::BlockHeight,
|
consensus::BlockHeight,
|
||||||
|
|
|
@ -18,6 +18,10 @@ and this library adheres to Rust's notion of
|
||||||
rewinds exceed supported bounds.
|
rewinds exceed supported bounds.
|
||||||
- `SqliteClientError::DiversifierIndexOutOfRange`, to report when the space
|
- `SqliteClientError::DiversifierIndexOutOfRange`, to report when the space
|
||||||
of available diversifier indices has been exhausted.
|
of available diversifier indices has been exhausted.
|
||||||
|
- `SqliteClientError::AccountIdDiscontinuity`, to report when a user attempts
|
||||||
|
to initialize the accounts table with a noncontiguous set of account identifiers.
|
||||||
|
- `SqliteClientError::AccountIdOutOfRange`, to report when the maximum account
|
||||||
|
identifier has been reached.
|
||||||
- An `unstable` feature flag; this is added to parts of the API that may change
|
- An `unstable` feature flag; this is added to parts of the API that may change
|
||||||
in any release. It enables `zcash_client_backend`'s `unstable` feature flag.
|
in any release. It enables `zcash_client_backend`'s `unstable` feature flag.
|
||||||
- New summary views that may be directly accessed in the sqlite database.
|
- New summary views that may be directly accessed in the sqlite database.
|
||||||
|
|
|
@ -73,6 +73,13 @@ pub enum SqliteClientError {
|
||||||
/// An error occurred deriving a spending key from a seed and an account
|
/// An error occurred deriving a spending key from a seed and an account
|
||||||
/// identifier.
|
/// identifier.
|
||||||
KeyDerivationError(AccountId),
|
KeyDerivationError(AccountId),
|
||||||
|
|
||||||
|
/// A caller attempted to initialize the accounts table with a discontinuous
|
||||||
|
/// set of account identifiers.
|
||||||
|
AccountIdDiscontinuity,
|
||||||
|
|
||||||
|
/// A caller attempted to construct a new account with an invalid account identifier.
|
||||||
|
AccountIdOutOfRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for SqliteClientError {
|
impl error::Error for SqliteClientError {
|
||||||
|
@ -113,6 +120,8 @@ impl fmt::Display for SqliteClientError {
|
||||||
SqliteClientError::BackendError(e) => write!(f, "{}", e),
|
SqliteClientError::BackendError(e) => write!(f, "{}", e),
|
||||||
SqliteClientError::DiversifierIndexOutOfRange => write!(f, "The space of available diversifier indices is exhausted"),
|
SqliteClientError::DiversifierIndexOutOfRange => write!(f, "The space of available diversifier indices is exhausted"),
|
||||||
SqliteClientError::KeyDerivationError(acct_id) => write!(f, "Key derivation failed for account {:?}", acct_id),
|
SqliteClientError::KeyDerivationError(acct_id) => write!(f, "Key derivation failed for account {:?}", acct_id),
|
||||||
|
SqliteClientError::AccountIdDiscontinuity => write!(f, "Wallet account identifiers must be sequential."),
|
||||||
|
SqliteClientError::AccountIdOutOfRange => write!(f, "Wallet account identifiers must be less than 0x7FFFFFFF."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -412,6 +412,10 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
||||||
.map(|a| AccountId::from(u32::from(a) + 1))
|
.map(|a| AccountId::from(u32::from(a) + 1))
|
||||||
.unwrap_or_else(|| AccountId::from(0));
|
.unwrap_or_else(|| AccountId::from(0));
|
||||||
|
|
||||||
|
if u32::from(account) >= 0x7FFFFFFF {
|
||||||
|
return Err(SqliteClientError::AccountIdOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
let usk = UnifiedSpendingKey::from_seed(
|
let usk = UnifiedSpendingKey::from_seed(
|
||||||
&stmts.wallet_db.params,
|
&stmts.wallet_db.params,
|
||||||
seed.expose_secret(),
|
seed.expose_secret(),
|
||||||
|
|
|
@ -586,6 +586,13 @@ pub fn init_accounts_table<P: consensus::Parameters>(
|
||||||
return Err(SqliteClientError::TableNotEmpty);
|
return Err(SqliteClientError::TableNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that the account identifiers are sequential and begin at zero.
|
||||||
|
if let Some(account_id) = keys.keys().max() {
|
||||||
|
if usize::try_from(u32::from(*account_id)).unwrap() >= keys.len() {
|
||||||
|
return Err(SqliteClientError::AccountIdDiscontinuity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Insert accounts atomically
|
// Insert accounts atomically
|
||||||
wdb.conn.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
|
wdb.conn.execute("BEGIN IMMEDIATE", NO_PARAMS)?;
|
||||||
for (account, key) in keys.iter() {
|
for (account, key) in keys.iter() {
|
||||||
|
@ -677,6 +684,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::SqliteClientError,
|
||||||
tests::{self, network},
|
tests::{self, network},
|
||||||
wallet::get_address,
|
wallet::get_address,
|
||||||
AccountId, WalletDb,
|
AccountId, WalletDb,
|
||||||
|
@ -1370,6 +1378,35 @@ mod tests {
|
||||||
init_accounts_table(&db_data, &ufvks).unwrap_err();
|
init_accounts_table(&db_data, &ufvks).unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_accounts_table_allows_no_gaps() {
|
||||||
|
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();
|
||||||
|
|
||||||
|
// allow sequential initialization
|
||||||
|
let seed = [0u8; 32];
|
||||||
|
let ufvks = |ids: &[u32]| {
|
||||||
|
ids.iter()
|
||||||
|
.map(|a| {
|
||||||
|
let account = AccountId::from(*a);
|
||||||
|
UnifiedSpendingKey::from_seed(&network(), &seed, account)
|
||||||
|
.map(|k| (account, k.to_unified_full_viewing_key()))
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// should fail if we have a gap
|
||||||
|
assert!(matches!(
|
||||||
|
init_accounts_table(&db_data, &ufvks(&[0, 2])),
|
||||||
|
Err(SqliteClientError::AccountIdDiscontinuity)
|
||||||
|
));
|
||||||
|
|
||||||
|
// should succeed if there are no gaps
|
||||||
|
assert!(init_accounts_table(&db_data, &ufvks(&[0, 1, 2])).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_blocks_table_only_works_once() {
|
fn init_blocks_table_only_works_once() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue