Merge pull request #1065 from nuttycom/primitives/padding_rule

zcash_primitives: Add explicit control over padding to the transaction builder.
This commit is contained in:
Kris Nuttycombe 2024-01-02 15:03:55 -07:00 committed by GitHub
commit 6c5bdf85ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 535 additions and 296 deletions

6
Cargo.lock generated
View File

@ -1461,8 +1461,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "orchard"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d31e68534df32024dcc89a8390ec6d7bef65edd87d91b45cfb481a2eb2d77c5"
source = "git+https://github.com/zcash/orchard.git?rev=189257391a5726d7f3f2568d547a5ef8603c7156#189257391a5726d7f3f2568d547a5ef8603c7156"
dependencies = [
"aes",
"bitvec",
@ -2088,7 +2087,7 @@ dependencies = [
[[package]]
name = "sapling-crypto"
version = "0.0.1"
source = "git+https://github.com/zcash/sapling-crypto.git?rev=df6681c1046bd28073befd7d42224e4a4c70032a#df6681c1046bd28073befd7d42224e4a4c70032a"
source = "git+https://github.com/zcash/sapling-crypto.git?rev=71711b9e4b775a625318070e0f56b7b652f12306#71711b9e4b775a625318070e0f56b7b652f12306"
dependencies = [
"aes",
"bellman",
@ -3076,6 +3075,7 @@ dependencies = [
"blake2b_simd",
"ff",
"jubjub",
"orchard",
"rand_core",
"sapling-crypto",
"zcash_address",

View File

@ -112,4 +112,5 @@ panic = 'abort'
codegen-units = 1
[patch.crates-io]
sapling = { package = "sapling-crypto", git = "https://github.com/zcash/sapling-crypto.git", rev = "df6681c1046bd28073befd7d42224e4a4c70032a" }
sapling = { package = "sapling-crypto", git = "https://github.com/zcash/sapling-crypto.git", rev = "71711b9e4b775a625318070e0f56b7b652f12306" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "189257391a5726d7f3f2568d547a5ef8603c7156" }

View File

@ -32,6 +32,9 @@ zcash_primitives.workspace = true
time = "0.3.22"
nonempty.workspace = true
# - CSPRNG
rand_core.workspace = true
# - Encodings
base64.workspace = true
bech32.workspace = true

View File

@ -1,5 +1,6 @@
use std::num::NonZeroU32;
use rand_core::OsRng;
use sapling::{
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
prover::{OutputProver, SpendProver},
@ -8,7 +9,7 @@ use zcash_primitives::{
consensus::{self, NetworkUpgrade},
memo::MemoBytes,
transaction::{
builder::Builder,
builder::{BuildConfig, Builder},
components::amount::{Amount, NonNegativeAmount},
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
Transaction, TxId,
@ -576,36 +577,59 @@ where
Some(dfvk.to_ovk(Scope::Internal))
};
let (sapling_anchor, sapling_inputs) = proposal.sapling_inputs().map_or_else(
|| Ok((sapling::Anchor::empty_tree(), vec![])),
|inputs| {
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _>>(|sapling_tree| {
let anchor = sapling_tree
.root_at_checkpoint_id(&inputs.anchor_height())?
.into();
let sapling_inputs = inputs
.notes()
.iter()
.map(|selected| {
match selected.note() {
WalletNote::Sapling(note) => {
let key = match selected.spending_key_scope() {
Scope::External => usk.sapling().clone(),
Scope::Internal => usk.sapling().derive_internal(),
};
let merkle_path = sapling_tree.witness_at_checkpoint_id_caching(
selected.note_commitment_tree_position(),
&inputs.anchor_height(),
)?;
Ok((key, note, merkle_path))
}
WalletNote::Orchard(_) => {
// FIXME: Implement this once `Proposal` has been refactored to
// include Orchard notes.
panic!("Orchard spends are not yet supported");
}
}
})
.collect::<Result<Vec<_>, Error<_, _, _, _>>>()?;
Ok((anchor, sapling_inputs))
})
},
)?;
// Create the transaction. The type of the proposal ensures that there
// are no possible transparent inputs, so we ignore those
let mut builder = Builder::new(params.clone(), proposal.min_target_height(), None);
let mut builder = Builder::new(
params.clone(),
proposal.min_target_height(),
BuildConfig::Standard {
sapling_anchor,
orchard_anchor: orchard::Anchor::empty_tree(),
},
);
if let Some(sapling_inputs) = proposal.sapling_inputs() {
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _>>(|sapling_tree| {
for selected in sapling_inputs.notes() {
match selected.note() {
WalletNote::Sapling(note) => {
let key = match selected.spending_key_scope() {
Scope::External => usk.sapling().clone(),
Scope::Internal => usk.sapling().derive_internal(),
};
let merkle_path = sapling_tree.witness_at_checkpoint_id_caching(
selected.note_commitment_tree_position(),
&sapling_inputs.anchor_height(),
)?;
builder.add_sapling_spend(key, note.clone(), merkle_path)?;
}
WalletNote::Orchard(_) => {
// FIXME: Implement this once `Proposal` has been refactored to
// include Orchard notes.
panic!("Orchard spends are not yet supported");
}
}
}
Ok(())
})?;
for (key, note, merkle_path) in sapling_inputs.into_iter() {
builder.add_sapling_spend(&key, note.clone(), merkle_path)?;
}
#[cfg(feature = "transparent-inputs")]
@ -720,7 +744,7 @@ where
// Build the transaction with the specified fee rule
let (tx, sapling_build_meta) =
builder.build(spend_prover, output_prover, proposal.fee_rule())?;
builder.build(OsRng, spend_prover, output_prover, proposal.fee_rule())?;
let internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
let sapling_outputs =

View File

@ -12,7 +12,7 @@ use zcash_primitives::{
},
};
pub mod common;
pub(crate) mod common;
pub mod fixed;
pub mod sapling;
pub mod standard;

View File

@ -3,7 +3,7 @@
use std::convert::Infallible;
use sapling::builder::{SaplingOutputInfo, SpendDescriptionInfo};
use sapling::builder::{OutputInfo, SpendInfo};
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
/// A trait that provides a minimized view of a Sapling input suitable for use in
@ -26,7 +26,7 @@ impl<N> InputView<N> for Infallible {
// `SpendDescriptionInfo` does not contain a note identifier, so we can only implement
// `InputView<()>`
impl InputView<()> for SpendDescriptionInfo {
impl InputView<()> for SpendInfo {
fn note_id(&self) -> &() {
&()
}
@ -44,7 +44,7 @@ pub trait OutputView {
fn value(&self) -> NonNegativeAmount;
}
impl OutputView for SaplingOutputInfo {
impl OutputView for OutputInfo {
fn value(&self) -> NonNegativeAmount {
NonNegativeAmount::try_from(self.value())
.expect("Output values should be checked at construction.")

View File

@ -19,6 +19,7 @@ ff.workspace = true
jubjub.workspace = true
rand_core.workspace = true
sapling.workspace = true
orchard.workspace = true
zcash_address.workspace = true
zcash_proofs.workspace = true

View File

@ -476,6 +476,8 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<B> {
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use blake2b_simd::Params;
use ff::Field;
use rand_core::OsRng;
@ -487,7 +489,7 @@ mod tests {
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
legacy::TransparentAddress,
transaction::{
builder::Builder,
builder::{BuildConfig, Builder},
components::{
amount::{Amount, NonNegativeAmount},
tze::{Authorized, Bundle, OutPoint, TzeIn, TzeOut},
@ -637,9 +639,19 @@ mod tests {
}
}
fn demo_builder<'a>(height: BlockHeight) -> DemoBuilder<Builder<'a, FutureNetwork, OsRng, ()>> {
fn demo_builder<'a>(
height: BlockHeight,
sapling_anchor: sapling::Anchor,
) -> DemoBuilder<Builder<'a, FutureNetwork, ()>> {
DemoBuilder {
txn_builder: Builder::new(FutureNetwork, height, None),
txn_builder: Builder::new(
FutureNetwork,
height,
BuildConfig::Standard {
sapling_anchor,
orchard_anchor: orchard::Anchor::empty_tree(),
},
),
extension_id: 0,
}
}
@ -823,9 +835,9 @@ mod tests {
tree.append(cm1).unwrap();
let witness1 = sapling::IncrementalWitness::from_tree(tree);
let mut builder_a = demo_builder(tx_height);
let mut builder_a = demo_builder(tx_height, witness1.root().into());
builder_a
.add_sapling_spend(extsk, note1, witness1.path().unwrap())
.add_sapling_spend::<Infallible>(&extsk, note1, witness1.path().unwrap())
.unwrap();
let value = NonNegativeAmount::const_from_u64(100000);
@ -836,7 +848,7 @@ mod tests {
.unwrap();
let (tx_a, _) = builder_a
.txn_builder
.build_zfuture(&prover, &prover, &fee_rule)
.build_zfuture(OsRng, &prover, &prover, &fee_rule)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_a = tx_a.tze_bundle().unwrap();
@ -845,7 +857,7 @@ mod tests {
// Transfer
//
let mut builder_b = demo_builder(tx_height + 1);
let mut builder_b = demo_builder(tx_height + 1, sapling::Anchor::empty_tree());
let prevout_a = (OutPoint::new(tx_a.txid(), 0), tze_a.vout[0].clone());
let value_xfr = (value - fee_rule.fixed_fee()).unwrap();
builder_b
@ -854,7 +866,7 @@ mod tests {
.unwrap();
let (tx_b, _) = builder_b
.txn_builder
.build_zfuture(&prover, &prover, &fee_rule)
.build_zfuture(OsRng, &prover, &prover, &fee_rule)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_b = tx_b.tze_bundle().unwrap();
@ -863,7 +875,7 @@ mod tests {
// Closing transaction
//
let mut builder_c = demo_builder(tx_height + 2);
let mut builder_c = demo_builder(tx_height + 2, sapling::Anchor::empty_tree());
let prevout_b = (OutPoint::new(tx_a.txid(), 0), tze_b.vout[0].clone());
builder_c
.demo_close(prevout_b, preimage_2)
@ -879,7 +891,7 @@ mod tests {
let (tx_c, _) = builder_c
.txn_builder
.build_zfuture(&prover, &prover, &fee_rule)
.build_zfuture(OsRng, &prover, &prover, &fee_rule)
.map_err(|e| format!("build failure: {:?}", e))
.unwrap();
let tze_c = tx_c.tze_bundle().unwrap();

View File

@ -10,7 +10,8 @@ and this library adheres to Rust's notion of
- Dependency on `bellman 0.14`.
- `zcash_primitives::consensus::sapling_zip212_enforcement`
- `zcash_primitives::transaction`:
- `builder::get_fee`
- `builder::{BuildConfig, FeeError, get_fee}`
- `builder::Error::SaplingBuilderNotAvailable`
- `components::sapling`:
- Sapling bundle component parsers, behind the `temporary-zcashd` feature
flag:
@ -55,15 +56,32 @@ and this library adheres to Rust's notion of
- `builder::Builder` now has a generic parameter for the type of progress
notifier, which needs to implement `sapling::builder::ProverProgress` in
order to build transactions.
- `builder::Builder::new` now takes a `BuildConfig` argument instead of an
optional Orchard anchor. Anchors for both Sapling and Orchard are now
required at the time of builder construction.
- `builder::Builder::{build, build_zfuture}` now take
`&impl SpendProver, &impl OutputProver` instead of `&impl TxProver`.
- `builder::Builder::add_sapling_spend` no longer takes a `diversifier`
argument as the diversifier may be obtained from the note.
- `builder::Builder::add_sapling_spend` now takes its `ExtendedSpendingKey`
argument by reference.
- `builder::Builder::{add_sapling_spend, add_sapling_output}` now return
`builder::Error`s instead of the underlying `sapling_crypto::builder::Error`s
when returning `Err`.
- `builder::Builder::add_orchard_spend` now takes its `SpendingKey` argument
by reference.
- `builder::Builder::with_progress_notifier` now consumes `self` and returns a
`Builder` typed on the provided channel.
- `builder::Builder::get_fee` now returns a `builder::FeeError` instead of the
bare `FeeRule::Error` when returning `Err`.
- `builder::Error::OrchardAnchorNotAvailable` has been renamed to
`OrchardBuilderNotAvailable`.
- `builder::{build, build_zfuture}` each now take an additional `rng` argument.
- `components::transparent::TxOut.value` now has type `NonNegativeAmount`
instead of `Amount`.
- `components::transparent::fees` has been moved to
- `components::sapling::MapAuth` trait methods now take `&mut self` instead
of `&self`.
- `components::transparent::fees` has been moved to
`zcash_primitives::transaction::fees::transparent`
- `components::transparent::builder::TransparentBuilder::{inputs, outputs}`
have changed to return `&[TransparentInputInfo]` and `&[TxOut]` respectively,
@ -102,7 +120,7 @@ and this library adheres to Rust's notion of
- `Bundle`
- `SpendDescription, SpendDescriptionV5`
- `OutputDescription, OutputDescriptionV5`
- `Authorization, Authorized, MapAuth`
- `Authorization, Authorized`
- `GrothProofBytes`
- `CompactOutputDescription` (moved to `sapling_crypto::note_encryption`).
- `Unproven`

View File

@ -91,6 +91,7 @@ incrementalmerkletree = { workspace = true, features = ["legacy-api", "test-depe
proptest.workspace = true
assert_matches.workspace = true
rand_xorshift.workspace = true
sapling = { workspace = true, features = ["test-dependencies"] }
orchard = { workspace = true, features = ["test-dependencies"] }
[target.'cfg(unix)'.dev-dependencies]

View File

@ -4,7 +4,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Through
use ff::Field;
use rand_core::OsRng;
use sapling::{
builder::SaplingBuilder,
self,
note_encryption::{
try_sapling_compact_note_decryption, try_sapling_note_decryption, CompactOutputDescription,
PreparedIncomingViewingKey, SaplingDomain,
@ -35,9 +35,15 @@ fn bench_note_decryption(c: &mut Criterion) {
let diversifier = Diversifier([0; 11]);
let pa = valid_ivk.to_payment_address(diversifier).unwrap();
let mut builder = SaplingBuilder::new(zip212_enforcement);
let mut builder = sapling::builder::Builder::new(
zip212_enforcement,
// We use the Coinbase bundle type because we don't need to use
// any inputs for this benchmark.
sapling::builder::BundleType::Coinbase,
sapling::Anchor::empty_tree(),
);
builder
.add_output(&mut rng, None, pa, NoteValue::from_raw(100), None)
.add_output(None, pa, NoteValue::from_raw(100), None)
.unwrap();
let (bundle, _) = builder
.build::<MockSpendProver, MockOutputProver, _, Amount>(&mut rng)

View File

@ -5,7 +5,7 @@ use std::error;
use std::fmt;
use std::sync::mpsc::Sender;
use rand::{rngs::OsRng, CryptoRng, RngCore};
use rand::{CryptoRng, RngCore};
use crate::{
consensus::{self, BlockHeight, BranchId, NetworkUpgrade},
@ -13,7 +13,7 @@ use crate::{
memo::MemoBytes,
sapling::{
self,
builder::{self as sapling_builder, SaplingBuilder, SaplingMetadata},
builder::SaplingMetadata,
prover::{OutputProver, SpendProver},
Note, PaymentAddress,
},
@ -53,9 +53,25 @@ use super::components::amount::NonNegativeAmount;
/// <https://zips.z.cash/zip-0203#changes-for-blossom>
const DEFAULT_TX_EXPIRY_DELTA: u32 = 40;
/// Errors that can occur during fee calculation.
#[derive(Debug)]
pub enum FeeError<FE> {
FeeRule(FE),
Bundle(&'static str),
}
impl<FE: fmt::Display> fmt::Display for FeeError<FE> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FeeError::FeeRule(e) => write!(f, "An error occurred in fee calculation: {}", e),
FeeError::Bundle(b) => write!(f, "Bundle structure invalid in fee calculation: {}", b),
}
}
}
/// Errors that can occur during transaction construction.
#[derive(Debug)]
pub enum Error<FeeError> {
pub enum Error<FE> {
/// Insufficient funds were provided to the transaction builder; the given
/// additional amount is required in order to construct the transaction.
InsufficientFunds(Amount),
@ -63,22 +79,25 @@ pub enum Error<FeeError> {
/// add a change output.
ChangeRequired(Amount),
/// An error occurred in computing the fees for a transaction.
Fee(FeeError),
Fee(FeeError<FE>),
/// An overflow or underflow occurred when computing value balances
Balance(BalanceError),
/// An error occurred in constructing the transparent parts of a transaction.
TransparentBuild(transparent::builder::Error),
/// An error occurred in constructing the Sapling parts of a transaction.
SaplingBuild(sapling_builder::Error),
SaplingBuild(sapling::builder::Error),
/// An error occurred in constructing the Orchard parts of a transaction.
OrchardBuild(orchard::builder::BuildError),
/// An error occurred in adding an Orchard Spend to a transaction.
OrchardSpend(orchard::builder::SpendError),
/// An error occurred in adding an Orchard Output to a transaction.
OrchardRecipient(orchard::builder::OutputError),
/// The builder was constructed either without an Orchard anchor or before NU5
/// activation, but an Orchard spend or recipient was added.
OrchardAnchorNotAvailable,
/// The builder was constructed without support for the Sapling pool, but a Sapling
/// spend or output was added.
SaplingBuilderNotAvailable,
/// The builder was constructed with a target height before NU5 activation, but an Orchard
/// spend or output was added.
OrchardBuilderNotAvailable,
/// An error occurred in constructing the TZE parts of a transaction.
#[cfg(feature = "zfuture")]
TzeBuild(tze::builder::Error),
@ -104,7 +123,11 @@ impl<FE: fmt::Display> fmt::Display for Error<FE> {
Error::OrchardBuild(err) => write!(f, "{:?}", err),
Error::OrchardSpend(err) => write!(f, "Could not add Orchard spend: {}", err),
Error::OrchardRecipient(err) => write!(f, "Could not add Orchard recipient: {}", err),
Error::OrchardAnchorNotAvailable => write!(
Error::SaplingBuilderNotAvailable => write!(
f,
"Cannot create Sapling transactions without a Sapling anchor"
),
Error::OrchardBuilderNotAvailable => write!(
f,
"Cannot create Orchard transactions without an Orchard anchor, or before NU5 activation"
),
@ -122,6 +145,24 @@ impl<FE> From<BalanceError> for Error<FE> {
}
}
impl<FE> From<FeeError<FE>> for Error<FE> {
fn from(e: FeeError<FE>) -> Self {
Error::Fee(e)
}
}
impl<FE> From<sapling::builder::Error> for Error<FE> {
fn from(e: sapling::builder::Error) -> Self {
Error::SaplingBuild(e)
}
}
impl<FE> From<orchard::builder::SpendError> for Error<FE> {
fn from(e: orchard::builder::SpendError) -> Self {
Error::OrchardSpend(e)
}
}
/// Reports on the progress made by the builder towards building a transaction.
pub struct Progress {
/// The number of steps completed.
@ -156,14 +197,52 @@ impl Progress {
}
}
/// Rules for how the builder should be configured for each shielded pool.
#[derive(Clone, Copy)]
pub enum BuildConfig {
Standard {
sapling_anchor: sapling::Anchor,
orchard_anchor: orchard::Anchor,
},
Coinbase,
}
impl BuildConfig {
/// Returns the Sapling bundle type and anchor for this configuration.
pub fn sapling_builder_config(&self) -> (sapling::builder::BundleType, sapling::Anchor) {
match self {
BuildConfig::Standard { sapling_anchor, .. } => {
(sapling::builder::BundleType::DEFAULT, *sapling_anchor)
}
BuildConfig::Coinbase => (
sapling::builder::BundleType::Coinbase,
sapling::Anchor::empty_tree(),
),
}
}
/// Returns the Orchard bundle type and anchor for this configuration.
pub fn orchard_builder_config(&self) -> (orchard::builder::BundleType, orchard::Anchor) {
match self {
BuildConfig::Standard { orchard_anchor, .. } => {
(orchard::builder::BundleType::DEFAULT, *orchard_anchor)
}
BuildConfig::Coinbase => (
orchard::builder::BundleType::Coinbase,
orchard::Anchor::empty_tree(),
),
}
}
}
/// Generates a [`Transaction`] from its inputs and outputs.
pub struct Builder<'a, P, R, U: sapling::builder::ProverProgress> {
pub struct Builder<'a, P, U: sapling::builder::ProverProgress> {
params: P,
rng: R,
build_config: BuildConfig,
target_height: BlockHeight,
expiry_height: BlockHeight,
transparent_builder: TransparentBuilder,
sapling_builder: SaplingBuilder,
sapling_builder: Option<sapling::builder::Builder>,
orchard_builder: Option<orchard::builder::Builder>,
// TODO: In the future, instead of taking the spending keys as arguments when calling
// `add_sapling_spend` or `add_orchard_spend`, we will build an unauthorized, unproven
@ -178,7 +257,7 @@ pub struct Builder<'a, P, R, U: sapling::builder::ProverProgress> {
progress_notifier: U,
}
impl<'a, P, R, U: sapling::builder::ProverProgress> Builder<'a, P, R, U> {
impl<'a, P, U: sapling::builder::ProverProgress> Builder<'a, P, U> {
/// Returns the network parameters that the builder has been configured for.
pub fn params(&self) -> &P {
&self.params
@ -204,78 +283,53 @@ impl<'a, P, R, U: sapling::builder::ProverProgress> Builder<'a, P, R, U> {
/// Returns the set of Sapling inputs currently committed to be consumed
/// by the transaction.
pub fn sapling_inputs(&self) -> &[sapling::builder::SpendDescriptionInfo] {
self.sapling_builder.inputs()
pub fn sapling_inputs(&self) -> &[sapling::builder::SpendInfo] {
self.sapling_builder
.as_ref()
.map_or_else(|| &[][..], |b| b.inputs())
}
/// Returns the set of Sapling outputs currently set to be produced by
/// the transaction.
pub fn sapling_outputs(&self) -> &[sapling::builder::SaplingOutputInfo] {
self.sapling_builder.outputs()
pub fn sapling_outputs(&self) -> &[sapling::builder::OutputInfo] {
self.sapling_builder
.as_ref()
.map_or_else(|| &[][..], |b| b.outputs())
}
}
impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng, ()> {
impl<'a, P: consensus::Parameters> Builder<'a, P, ()> {
/// Creates a new `Builder` targeted for inclusion in the block with the given height,
/// using default values for general transaction fields and the default OS random.
/// using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
pub fn new(
params: P,
target_height: BlockHeight,
orchard_anchor: Option<orchard::tree::Anchor>,
) -> Self {
Builder::new_with_rng(params, target_height, orchard_anchor, OsRng)
}
}
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R, ()> {
/// Creates a new `Builder` targeted for inclusion in the block with the given height
/// and randomness source, using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta (20 blocks).
pub fn new_with_rng(
params: P,
target_height: BlockHeight,
orchard_anchor: Option<orchard::tree::Anchor>,
rng: R,
) -> Self {
pub fn new(params: P, target_height: BlockHeight, build_config: BuildConfig) -> Self {
let orchard_builder = if params.is_nu_active(NetworkUpgrade::Nu5, target_height) {
orchard_anchor.map(|anchor| {
orchard::builder::Builder::new(
orchard::bundle::Flags::from_parts(true, true),
anchor,
)
})
let (bundle_type, anchor) = build_config.orchard_builder_config();
Some(orchard::builder::Builder::new(bundle_type, anchor))
} else {
None
};
Self::new_internal(params, rng, target_height, orchard_builder)
}
/// Common utility function for builder construction.
fn new_internal(
params: P,
rng: R,
target_height: BlockHeight,
orchard_builder: Option<orchard::builder::Builder>,
) -> Self {
let zip212_enforcement = consensus::sapling_zip212_enforcement(&params, target_height);
let sapling_builder = Some({
let (bundle_type, anchor) = build_config.sapling_builder_config();
sapling::builder::Builder::new(
consensus::sapling_zip212_enforcement(&params, target_height),
bundle_type,
anchor,
)
});
Builder {
params,
rng,
build_config,
target_height,
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
transparent_builder: TransparentBuilder::empty(),
sapling_builder: SaplingBuilder::new(zip212_enforcement),
sapling_builder,
orchard_builder,
sapling_asks: vec![],
orchard_saks: Vec::new(),
@ -296,10 +350,10 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R, ()>
pub fn with_progress_notifier(
self,
progress_notifier: Sender<Progress>,
) -> Builder<'a, P, R, Sender<Progress>> {
) -> Builder<'a, P, Sender<Progress>> {
Builder {
params: self.params,
rng: self.rng,
build_config: self.build_config,
target_height: self.target_height,
expiry_height: self.expiry_height,
transparent_builder: self.transparent_builder,
@ -313,43 +367,41 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R, ()>
}
}
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::ProverProgress>
Builder<'a, P, R, U>
{
impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<'a, P, U> {
/// Adds an Orchard note to be spent in this bundle.
///
/// Returns an error if the given Merkle path does not have the required anchor for
/// the given note.
pub fn add_orchard_spend<FeeError>(
pub fn add_orchard_spend<FE>(
&mut self,
sk: orchard::keys::SpendingKey,
sk: &orchard::keys::SpendingKey,
note: orchard::Note,
merkle_path: orchard::tree::MerklePath,
) -> Result<(), Error<FeeError>> {
self.orchard_builder
.as_mut()
.ok_or(Error::OrchardAnchorNotAvailable)?
.add_spend(orchard::keys::FullViewingKey::from(&sk), note, merkle_path)
.map_err(Error::OrchardSpend)?;
) -> Result<(), Error<FE>> {
if let Some(builder) = self.orchard_builder.as_mut() {
builder.add_spend(orchard::keys::FullViewingKey::from(sk), note, merkle_path)?;
self.orchard_saks
.push(orchard::keys::SpendAuthorizingKey::from(&sk));
self.orchard_saks
.push(orchard::keys::SpendAuthorizingKey::from(sk));
Ok(())
Ok(())
} else {
Err(Error::OrchardBuilderNotAvailable)
}
}
/// Adds an Orchard recipient to the transaction.
pub fn add_orchard_output<FeeError>(
pub fn add_orchard_output<FE>(
&mut self,
ovk: Option<orchard::keys::OutgoingViewingKey>,
recipient: orchard::Address,
value: u64,
memo: MemoBytes,
) -> Result<(), Error<FeeError>> {
) -> Result<(), Error<FE>> {
self.orchard_builder
.as_mut()
.ok_or(Error::OrchardAnchorNotAvailable)?
.add_recipient(
.ok_or(Error::OrchardBuilderNotAvailable)?
.add_output(
ovk,
recipient,
orchard::value::NoteValue::from_raw(value),
@ -362,35 +414,40 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
///
/// Returns an error if the given Merkle path does not have the same anchor as the
/// paths for previous Sapling notes.
pub fn add_sapling_spend(
pub fn add_sapling_spend<FE>(
&mut self,
extsk: sapling::zip32::ExtendedSpendingKey,
extsk: &sapling::zip32::ExtendedSpendingKey,
note: Note,
merkle_path: sapling::MerklePath,
) -> Result<(), sapling_builder::Error> {
self.sapling_builder
.add_spend(&mut self.rng, &extsk, note, merkle_path)?;
) -> Result<(), Error<FE>> {
if let Some(builder) = self.sapling_builder.as_mut() {
builder.add_spend(extsk, note, merkle_path)?;
self.sapling_asks.push(extsk.expsk.ask);
Ok(())
self.sapling_asks.push(extsk.expsk.ask.clone());
Ok(())
} else {
Err(Error::SaplingBuilderNotAvailable)
}
}
/// Adds a Sapling address to send funds to.
pub fn add_sapling_output(
pub fn add_sapling_output<FE>(
&mut self,
ovk: Option<sapling::keys::OutgoingViewingKey>,
to: PaymentAddress,
value: NonNegativeAmount,
memo: MemoBytes,
) -> Result<(), sapling_builder::Error> {
self.sapling_builder.add_output(
&mut self.rng,
ovk,
to,
sapling::value::NoteValue::from_raw(value.into()),
Some(*memo.as_array()),
)
) -> Result<(), Error<FE>> {
self.sapling_builder
.as_mut()
.ok_or(Error::SaplingBuilderNotAvailable)?
.add_output(
ovk,
to,
sapling::value::NoteValue::from_raw(value.into()),
Some(*memo.as_array()),
)
.map_err(Error::SaplingBuild)
}
/// Adds a transparent coin to be spent in this transaction.
@ -418,14 +475,17 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
fn value_balance(&self) -> Result<Amount, BalanceError> {
let value_balances = [
self.transparent_builder.value_balance()?,
self.sapling_builder.value_balance(),
if let Some(builder) = &self.orchard_builder {
builder
.value_balance()
.map_err(|_| BalanceError::Overflow)?
} else {
Amount::zero()
},
self.sapling_builder
.as_ref()
.map_or_else(Amount::zero, |builder| builder.value_balance::<Amount>()),
self.orchard_builder.as_ref().map_or_else(
|| Ok(Amount::zero()),
|builder| {
builder
.value_balance::<Amount>()
.map_err(|_| BalanceError::Overflow)
},
)?,
#[cfg(feature = "zfuture")]
self.tze_builder.value_balance()?,
];
@ -440,46 +500,102 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
///
/// This fee is a function of the spends and outputs that have been added to the builder,
/// pursuant to the specified [`FeeRule`].
pub fn get_fee<FR: FeeRule>(&self, fee_rule: &FR) -> Result<NonNegativeAmount, FR::Error> {
pub fn get_fee<FR: FeeRule>(
&self,
fee_rule: &FR,
) -> Result<NonNegativeAmount, FeeError<FR::Error>> {
#[cfg(feature = "transparent-inputs")]
let transparent_inputs = self.transparent_builder.inputs();
#[cfg(not(feature = "transparent-inputs"))]
let transparent_inputs: &[Infallible] = &[];
fee_rule.fee_required(
&self.params,
self.target_height,
transparent_inputs,
self.transparent_builder.outputs(),
self.sapling_builder.inputs().len(),
self.sapling_builder.bundle_output_count(),
match std::cmp::max(
self.orchard_builder
.as_ref()
.map_or(0, |builder| builder.outputs().len()),
self.orchard_builder
.as_ref()
.map_or(0, |builder| builder.spends().len()),
) {
1 => 2,
n => n,
},
)
let sapling_spends = self
.sapling_builder
.as_ref()
.map_or(0, |builder| builder.inputs().len());
fee_rule
.fee_required(
&self.params,
self.target_height,
transparent_inputs,
self.transparent_builder.outputs(),
sapling_spends,
self.sapling_builder.as_ref().map_or(Ok(0), |builder| {
self.build_config
.sapling_builder_config()
.0
.num_outputs(sapling_spends, builder.outputs().len())
.map_err(FeeError::Bundle)
})?,
self.orchard_builder.as_ref().map_or(Ok(0), |builder| {
self.build_config
.orchard_builder_config()
.0
.num_actions(builder.spends().len(), builder.outputs().len())
.map_err(FeeError::Bundle)
})?,
)
.map_err(FeeError::FeeRule)
}
#[cfg(feature = "zfuture")]
pub fn get_fee_zfuture<FR: FeeRule + FutureFeeRule>(
&self,
fee_rule: &FR,
) -> Result<NonNegativeAmount, FeeError<FR::Error>> {
#[cfg(feature = "transparent-inputs")]
let transparent_inputs = self.transparent_builder.inputs();
#[cfg(not(feature = "transparent-inputs"))]
let transparent_inputs: &[Infallible] = &[];
let sapling_spends = self
.sapling_builder
.as_ref()
.map_or(0, |builder| builder.inputs().len());
fee_rule
.fee_required_zfuture(
&self.params,
self.target_height,
transparent_inputs,
self.transparent_builder.outputs(),
sapling_spends,
self.sapling_builder.as_ref().map_or(Ok(0), |builder| {
self.build_config
.sapling_builder_config()
.0
.num_outputs(sapling_spends, builder.outputs().len())
.map_err(FeeError::Bundle)
})?,
self.orchard_builder.as_ref().map_or(Ok(0), |builder| {
self.build_config
.orchard_builder_config()
.0
.num_actions(builder.spends().len(), builder.outputs().len())
.map_err(FeeError::Bundle)
})?,
self.tze_builder.inputs(),
self.tze_builder.outputs(),
)
.map_err(FeeError::FeeRule)
}
/// Builds a transaction from the configured spends and outputs.
///
/// Upon success, returns a tuple containing the final transaction, and the
/// [`SaplingMetadata`] generated during the build process.
pub fn build<SP: SpendProver, OP: OutputProver, FR: FeeRule>(
pub fn build<R: RngCore + CryptoRng, SP: SpendProver, OP: OutputProver, FR: FeeRule>(
self,
rng: R,
spend_prover: &SP,
output_prover: &OP,
fee_rule: &FR,
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
let fee = self.get_fee(fee_rule).map_err(Error::Fee)?;
self.build_internal(spend_prover, output_prover, fee.into())
self.build_internal(rng, spend_prover, output_prover, fee)
}
/// Builds a transaction from the configured spends and outputs.
@ -487,47 +603,28 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
/// Upon success, returns a tuple containing the final transaction, and the
/// [`SaplingMetadata`] generated during the build process.
#[cfg(feature = "zfuture")]
pub fn build_zfuture<SP: SpendProver, OP: OutputProver, FR: FutureFeeRule>(
pub fn build_zfuture<
R: RngCore + CryptoRng,
SP: SpendProver,
OP: OutputProver,
FR: FutureFeeRule,
>(
self,
rng: R,
spend_prover: &SP,
output_prover: &OP,
fee_rule: &FR,
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
#[cfg(feature = "transparent-inputs")]
let transparent_inputs = self.transparent_builder.inputs();
#[cfg(not(feature = "transparent-inputs"))]
let transparent_inputs: &[Infallible] = &[];
let fee = fee_rule
.fee_required_zfuture(
&self.params,
self.target_height,
transparent_inputs,
self.transparent_builder.outputs(),
self.sapling_builder.inputs().len(),
self.sapling_builder.bundle_output_count(),
std::cmp::max(
self.orchard_builder
.as_ref()
.map_or(0, |builder| builder.outputs().len()),
self.orchard_builder
.as_ref()
.map_or(0, |builder| builder.spends().len()),
),
self.tze_builder.inputs(),
self.tze_builder.outputs(),
)
.map_err(Error::Fee)?;
self.build_internal(spend_prover, output_prover, fee.into())
let fee = self.get_fee_zfuture(fee_rule).map_err(Error::Fee)?;
self.build_internal(rng, spend_prover, output_prover, fee)
}
fn build_internal<SP: SpendProver, OP: OutputProver, FE>(
fn build_internal<R: RngCore + CryptoRng, SP: SpendProver, OP: OutputProver, FE>(
self,
mut rng: R,
spend_prover: &SP,
output_prover: &OP,
fee: Amount,
fee: NonNegativeAmount,
) -> Result<(Transaction, SaplingMetadata), Error<FE>> {
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
@ -539,7 +636,8 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
//
// After fees are accounted for, the value balance of the transaction must be zero.
let balance_after_fees = (self.value_balance()? - fee).ok_or(BalanceError::Underflow)?;
let balance_after_fees =
(self.value_balance()? - fee.into()).ok_or(BalanceError::Underflow)?;
match balance_after_fees.cmp(&Amount::zero()) {
Ordering::Less => {
@ -553,35 +651,45 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
let transparent_bundle = self.transparent_builder.build();
let mut rng = self.rng;
let (sapling_bundle, tx_metadata) = match self
.sapling_builder
.build::<SP, OP, _, _>(&mut rng)
.map_err(Error::SaplingBuild)?
.map(|(bundle, tx_metadata)| {
// We need to create proofs before signatures, because we still support
// creating V4 transactions, which commit to the Sapling proofs in the
// transaction digest.
(
bundle.create_proofs(
spend_prover,
output_prover,
&mut rng,
self.progress_notifier,
),
tx_metadata,
)
}) {
.and_then(|builder| {
builder
.build::<SP, OP, _, _>(&mut rng)
.map_err(Error::SaplingBuild)
.transpose()
.map(|res| {
res.map(|(bundle, tx_metadata)| {
// We need to create proofs before signatures, because we still support
// creating V4 transactions, which commit to the Sapling proofs in the
// transaction digest.
(
bundle.create_proofs(
spend_prover,
output_prover,
&mut rng,
self.progress_notifier,
),
tx_metadata,
)
})
})
})
.transpose()?
{
Some((bundle, meta)) => (Some(bundle), meta),
None => (None, SaplingMetadata::empty()),
};
let orchard_bundle: Option<orchard::Bundle<_, Amount>> =
if let Some(builder) = self.orchard_builder {
Some(builder.build(&mut rng).map_err(Error::OrchardBuild)?)
} else {
None
};
let orchard_bundle: Option<orchard::Bundle<_, Amount>> = self
.orchard_builder
.and_then(|builder| {
builder
.build(&mut rng)
.map_err(Error::OrchardBuild)
.transpose()
})
.transpose()?;
#[cfg(feature = "zfuture")]
let (tze_bundle, tze_signers) = self.tze_builder.build();
@ -674,8 +782,8 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::
}
#[cfg(feature = "zfuture")]
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::ProverProgress>
ExtensionTxBuilder<'a> for Builder<'a, P, R, U>
impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> ExtensionTxBuilder<'a>
for Builder<'a, P, U>
{
type BuildCtx = TransactionData<Unauthorized>;
type BuildError = tze::builder::Error;
@ -714,7 +822,7 @@ mod testing {
use super::{Builder, Error, SaplingMetadata};
use crate::{
consensus::{self, BlockHeight},
consensus,
sapling::{
self,
prover::mock::{MockOutputProver, MockSpendProver},
@ -723,21 +831,13 @@ mod testing {
transaction::Transaction,
};
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R, ()> {
/// Creates a new `Builder` targeted for inclusion in the block with the given height
/// and randomness source, using default values for general transaction fields.
///
/// # Default values
///
/// The expiry height will be set to the given height plus the default transaction
/// expiry delta.
///
/// WARNING: DO NOT USE IN PRODUCTION
pub fn test_only_new_with_rng(
params: P,
height: BlockHeight,
impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<'a, P, U> {
/// Build the transaction using mocked randomness and proving capabilities.
/// DO NOT USE EXCEPT FOR UNIT TESTING.
pub fn mock_build<R: RngCore>(
self,
rng: R,
) -> Builder<'a, P, impl RngCore + CryptoRng, ()> {
) -> Result<(Transaction, SaplingMetadata), Error<Infallible>> {
struct FakeCryptoRng<R: RngCore>(R);
impl<R: RngCore> CryptoRng for FakeCryptoRng<R> {}
impl<R: RngCore> RngCore for FakeCryptoRng<R> {
@ -757,19 +857,10 @@ mod testing {
self.0.try_fill_bytes(dest)
}
}
Builder::new_internal(params, FakeCryptoRng(rng), height, None)
}
}
impl<
'a,
P: consensus::Parameters,
R: RngCore + CryptoRng,
U: sapling::builder::ProverProgress,
> Builder<'a, P, R, U>
{
pub fn mock_build(self) -> Result<(Transaction, SaplingMetadata), Error<Infallible>> {
#[allow(deprecated)]
self.build(
FakeCryptoRng(rng),
&MockSpendProver,
&MockOutputProver,
&fixed::FeeRule::standard(),
@ -780,6 +871,8 @@ mod testing {
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use assert_matches::assert_matches;
use ff::Field;
use incrementalmerkletree::{frontier::CommitmentTree, witness::IncrementalWitness};
@ -790,7 +883,10 @@ mod tests {
legacy::TransparentAddress,
memo::MemoBytes,
sapling::{self, zip32::ExtendedSpendingKey, Node, Rseed},
transaction::components::amount::{Amount, BalanceError, NonNegativeAmount},
transaction::{
builder::BuildConfig,
components::amount::{Amount, BalanceError, NonNegativeAmount},
},
};
use super::{Builder, Error};
@ -802,11 +898,7 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
use crate::{
legacy::keys::{AccountPrivKey, IncomingViewingKey},
sapling::note_encryption::Zip212Enforcement,
transaction::{
builder::{SaplingBuilder, DEFAULT_TX_EXPIRY_DELTA},
OutPoint, TxOut,
},
transaction::{builder::DEFAULT_TX_EXPIRY_DELTA, OutPoint, TxOut},
zip32::AccountId,
};
@ -825,11 +917,14 @@ mod tests {
// Create a builder with 0 fee, so we can construct t outputs
let mut builder = builder::Builder {
params: TEST_NETWORK,
rng: OsRng,
build_config: BuildConfig::Standard {
sapling_anchor: sapling::Anchor::empty_tree(),
orchard_anchor: orchard::Anchor::empty_tree(),
},
target_height: sapling_activation_height,
expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA,
transparent_builder: TransparentBuilder::empty(),
sapling_builder: SaplingBuilder::new(Zip212Enforcement::Off),
sapling_builder: None,
#[cfg(feature = "zfuture")]
tze_builder: TzeBuilder::empty(),
#[cfg(not(feature = "zfuture"))]
@ -867,7 +962,7 @@ mod tests {
)
.unwrap();
let (tx, _) = builder.mock_build().unwrap();
let (tx, _) = builder.mock_build(OsRng).unwrap();
// No binding signature, because only t input and outputs
assert!(tx.sapling_bundle.is_none());
}
@ -892,11 +987,16 @@ mod tests {
let tx_height = TEST_NETWORK
.activation_height(NetworkUpgrade::Sapling)
.unwrap();
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
let build_config = BuildConfig::Standard {
sapling_anchor: witness1.root().into(),
orchard_anchor: orchard::Anchor::empty_tree(),
};
let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config);
// Create a tx with a sapling spend. binding_sig should be present
builder
.add_sapling_spend(extsk, note1, witness1.path().unwrap())
.add_sapling_spend::<Infallible>(&extsk, note1, witness1.path().unwrap())
.unwrap();
builder
@ -907,7 +1007,7 @@ mod tests {
.unwrap();
// A binding signature (and bundle) is present because there is a Sapling spend.
let (tx, _) = builder.mock_build().unwrap();
let (tx, _) = builder.mock_build(OsRng).unwrap();
assert!(tx.sapling_bundle().is_some());
}
@ -926,9 +1026,13 @@ mod tests {
// Fails with no inputs or outputs
// 0.0001 t-ZEC fee
{
let builder = Builder::new(TEST_NETWORK, tx_height, None);
let build_config = BuildConfig::Standard {
sapling_anchor: sapling::Anchor::empty_tree(),
orchard_anchor: orchard::Anchor::empty_tree(),
};
let builder = Builder::new(TEST_NETWORK, tx_height, build_config);
assert_matches!(
builder.mock_build(),
builder.mock_build(OsRng),
Err(Error::InsufficientFunds(expected)) if expected == MINIMUM_FEE.into()
);
}
@ -940,9 +1044,13 @@ mod tests {
// Fail if there is only a Sapling output
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
{
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
let build_config = BuildConfig::Standard {
sapling_anchor: sapling::Anchor::empty_tree(),
orchard_anchor: orchard::Anchor::empty_tree(),
};
let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config);
builder
.add_sapling_output(
.add_sapling_output::<Infallible>(
ovk,
to,
NonNegativeAmount::const_from_u64(50000),
@ -950,7 +1058,7 @@ mod tests {
)
.unwrap();
assert_matches!(
builder.mock_build(),
builder.mock_build(OsRng),
Err(Error::InsufficientFunds(expected)) if
expected == (NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
);
@ -959,7 +1067,11 @@ mod tests {
// Fail if there is only a transparent output
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
{
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
let build_config = BuildConfig::Standard {
sapling_anchor: sapling::Anchor::empty_tree(),
orchard_anchor: orchard::Anchor::empty_tree(),
};
let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config);
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
@ -967,7 +1079,7 @@ mod tests {
)
.unwrap();
assert_matches!(
builder.mock_build(),
builder.mock_build(OsRng),
Err(Error::InsufficientFunds(expected)) if expected ==
(NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
);
@ -985,12 +1097,16 @@ mod tests {
// Fail if there is insufficient input
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in
{
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
let build_config = BuildConfig::Standard {
sapling_anchor: witness1.root().into(),
orchard_anchor: orchard::Anchor::empty_tree(),
};
let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config);
builder
.add_sapling_spend(extsk.clone(), note1.clone(), witness1.path().unwrap())
.add_sapling_spend::<Infallible>(&extsk, note1.clone(), witness1.path().unwrap())
.unwrap();
builder
.add_sapling_output(
.add_sapling_output::<Infallible>(
ovk,
to,
NonNegativeAmount::const_from_u64(30000),
@ -1004,7 +1120,7 @@ mod tests {
)
.unwrap();
assert_matches!(
builder.mock_build(),
builder.mock_build(OsRng),
Err(Error::InsufficientFunds(expected)) if expected == Amount::const_from_i64(1)
);
}
@ -1021,15 +1137,19 @@ mod tests {
// Succeeds if there is sufficient input
// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in
{
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
let build_config = BuildConfig::Standard {
sapling_anchor: witness1.root().into(),
orchard_anchor: orchard::Anchor::empty_tree(),
};
let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config);
builder
.add_sapling_spend(extsk.clone(), note1, witness1.path().unwrap())
.add_sapling_spend::<Infallible>(&extsk, note1, witness1.path().unwrap())
.unwrap();
builder
.add_sapling_spend(extsk, note2, witness2.path().unwrap())
.add_sapling_spend::<Infallible>(&extsk, note2, witness2.path().unwrap())
.unwrap();
builder
.add_sapling_output(
.add_sapling_output::<Infallible>(
ovk,
to,
NonNegativeAmount::const_from_u64(30000),
@ -1043,7 +1163,7 @@ mod tests {
)
.unwrap();
assert_matches!(
builder.mock_build(),
builder.mock_build(OsRng),
Ok((tx, _)) if tx.fee_paid(|_| Err(BalanceError::Overflow)).unwrap() == Amount::const_from_i64(10_000)
);
}

View File

@ -9,8 +9,8 @@ use zcash_note_encryption::{EphemeralKeyBytes, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTE
use crate::{
sapling::{
bundle::{
Authorized, Bundle, GrothProofBytes, OutputDescription, OutputDescriptionV5,
SpendDescription, SpendDescriptionV5,
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
OutputDescriptionV5, SpendDescription, SpendDescriptionV5,
},
note::ExtractedNoteCommitment,
value::ValueCommitment,
@ -21,6 +21,51 @@ use crate::{
use super::{Amount, GROTH_PROOF_SIZE};
/// A map from one bundle authorization to another.
///
/// For use with [`TransactionData::map_authorization`].
///
/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
pub trait MapAuth<A: Authorization, B: Authorization> {
fn map_spend_proof(&mut self, p: A::SpendProof) -> B::SpendProof;
fn map_output_proof(&mut self, p: A::OutputProof) -> B::OutputProof;
fn map_auth_sig(&mut self, s: A::AuthSig) -> B::AuthSig;
fn map_authorization(&mut self, a: A) -> B;
}
/// The identity map.
///
/// This can be used with [`TransactionData::map_authorization`] when you want to map the
/// authorization of a subset of a transaction's bundles.
///
/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
impl MapAuth<Authorized, Authorized> for () {
fn map_spend_proof(
&mut self,
p: <Authorized as Authorization>::SpendProof,
) -> <Authorized as Authorization>::SpendProof {
p
}
fn map_output_proof(
&mut self,
p: <Authorized as Authorization>::OutputProof,
) -> <Authorized as Authorization>::OutputProof {
p
}
fn map_auth_sig(
&mut self,
s: <Authorized as Authorization>::AuthSig,
) -> <Authorized as Authorization>::AuthSig {
s
}
fn map_authorization(&mut self, a: Authorized) -> Authorized {
a
}
}
/// Consensus rules (§4.4) & (§4.5):
/// - Canonical encoding is enforced here.
/// - "Not small order" is enforced here.

View File

@ -488,7 +488,7 @@ impl<A: Authorization> TransactionData<A> {
pub fn map_authorization<B: Authorization>(
self,
f_transparent: impl transparent::MapAuth<A::TransparentAuth, B::TransparentAuth>,
f_sapling: impl sapling::bundle::MapAuth<A::SaplingAuth, B::SaplingAuth>,
mut f_sapling: impl sapling_serialization::MapAuth<A::SaplingAuth, B::SaplingAuth>,
mut f_orchard: impl orchard_serialization::MapAuth<A::OrchardAuth, B::OrchardAuth>,
#[cfg(feature = "zfuture")] f_tze: impl tze::MapAuth<A::TzeAuth, B::TzeAuth>,
) -> TransactionData<B> {
@ -501,7 +501,15 @@ impl<A: Authorization> TransactionData<A> {
.transparent_bundle
.map(|b| b.map_authorization(f_transparent)),
sprout_bundle: self.sprout_bundle,
sapling_bundle: self.sapling_bundle.map(|b| b.map_authorization(f_sapling)),
sapling_bundle: self.sapling_bundle.map(|b| {
b.map_authorization(
&mut f_sapling,
|f, p| f.map_spend_proof(p),
|f, p| f.map_output_proof(p),
|f, s| f.map_auth_sig(s),
|f, a| f.map_authorization(a),
)
}),
orchard_bundle: self.orchard_bundle.map(|b| {
b.map_authorization(
&mut f_orchard,