Add tests for transaction builder support.
This commit is contained in:
parent
ca8e24eef5
commit
4a954c7f8f
|
@ -11,3 +11,9 @@ edition = "2018"
|
|||
[dependencies]
|
||||
blake2b_simd = "0.5"
|
||||
zcash_primitives = { version = "0.3.0", path = "../zcash_primitives" }
|
||||
|
||||
[dev-dependencies]
|
||||
ff = { version = "0.7", path = "../ff" }
|
||||
jubjub = { version = "0.4", path = "../jubjub" }
|
||||
zcash_proofs = { path = "../zcash_proofs" }
|
||||
rand_core = "0.5.1"
|
||||
|
|
|
@ -304,21 +304,28 @@ fn builder_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u
|
|||
(hash_1, hash_2)
|
||||
}
|
||||
|
||||
pub struct DemoBuilder<'a, B> {
|
||||
txn_builder: &'a mut B,
|
||||
extension_id: usize,
|
||||
pub struct DemoBuilder<B> {
|
||||
pub txn_builder: B,
|
||||
pub extension_id: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DemoBuildError<E> {
|
||||
BaseBuilderError(E),
|
||||
ExpectedOpen,
|
||||
ExpectedClose,
|
||||
PrevoutParseFailure(Error),
|
||||
TransferMismatch { expected: [u8; 32], actual: [u8; 32] },
|
||||
CloseMismatch { expected: [u8; 32], actual: [u8; 32] },
|
||||
TransferMismatch {
|
||||
expected: [u8; 32],
|
||||
actual: [u8; 32],
|
||||
},
|
||||
CloseMismatch {
|
||||
expected: [u8; 32],
|
||||
actual: [u8; 32],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
|
||||
impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> {
|
||||
pub fn demo_open(
|
||||
&mut self,
|
||||
value: Amount,
|
||||
|
@ -343,15 +350,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
|
|||
let (hash_1, hash_2) = builder_hashes(&preimage_1, &preimage_2);
|
||||
|
||||
// eagerly validate the relationship between prevout.1 and preimage_1
|
||||
match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) {
|
||||
Ok(Precondition::Open(hash)) =>
|
||||
match Precondition::from_payload(
|
||||
prevout.1.precondition.mode,
|
||||
&prevout.1.precondition.payload,
|
||||
) {
|
||||
Ok(Precondition::Open(hash)) => {
|
||||
if hash.0 != hash_1 {
|
||||
Err(DemoBuildError::TransferMismatch { expected: hash.0, actual: hash_1})?
|
||||
}
|
||||
Ok(Precondition::Close(_)) =>
|
||||
Err(DemoBuildError::ExpectedOpen)?,
|
||||
Err(parse_failure) =>
|
||||
Err(DemoBuildError::PrevoutParseFailure(parse_failure))?
|
||||
Err(DemoBuildError::TransferMismatch {
|
||||
expected: hash.0,
|
||||
actual: hash_1,
|
||||
})?
|
||||
}
|
||||
}
|
||||
Ok(Precondition::Close(_)) => Err(DemoBuildError::ExpectedOpen)?,
|
||||
Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure))?,
|
||||
}
|
||||
|
||||
self.txn_builder
|
||||
|
@ -360,12 +372,13 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
|
|||
})
|
||||
.map_err(DemoBuildError::BaseBuilderError)?;
|
||||
|
||||
self.txn_builder.add_tze_output(
|
||||
self.extension_id,
|
||||
transfer_amount, // can this be > prevout.1.value?
|
||||
&Precondition::close(hash_2),
|
||||
)
|
||||
.map_err(DemoBuildError::BaseBuilderError)
|
||||
self.txn_builder
|
||||
.add_tze_output(
|
||||
self.extension_id,
|
||||
transfer_amount, // can this be > prevout.1.value?
|
||||
&Precondition::close(hash_2),
|
||||
)
|
||||
.map_err(DemoBuildError::BaseBuilderError)
|
||||
}
|
||||
|
||||
pub fn demo_close(
|
||||
|
@ -380,15 +393,20 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
|
|||
};
|
||||
|
||||
// eagerly validate the relationship between prevout.1 and preimage_2
|
||||
match Precondition::from_payload(prevout.1.precondition.mode, &prevout.1.precondition.payload) {
|
||||
Ok(Precondition::Open(_)) =>
|
||||
Err(DemoBuildError::ExpectedClose)?,
|
||||
Ok(Precondition::Close(hash)) =>
|
||||
match Precondition::from_payload(
|
||||
prevout.1.precondition.mode,
|
||||
&prevout.1.precondition.payload,
|
||||
) {
|
||||
Ok(Precondition::Open(_)) => Err(DemoBuildError::ExpectedClose)?,
|
||||
Ok(Precondition::Close(hash)) => {
|
||||
if hash.0 != hash_2 {
|
||||
Err(DemoBuildError::CloseMismatch { expected: hash.0, actual: hash_2})?
|
||||
}
|
||||
Err(parse_failure) =>
|
||||
Err(DemoBuildError::PrevoutParseFailure(parse_failure))?
|
||||
Err(DemoBuildError::CloseMismatch {
|
||||
expected: hash.0,
|
||||
actual: hash_2,
|
||||
})?
|
||||
}
|
||||
}
|
||||
Err(parse_failure) => Err(DemoBuildError::PrevoutParseFailure(parse_failure))?,
|
||||
}
|
||||
|
||||
self.txn_builder
|
||||
|
@ -401,17 +419,33 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<'a, B> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ff::{Field, PrimeField};
|
||||
use blake2b_simd::Params;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use zcash_proofs::prover::LocalTxProver;
|
||||
|
||||
use super::{close, open, Context, Precondition, Program, Witness};
|
||||
use zcash_primitives::{
|
||||
consensus::{
|
||||
BranchId,
|
||||
TestNetwork,
|
||||
},
|
||||
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
|
||||
legacy::TransparentAddress,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
primitives::Rseed,
|
||||
sapling::Node,
|
||||
transaction::{
|
||||
builder::Builder,
|
||||
components::{Amount, OutPoint, TzeIn, TzeOut},
|
||||
Transaction, TransactionData,
|
||||
},
|
||||
zip32::ExtendedSpendingKey,
|
||||
};
|
||||
|
||||
use super::{close, open, Context, DemoBuilder, Precondition, Program, Witness};
|
||||
|
||||
|
||||
#[test]
|
||||
fn precondition_open_round_trip() {
|
||||
let data = vec![7; 32];
|
||||
|
@ -519,7 +553,7 @@ mod tests {
|
|||
precondition: tze::Precondition::from(0, &Precondition::open(hash_1)),
|
||||
};
|
||||
|
||||
let mut mtx_a = TransactionData::nu4();
|
||||
let mut mtx_a = TransactionData::future();
|
||||
mtx_a.tze_outputs.push(out_a);
|
||||
let tx_a = mtx_a.freeze().unwrap();
|
||||
|
||||
|
@ -535,7 +569,7 @@ mod tests {
|
|||
value: Amount::from_u64(1).unwrap(),
|
||||
precondition: tze::Precondition::from(0, &Precondition::close(hash_2)),
|
||||
};
|
||||
let mut mtx_b = TransactionData::nu4();
|
||||
let mut mtx_b = TransactionData::future();
|
||||
mtx_b.tze_inputs.push(in_b);
|
||||
mtx_b.tze_outputs.push(out_b);
|
||||
let tx_b = mtx_b.freeze().unwrap();
|
||||
|
@ -549,7 +583,7 @@ mod tests {
|
|||
witness: tze::Witness::from(0, &Witness::close(preimage_2)),
|
||||
};
|
||||
|
||||
let mut mtx_c = TransactionData::nu4();
|
||||
let mut mtx_c = TransactionData::future();
|
||||
mtx_c.tze_inputs.push(in_c);
|
||||
let tx_c = mtx_c.freeze().unwrap();
|
||||
|
||||
|
@ -579,4 +613,118 @@ mod tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demo_builder_program() {
|
||||
let preimage_1 = [1; 32];
|
||||
let preimage_2 = [2; 32];
|
||||
|
||||
let prover = LocalTxProver::with_default_location().unwrap();
|
||||
|
||||
//
|
||||
// Opening transaction
|
||||
//
|
||||
|
||||
let mut rng = OsRng;
|
||||
let mut builder_a: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0);
|
||||
|
||||
// create some inputs to spend
|
||||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let to = extsk.default_address().unwrap().1;
|
||||
let note1 = to
|
||||
.create_note(110000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
|
||||
.unwrap();
|
||||
let cm1 = Node::new(note1.cmu().to_repr());
|
||||
let mut tree = CommitmentTree::new();
|
||||
// fake that the note appears in some previous
|
||||
// shielded output
|
||||
tree.append(cm1).unwrap();
|
||||
let witness1 = IncrementalWitness::from_tree(&tree);
|
||||
|
||||
builder_a
|
||||
.add_sapling_spend(
|
||||
extsk.clone(),
|
||||
*to.diversifier(),
|
||||
note1.clone(),
|
||||
witness1.path().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut db_a = DemoBuilder {
|
||||
txn_builder: &mut builder_a,
|
||||
extension_id: 0,
|
||||
};
|
||||
|
||||
let value = Amount::from_u64(100000).unwrap();
|
||||
db_a.demo_open(value, preimage_1, preimage_2)
|
||||
.map_err(|e| format!("open failure: {:?}", e))
|
||||
.unwrap();
|
||||
let (tx_a, _) = builder_a
|
||||
.build(BranchId::Canopy, &prover)
|
||||
.map_err(|e| format!("build failure: {:?}", e))
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// Transfer
|
||||
//
|
||||
|
||||
let mut builder_b: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0);
|
||||
let mut db_b = DemoBuilder {
|
||||
txn_builder: &mut builder_b,
|
||||
extension_id: 0,
|
||||
};
|
||||
let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.data.tze_outputs[0].clone());
|
||||
let value_xfr = Amount::from_u64(90000).unwrap();
|
||||
db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, preimage_2)
|
||||
.map_err(|e| format!("transfer failure: {:?}", e))
|
||||
.unwrap();
|
||||
let (tx_b, _) = builder_b
|
||||
.build(BranchId::Canopy, &prover)
|
||||
.map_err(|e| format!("build failure: {:?}", e))
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// Closing transaction
|
||||
//
|
||||
|
||||
let mut builder_c: Builder<'_, TestNetwork, OsRng> = Builder::new_future(0);
|
||||
let mut db_c = DemoBuilder {
|
||||
txn_builder: &mut builder_c,
|
||||
extension_id: 0,
|
||||
};
|
||||
let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.data.tze_outputs[0].clone());
|
||||
db_c.demo_close(prevout_b, preimage_2)
|
||||
.map_err(|e| format!("close failure: {:?}", e))
|
||||
.unwrap();
|
||||
|
||||
builder_c.add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::from_u64(80000).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let (tx_c, _) = builder_c
|
||||
.build(BranchId::Canopy, &prover)
|
||||
.map_err(|e| format!("build failure: {:?}", e))
|
||||
.unwrap();
|
||||
|
||||
// Verify tx_b
|
||||
let ctx0 = Ctx { tx: &tx_b };
|
||||
assert_eq!(
|
||||
Program.verify(
|
||||
&tx_a.data.tze_outputs[0].precondition,
|
||||
&tx_b.data.tze_inputs[0].witness,
|
||||
&ctx0
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// Verify tx_c
|
||||
let ctx1 = Ctx { tx: &tx_b };
|
||||
assert_eq!(
|
||||
Program.verify(
|
||||
&tx_b.data.tze_outputs[0].precondition,
|
||||
&tx_c.data.tze_inputs[0].witness,
|
||||
&ctx1
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ pub trait TxProver {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mock {
|
||||
pub mod mock {
|
||||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
|
||||
|
|
|
@ -371,20 +371,14 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, OsRng> {
|
|||
pub fn new(height: u32) -> Self {
|
||||
Builder::new_with_rng(height, OsRng)
|
||||
}
|
||||
|
||||
pub fn new_future(height: u32) -> Self {
|
||||
Builder::new_with_rng_future(height, 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).
|
||||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, P, R> {
|
||||
let mut mtx = TransactionData::new();
|
||||
fn new_with_mtx(height: u32, rng: R, mut mtx: TransactionData) -> Builder<'a, P, R> {
|
||||
mtx.expiry_height = height + DEFAULT_TX_EXPIRY_DELTA;
|
||||
|
||||
Builder {
|
||||
|
@ -402,6 +396,25 @@ 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).
|
||||
///
|
||||
/// The fee will be set to the default fee (0.0001 ZEC).
|
||||
pub fn new_with_rng(height: u32, rng: R) -> Builder<'a, P, R> {
|
||||
let mtx = TransactionData::new();
|
||||
Self::new_with_mtx(height, rng, mtx)
|
||||
}
|
||||
|
||||
pub fn new_with_rng_future(height: u32, rng: R) -> Builder<'a, P, R> {
|
||||
let mtx = TransactionData::future();
|
||||
Self::new_with_mtx(height, rng, mtx)
|
||||
}
|
||||
|
||||
/// Adds a Sapling note to be spent in this transaction.
|
||||
///
|
||||
/// Returns an error if the given Merkle path does not have the same anchor as the
|
||||
|
@ -508,7 +521,6 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
mut self,
|
||||
consensus_branch_id: consensus::BranchId,
|
||||
prover: &impl TxProver,
|
||||
// epoch: &Epoch<TransactionData>
|
||||
) -> Result<(Transaction, TransactionMetadata), Error> {
|
||||
let mut tx_metadata = TransactionMetadata::new();
|
||||
|
||||
|
@ -517,7 +529,9 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
//
|
||||
|
||||
// Valid change
|
||||
let change = self.mtx.value_balance - self.fee + self.transparent_inputs.value_sum()
|
||||
let change = self.mtx.value_balance
|
||||
- self.fee
|
||||
+ self.transparent_inputs.value_sum()
|
||||
- self.mtx.vout.iter().map(|vo| vo.value).sum::<Amount>()
|
||||
+ self
|
||||
.tze_inputs
|
||||
|
@ -735,15 +749,15 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
}
|
||||
|
||||
// Add a binding signature if needed
|
||||
if binding_sig_needed {
|
||||
self.mtx.binding_sig = Some(
|
||||
self.mtx.binding_sig = if binding_sig_needed {
|
||||
Some(
|
||||
prover
|
||||
.binding_sig(&mut ctx, self.mtx.value_balance, &sighash)
|
||||
.map_err(|()| Error::BindingSig)?,
|
||||
);
|
||||
.map_err(|_| Error::BindingSig)?,
|
||||
)
|
||||
} else {
|
||||
self.mtx.binding_sig = None;
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
// // Create TZE input witnesses
|
||||
for tze_in in self.tze_inputs.builders {
|
||||
|
@ -784,7 +798,6 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a
|
|||
where
|
||||
WBuilder: 'a + (FnOnce(&Self::BuildCtx) -> Result<W, Self::BuildError>),
|
||||
{
|
||||
// where WBuilder: WitnessBuilder<Self::BuildCtx, Witness = impl ToPayload, Error = Self::BuildError> {
|
||||
self.tze_inputs.push(extension_id, prevout, witness_builder);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ impl fmt::Display for TxId {
|
|||
/// A Zcash transaction.
|
||||
#[derive(Debug)]
|
||||
pub struct Transaction {
|
||||
txid: TxId,
|
||||
data: TransactionData,
|
||||
pub txid: TxId,
|
||||
pub data: TransactionData,
|
||||
}
|
||||
|
||||
impl Deref for Transaction {
|
||||
|
@ -142,7 +142,7 @@ impl TransactionData {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn nu4() -> Self {
|
||||
pub fn future() -> Self {
|
||||
TransactionData {
|
||||
overwintered: true,
|
||||
version: FUTURE_TX_VERSION,
|
||||
|
@ -310,10 +310,11 @@ impl Transaction {
|
|||
let is_sapling_v4 = self.overwintered
|
||||
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
|
||||
&& self.version == SAPLING_TX_VERSION;
|
||||
let is_nu4_v5 = self.overwintered
|
||||
let has_tze = self.overwintered
|
||||
&& self.version_group_id == FUTURE_VERSION_GROUP_ID
|
||||
&& self.version == FUTURE_TX_VERSION;
|
||||
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || is_nu4_v5) {
|
||||
|
||||
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4 || has_tze) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unknown transaction format",
|
||||
|
@ -322,16 +323,16 @@ impl Transaction {
|
|||
|
||||
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
|
||||
if is_nu4_v5 {
|
||||
if has_tze {
|
||||
Vector::write(&mut writer, &self.tze_inputs, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.tze_outputs, |w, e| e.write(w))?;
|
||||
}
|
||||
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
||||
if is_overwinter_v3 || is_sapling_v4 || is_nu4_v5 {
|
||||
if is_overwinter_v3 || is_sapling_v4 || has_tze {
|
||||
writer.write_u32::<LittleEndian>(self.expiry_height)?;
|
||||
}
|
||||
|
||||
if is_sapling_v4 || is_nu4_v5 {
|
||||
if is_sapling_v4 || has_tze {
|
||||
writer.write_all(&self.value_balance.to_i64_le_bytes())?;
|
||||
Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
|
||||
|
@ -376,7 +377,7 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
if (is_sapling_v4 || is_nu4_v5)
|
||||
if (is_sapling_v4 || has_tze)
|
||||
&& !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty())
|
||||
{
|
||||
match self.binding_sig {
|
||||
|
|
|
@ -6,7 +6,7 @@ use group::GroupEncoding;
|
|||
use super::{
|
||||
components::{Amount, TxOut},
|
||||
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
|
||||
SAPLING_VERSION_GROUP_ID,
|
||||
SAPLING_VERSION_GROUP_ID, FUTURE_VERSION_GROUP_ID,
|
||||
};
|
||||
use crate::{consensus, legacy::Script};
|
||||
|
||||
|
@ -54,6 +54,7 @@ impl SigHashVersion {
|
|||
match tx.version_group_id {
|
||||
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
|
||||
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
|
||||
FUTURE_VERSION_GROUP_ID => SigHashVersion::Sapling, //FIXME
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue