Skip to content

Commit

Permalink
Merge pull request #31 from ChainSafe/ec2/ephemeral-addr
Browse files Browse the repository at this point in the history
Implement handling of EphemeralAddresses
  • Loading branch information
willemolding authored Nov 22, 2024
2 parents f259f4a + 41ec5c6 commit 90aa170
Show file tree
Hide file tree
Showing 11 changed files with 830 additions and 64 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zcash_client_memory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async-trait = { version = "0.1" }
# - Test dependencies
proptest = { workspace = true, optional = true }
wasm_sync = "0.1.2"
time.workspace = true

[dev-dependencies]
ciborium = "0.2.2"
Expand Down
12 changes: 11 additions & 1 deletion zcash_client_memory/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ pub enum Error {
Io(std::io::Error),
#[error("Corrupted Data: {0}")]
CorruptedData(String),
#[error("An error occurred while processing an account due to a failure in deriving the account's keys: {0}")]
#[error("An error occurred while processing an account due to a failure in deriving the account's keys: {0}"
)]
BadAccountData(String),
#[error("Blocks are non sequental")]
NonSequentialBlocks,
Expand All @@ -65,10 +66,19 @@ pub enum Error {
#[cfg(feature = "transparent-inputs")]
#[error("Requested gap limit {1} reached for account {0:?}")]
ReachedGapLimit(AccountId, u32),
#[cfg(feature = "transparent-inputs")]
#[error("Transparent derivation: {0}")]
TransparentDerivation(bip32::Error),
#[error("Address Conversion error: {0}")]
ConversionError(ConversionError<&'static str>),
}
#[cfg(feature = "transparent-inputs")]

impl From<bip32::Error> for Error {
fn from(value: bip32::Error) -> Self {
Error::TransparentDerivation(value)
}
}
impl From<ConversionError<&'static str>> for Error {
fn from(value: ConversionError<&'static str>) -> Self {
Error::ConversionError(value)
Expand Down
74 changes: 62 additions & 12 deletions zcash_client_memory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use shardtree::{
store::{memory::MemoryShardStore, ShardStore as _},
ShardTree,
};
use std::cmp::min;
use std::{
collections::{btree_map::Entry, BTreeMap, BTreeSet},
num::NonZeroU32,
Expand All @@ -31,7 +32,7 @@ use zcash_primitives::{
transaction::{components::OutPoint, TxId},
};

use zcash_client_backend::data_api::SAPLING_SHARD_HEIGHT;
use zcash_client_backend::data_api::{GAP_LIMIT, SAPLING_SHARD_HEIGHT};
use zcash_client_backend::{
data_api::{
scanning::{ScanPriority, ScanRange},
Expand Down Expand Up @@ -67,6 +68,7 @@ pub(crate) const PRUNING_DEPTH: u32 = 100;
pub(crate) const VERIFY_LOOKAHEAD: u32 = 10;

use types::serialization::*;
use zcash_primitives::transaction::Transaction;

/// The main in-memory wallet database. Implements all the traits needed to be used as a backend.
#[serde_as]
Expand Down Expand Up @@ -232,6 +234,63 @@ impl<P: consensus::Parameters> MemoryWalletDb<P> {
Ok((id, account))
}

#[cfg(feature = "transparent-inputs")]
pub fn first_unsafe_index(&self, account_id: AccountId) -> Result<u32, Error> {
let first_unmined_index = if let Some(account) = self.accounts.get(account_id) {
let mut idx = 0;
for ((tidx, eph_addr)) in account.ephemeral_addresses.iter().rev() {
if let Some(_) = eph_addr
.seen
.and_then(|txid| self.tx_table.get(&txid))
.and_then(|tx| tx.mined_height())
{
idx = tidx.checked_add(1).unwrap();
break;
}
}
idx
} else {
0
};

Ok(min(
1 << 31,
first_unmined_index.checked_add(GAP_LIMIT).unwrap(),
))
}

pub fn get_funding_accounts(&self, tx: &Transaction) -> Result<BTreeSet<AccountId>, Error> {
let mut funding_accounts = BTreeSet::new();
#[cfg(feature = "transparent-inputs")]
funding_accounts.extend(
self.transparent_received_outputs.detect_spending_accounts(
tx.transparent_bundle()
.iter()
.flat_map(|bundle| bundle.vin.iter().map(|txin| &txin.prevout)),
)?,
);

funding_accounts.extend(self.received_notes.detect_sapling_spending_accounts(
tx.sapling_bundle().iter().flat_map(|bundle| {
bundle
.shielded_spends()
.iter()
.map(|spend| spend.nullifier())
}),
)?);

#[cfg(feature = "orchard")]
funding_accounts.extend(
self.received_notes.detect_orchard_spending_accounts(
tx.orchard_bundle()
.iter()
.flat_map(|bundle| bundle.actions().iter().map(|action| action.nullifier())),
)?,
);

Ok(funding_accounts)
}

pub(crate) fn get_received_notes(&self) -> &ReceivedNoteTable {
&self.received_notes
}
Expand Down Expand Up @@ -906,20 +965,12 @@ impl<P: consensus::Parameters> MemoryWalletDb<P> {
}
}

#[cfg(feature = "transparent-inputs")]
pub(crate) fn find_account_for_transparent_address(
&self,
address: &TransparentAddress,
) -> Result<Option<AccountId>, Error> {
Ok(self
.accounts
.iter()
.find(|(_, account)| {
account
.addresses()
.iter()
.any(|(_, unified_address)| unified_address.transparent() == Some(address))
})
.map(|(id, _)| id.clone()))
self.accounts.find_account_for_transparent_address(address)
}

pub(crate) fn mark_transparent_output_spent(
Expand Down Expand Up @@ -1027,7 +1078,6 @@ impl<P: consensus::Parameters> MemoryWalletDb<P> {

#[cfg(test)]
mod test {

use ciborium::into_writer;

use crate::MemoryWalletDb;
Expand Down
39 changes: 23 additions & 16 deletions zcash_client_memory/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,29 @@ where
receiving_account,
..
} => {
let account = self.get_account(receiving_account)?.unwrap();
let idx = *account
.addresses()
.iter()
.find(|(_, addr)| addr.transparent() == Some(&ephemeral_address))
.unwrap()
.0;
let idx = idx.try_into().unwrap();

Ok((
// TODO: Use the ephemeral address index to look up the address
// and find the correct index
note.value.into_u64(),
Some(Address::from(ephemeral_address)),
Some((Address::from(ephemeral_address), idx)),
))
#[cfg(feature = "transparent-inputs")]
{
let account = self.get_account(receiving_account)?.unwrap();
let (_addr, meta) = account
.ephemeral_addresses()?
.into_iter()
.find(|(addr, _)| addr == &ephemeral_address)
.unwrap();
Ok((
// TODO: Use the ephemeral address index to look up the address
// and find the correct index
note.value.into_u64(),
Some(Address::from(ephemeral_address)),
Some((
Address::from(ephemeral_address),
meta.address_index().index(),
)),
))
}
#[cfg(not(feature = "transparent-inputs"))]
{
unimplemented!("EphemeralTransparent recipients are not supported without the `transparent-inputs` feature.")
}
}
Recipient::InternalAccount { .. } => Ok((note.value.into_u64(), None, None)),
})
Expand Down
Loading

0 comments on commit 90aa170

Please sign in to comment.