zcash_client_backend: Track external addresses in inter-account transactions.
Previously, if the funding account for a received transaction output was determined to be an account known to the wallet, the output was recorded as though it were sent to an internal (change) address of the wallet.
This commit is contained in:
parent
404132bce5
commit
151e6e526e
|
@ -103,6 +103,8 @@ and this library adheres to Rust's notion of
|
||||||
feature flag.
|
feature flag.
|
||||||
- `zcash_client_backend::proto`:
|
- `zcash_client_backend::proto`:
|
||||||
- `ProposalDecodingError` has a new variant `TransparentMemo`.
|
- `ProposalDecodingError` has a new variant `TransparentMemo`.
|
||||||
|
- `zcash_client_backend::wallet::Recipient::InternalAccount` is now a structured
|
||||||
|
variant with an additional `external_address` field.
|
||||||
- `zcash_client_backend::zip321::render::amount_str` now takes a
|
- `zcash_client_backend::zip321::render::amount_str` now takes a
|
||||||
`NonNegativeAmount` rather than a signed `Amount` as its argument.
|
`NonNegativeAmount` rather than a signed `Amount` as its argument.
|
||||||
- `zcash_client_backend::zip321::parse::parse_amount` now parses a
|
- `zcash_client_backend::zip321::parse::parse_amount` now parses a
|
||||||
|
|
|
@ -1049,10 +1049,11 @@ where
|
||||||
memo.clone(),
|
memo.clone(),
|
||||||
)?;
|
)?;
|
||||||
sapling_output_meta.push((
|
sapling_output_meta.push((
|
||||||
Recipient::InternalAccount(
|
Recipient::InternalAccount {
|
||||||
account,
|
receiving_account: account,
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
external_address: None,
|
||||||
),
|
note: PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||||
|
},
|
||||||
change_value.value(),
|
change_value.value(),
|
||||||
Some(memo),
|
Some(memo),
|
||||||
))
|
))
|
||||||
|
@ -1072,10 +1073,11 @@ where
|
||||||
memo.clone(),
|
memo.clone(),
|
||||||
)?;
|
)?;
|
||||||
orchard_output_meta.push((
|
orchard_output_meta.push((
|
||||||
Recipient::InternalAccount(
|
Recipient::InternalAccount {
|
||||||
account,
|
receiving_account: account,
|
||||||
PoolType::Shielded(ShieldedProtocol::Orchard),
|
external_address: None,
|
||||||
),
|
note: PoolType::Shielded(ShieldedProtocol::Orchard),
|
||||||
|
},
|
||||||
change_value.value(),
|
change_value.value(),
|
||||||
Some(memo),
|
Some(memo),
|
||||||
))
|
))
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//! light client.
|
//! light client.
|
||||||
|
|
||||||
use incrementalmerkletree::Position;
|
use incrementalmerkletree::Position;
|
||||||
|
use zcash_keys::address::Address;
|
||||||
use zcash_note_encryption::EphemeralKeyBytes;
|
use zcash_note_encryption::EphemeralKeyBytes;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::BlockHeight,
|
consensus::BlockHeight,
|
||||||
|
@ -70,7 +71,11 @@ pub enum Recipient<AccountId, N> {
|
||||||
Transparent(TransparentAddress),
|
Transparent(TransparentAddress),
|
||||||
Sapling(sapling::PaymentAddress),
|
Sapling(sapling::PaymentAddress),
|
||||||
Unified(UnifiedAddress, PoolType),
|
Unified(UnifiedAddress, PoolType),
|
||||||
InternalAccount(AccountId, N),
|
InternalAccount {
|
||||||
|
receiving_account: AccountId,
|
||||||
|
external_address: Option<Address>,
|
||||||
|
note: N,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AccountId, N> Recipient<AccountId, N> {
|
impl<AccountId, N> Recipient<AccountId, N> {
|
||||||
|
@ -79,7 +84,15 @@ impl<AccountId, N> Recipient<AccountId, N> {
|
||||||
Recipient::Transparent(t) => Recipient::Transparent(t),
|
Recipient::Transparent(t) => Recipient::Transparent(t),
|
||||||
Recipient::Sapling(s) => Recipient::Sapling(s),
|
Recipient::Sapling(s) => Recipient::Sapling(s),
|
||||||
Recipient::Unified(u, p) => Recipient::Unified(u, p),
|
Recipient::Unified(u, p) => Recipient::Unified(u, p),
|
||||||
Recipient::InternalAccount(a, n) => Recipient::InternalAccount(a, f(n)),
|
Recipient::InternalAccount {
|
||||||
|
receiving_account,
|
||||||
|
external_address,
|
||||||
|
note,
|
||||||
|
} => Recipient::InternalAccount {
|
||||||
|
receiving_account,
|
||||||
|
external_address,
|
||||||
|
note: f(note),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +103,15 @@ impl<AccountId, N> Recipient<AccountId, Option<N>> {
|
||||||
Recipient::Transparent(t) => Some(Recipient::Transparent(t)),
|
Recipient::Transparent(t) => Some(Recipient::Transparent(t)),
|
||||||
Recipient::Sapling(s) => Some(Recipient::Sapling(s)),
|
Recipient::Sapling(s) => Some(Recipient::Sapling(s)),
|
||||||
Recipient::Unified(u, p) => Some(Recipient::Unified(u, p)),
|
Recipient::Unified(u, p) => Some(Recipient::Unified(u, p)),
|
||||||
Recipient::InternalAccount(a, n) => n.map(|n0| Recipient::InternalAccount(a, n0)),
|
Recipient::InternalAccount {
|
||||||
|
receiving_account,
|
||||||
|
external_address,
|
||||||
|
note,
|
||||||
|
} => note.map(|n0| Recipient::InternalAccount {
|
||||||
|
receiving_account,
|
||||||
|
external_address,
|
||||||
|
note: n0,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ use zcash_client_backend::{
|
||||||
wallet::{Note, NoteId, ReceivedNote, Recipient, WalletTransparentOutput},
|
wallet::{Note, NoteId, ReceivedNote, Recipient, WalletTransparentOutput},
|
||||||
DecryptedOutput, PoolType, ShieldedProtocol, TransferType,
|
DecryptedOutput, PoolType, ShieldedProtocol, TransferType,
|
||||||
};
|
};
|
||||||
|
use zcash_keys::address::Address;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
|
@ -1066,10 +1067,11 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
//TODO: Recover the UA, if possible.
|
//TODO: Recover the UA, if possible.
|
||||||
Recipient::Sapling(output.note().recipient())
|
Recipient::Sapling(output.note().recipient())
|
||||||
} else {
|
} else {
|
||||||
Recipient::InternalAccount(
|
Recipient::InternalAccount {
|
||||||
*output.account(),
|
receiving_account: *output.account(),
|
||||||
Note::Sapling(output.note().clone()),
|
external_address: None,
|
||||||
)
|
note: Note::Sapling(output.note().clone()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
wallet::put_sent_output(
|
wallet::put_sent_output(
|
||||||
|
@ -1083,7 +1085,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
Some(output.memo()),
|
Some(output.memo()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if matches!(recipient, Recipient::InternalAccount(_, _)) {
|
if matches!(recipient, Recipient::InternalAccount { .. }) {
|
||||||
wallet::sapling::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
wallet::sapling::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1091,11 +1093,12 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
wallet::sapling::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
wallet::sapling::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
||||||
|
|
||||||
if let Some(account_id) = funding_account {
|
if let Some(account_id) = funding_account {
|
||||||
// Even if the recipient address is external, record the send as internal.
|
let recipient = Recipient::InternalAccount {
|
||||||
let recipient = Recipient::InternalAccount(
|
receiving_account: *output.account(),
|
||||||
*output.account(),
|
// TODO: recover the actual UA, if possible
|
||||||
Note::Sapling(output.note().clone()),
|
external_address: Some(Address::Sapling(output.note().recipient())),
|
||||||
);
|
note: Note::Sapling(output.note().clone()),
|
||||||
|
};
|
||||||
|
|
||||||
wallet::put_sent_output(
|
wallet::put_sent_output(
|
||||||
wdb.conn.0,
|
wdb.conn.0,
|
||||||
|
@ -1128,10 +1131,11 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
PoolType::Shielded(ShieldedProtocol::Orchard),
|
PoolType::Shielded(ShieldedProtocol::Orchard),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Recipient::InternalAccount(
|
Recipient::InternalAccount {
|
||||||
*output.account(),
|
receiving_account: *output.account(),
|
||||||
Note::Orchard(*output.note()),
|
external_address: None,
|
||||||
)
|
note: Note::Orchard(*output.note()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
wallet::put_sent_output(
|
wallet::put_sent_output(
|
||||||
|
@ -1145,7 +1149,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
Some(output.memo()),
|
Some(output.memo()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if matches!(recipient, Recipient::InternalAccount(_, _)) {
|
if matches!(recipient, Recipient::InternalAccount { .. }) {
|
||||||
wallet::orchard::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
wallet::orchard::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1154,10 +1158,17 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
|
|
||||||
if let Some(account_id) = funding_account {
|
if let Some(account_id) = funding_account {
|
||||||
// Even if the recipient address is external, record the send as internal.
|
// Even if the recipient address is external, record the send as internal.
|
||||||
let recipient = Recipient::InternalAccount(
|
let recipient = Recipient::InternalAccount {
|
||||||
*output.account(),
|
receiving_account: *output.account(),
|
||||||
Note::Orchard(*output.note()),
|
// TODO: recover the actual UA, if possible
|
||||||
);
|
external_address: Some(Address::Unified(
|
||||||
|
UnifiedAddress::from_receivers(
|
||||||
|
Some(output.note().recipient()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
).expect("UA has an Orchard receiver by construction."))),
|
||||||
|
note: Note::Orchard(*output.note()),
|
||||||
|
};
|
||||||
|
|
||||||
wallet::put_sent_output(
|
wallet::put_sent_output(
|
||||||
wdb.conn.0,
|
wdb.conn.0,
|
||||||
|
@ -1288,13 +1299,17 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match output.recipient() {
|
match output.recipient() {
|
||||||
Recipient::InternalAccount(account, Note::Sapling(note)) => {
|
Recipient::InternalAccount {
|
||||||
|
receiving_account,
|
||||||
|
note: Note::Sapling(note),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
wallet::sapling::put_received_note(
|
wallet::sapling::put_received_note(
|
||||||
wdb.conn.0,
|
wdb.conn.0,
|
||||||
&DecryptedOutput::new(
|
&DecryptedOutput::new(
|
||||||
output.output_index(),
|
output.output_index(),
|
||||||
note.clone(),
|
note.clone(),
|
||||||
*account,
|
*receiving_account,
|
||||||
output
|
output
|
||||||
.memo()
|
.memo()
|
||||||
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
||||||
|
@ -1305,13 +1320,17 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
Recipient::InternalAccount(account, Note::Orchard(note)) => {
|
Recipient::InternalAccount {
|
||||||
|
receiving_account,
|
||||||
|
note: Note::Orchard(note),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
wallet::orchard::put_received_note(
|
wallet::orchard::put_received_note(
|
||||||
wdb.conn.0,
|
wdb.conn.0,
|
||||||
&DecryptedOutput::new(
|
&DecryptedOutput::new(
|
||||||
output.output_index(),
|
output.output_index(),
|
||||||
*note,
|
*note,
|
||||||
*account,
|
*receiving_account,
|
||||||
output
|
output
|
||||||
.memo()
|
.memo()
|
||||||
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
||||||
|
|
|
@ -2527,9 +2527,13 @@ fn recipient_params<P: consensus::Parameters>(
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||||
),
|
),
|
||||||
Recipient::Unified(addr, pool) => (Some(addr.encode(params)), None, *pool),
|
Recipient::Unified(addr, pool) => (Some(addr.encode(params)), None, *pool),
|
||||||
Recipient::InternalAccount(id, note) => (
|
Recipient::InternalAccount {
|
||||||
None,
|
receiving_account,
|
||||||
Some(id.to_owned()),
|
external_address,
|
||||||
|
note,
|
||||||
|
} => (
|
||||||
|
external_address.as_ref().map(|a| a.encode(params)),
|
||||||
|
Some(*receiving_account),
|
||||||
PoolType::Shielded(note.protocol()),
|
PoolType::Shielded(note.protocol()),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue