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:
commit
6c5bdf85ce
|
@ -1461,8 +1461,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orchard"
|
name = "orchard"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/zcash/orchard.git?rev=189257391a5726d7f3f2568d547a5ef8603c7156#189257391a5726d7f3f2568d547a5ef8603c7156"
|
||||||
checksum = "5d31e68534df32024dcc89a8390ec6d7bef65edd87d91b45cfb481a2eb2d77c5"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
@ -2088,7 +2087,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sapling-crypto"
|
name = "sapling-crypto"
|
||||||
version = "0.0.1"
|
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 = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bellman",
|
"bellman",
|
||||||
|
@ -3076,6 +3075,7 @@ dependencies = [
|
||||||
"blake2b_simd",
|
"blake2b_simd",
|
||||||
"ff",
|
"ff",
|
||||||
"jubjub",
|
"jubjub",
|
||||||
|
"orchard",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"sapling-crypto",
|
"sapling-crypto",
|
||||||
"zcash_address",
|
"zcash_address",
|
||||||
|
|
|
@ -112,4 +112,5 @@ panic = 'abort'
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[patch.crates-io]
|
[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" }
|
||||||
|
|
|
@ -32,6 +32,9 @@ zcash_primitives.workspace = true
|
||||||
time = "0.3.22"
|
time = "0.3.22"
|
||||||
nonempty.workspace = true
|
nonempty.workspace = true
|
||||||
|
|
||||||
|
# - CSPRNG
|
||||||
|
rand_core.workspace = true
|
||||||
|
|
||||||
# - Encodings
|
# - Encodings
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
bech32.workspace = true
|
bech32.workspace = true
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
use rand_core::OsRng;
|
||||||
use sapling::{
|
use sapling::{
|
||||||
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
|
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
|
||||||
prover::{OutputProver, SpendProver},
|
prover::{OutputProver, SpendProver},
|
||||||
|
@ -8,7 +9,7 @@ use zcash_primitives::{
|
||||||
consensus::{self, NetworkUpgrade},
|
consensus::{self, NetworkUpgrade},
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
transaction::{
|
transaction::{
|
||||||
builder::Builder,
|
builder::{BuildConfig, Builder},
|
||||||
components::amount::{Amount, NonNegativeAmount},
|
components::amount::{Amount, NonNegativeAmount},
|
||||||
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
|
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
|
||||||
Transaction, TxId,
|
Transaction, TxId,
|
||||||
|
@ -576,36 +577,59 @@ where
|
||||||
Some(dfvk.to_ovk(Scope::Internal))
|
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
|
// Create the transaction. The type of the proposal ensures that there
|
||||||
// are no possible transparent inputs, so we ignore those
|
// 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() {
|
for (key, note, merkle_path) in sapling_inputs.into_iter() {
|
||||||
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _>>(|sapling_tree| {
|
builder.add_sapling_spend(&key, note.clone(), merkle_path)?;
|
||||||
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(())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -720,7 +744,7 @@ where
|
||||||
|
|
||||||
// Build the transaction with the specified fee rule
|
// Build the transaction with the specified fee rule
|
||||||
let (tx, sapling_build_meta) =
|
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 internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
|
||||||
let sapling_outputs =
|
let sapling_outputs =
|
||||||
|
|
|
@ -12,7 +12,7 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod common;
|
pub(crate) mod common;
|
||||||
pub mod fixed;
|
pub mod fixed;
|
||||||
pub mod sapling;
|
pub mod sapling;
|
||||||
pub mod standard;
|
pub mod standard;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use sapling::builder::{SaplingOutputInfo, SpendDescriptionInfo};
|
use sapling::builder::{OutputInfo, SpendInfo};
|
||||||
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||||
|
|
||||||
/// A trait that provides a minimized view of a Sapling input suitable for use in
|
/// 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
|
// `SpendDescriptionInfo` does not contain a note identifier, so we can only implement
|
||||||
// `InputView<()>`
|
// `InputView<()>`
|
||||||
impl InputView<()> for SpendDescriptionInfo {
|
impl InputView<()> for SpendInfo {
|
||||||
fn note_id(&self) -> &() {
|
fn note_id(&self) -> &() {
|
||||||
&()
|
&()
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ pub trait OutputView {
|
||||||
fn value(&self) -> NonNegativeAmount;
|
fn value(&self) -> NonNegativeAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputView for SaplingOutputInfo {
|
impl OutputView for OutputInfo {
|
||||||
fn value(&self) -> NonNegativeAmount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
NonNegativeAmount::try_from(self.value())
|
NonNegativeAmount::try_from(self.value())
|
||||||
.expect("Output values should be checked at construction.")
|
.expect("Output values should be checked at construction.")
|
||||||
|
|
|
@ -19,6 +19,7 @@ ff.workspace = true
|
||||||
jubjub.workspace = true
|
jubjub.workspace = true
|
||||||
rand_core.workspace = true
|
rand_core.workspace = true
|
||||||
sapling.workspace = true
|
sapling.workspace = true
|
||||||
|
orchard.workspace = true
|
||||||
zcash_address.workspace = true
|
zcash_address.workspace = true
|
||||||
zcash_proofs.workspace = true
|
zcash_proofs.workspace = true
|
||||||
|
|
||||||
|
|
|
@ -476,6 +476,8 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<B> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use blake2b_simd::Params;
|
use blake2b_simd::Params;
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
@ -487,7 +489,7 @@ mod tests {
|
||||||
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
|
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
transaction::{
|
transaction::{
|
||||||
builder::Builder,
|
builder::{BuildConfig, Builder},
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, NonNegativeAmount},
|
amount::{Amount, NonNegativeAmount},
|
||||||
tze::{Authorized, Bundle, OutPoint, TzeIn, TzeOut},
|
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 {
|
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,
|
extension_id: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -823,9 +835,9 @@ mod tests {
|
||||||
tree.append(cm1).unwrap();
|
tree.append(cm1).unwrap();
|
||||||
let witness1 = sapling::IncrementalWitness::from_tree(tree);
|
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
|
builder_a
|
||||||
.add_sapling_spend(extsk, note1, witness1.path().unwrap())
|
.add_sapling_spend::<Infallible>(&extsk, note1, witness1.path().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let value = NonNegativeAmount::const_from_u64(100000);
|
let value = NonNegativeAmount::const_from_u64(100000);
|
||||||
|
@ -836,7 +848,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx_a, _) = builder_a
|
let (tx_a, _) = builder_a
|
||||||
.txn_builder
|
.txn_builder
|
||||||
.build_zfuture(&prover, &prover, &fee_rule)
|
.build_zfuture(OsRng, &prover, &prover, &fee_rule)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_a = tx_a.tze_bundle().unwrap();
|
let tze_a = tx_a.tze_bundle().unwrap();
|
||||||
|
@ -845,7 +857,7 @@ mod tests {
|
||||||
// Transfer
|
// 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 prevout_a = (OutPoint::new(tx_a.txid(), 0), tze_a.vout[0].clone());
|
||||||
let value_xfr = (value - fee_rule.fixed_fee()).unwrap();
|
let value_xfr = (value - fee_rule.fixed_fee()).unwrap();
|
||||||
builder_b
|
builder_b
|
||||||
|
@ -854,7 +866,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx_b, _) = builder_b
|
let (tx_b, _) = builder_b
|
||||||
.txn_builder
|
.txn_builder
|
||||||
.build_zfuture(&prover, &prover, &fee_rule)
|
.build_zfuture(OsRng, &prover, &prover, &fee_rule)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_b = tx_b.tze_bundle().unwrap();
|
let tze_b = tx_b.tze_bundle().unwrap();
|
||||||
|
@ -863,7 +875,7 @@ mod tests {
|
||||||
// Closing transaction
|
// 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());
|
let prevout_b = (OutPoint::new(tx_a.txid(), 0), tze_b.vout[0].clone());
|
||||||
builder_c
|
builder_c
|
||||||
.demo_close(prevout_b, preimage_2)
|
.demo_close(prevout_b, preimage_2)
|
||||||
|
@ -879,7 +891,7 @@ mod tests {
|
||||||
|
|
||||||
let (tx_c, _) = builder_c
|
let (tx_c, _) = builder_c
|
||||||
.txn_builder
|
.txn_builder
|
||||||
.build_zfuture(&prover, &prover, &fee_rule)
|
.build_zfuture(OsRng, &prover, &prover, &fee_rule)
|
||||||
.map_err(|e| format!("build failure: {:?}", e))
|
.map_err(|e| format!("build failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tze_c = tx_c.tze_bundle().unwrap();
|
let tze_c = tx_c.tze_bundle().unwrap();
|
||||||
|
|
|
@ -10,7 +10,8 @@ and this library adheres to Rust's notion of
|
||||||
- Dependency on `bellman 0.14`.
|
- Dependency on `bellman 0.14`.
|
||||||
- `zcash_primitives::consensus::sapling_zip212_enforcement`
|
- `zcash_primitives::consensus::sapling_zip212_enforcement`
|
||||||
- `zcash_primitives::transaction`:
|
- `zcash_primitives::transaction`:
|
||||||
- `builder::get_fee`
|
- `builder::{BuildConfig, FeeError, get_fee}`
|
||||||
|
- `builder::Error::SaplingBuilderNotAvailable`
|
||||||
- `components::sapling`:
|
- `components::sapling`:
|
||||||
- Sapling bundle component parsers, behind the `temporary-zcashd` feature
|
- Sapling bundle component parsers, behind the `temporary-zcashd` feature
|
||||||
flag:
|
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
|
- `builder::Builder` now has a generic parameter for the type of progress
|
||||||
notifier, which needs to implement `sapling::builder::ProverProgress` in
|
notifier, which needs to implement `sapling::builder::ProverProgress` in
|
||||||
order to build transactions.
|
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
|
- `builder::Builder::{build, build_zfuture}` now take
|
||||||
`&impl SpendProver, &impl OutputProver` instead of `&impl TxProver`.
|
`&impl SpendProver, &impl OutputProver` instead of `&impl TxProver`.
|
||||||
- `builder::Builder::add_sapling_spend` no longer takes a `diversifier`
|
- `builder::Builder::add_sapling_spend` no longer takes a `diversifier`
|
||||||
argument as the diversifier may be obtained from the note.
|
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::Builder::with_progress_notifier` now consumes `self` and returns a
|
||||||
`Builder` typed on the provided channel.
|
`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`
|
- `components::transparent::TxOut.value` now has type `NonNegativeAmount`
|
||||||
instead of `Amount`.
|
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`
|
`zcash_primitives::transaction::fees::transparent`
|
||||||
- `components::transparent::builder::TransparentBuilder::{inputs, outputs}`
|
- `components::transparent::builder::TransparentBuilder::{inputs, outputs}`
|
||||||
have changed to return `&[TransparentInputInfo]` and `&[TxOut]` respectively,
|
have changed to return `&[TransparentInputInfo]` and `&[TxOut]` respectively,
|
||||||
|
@ -102,7 +120,7 @@ and this library adheres to Rust's notion of
|
||||||
- `Bundle`
|
- `Bundle`
|
||||||
- `SpendDescription, SpendDescriptionV5`
|
- `SpendDescription, SpendDescriptionV5`
|
||||||
- `OutputDescription, OutputDescriptionV5`
|
- `OutputDescription, OutputDescriptionV5`
|
||||||
- `Authorization, Authorized, MapAuth`
|
- `Authorization, Authorized`
|
||||||
- `GrothProofBytes`
|
- `GrothProofBytes`
|
||||||
- `CompactOutputDescription` (moved to `sapling_crypto::note_encryption`).
|
- `CompactOutputDescription` (moved to `sapling_crypto::note_encryption`).
|
||||||
- `Unproven`
|
- `Unproven`
|
||||||
|
|
|
@ -91,6 +91,7 @@ incrementalmerkletree = { workspace = true, features = ["legacy-api", "test-depe
|
||||||
proptest.workspace = true
|
proptest.workspace = true
|
||||||
assert_matches.workspace = true
|
assert_matches.workspace = true
|
||||||
rand_xorshift.workspace = true
|
rand_xorshift.workspace = true
|
||||||
|
sapling = { workspace = true, features = ["test-dependencies"] }
|
||||||
orchard = { workspace = true, features = ["test-dependencies"] }
|
orchard = { workspace = true, features = ["test-dependencies"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Through
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use sapling::{
|
use sapling::{
|
||||||
builder::SaplingBuilder,
|
self,
|
||||||
note_encryption::{
|
note_encryption::{
|
||||||
try_sapling_compact_note_decryption, try_sapling_note_decryption, CompactOutputDescription,
|
try_sapling_compact_note_decryption, try_sapling_note_decryption, CompactOutputDescription,
|
||||||
PreparedIncomingViewingKey, SaplingDomain,
|
PreparedIncomingViewingKey, SaplingDomain,
|
||||||
|
@ -35,9 +35,15 @@ fn bench_note_decryption(c: &mut Criterion) {
|
||||||
let diversifier = Diversifier([0; 11]);
|
let diversifier = Diversifier([0; 11]);
|
||||||
let pa = valid_ivk.to_payment_address(diversifier).unwrap();
|
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
|
builder
|
||||||
.add_output(&mut rng, None, pa, NoteValue::from_raw(100), None)
|
.add_output(None, pa, NoteValue::from_raw(100), None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (bundle, _) = builder
|
let (bundle, _) = builder
|
||||||
.build::<MockSpendProver, MockOutputProver, _, Amount>(&mut rng)
|
.build::<MockSpendProver, MockOutputProver, _, Amount>(&mut rng)
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus::{self, BlockHeight, BranchId, NetworkUpgrade},
|
consensus::{self, BlockHeight, BranchId, NetworkUpgrade},
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{
|
sapling::{
|
||||||
self,
|
self,
|
||||||
builder::{self as sapling_builder, SaplingBuilder, SaplingMetadata},
|
builder::SaplingMetadata,
|
||||||
prover::{OutputProver, SpendProver},
|
prover::{OutputProver, SpendProver},
|
||||||
Note, PaymentAddress,
|
Note, PaymentAddress,
|
||||||
},
|
},
|
||||||
|
@ -53,9 +53,25 @@ use super::components::amount::NonNegativeAmount;
|
||||||
/// <https://zips.z.cash/zip-0203#changes-for-blossom>
|
/// <https://zips.z.cash/zip-0203#changes-for-blossom>
|
||||||
const DEFAULT_TX_EXPIRY_DELTA: u32 = 40;
|
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.
|
/// Errors that can occur during transaction construction.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<FeeError> {
|
pub enum Error<FE> {
|
||||||
/// Insufficient funds were provided to the transaction builder; the given
|
/// Insufficient funds were provided to the transaction builder; the given
|
||||||
/// additional amount is required in order to construct the transaction.
|
/// additional amount is required in order to construct the transaction.
|
||||||
InsufficientFunds(Amount),
|
InsufficientFunds(Amount),
|
||||||
|
@ -63,22 +79,25 @@ pub enum Error<FeeError> {
|
||||||
/// add a change output.
|
/// add a change output.
|
||||||
ChangeRequired(Amount),
|
ChangeRequired(Amount),
|
||||||
/// An error occurred in computing the fees for a transaction.
|
/// An error occurred in computing the fees for a transaction.
|
||||||
Fee(FeeError),
|
Fee(FeeError<FE>),
|
||||||
/// An overflow or underflow occurred when computing value balances
|
/// An overflow or underflow occurred when computing value balances
|
||||||
Balance(BalanceError),
|
Balance(BalanceError),
|
||||||
/// An error occurred in constructing the transparent parts of a transaction.
|
/// An error occurred in constructing the transparent parts of a transaction.
|
||||||
TransparentBuild(transparent::builder::Error),
|
TransparentBuild(transparent::builder::Error),
|
||||||
/// An error occurred in constructing the Sapling parts of a transaction.
|
/// 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.
|
/// An error occurred in constructing the Orchard parts of a transaction.
|
||||||
OrchardBuild(orchard::builder::BuildError),
|
OrchardBuild(orchard::builder::BuildError),
|
||||||
/// An error occurred in adding an Orchard Spend to a transaction.
|
/// An error occurred in adding an Orchard Spend to a transaction.
|
||||||
OrchardSpend(orchard::builder::SpendError),
|
OrchardSpend(orchard::builder::SpendError),
|
||||||
/// An error occurred in adding an Orchard Output to a transaction.
|
/// An error occurred in adding an Orchard Output to a transaction.
|
||||||
OrchardRecipient(orchard::builder::OutputError),
|
OrchardRecipient(orchard::builder::OutputError),
|
||||||
/// The builder was constructed either without an Orchard anchor or before NU5
|
/// The builder was constructed without support for the Sapling pool, but a Sapling
|
||||||
/// activation, but an Orchard spend or recipient was added.
|
/// spend or output was added.
|
||||||
OrchardAnchorNotAvailable,
|
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.
|
/// An error occurred in constructing the TZE parts of a transaction.
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
TzeBuild(tze::builder::Error),
|
TzeBuild(tze::builder::Error),
|
||||||
|
@ -104,7 +123,11 @@ impl<FE: fmt::Display> fmt::Display for Error<FE> {
|
||||||
Error::OrchardBuild(err) => write!(f, "{:?}", err),
|
Error::OrchardBuild(err) => write!(f, "{:?}", err),
|
||||||
Error::OrchardSpend(err) => write!(f, "Could not add Orchard spend: {}", err),
|
Error::OrchardSpend(err) => write!(f, "Could not add Orchard spend: {}", err),
|
||||||
Error::OrchardRecipient(err) => write!(f, "Could not add Orchard recipient: {}", 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,
|
f,
|
||||||
"Cannot create Orchard transactions without an Orchard anchor, or before NU5 activation"
|
"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.
|
/// Reports on the progress made by the builder towards building a transaction.
|
||||||
pub struct Progress {
|
pub struct Progress {
|
||||||
/// The number of steps completed.
|
/// 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.
|
/// 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,
|
params: P,
|
||||||
rng: R,
|
build_config: BuildConfig,
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
expiry_height: BlockHeight,
|
expiry_height: BlockHeight,
|
||||||
transparent_builder: TransparentBuilder,
|
transparent_builder: TransparentBuilder,
|
||||||
sapling_builder: SaplingBuilder,
|
sapling_builder: Option<sapling::builder::Builder>,
|
||||||
orchard_builder: Option<orchard::builder::Builder>,
|
orchard_builder: Option<orchard::builder::Builder>,
|
||||||
// TODO: In the future, instead of taking the spending keys as arguments when calling
|
// 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
|
// `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,
|
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.
|
/// Returns the network parameters that the builder has been configured for.
|
||||||
pub fn params(&self) -> &P {
|
pub fn params(&self) -> &P {
|
||||||
&self.params
|
&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
|
/// Returns the set of Sapling inputs currently committed to be consumed
|
||||||
/// by the transaction.
|
/// by the transaction.
|
||||||
pub fn sapling_inputs(&self) -> &[sapling::builder::SpendDescriptionInfo] {
|
pub fn sapling_inputs(&self) -> &[sapling::builder::SpendInfo] {
|
||||||
self.sapling_builder.inputs()
|
self.sapling_builder
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| &[][..], |b| b.inputs())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the set of Sapling outputs currently set to be produced by
|
/// Returns the set of Sapling outputs currently set to be produced by
|
||||||
/// the transaction.
|
/// the transaction.
|
||||||
pub fn sapling_outputs(&self) -> &[sapling::builder::SaplingOutputInfo] {
|
pub fn sapling_outputs(&self) -> &[sapling::builder::OutputInfo] {
|
||||||
self.sapling_builder.outputs()
|
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,
|
/// 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
|
/// # Default values
|
||||||
///
|
///
|
||||||
/// The expiry height will be set to the given height plus the default transaction
|
/// The expiry height will be set to the given height plus the default transaction
|
||||||
/// expiry delta (20 blocks).
|
/// expiry delta (20 blocks).
|
||||||
pub fn new(
|
pub fn new(params: P, target_height: BlockHeight, build_config: BuildConfig) -> Self {
|
||||||
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 {
|
|
||||||
let orchard_builder = if params.is_nu_active(NetworkUpgrade::Nu5, target_height) {
|
let orchard_builder = if params.is_nu_active(NetworkUpgrade::Nu5, target_height) {
|
||||||
orchard_anchor.map(|anchor| {
|
let (bundle_type, anchor) = build_config.orchard_builder_config();
|
||||||
orchard::builder::Builder::new(
|
Some(orchard::builder::Builder::new(bundle_type, anchor))
|
||||||
orchard::bundle::Flags::from_parts(true, true),
|
|
||||||
anchor,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::new_internal(params, rng, target_height, orchard_builder)
|
let sapling_builder = Some({
|
||||||
}
|
let (bundle_type, anchor) = build_config.sapling_builder_config();
|
||||||
|
sapling::builder::Builder::new(
|
||||||
/// Common utility function for builder construction.
|
consensus::sapling_zip212_enforcement(¶ms, target_height),
|
||||||
fn new_internal(
|
bundle_type,
|
||||||
params: P,
|
anchor,
|
||||||
rng: R,
|
)
|
||||||
target_height: BlockHeight,
|
});
|
||||||
orchard_builder: Option<orchard::builder::Builder>,
|
|
||||||
) -> Self {
|
|
||||||
let zip212_enforcement = consensus::sapling_zip212_enforcement(¶ms, target_height);
|
|
||||||
|
|
||||||
Builder {
|
Builder {
|
||||||
params,
|
params,
|
||||||
rng,
|
build_config,
|
||||||
target_height,
|
target_height,
|
||||||
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
|
expiry_height: target_height + DEFAULT_TX_EXPIRY_DELTA,
|
||||||
transparent_builder: TransparentBuilder::empty(),
|
transparent_builder: TransparentBuilder::empty(),
|
||||||
sapling_builder: SaplingBuilder::new(zip212_enforcement),
|
sapling_builder,
|
||||||
orchard_builder,
|
orchard_builder,
|
||||||
sapling_asks: vec![],
|
sapling_asks: vec![],
|
||||||
orchard_saks: Vec::new(),
|
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(
|
pub fn with_progress_notifier(
|
||||||
self,
|
self,
|
||||||
progress_notifier: Sender<Progress>,
|
progress_notifier: Sender<Progress>,
|
||||||
) -> Builder<'a, P, R, Sender<Progress>> {
|
) -> Builder<'a, P, Sender<Progress>> {
|
||||||
Builder {
|
Builder {
|
||||||
params: self.params,
|
params: self.params,
|
||||||
rng: self.rng,
|
build_config: self.build_config,
|
||||||
target_height: self.target_height,
|
target_height: self.target_height,
|
||||||
expiry_height: self.expiry_height,
|
expiry_height: self.expiry_height,
|
||||||
transparent_builder: self.transparent_builder,
|
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>
|
impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<'a, P, U> {
|
||||||
Builder<'a, P, R, U>
|
|
||||||
{
|
|
||||||
/// Adds an Orchard note to be spent in this bundle.
|
/// 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
|
/// Returns an error if the given Merkle path does not have the required anchor for
|
||||||
/// the given note.
|
/// the given note.
|
||||||
pub fn add_orchard_spend<FeeError>(
|
pub fn add_orchard_spend<FE>(
|
||||||
&mut self,
|
&mut self,
|
||||||
sk: orchard::keys::SpendingKey,
|
sk: &orchard::keys::SpendingKey,
|
||||||
note: orchard::Note,
|
note: orchard::Note,
|
||||||
merkle_path: orchard::tree::MerklePath,
|
merkle_path: orchard::tree::MerklePath,
|
||||||
) -> Result<(), Error<FeeError>> {
|
) -> Result<(), Error<FE>> {
|
||||||
self.orchard_builder
|
if let Some(builder) = self.orchard_builder.as_mut() {
|
||||||
.as_mut()
|
builder.add_spend(orchard::keys::FullViewingKey::from(sk), note, merkle_path)?;
|
||||||
.ok_or(Error::OrchardAnchorNotAvailable)?
|
|
||||||
.add_spend(orchard::keys::FullViewingKey::from(&sk), note, merkle_path)
|
|
||||||
.map_err(Error::OrchardSpend)?;
|
|
||||||
|
|
||||||
self.orchard_saks
|
self.orchard_saks
|
||||||
.push(orchard::keys::SpendAuthorizingKey::from(&sk));
|
.push(orchard::keys::SpendAuthorizingKey::from(sk));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::OrchardBuilderNotAvailable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an Orchard recipient to the transaction.
|
/// Adds an Orchard recipient to the transaction.
|
||||||
pub fn add_orchard_output<FeeError>(
|
pub fn add_orchard_output<FE>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ovk: Option<orchard::keys::OutgoingViewingKey>,
|
ovk: Option<orchard::keys::OutgoingViewingKey>,
|
||||||
recipient: orchard::Address,
|
recipient: orchard::Address,
|
||||||
value: u64,
|
value: u64,
|
||||||
memo: MemoBytes,
|
memo: MemoBytes,
|
||||||
) -> Result<(), Error<FeeError>> {
|
) -> Result<(), Error<FE>> {
|
||||||
self.orchard_builder
|
self.orchard_builder
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or(Error::OrchardAnchorNotAvailable)?
|
.ok_or(Error::OrchardBuilderNotAvailable)?
|
||||||
.add_recipient(
|
.add_output(
|
||||||
ovk,
|
ovk,
|
||||||
recipient,
|
recipient,
|
||||||
orchard::value::NoteValue::from_raw(value),
|
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
|
/// Returns an error if the given Merkle path does not have the same anchor as the
|
||||||
/// paths for previous Sapling notes.
|
/// paths for previous Sapling notes.
|
||||||
pub fn add_sapling_spend(
|
pub fn add_sapling_spend<FE>(
|
||||||
&mut self,
|
&mut self,
|
||||||
extsk: sapling::zip32::ExtendedSpendingKey,
|
extsk: &sapling::zip32::ExtendedSpendingKey,
|
||||||
note: Note,
|
note: Note,
|
||||||
merkle_path: sapling::MerklePath,
|
merkle_path: sapling::MerklePath,
|
||||||
) -> Result<(), sapling_builder::Error> {
|
) -> Result<(), Error<FE>> {
|
||||||
self.sapling_builder
|
if let Some(builder) = self.sapling_builder.as_mut() {
|
||||||
.add_spend(&mut self.rng, &extsk, note, merkle_path)?;
|
builder.add_spend(extsk, note, merkle_path)?;
|
||||||
|
|
||||||
self.sapling_asks.push(extsk.expsk.ask);
|
self.sapling_asks.push(extsk.expsk.ask.clone());
|
||||||
|
Ok(())
|
||||||
Ok(())
|
} else {
|
||||||
|
Err(Error::SaplingBuilderNotAvailable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a Sapling address to send funds to.
|
/// Adds a Sapling address to send funds to.
|
||||||
pub fn add_sapling_output(
|
pub fn add_sapling_output<FE>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ovk: Option<sapling::keys::OutgoingViewingKey>,
|
ovk: Option<sapling::keys::OutgoingViewingKey>,
|
||||||
to: PaymentAddress,
|
to: PaymentAddress,
|
||||||
value: NonNegativeAmount,
|
value: NonNegativeAmount,
|
||||||
memo: MemoBytes,
|
memo: MemoBytes,
|
||||||
) -> Result<(), sapling_builder::Error> {
|
) -> Result<(), Error<FE>> {
|
||||||
self.sapling_builder.add_output(
|
self.sapling_builder
|
||||||
&mut self.rng,
|
.as_mut()
|
||||||
ovk,
|
.ok_or(Error::SaplingBuilderNotAvailable)?
|
||||||
to,
|
.add_output(
|
||||||
sapling::value::NoteValue::from_raw(value.into()),
|
ovk,
|
||||||
Some(*memo.as_array()),
|
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.
|
/// 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> {
|
fn value_balance(&self) -> Result<Amount, BalanceError> {
|
||||||
let value_balances = [
|
let value_balances = [
|
||||||
self.transparent_builder.value_balance()?,
|
self.transparent_builder.value_balance()?,
|
||||||
self.sapling_builder.value_balance(),
|
self.sapling_builder
|
||||||
if let Some(builder) = &self.orchard_builder {
|
.as_ref()
|
||||||
builder
|
.map_or_else(Amount::zero, |builder| builder.value_balance::<Amount>()),
|
||||||
.value_balance()
|
self.orchard_builder.as_ref().map_or_else(
|
||||||
.map_err(|_| BalanceError::Overflow)?
|
|| Ok(Amount::zero()),
|
||||||
} else {
|
|builder| {
|
||||||
Amount::zero()
|
builder
|
||||||
},
|
.value_balance::<Amount>()
|
||||||
|
.map_err(|_| BalanceError::Overflow)
|
||||||
|
},
|
||||||
|
)?,
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
self.tze_builder.value_balance()?,
|
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,
|
/// This fee is a function of the spends and outputs that have been added to the builder,
|
||||||
/// pursuant to the specified [`FeeRule`].
|
/// 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")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
let transparent_inputs = self.transparent_builder.inputs();
|
let transparent_inputs = self.transparent_builder.inputs();
|
||||||
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
let transparent_inputs: &[Infallible] = &[];
|
let transparent_inputs: &[Infallible] = &[];
|
||||||
|
|
||||||
fee_rule.fee_required(
|
let sapling_spends = self
|
||||||
&self.params,
|
.sapling_builder
|
||||||
self.target_height,
|
.as_ref()
|
||||||
transparent_inputs,
|
.map_or(0, |builder| builder.inputs().len());
|
||||||
self.transparent_builder.outputs(),
|
|
||||||
self.sapling_builder.inputs().len(),
|
fee_rule
|
||||||
self.sapling_builder.bundle_output_count(),
|
.fee_required(
|
||||||
match std::cmp::max(
|
&self.params,
|
||||||
self.orchard_builder
|
self.target_height,
|
||||||
.as_ref()
|
transparent_inputs,
|
||||||
.map_or(0, |builder| builder.outputs().len()),
|
self.transparent_builder.outputs(),
|
||||||
self.orchard_builder
|
sapling_spends,
|
||||||
.as_ref()
|
self.sapling_builder.as_ref().map_or(Ok(0), |builder| {
|
||||||
.map_or(0, |builder| builder.spends().len()),
|
self.build_config
|
||||||
) {
|
.sapling_builder_config()
|
||||||
1 => 2,
|
.0
|
||||||
n => n,
|
.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.
|
/// Builds a transaction from the configured spends and outputs.
|
||||||
///
|
///
|
||||||
/// Upon success, returns a tuple containing the final transaction, and the
|
/// Upon success, returns a tuple containing the final transaction, and the
|
||||||
/// [`SaplingMetadata`] generated during the build process.
|
/// [`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,
|
self,
|
||||||
|
rng: R,
|
||||||
spend_prover: &SP,
|
spend_prover: &SP,
|
||||||
output_prover: &OP,
|
output_prover: &OP,
|
||||||
fee_rule: &FR,
|
fee_rule: &FR,
|
||||||
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
||||||
let fee = self.get_fee(fee_rule).map_err(Error::Fee)?;
|
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.
|
/// 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
|
/// Upon success, returns a tuple containing the final transaction, and the
|
||||||
/// [`SaplingMetadata`] generated during the build process.
|
/// [`SaplingMetadata`] generated during the build process.
|
||||||
#[cfg(feature = "zfuture")]
|
#[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,
|
self,
|
||||||
|
rng: R,
|
||||||
spend_prover: &SP,
|
spend_prover: &SP,
|
||||||
output_prover: &OP,
|
output_prover: &OP,
|
||||||
fee_rule: &FR,
|
fee_rule: &FR,
|
||||||
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
let fee = self.get_fee_zfuture(fee_rule).map_err(Error::Fee)?;
|
||||||
let transparent_inputs = self.transparent_builder.inputs();
|
self.build_internal(rng, spend_prover, output_prover, fee)
|
||||||
|
|
||||||
#[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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_internal<SP: SpendProver, OP: OutputProver, FE>(
|
fn build_internal<R: RngCore + CryptoRng, SP: SpendProver, OP: OutputProver, FE>(
|
||||||
self,
|
self,
|
||||||
|
mut rng: R,
|
||||||
spend_prover: &SP,
|
spend_prover: &SP,
|
||||||
output_prover: &OP,
|
output_prover: &OP,
|
||||||
fee: Amount,
|
fee: NonNegativeAmount,
|
||||||
) -> Result<(Transaction, SaplingMetadata), Error<FE>> {
|
) -> Result<(Transaction, SaplingMetadata), Error<FE>> {
|
||||||
let consensus_branch_id = BranchId::for_height(&self.params, self.target_height);
|
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.
|
// 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()) {
|
match balance_after_fees.cmp(&Amount::zero()) {
|
||||||
Ordering::Less => {
|
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 transparent_bundle = self.transparent_builder.build();
|
||||||
|
|
||||||
let mut rng = self.rng;
|
|
||||||
let (sapling_bundle, tx_metadata) = match self
|
let (sapling_bundle, tx_metadata) = match self
|
||||||
.sapling_builder
|
.sapling_builder
|
||||||
.build::<SP, OP, _, _>(&mut rng)
|
.and_then(|builder| {
|
||||||
.map_err(Error::SaplingBuild)?
|
builder
|
||||||
.map(|(bundle, tx_metadata)| {
|
.build::<SP, OP, _, _>(&mut rng)
|
||||||
// We need to create proofs before signatures, because we still support
|
.map_err(Error::SaplingBuild)
|
||||||
// creating V4 transactions, which commit to the Sapling proofs in the
|
.transpose()
|
||||||
// transaction digest.
|
.map(|res| {
|
||||||
(
|
res.map(|(bundle, tx_metadata)| {
|
||||||
bundle.create_proofs(
|
// We need to create proofs before signatures, because we still support
|
||||||
spend_prover,
|
// creating V4 transactions, which commit to the Sapling proofs in the
|
||||||
output_prover,
|
// transaction digest.
|
||||||
&mut rng,
|
(
|
||||||
self.progress_notifier,
|
bundle.create_proofs(
|
||||||
),
|
spend_prover,
|
||||||
tx_metadata,
|
output_prover,
|
||||||
)
|
&mut rng,
|
||||||
}) {
|
self.progress_notifier,
|
||||||
|
),
|
||||||
|
tx_metadata,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
{
|
||||||
Some((bundle, meta)) => (Some(bundle), meta),
|
Some((bundle, meta)) => (Some(bundle), meta),
|
||||||
None => (None, SaplingMetadata::empty()),
|
None => (None, SaplingMetadata::empty()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let orchard_bundle: Option<orchard::Bundle<_, Amount>> =
|
let orchard_bundle: Option<orchard::Bundle<_, Amount>> = self
|
||||||
if let Some(builder) = self.orchard_builder {
|
.orchard_builder
|
||||||
Some(builder.build(&mut rng).map_err(Error::OrchardBuild)?)
|
.and_then(|builder| {
|
||||||
} else {
|
builder
|
||||||
None
|
.build(&mut rng)
|
||||||
};
|
.map_err(Error::OrchardBuild)
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
let (tze_bundle, tze_signers) = self.tze_builder.build();
|
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")]
|
#[cfg(feature = "zfuture")]
|
||||||
impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng, U: sapling::builder::ProverProgress>
|
impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> ExtensionTxBuilder<'a>
|
||||||
ExtensionTxBuilder<'a> for Builder<'a, P, R, U>
|
for Builder<'a, P, U>
|
||||||
{
|
{
|
||||||
type BuildCtx = TransactionData<Unauthorized>;
|
type BuildCtx = TransactionData<Unauthorized>;
|
||||||
type BuildError = tze::builder::Error;
|
type BuildError = tze::builder::Error;
|
||||||
|
@ -714,7 +822,7 @@ mod testing {
|
||||||
|
|
||||||
use super::{Builder, Error, SaplingMetadata};
|
use super::{Builder, Error, SaplingMetadata};
|
||||||
use crate::{
|
use crate::{
|
||||||
consensus::{self, BlockHeight},
|
consensus,
|
||||||
sapling::{
|
sapling::{
|
||||||
self,
|
self,
|
||||||
prover::mock::{MockOutputProver, MockSpendProver},
|
prover::mock::{MockOutputProver, MockSpendProver},
|
||||||
|
@ -723,21 +831,13 @@ mod testing {
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R, ()> {
|
impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<'a, P, U> {
|
||||||
/// Creates a new `Builder` targeted for inclusion in the block with the given height
|
/// Build the transaction using mocked randomness and proving capabilities.
|
||||||
/// and randomness source, using default values for general transaction fields.
|
/// DO NOT USE EXCEPT FOR UNIT TESTING.
|
||||||
///
|
pub fn mock_build<R: RngCore>(
|
||||||
/// # Default values
|
self,
|
||||||
///
|
|
||||||
/// 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,
|
|
||||||
rng: R,
|
rng: R,
|
||||||
) -> Builder<'a, P, impl RngCore + CryptoRng, ()> {
|
) -> Result<(Transaction, SaplingMetadata), Error<Infallible>> {
|
||||||
struct FakeCryptoRng<R: RngCore>(R);
|
struct FakeCryptoRng<R: RngCore>(R);
|
||||||
impl<R: RngCore> CryptoRng for FakeCryptoRng<R> {}
|
impl<R: RngCore> CryptoRng for FakeCryptoRng<R> {}
|
||||||
impl<R: RngCore> RngCore for FakeCryptoRng<R> {
|
impl<R: RngCore> RngCore for FakeCryptoRng<R> {
|
||||||
|
@ -757,19 +857,10 @@ mod testing {
|
||||||
self.0.try_fill_bytes(dest)
|
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)]
|
#[allow(deprecated)]
|
||||||
self.build(
|
self.build(
|
||||||
|
FakeCryptoRng(rng),
|
||||||
&MockSpendProver,
|
&MockSpendProver,
|
||||||
&MockOutputProver,
|
&MockOutputProver,
|
||||||
&fixed::FeeRule::standard(),
|
&fixed::FeeRule::standard(),
|
||||||
|
@ -780,6 +871,8 @@ mod testing {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use incrementalmerkletree::{frontier::CommitmentTree, witness::IncrementalWitness};
|
use incrementalmerkletree::{frontier::CommitmentTree, witness::IncrementalWitness};
|
||||||
|
@ -790,7 +883,10 @@ mod tests {
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{self, zip32::ExtendedSpendingKey, Node, Rseed},
|
sapling::{self, zip32::ExtendedSpendingKey, Node, Rseed},
|
||||||
transaction::components::amount::{Amount, BalanceError, NonNegativeAmount},
|
transaction::{
|
||||||
|
builder::BuildConfig,
|
||||||
|
components::amount::{Amount, BalanceError, NonNegativeAmount},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Builder, Error};
|
use super::{Builder, Error};
|
||||||
|
@ -802,11 +898,7 @@ mod tests {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use crate::{
|
use crate::{
|
||||||
legacy::keys::{AccountPrivKey, IncomingViewingKey},
|
legacy::keys::{AccountPrivKey, IncomingViewingKey},
|
||||||
sapling::note_encryption::Zip212Enforcement,
|
transaction::{builder::DEFAULT_TX_EXPIRY_DELTA, OutPoint, TxOut},
|
||||||
transaction::{
|
|
||||||
builder::{SaplingBuilder, DEFAULT_TX_EXPIRY_DELTA},
|
|
||||||
OutPoint, TxOut,
|
|
||||||
},
|
|
||||||
zip32::AccountId,
|
zip32::AccountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -825,11 +917,14 @@ mod tests {
|
||||||
// Create a builder with 0 fee, so we can construct t outputs
|
// Create a builder with 0 fee, so we can construct t outputs
|
||||||
let mut builder = builder::Builder {
|
let mut builder = builder::Builder {
|
||||||
params: TEST_NETWORK,
|
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,
|
target_height: sapling_activation_height,
|
||||||
expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA,
|
expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA,
|
||||||
transparent_builder: TransparentBuilder::empty(),
|
transparent_builder: TransparentBuilder::empty(),
|
||||||
sapling_builder: SaplingBuilder::new(Zip212Enforcement::Off),
|
sapling_builder: None,
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
tze_builder: TzeBuilder::empty(),
|
tze_builder: TzeBuilder::empty(),
|
||||||
#[cfg(not(feature = "zfuture"))]
|
#[cfg(not(feature = "zfuture"))]
|
||||||
|
@ -867,7 +962,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (tx, _) = builder.mock_build().unwrap();
|
let (tx, _) = builder.mock_build(OsRng).unwrap();
|
||||||
// No binding signature, because only t input and outputs
|
// No binding signature, because only t input and outputs
|
||||||
assert!(tx.sapling_bundle.is_none());
|
assert!(tx.sapling_bundle.is_none());
|
||||||
}
|
}
|
||||||
|
@ -892,11 +987,16 @@ mod tests {
|
||||||
let tx_height = TEST_NETWORK
|
let tx_height = TEST_NETWORK
|
||||||
.activation_height(NetworkUpgrade::Sapling)
|
.activation_height(NetworkUpgrade::Sapling)
|
||||||
.unwrap();
|
.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
|
// Create a tx with a sapling spend. binding_sig should be present
|
||||||
builder
|
builder
|
||||||
.add_sapling_spend(extsk, note1, witness1.path().unwrap())
|
.add_sapling_spend::<Infallible>(&extsk, note1, witness1.path().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
builder
|
builder
|
||||||
|
@ -907,7 +1007,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// A binding signature (and bundle) is present because there is a Sapling spend.
|
// 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());
|
assert!(tx.sapling_bundle().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -926,9 +1026,13 @@ mod tests {
|
||||||
// Fails with no inputs or outputs
|
// Fails with no inputs or outputs
|
||||||
// 0.0001 t-ZEC fee
|
// 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!(
|
assert_matches!(
|
||||||
builder.mock_build(),
|
builder.mock_build(OsRng),
|
||||||
Err(Error::InsufficientFunds(expected)) if expected == MINIMUM_FEE.into()
|
Err(Error::InsufficientFunds(expected)) if expected == MINIMUM_FEE.into()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -940,9 +1044,13 @@ mod tests {
|
||||||
// Fail if there is only a Sapling output
|
// Fail if there is only a Sapling output
|
||||||
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
|
// 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
|
builder
|
||||||
.add_sapling_output(
|
.add_sapling_output::<Infallible>(
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
NonNegativeAmount::const_from_u64(50000),
|
NonNegativeAmount::const_from_u64(50000),
|
||||||
|
@ -950,7 +1058,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
builder.mock_build(),
|
builder.mock_build(OsRng),
|
||||||
Err(Error::InsufficientFunds(expected)) if
|
Err(Error::InsufficientFunds(expected)) if
|
||||||
expected == (NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
|
expected == (NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
|
||||||
);
|
);
|
||||||
|
@ -959,7 +1067,11 @@ mod tests {
|
||||||
// Fail if there is only a transparent output
|
// Fail if there is only a transparent output
|
||||||
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
|
// 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
|
builder
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
|
@ -967,7 +1079,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
builder.mock_build(),
|
builder.mock_build(OsRng),
|
||||||
Err(Error::InsufficientFunds(expected)) if expected ==
|
Err(Error::InsufficientFunds(expected)) if expected ==
|
||||||
(NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
|
(NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
|
||||||
);
|
);
|
||||||
|
@ -985,12 +1097,16 @@ mod tests {
|
||||||
// Fail if there is insufficient input
|
// 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
|
// 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
|
builder
|
||||||
.add_sapling_spend(extsk.clone(), note1.clone(), witness1.path().unwrap())
|
.add_sapling_spend::<Infallible>(&extsk, note1.clone(), witness1.path().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_sapling_output(
|
.add_sapling_output::<Infallible>(
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
NonNegativeAmount::const_from_u64(30000),
|
NonNegativeAmount::const_from_u64(30000),
|
||||||
|
@ -1004,7 +1120,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
builder.mock_build(),
|
builder.mock_build(OsRng),
|
||||||
Err(Error::InsufficientFunds(expected)) if expected == Amount::const_from_i64(1)
|
Err(Error::InsufficientFunds(expected)) if expected == Amount::const_from_i64(1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1021,15 +1137,19 @@ mod tests {
|
||||||
// Succeeds if there is sufficient input
|
// 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
|
// 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
|
builder
|
||||||
.add_sapling_spend(extsk.clone(), note1, witness1.path().unwrap())
|
.add_sapling_spend::<Infallible>(&extsk, note1, witness1.path().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_sapling_spend(extsk, note2, witness2.path().unwrap())
|
.add_sapling_spend::<Infallible>(&extsk, note2, witness2.path().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_sapling_output(
|
.add_sapling_output::<Infallible>(
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
NonNegativeAmount::const_from_u64(30000),
|
NonNegativeAmount::const_from_u64(30000),
|
||||||
|
@ -1043,7 +1163,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
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)
|
Ok((tx, _)) if tx.fee_paid(|_| Err(BalanceError::Overflow)).unwrap() == Amount::const_from_i64(10_000)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ use zcash_note_encryption::{EphemeralKeyBytes, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTE
|
||||||
use crate::{
|
use crate::{
|
||||||
sapling::{
|
sapling::{
|
||||||
bundle::{
|
bundle::{
|
||||||
Authorized, Bundle, GrothProofBytes, OutputDescription, OutputDescriptionV5,
|
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
|
||||||
SpendDescription, SpendDescriptionV5,
|
OutputDescriptionV5, SpendDescription, SpendDescriptionV5,
|
||||||
},
|
},
|
||||||
note::ExtractedNoteCommitment,
|
note::ExtractedNoteCommitment,
|
||||||
value::ValueCommitment,
|
value::ValueCommitment,
|
||||||
|
@ -21,6 +21,51 @@ use crate::{
|
||||||
|
|
||||||
use super::{Amount, GROTH_PROOF_SIZE};
|
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):
|
/// Consensus rules (§4.4) & (§4.5):
|
||||||
/// - Canonical encoding is enforced here.
|
/// - Canonical encoding is enforced here.
|
||||||
/// - "Not small order" is enforced here.
|
/// - "Not small order" is enforced here.
|
||||||
|
|
|
@ -488,7 +488,7 @@ impl<A: Authorization> TransactionData<A> {
|
||||||
pub fn map_authorization<B: Authorization>(
|
pub fn map_authorization<B: Authorization>(
|
||||||
self,
|
self,
|
||||||
f_transparent: impl transparent::MapAuth<A::TransparentAuth, B::TransparentAuth>,
|
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>,
|
mut f_orchard: impl orchard_serialization::MapAuth<A::OrchardAuth, B::OrchardAuth>,
|
||||||
#[cfg(feature = "zfuture")] f_tze: impl tze::MapAuth<A::TzeAuth, B::TzeAuth>,
|
#[cfg(feature = "zfuture")] f_tze: impl tze::MapAuth<A::TzeAuth, B::TzeAuth>,
|
||||||
) -> TransactionData<B> {
|
) -> TransactionData<B> {
|
||||||
|
@ -501,7 +501,15 @@ impl<A: Authorization> TransactionData<A> {
|
||||||
.transparent_bundle
|
.transparent_bundle
|
||||||
.map(|b| b.map_authorization(f_transparent)),
|
.map(|b| b.map_authorization(f_transparent)),
|
||||||
sprout_bundle: self.sprout_bundle,
|
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| {
|
orchard_bundle: self.orchard_bundle.map(|b| {
|
||||||
b.map_authorization(
|
b.map_authorization(
|
||||||
&mut f_orchard,
|
&mut f_orchard,
|
||||||
|
|
Loading…
Reference in New Issue