Apply suggestions from code review
Co-authored-by: str4d <thestr4d@gmail.com>
This commit is contained in:
parent
6aabe60d21
commit
74b487e4c9
|
@ -2104,8 +2104,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sapling-crypto"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/sapling-crypto?rev=346a6507413eb2388b2967c014672cd83199648a#346a6507413eb2388b2967c014672cd83199648a"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d183012062dfdde85f7e3e758328fcf6e9846d8dd3fce35b04d0efcb6677b0e0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bellman",
|
||||
|
|
|
@ -49,7 +49,7 @@ bitvec = "1"
|
|||
blake2s_simd = "1"
|
||||
bls12_381 = "0.8"
|
||||
jubjub = "0.10"
|
||||
sapling = { package = "sapling-crypto", version = "0.1" }
|
||||
sapling = { package = "sapling-crypto", version = "0.1.1" }
|
||||
|
||||
# - Orchard
|
||||
nonempty = "0.7"
|
||||
|
@ -115,6 +115,3 @@ zip32 = "0.1"
|
|||
lto = true
|
||||
panic = 'abort'
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
sapling = { package = "sapling-crypto", git = "https://github.com/zcash/sapling-crypto", rev = "346a6507413eb2388b2967c014672cd83199648a" }
|
||||
|
|
|
@ -35,6 +35,7 @@ and this library adheres to Rust's notion of
|
|||
## [0.11.0-pre-release] Unreleased
|
||||
|
||||
### Added
|
||||
- `zcash_client_backend::PoolType::is_receiver`
|
||||
- `zcash_client_backend::data_api`:
|
||||
- `InputSource`
|
||||
- `ScannedBlock::{into_commitments, sapling}`
|
||||
|
@ -206,6 +207,11 @@ and this library adheres to Rust's notion of
|
|||
it possible to represent multi-step transactions, such as a deshielding
|
||||
transaction followed by a zero-conf transfer as required by ZIP 320. Individual
|
||||
transaction proposals are now represented by the `proposal::Step` type.
|
||||
- `ProposalError` has new variants:
|
||||
- `ReferenceError`
|
||||
- `StepDoubleSpend`
|
||||
- `ChainDoubleSpend`
|
||||
- `PaymentPoolsMismatch`
|
||||
|
||||
- `zcash_client_backend::fees`:
|
||||
- `ChangeStrategy::compute_balance` arguments have changed.
|
||||
|
|
|
@ -11,10 +11,7 @@ use zcash_primitives::{
|
|||
memo::MemoBytes,
|
||||
transaction::{
|
||||
builder::{BuildConfig, BuildResult, Builder},
|
||||
components::{
|
||||
amount::{Amount, NonNegativeAmount},
|
||||
TxOut,
|
||||
},
|
||||
components::amount::{Amount, NonNegativeAmount},
|
||||
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
|
||||
Transaction, TxId,
|
||||
},
|
||||
|
@ -30,6 +27,7 @@ use crate::{
|
|||
decrypt_transaction,
|
||||
fees::{self, DustOutputPolicy},
|
||||
keys::UnifiedSpendingKey,
|
||||
proposal::ProposalError,
|
||||
proposal::{self, Proposal},
|
||||
wallet::{Note, OvkPolicy, Recipient},
|
||||
zip321::{self, Payment},
|
||||
|
@ -45,10 +43,12 @@ use super::InputSource;
|
|||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::proposal::ProposalError, input_selection::ShieldingSelector,
|
||||
sapling::keys::OutgoingViewingKey, std::convert::Infallible,
|
||||
zcash_keys::encoding::AddressCodec, zcash_primitives::legacy::TransparentAddress,
|
||||
zcash_primitives::transaction::components::OutPoint,
|
||||
input_selection::ShieldingSelector,
|
||||
sapling::keys::OutgoingViewingKey,
|
||||
std::convert::Infallible,
|
||||
zcash_keys::encoding::AddressCodec,
|
||||
zcash_primitives::legacy::TransparentAddress,
|
||||
zcash_primitives::transaction::components::{OutPoint, TxOut},
|
||||
};
|
||||
|
||||
/// Scans a [`Transaction`] for any information that can be decrypted by the accounts in
|
||||
|
@ -522,13 +522,14 @@ where
|
|||
/// Construct, prove, and sign a transaction or series of transactions using the inputs supplied by
|
||||
/// the given proposal, and persist it to the wallet database.
|
||||
///
|
||||
/// Returns the database identifier for eacy newly constructed transaction, or an error if
|
||||
/// Returns the database identifier for each newly constructed transaction, or an error if
|
||||
/// an error occurs in transaction construction, proving, or signing.
|
||||
///
|
||||
/// When evaluating multi-step proposals, only transparent outputs of any given step may be spent
|
||||
/// in later steps; attempting to spend a shielded note (including change) output by an earlier
|
||||
/// step is not supported, because the ultimate positions of those notes cannot be known and
|
||||
/// therefore the required spend proofs for such notes cannot be constructed.
|
||||
/// step is not supported, because the ultimate positions of those notes in the global note
|
||||
/// commitment tree cannot be known until the transaction that produces those notes is mined,
|
||||
/// and therefore the required spend proofs for such notes cannot be constructed.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn create_proposed_transactions<DbT, ParamsT, InputsErrT, FeeRuleT, N>(
|
||||
|
@ -610,24 +611,33 @@ where
|
|||
// supported. Maybe support this at some point? Doing so would require a higher-level
|
||||
// approach in the wallet that waits for transactions with shielded outputs to be
|
||||
// mined and only then attempts to perform the next step.
|
||||
if proposal_step.prior_step_inputs().iter().any(|s_ref| {
|
||||
prior_step_results.len() <= s_ref.step_index()
|
||||
|| match s_ref.output_index() {
|
||||
proposal::StepOutputIndex::Payment(i) => prior_step_results[s_ref.step_index()]
|
||||
for s_ref in proposal_step.prior_step_inputs() {
|
||||
prior_step_results.get(s_ref.step_index()).map_or_else(
|
||||
|| {
|
||||
// Return an error in case the step index doesn't match up with a step
|
||||
Err(Error::Proposal(ProposalError::ReferenceError(*s_ref)))
|
||||
},
|
||||
|step| match s_ref.output_index() {
|
||||
proposal::StepOutputIndex::Payment(i) => {
|
||||
let prior_pool = step
|
||||
.0
|
||||
.payment_pools()
|
||||
.get(&i)
|
||||
.iter()
|
||||
.all(|pool| matches!(pool, PoolType::Shielded(_))),
|
||||
.ok_or(Error::Proposal(ProposalError::ReferenceError(*s_ref)))?;
|
||||
|
||||
if matches!(prior_pool, PoolType::Shielded(_)) {
|
||||
Err(Error::ProposalNotSupported)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
proposal::StepOutputIndex::Change(_) => {
|
||||
// Only shielded change is supported by zcash_client_backend, so multi-step
|
||||
// transactions cannot yet spend prior transactions' change outputs.
|
||||
true
|
||||
Err(Error::ProposalNotSupported)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
return Err(Error::ProposalNotSupported);
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
let account = wallet_db
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
pub use zcash_keys::address;
|
||||
pub mod data_api;
|
||||
mod decrypt;
|
||||
use zcash_keys::address::Address;
|
||||
pub use zcash_keys::encoding;
|
||||
pub mod fees;
|
||||
pub use zcash_keys::keys;
|
||||
|
@ -109,6 +110,27 @@ pub enum PoolType {
|
|||
Shielded(ShieldedProtocol),
|
||||
}
|
||||
|
||||
impl PoolType {
|
||||
pub fn is_receiver(&self, addr: &Address) -> bool {
|
||||
match addr {
|
||||
Address::Sapling(_) => matches!(self, PoolType::Shielded(ShieldedProtocol::Sapling)),
|
||||
Address::Transparent(_) => matches!(self, PoolType::Transparent),
|
||||
Address::Unified(ua) => match self {
|
||||
PoolType::Transparent => ua.transparent().is_some(),
|
||||
PoolType::Shielded(ShieldedProtocol::Sapling) => ua.sapling().is_some(),
|
||||
#[cfg(zcash_unstable = "orchard")]
|
||||
PoolType::Shielded(ShieldedProtocol::Orchard) => {
|
||||
#[cfg(feature = "orchard")]
|
||||
return ua.orchard().is_some();
|
||||
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PoolType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
|
|
@ -44,6 +44,9 @@ pub enum ProposalError {
|
|||
StepDoubleSpend(StepOutput),
|
||||
/// An attempted double-spend of an output belonging to the wallet was detected.
|
||||
ChainDoubleSpend(PoolType, TxId, u32),
|
||||
/// There was a mismatch between the payments in the proposal's transaction request
|
||||
/// and the payment pool selection values.
|
||||
PaymentPoolsMismatch,
|
||||
}
|
||||
|
||||
impl Display for ProposalError {
|
||||
|
@ -83,6 +86,10 @@ impl Display for ProposalError {
|
|||
"The proposal attempts to spend the same output twice: {}, {}, {}",
|
||||
pool, txid, index
|
||||
),
|
||||
ProposalError::PaymentPoolsMismatch => write!(
|
||||
f,
|
||||
"The chosen payment pools did not match the payments of the transaction request."
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,10 +179,9 @@ impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
|
|||
}
|
||||
}
|
||||
// check that there are no double-spends
|
||||
if consumed_prior_inputs.contains(prior_ref) {
|
||||
if !consumed_prior_inputs.insert(*prior_ref) {
|
||||
return Err(ProposalError::StepDoubleSpend(*prior_ref));
|
||||
}
|
||||
consumed_prior_inputs.insert(*prior_ref);
|
||||
}
|
||||
|
||||
for t_out in step.transparent_inputs() {
|
||||
|
@ -184,10 +190,9 @@ impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
|
|||
TxId::from_bytes(*t_out.outpoint().hash()),
|
||||
t_out.outpoint().n(),
|
||||
);
|
||||
if consumed_chain_inputs.contains(&key) {
|
||||
if !consumed_chain_inputs.insert(key) {
|
||||
return Err(ProposalError::ChainDoubleSpend(key.0, key.1, key.2));
|
||||
}
|
||||
consumed_chain_inputs.insert(key);
|
||||
}
|
||||
|
||||
for s_out in step.shielded_inputs().iter().flat_map(|i| i.notes().iter()) {
|
||||
|
@ -200,10 +205,9 @@ impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
|
|||
*s_out.txid(),
|
||||
s_out.output_index().into(),
|
||||
);
|
||||
if consumed_chain_inputs.contains(&key) {
|
||||
if !consumed_chain_inputs.insert(key) {
|
||||
return Err(ProposalError::ChainDoubleSpend(key.0, key.1, key.2));
|
||||
}
|
||||
consumed_chain_inputs.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +347,11 @@ impl<NoteRef> Step<NoteRef> {
|
|||
/// Parameters:
|
||||
/// * `transaction_request`: The ZIP 321 transaction request describing the payments
|
||||
/// to be made.
|
||||
/// * `payment_pools`: A map from payment index to pool type.
|
||||
/// * `payment_pools`: A map from payment index to pool type. The set of payment indices
|
||||
/// provided here must exactly match the set of payment indices in the [`TransactionRequest`],
|
||||
/// and the selected pool for an index must correspond to a valid receiver of the
|
||||
/// address at that index (or the address itself in the case of bare transparent or Sapling
|
||||
/// addresses).
|
||||
/// * `transparent_inputs`: The set of previous transparent outputs to be spent.
|
||||
/// * `shielded_inputs`: The sets of previous shielded outputs to be spent.
|
||||
/// * `balance`: The change outputs to be added the transaction and the fee to be paid.
|
||||
|
@ -360,6 +368,21 @@ impl<NoteRef> Step<NoteRef> {
|
|||
balance: TransactionBalance,
|
||||
is_shielding: bool,
|
||||
) -> Result<Self, ProposalError> {
|
||||
// Verify that the set of payment pools matches exactly a set of valid payment recipients
|
||||
if transaction_request.payments().len() != payment_pools.len() {
|
||||
return Err(ProposalError::PaymentPoolsMismatch);
|
||||
}
|
||||
for (idx, pool) in &payment_pools {
|
||||
if !transaction_request
|
||||
.payments()
|
||||
.get(idx)
|
||||
.iter()
|
||||
.any(|payment| pool.is_receiver(&payment.recipient_address))
|
||||
{
|
||||
return Err(ProposalError::PaymentPoolsMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
let transparent_input_total = transparent_inputs
|
||||
.iter()
|
||||
.map(|out| out.txout().value)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use rusqlite::{self, types::ToSql, OptionalExtension};
|
||||
use schemer;
|
||||
use schemer_rusqlite::RusqliteMigration;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
Loading…
Reference in New Issue