use futures::StreamExt;
use rkyv::Deserialize;
use dusk_bytes::Serializable;
use dusk_core::BlsScalar;
use dusk_core::transfer::phoenix::{
NoteLeaf, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey,
ViewKey as PhoenixViewKey,
};
use wallet_core::keys::{
derive_phoenix_pk, derive_phoenix_sk, derive_phoenix_vk,
};
use zeroize::Zeroize;
use super::{LocalStore, MAX_PROFILES, TREE_LEAF};
use crate::Error;
use crate::clients::{Cache, TRANSFER_CONTRACT};
use crate::rues::{CONTRACTS_TARGET, HttpClient as RuesHttpClient};
pub(crate) async fn sync_db(
client: &RuesHttpClient,
cache: &Cache,
store: &LocalStore,
status: fn(&str),
) -> Result<(), Error> {
let seed = store.get_seed();
let mut keys: Vec<(PhoenixSecretKey, PhoenixViewKey, PhoenixPublicKey)> =
(0..MAX_PROFILES)
.map(|i| {
#[allow(clippy::cast_possible_truncation)]
let i = i as u8;
(
derive_phoenix_sk(seed, i),
derive_phoenix_vk(seed, i),
derive_phoenix_pk(seed, i),
)
})
.collect();
status("Getting cached note position...");
let last_pos = cache.last_pos().inspect_err(|_| {
zeroize_secret_keys(&mut keys);
})?;
let pos_to_search = last_pos.map(|p| p + 1).unwrap_or_default();
let mut last_pos = last_pos.unwrap_or_default();
status("Fetching fresh notes...");
let req = rkyv::to_bytes::<_, 8>(&(pos_to_search))
.map_err(|_| Error::Rkyv)?
.to_vec();
let mut stream = client
.call_raw(
CONTRACTS_TARGET,
TRANSFER_CONTRACT,
"leaves_from_pos",
&req,
true,
)
.await
.inspect_err(|_| zeroize_secret_keys(&mut keys))?
.bytes_stream();
status("Connection established...");
status("Streaming notes...");
let mut buffer = vec![];
let mut note_data = Vec::new();
while let Some(http_chunk) = stream.next().await {
buffer.extend_from_slice(
&http_chunk.inspect_err(|_| zeroize_secret_keys(&mut keys))?,
);
let mut leaf_chunk = buffer.chunks_exact(TREE_LEAF);
for leaf_bytes in leaf_chunk.by_ref() {
let NoteLeaf { block_height, note } =
rkyv::check_archived_root::<NoteLeaf>(leaf_bytes)
.map_err(|_| Error::Rkyv)
.inspect_err(|_| zeroize_secret_keys(&mut keys))?
.deserialize(&mut rkyv::Infallible)
.unwrap();
last_pos = std::cmp::max(last_pos, *note.pos());
note_data.push((block_height, note));
}
buffer = leaf_chunk.remainder().to_vec();
}
let mut err = Ok(());
'outer: for (sk, vk, pk) in &keys {
let pk_bs58 = bs58::encode(pk.to_bytes()).into_string();
for (block_height, note) in ¬e_data {
if vk.owns(note.stealth_address()) {
let nullifier = note.gen_nullifier(sk);
let result =
fetch_existing_nullifiers_remote(client, &[nullifier])
.await
.and_then(|fetch_res| {
let spent = !fetch_res.is_empty();
let note = (note.clone(), nullifier);
if spent {
cache.insert_spent(
&pk_bs58,
*block_height,
note,
)?;
} else {
cache.insert(&pk_bs58, *block_height, note)?;
}
Ok(())
});
if result.is_err() {
err = result;
break 'outer;
}
}
}
}
zeroize_secret_keys(&mut keys);
err?;
for (_, _, pk) in keys {
let nullifiers: Vec<BlsScalar> = cache.unspent_notes_id(&pk)?;
if !nullifiers.is_empty() {
let existing =
fetch_existing_nullifiers_remote(client, nullifiers.as_slice())
.await?;
cache.spend_notes(&pk, existing.as_slice())?;
}
}
cache.insert_last_pos(last_pos)?;
Ok(())
}
fn zeroize_secret_keys(
keys: &mut [(PhoenixSecretKey, PhoenixViewKey, PhoenixPublicKey)],
) {
for (sk, _, _) in keys.iter_mut() {
sk.zeroize();
}
}
pub(crate) async fn fetch_existing_nullifiers_remote(
client: &RuesHttpClient,
nullifiers: &[BlsScalar],
) -> Result<Vec<BlsScalar>, Error> {
if nullifiers.is_empty() {
return Ok(vec![]);
}
let nullifiers = nullifiers.to_vec();
let data = client
.contract_query::<_, _, 1024>(
TRANSFER_CONTRACT,
"existing_nullifiers",
&nullifiers,
)
.await?;
let nullifiers = rkyv::check_archived_root::<Vec<BlsScalar>>(&data)
.map_err(|_| Error::Rkyv)?
.deserialize(&mut rkyv::Infallible)
.unwrap();
Ok(nullifiers)
}