Checking of Sprout anchors in non-finalized state (#3123)
* Do prelim checking of Sprout anchors in non-finalized state Does not check intra-transaction interstitial states yet * Populate sprout anchors to allow other state tests to pass * Preliminary interstitial sprout note commitment tree anchor checks implementation * Make sure only prior anchors are checked in the same transaction * Add tests * Refactor a comment * Refactor rustdoc Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * Use the first `JoinSplit`s from mainnet * Print debug messages * Use correct blocks for the tests Co-authored-by: Marek <mail@marek.onl> Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
This commit is contained in:
parent
4ce6fbccc4
commit
b973b7a622
|
@ -1,4 +1,4 @@
|
|||
// From https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs#L512
|
||||
// From https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs#L585
|
||||
pub const HEX_EMPTY_ROOTS: [&str; 33] = [
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
"817de36ab2d57feb077634bca77819c8e0bd298c04f6fed0e6a83cc1356ca155",
|
||||
|
|
|
@ -24,7 +24,6 @@ pub struct JoinSplit<P: ZkSnarkProof> {
|
|||
pub vpub_old: Amount<NonNegative>,
|
||||
/// A value that the JoinSplit transfer inserts into the transparent value
|
||||
/// pool.
|
||||
///
|
||||
pub vpub_new: Amount<NonNegative>,
|
||||
/// A root of the Sprout note commitment tree at some block height in the
|
||||
/// past, or the root produced by a previous JoinSplit transfer in this
|
||||
|
|
|
@ -46,9 +46,9 @@ use std::{collections::HashMap, fmt, iter};
|
|||
///
|
||||
/// A transaction is an encoded data structure that facilitates the transfer of
|
||||
/// value between two public key addresses on the Zcash ecosystem. Everything is
|
||||
/// designed to ensure that transactions can created, propagated on the network,
|
||||
/// validated, and finally added to the global ledger of transactions (the
|
||||
/// blockchain).
|
||||
/// designed to ensure that transactions can be created, propagated on the
|
||||
/// network, validated, and finally added to the global ledger of transactions
|
||||
/// (the blockchain).
|
||||
///
|
||||
/// Zcash has a number of different transaction formats. They are represented
|
||||
/// internally by different enum variants. Because we checkpoint on Canopy
|
||||
|
@ -612,6 +612,61 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return if the transaction has any Sprout JoinSplit data.
|
||||
pub fn has_sprout_joinsplit_data(&self) -> bool {
|
||||
match self {
|
||||
// No JoinSplits
|
||||
Transaction::V1 { .. } | Transaction::V5 { .. } => false,
|
||||
|
||||
// JoinSplits-on-BCTV14
|
||||
Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
|
||||
joinsplit_data.is_some()
|
||||
}
|
||||
|
||||
// JoinSplits-on-Groth16
|
||||
Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Sprout note commitments in this transaction.
|
||||
pub fn sprout_note_commitments(
|
||||
&self,
|
||||
) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
|
||||
match self {
|
||||
// Return [`NoteCommitment`]s with [`Bctv14Proof`]s.
|
||||
Transaction::V2 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
}
|
||||
| Transaction::V3 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
} => Box::new(joinsplit_data.note_commitments()),
|
||||
|
||||
// Return [`NoteCommitment`]s with [`Groth16Proof`]s.
|
||||
Transaction::V4 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
} => Box::new(joinsplit_data.note_commitments()),
|
||||
|
||||
// Return an empty iterator.
|
||||
Transaction::V2 {
|
||||
joinsplit_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V3 {
|
||||
joinsplit_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V4 {
|
||||
joinsplit_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V1 { .. }
|
||||
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
// sapling
|
||||
|
||||
/// Access the deduplicated [`sapling::tree::Root`]s in this transaction,
|
||||
|
@ -741,45 +796,6 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the Sprout note commitments in this transaction.
|
||||
pub fn sprout_note_commitments(
|
||||
&self,
|
||||
) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
|
||||
match self {
|
||||
// Return [`NoteCommitment`]s with [`Bctv14Proof`]s.
|
||||
Transaction::V2 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
}
|
||||
| Transaction::V3 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
} => Box::new(joinsplit_data.note_commitments()),
|
||||
|
||||
// Return [`NoteCommitment`]s with [`Groth16Proof`]s.
|
||||
Transaction::V4 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
} => Box::new(joinsplit_data.note_commitments()),
|
||||
|
||||
// Return an empty iterator.
|
||||
Transaction::V2 {
|
||||
joinsplit_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V3 {
|
||||
joinsplit_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V4 {
|
||||
joinsplit_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V1 { .. }
|
||||
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Sapling note commitments in this transaction, regardless of version.
|
||||
pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
|
||||
// This function returns a boxed iterator because the different
|
||||
|
@ -1036,7 +1052,7 @@ impl Transaction {
|
|||
.joinsplits_mut()
|
||||
.map(|joinsplit| &mut joinsplit.vpub_old),
|
||||
),
|
||||
// JoinSplits with Groth Proofs
|
||||
// JoinSplits with Groth16 Proofs
|
||||
Transaction::V4 {
|
||||
joinsplit_data: Some(joinsplit_data),
|
||||
..
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
//! Checks for whether cited anchors are previously-computed note commitment
|
||||
//! tree roots.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use zebra_chain::sprout;
|
||||
|
||||
use crate::{
|
||||
service::{finalized_state::FinalizedState, non_finalized_state::Chain},
|
||||
PreparedBlock, ValidateContextError,
|
||||
};
|
||||
|
||||
/// Check that all the Sprout, Sapling, and Orchard anchors specified by
|
||||
/// Check that the Sprout, Sapling, and Orchard anchors specified by
|
||||
/// transactions in this block have been computed previously within the context
|
||||
/// of its parent chain.
|
||||
/// of its parent chain. We do not check any anchors in checkpointed blocks, which avoids
|
||||
/// JoinSplits<BCTV14Proof>
|
||||
///
|
||||
/// Sprout anchors may refer to some earlier block's final treestate (like
|
||||
/// Sapling and Orchard do exclusively) _or_ to the interstisial output
|
||||
|
@ -40,28 +45,52 @@ pub(crate) fn anchors_refer_to_earlier_treestates(
|
|||
prepared: &PreparedBlock,
|
||||
) -> Result<(), ValidateContextError> {
|
||||
for transaction in prepared.block.transactions.iter() {
|
||||
// Sprout JoinSplits, with interstitial treestates to check as well
|
||||
// Sprout JoinSplits, with interstitial treestates to check as well.
|
||||
//
|
||||
// The FIRST JOINSPLIT in a transaction MUST refer to the output treestate
|
||||
// of a previous block.
|
||||
if transaction.has_sprout_joinsplit_data() {
|
||||
// > The anchor of each JoinSplit description in a transaction MUST refer to
|
||||
// > either some earlier block’s final Sprout treestate, or to the interstitial
|
||||
// > output treestate of any prior JoinSplit description in the same transaction.
|
||||
//
|
||||
// https://zips.z.cash/protocol/protocol.pdf#joinsplit
|
||||
let mut interstitial_roots: HashSet<sprout::tree::Root> = HashSet::new();
|
||||
|
||||
// if let Some(sprout_shielded_data) = transaction.joinsplit_data {
|
||||
// for joinsplit in transaction.sprout_groth16_joinsplits() {
|
||||
// if !parent_chain.sprout_anchors.contains(joinsplit.anchor)
|
||||
// && !finalized_state.contains_sprout_anchor(&joinsplit.anchor)
|
||||
// {
|
||||
// if !(joinsplit == &sprout_shielded_data.first) {
|
||||
// // TODO: check interstitial treestates of the earlier JoinSplits
|
||||
// // in this transaction against this anchor
|
||||
// unimplemented!()
|
||||
// } else {
|
||||
// return Err(ValidateContextError::UnknownSproutAnchor {
|
||||
// anchor: joinsplit.anchor,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
let mut interstitial_note_commitment_tree = parent_chain.sprout_note_commitment_tree();
|
||||
|
||||
for joinsplit in transaction.sprout_groth16_joinsplits() {
|
||||
// Check all anchor sets, including the one for interstitial anchors.
|
||||
//
|
||||
// Note that [`interstitial_roots`] is always empty in the first
|
||||
// iteration of the loop. This is because:
|
||||
//
|
||||
// > "The anchor of each JoinSplit description in a transaction
|
||||
// > MUST refer to [...] to the interstitial output treestate of
|
||||
// > any **prior** JoinSplit description in the same transaction."
|
||||
if !parent_chain.sprout_anchors.contains(&joinsplit.anchor)
|
||||
&& !finalized_state.contains_sprout_anchor(&joinsplit.anchor)
|
||||
&& (!interstitial_roots.contains(&joinsplit.anchor))
|
||||
{
|
||||
return Err(ValidateContextError::UnknownSproutAnchor {
|
||||
anchor: joinsplit.anchor,
|
||||
});
|
||||
}
|
||||
|
||||
tracing::debug!(?joinsplit.anchor, "validated sprout anchor");
|
||||
|
||||
// Add new anchors to the interstitial note commitment tree.
|
||||
for cm in joinsplit.commitments {
|
||||
interstitial_note_commitment_tree
|
||||
.append(cm)
|
||||
.expect("note commitment should be appendable to the tree");
|
||||
}
|
||||
|
||||
interstitial_roots.insert(interstitial_note_commitment_tree.root());
|
||||
|
||||
tracing::debug!(?joinsplit.anchor, "observed sprout anchor");
|
||||
}
|
||||
}
|
||||
|
||||
// Sapling Spends
|
||||
//
|
||||
|
|
|
@ -1,17 +1,133 @@
|
|||
//! Tests for whether cited anchors are checked properly.
|
||||
|
||||
use std::{convert::TryInto, ops::Deref, sync::Arc};
|
||||
|
||||
use zebra_chain::{
|
||||
amount::Amount,
|
||||
block::{Block, Height},
|
||||
primitives::Groth16Proof,
|
||||
serialization::ZcashDeserializeInto,
|
||||
transaction::{LockTime, Transaction},
|
||||
sprout::JoinSplit,
|
||||
transaction::{JoinSplitData, LockTime, Transaction},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
arbitrary::Prepare,
|
||||
tests::setup::{new_state_with_mainnet_genesis, transaction_v4_from_coinbase},
|
||||
PreparedBlock,
|
||||
};
|
||||
|
||||
// sapling
|
||||
// Sprout
|
||||
|
||||
/// Check that, when primed with the first blocks that contain Sprout anchors, a
|
||||
/// Sprout Spend's referenced anchor is validated.
|
||||
#[test]
|
||||
fn check_sprout_anchors() {
|
||||
zebra_test::init();
|
||||
|
||||
let (mut state, _genesis) = new_state_with_mainnet_genesis();
|
||||
|
||||
// Bootstrap a block at height == 1.
|
||||
let block_1 = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
|
||||
.zcash_deserialize_into::<Block>()
|
||||
.expect("block should deserialize");
|
||||
|
||||
// Bootstrap a block just before the first Sprout anchors.
|
||||
let block_395 = zebra_test::vectors::BLOCK_MAINNET_395_BYTES
|
||||
.zcash_deserialize_into::<Block>()
|
||||
.expect("block should deserialize");
|
||||
|
||||
// Add initial transactions to [`block_1`].
|
||||
let block_1 = prepare_sprout_block(block_1, block_395);
|
||||
|
||||
// Validate and commit [`block_1`]. This will add an anchor referencing the
|
||||
// empty note commitment tree to the state.
|
||||
assert!(state.validate_and_commit(block_1).is_ok());
|
||||
|
||||
// Bootstrap a block at height == 2 that references the Sprout note commitment tree state
|
||||
// from [`block_1`].
|
||||
let block_2 = zebra_test::vectors::BLOCK_MAINNET_2_BYTES
|
||||
.zcash_deserialize_into::<Block>()
|
||||
.expect("block should deserialize");
|
||||
|
||||
// Exercise Sprout anchor checking with the first shielded transactions with
|
||||
// anchors.
|
||||
let block_396 = zebra_test::vectors::BLOCK_MAINNET_396_BYTES
|
||||
.zcash_deserialize_into::<Block>()
|
||||
.expect("block should deserialize");
|
||||
|
||||
// Add the transactions with the first anchors to [`block_2`].
|
||||
let block_2 = prepare_sprout_block(block_2, block_396);
|
||||
|
||||
// Validate and commit [`block_2`]. This will also check the anchors.
|
||||
assert_eq!(state.validate_and_commit(block_2), Ok(()));
|
||||
}
|
||||
|
||||
fn prepare_sprout_block(mut block_to_prepare: Block, reference_block: Block) -> PreparedBlock {
|
||||
// Convert the coinbase transaction to a version that the non-finalized state will accept.
|
||||
block_to_prepare.transactions[0] =
|
||||
transaction_v4_from_coinbase(&block_to_prepare.transactions[0]).into();
|
||||
|
||||
reference_block
|
||||
.transactions
|
||||
.into_iter()
|
||||
.filter(|tx| tx.has_sprout_joinsplit_data())
|
||||
.for_each(|tx| {
|
||||
let joinsplit_data = match tx.deref() {
|
||||
Transaction::V2 { joinsplit_data, .. } => joinsplit_data.clone(),
|
||||
_ => unreachable!("These are known v2 transactions"),
|
||||
};
|
||||
|
||||
// Change [`joinsplit_data`] so that the transaction passes the
|
||||
// semantic validation. Namely, set the value balance to zero, and
|
||||
// use a dummy Groth16 proof instead of a BCTV14 one.
|
||||
let joinsplit_data = joinsplit_data.map(|s| {
|
||||
let mut new_joinsplits: Vec<JoinSplit<Groth16Proof>> = Vec::new();
|
||||
|
||||
for old_joinsplit in s.joinsplits() {
|
||||
new_joinsplits.push(JoinSplit {
|
||||
vpub_old: Amount::zero(),
|
||||
vpub_new: Amount::zero(),
|
||||
anchor: old_joinsplit.anchor,
|
||||
nullifiers: old_joinsplit.nullifiers,
|
||||
commitments: old_joinsplit.commitments,
|
||||
ephemeral_key: old_joinsplit.ephemeral_key,
|
||||
random_seed: old_joinsplit.random_seed,
|
||||
vmacs: old_joinsplit.vmacs.clone(),
|
||||
zkproof: Groth16Proof::from([0; 192]),
|
||||
enc_ciphertexts: old_joinsplit.enc_ciphertexts,
|
||||
})
|
||||
}
|
||||
|
||||
match new_joinsplits.split_first() {
|
||||
None => unreachable!("the new joinsplits are never empty"),
|
||||
|
||||
Some((first, rest)) => JoinSplitData {
|
||||
first: first.clone(),
|
||||
rest: rest.to_vec(),
|
||||
pub_key: s.pub_key,
|
||||
sig: s.sig,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// Add the new adjusted transaction to [`block_to_prepare`].
|
||||
block_to_prepare
|
||||
.transactions
|
||||
.push(Arc::new(Transaction::V4 {
|
||||
inputs: Vec::new(),
|
||||
outputs: Vec::new(),
|
||||
lock_time: LockTime::min_lock_time(),
|
||||
expiry_height: Height(0),
|
||||
joinsplit_data,
|
||||
sapling_shielded_data: None,
|
||||
}))
|
||||
});
|
||||
|
||||
Arc::new(block_to_prepare).prepare()
|
||||
}
|
||||
|
||||
// Sapling
|
||||
|
||||
/// Check that, when primed with the first Sapling blocks, a Sapling Spend's referenced anchor is
|
||||
/// validated.
|
||||
|
|
|
@ -324,6 +324,7 @@ proptest! {
|
|||
|
||||
// Allows anchor checks to pass
|
||||
state.disk.populate_with_anchors(&block1);
|
||||
state.disk.populate_with_anchors(&block2);
|
||||
|
||||
let mut previous_mem = state.mem.clone();
|
||||
|
||||
|
|
|
@ -631,11 +631,11 @@ impl FinalizedState {
|
|||
self.db.zs_contains(orchard_nullifiers, &orchard_nullifier)
|
||||
}
|
||||
|
||||
// /// Returns `true` if the finalized state contains `sprout_anchor`.
|
||||
// pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
|
||||
// let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||
// self.db.zs_contains(sprout_anchors, &sprout_anchor)
|
||||
// }
|
||||
/// Returns `true` if the finalized state contains `sprout_anchor`.
|
||||
pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
|
||||
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||
self.db.zs_contains(sprout_anchors, &sprout_anchor)
|
||||
}
|
||||
|
||||
/// Returns `true` if the finalized state contains `sapling_anchor`.
|
||||
pub fn contains_sapling_anchor(&self, sapling_anchor: &sapling::tree::Root) -> bool {
|
||||
|
@ -790,15 +790,15 @@ impl FinalizedState {
|
|||
pub fn populate_with_anchors(&self, block: &Block) {
|
||||
let mut batch = rocksdb::WriteBatch::default();
|
||||
|
||||
// let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||
let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
|
||||
let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
|
||||
|
||||
for transaction in block.transactions.iter() {
|
||||
// Sprout
|
||||
// for joinsplit in transaction.sprout_groth16_joinsplits() {
|
||||
// batch.zs_insert(sprout_anchors, joinsplit.anchor, ());
|
||||
// }
|
||||
for joinsplit in transaction.sprout_groth16_joinsplits() {
|
||||
batch.zs_insert(sprout_anchors, joinsplit.anchor, ());
|
||||
}
|
||||
|
||||
// Sapling
|
||||
for anchor in transaction.sapling_anchors() {
|
||||
|
|
|
@ -60,9 +60,9 @@ pub struct Chain {
|
|||
pub(crate) history_tree: HistoryTree,
|
||||
|
||||
/// The Sprout anchors created by `blocks`.
|
||||
pub(super) sprout_anchors: HashMultiSet<sprout::tree::Root>,
|
||||
pub(crate) sprout_anchors: HashMultiSet<sprout::tree::Root>,
|
||||
/// The Sprout anchors created by each block in `blocks`.
|
||||
pub(super) sprout_anchors_by_height: BTreeMap<block::Height, sprout::tree::Root>,
|
||||
pub(crate) sprout_anchors_by_height: BTreeMap<block::Height, sprout::tree::Root>,
|
||||
/// The Sapling anchors created by `blocks`.
|
||||
pub(crate) sapling_anchors: HashMultiSet<sapling::tree::Root>,
|
||||
/// The Sapling anchors created by each block in `blocks`.
|
||||
|
@ -394,6 +394,14 @@ impl Chain {
|
|||
chain_value_pools: self.chain_value_pools,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a clone of the Sprout note commitment tree for this chain.
|
||||
///
|
||||
/// Useful when calculating interstitial note commitment trees for each JoinSplit in a Sprout
|
||||
/// shielded transaction.
|
||||
pub fn sprout_note_commitment_tree(&self) -> sprout::tree::NoteCommitmentTree {
|
||||
self.sprout_note_commitment_tree.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// The revert position being performed on a chain.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
04000000b356199270b1cd6e96759c2a1a224c5c65364a4072d8b1e56f348b4a690100008120191929df6a6dbb5d7f61788155ac0b3f92a431efb2c2f6783fd99bebf39c0000000000000000000000000000000000000000000000000000000000000000518c1358c97d011eaa9da5470d00000000000000000000000000000000000000000000000000000ffd400500cb784fb5929563200bd38f5ad8b0751d052abcef16c3bbcef9af69318fe831e4123aa9e0cd401fce810547653ba354c39d219705ed30ef25b68d7a773c131d82ceb7f09e97a7ca03058ca4b60df665b35e70a1017c8120475b2fdaed0b62dceb31149cd4263a2fc402f07bd29f50a7c5ee6633aa9270894a05883eddc5043679a86fef6b75dad2e066e9c1d5684de00789de1258be8884490bd3eccb621c503bccb9489a8c509e0351cca30ff42f3de4b9e0f910ab5a28f4006def44066683af258c12b3e587a0a137a6f1fd106d3aa43f14495b943cce73ca91a712b6afb9fac934d750015953e1cff110dd395d786d673ec0be906397c23f459007c2f4d6d91d074d482023bebba58b70ffd0757c570b188fcd8f20b433fd2b652c6b34cb3e0221fae43b38e81e55f2dbac4f2f1676780a71a87a7dca7f9fb44a3a3ab500d719bcf9e568004fec8bbe02c2d5553501997c8bb05dd259979d53d60a70910136128c4b2b1fc62e571c702de5b93b2515a56664326ce1959b8d0db5fc554b5299c1635d963d27537e965fc9bb070b1ce54f4ad087e9c31cec127f8e4e0a1a0c4d7377c61b61bd11429ba4e984ee6321385ec09d97ae785fb24a10c355393e1555f3be1b119af65b0b9a243ceed71b7d22492734ba3da7e272f09e31dc2d7785ed86cf1ef3e974dd0d5ac4ed41756e935b4ed61bb09841f601fe54e2826a780b5e1b22d81dbadc5cd79e6d368308722d90c511d0c56075d105054f547ce7729517db1b7655c74e1b6723ded3745498f53e95e9553ef6346ceda50c316a0529b279b753cdd2aca22a48f9dbb10a2141de61c29c07b847243de6d777d153237340ea2dabe7e840ae2c297d9f731629a98568e6ebfc3e1f25d8acac94abff4b82ada46b477c083db3408f0a1925f3531fd2e81361883452a810a7963ee9901fb452010e4c86f8091cb5705d92563fc7ea85a1ea7f158637b647143a35b001f75ac724de453845cbec1666c90a1ecdde5e8c0f82dad4d2e3ca325dd1400ab955495c7aed30799e61bfb8f456f5f94ad58a441ddcc1fb05b0441c2526b5b96827e7706ee917fa98479d6635255a1ce2fbcdcf02fdeb02f1b64ba9e530e7f987a9138ff73b10179d357c32c35558e34f27eae53fb2453cc5adb7b1eb044be9f457b24975d73fd2fc7ed16c03f60b949685c31c36ae135a8fa1f354e87f1776cb08dffc10d943ac8de556f3915ed4c1226783d973a81420c3999d68eb21c181750e713a94ee2b745794cc44c81314349d1cafcd24052ee9f0ece388efff25220ce9360357e8159bd10722b9dcde35bdd55933de0245cb42769ed9806520a8a5da91b253c310437b0f5e1e7c5c31a7ce20578b6a249fdce1b1d57f60358bc81f6bc462a639a079de30d47627c246ce41fa7f29e4021cc96fb19cb78b96a1b5234036532e1341f7054c402747c5af5d6209a9c8e578d7cf486e00c71c60a10eed2889e51791b91da5d3e6d65dff12775797e8542f0a4e3b2b277d3147bbe7b07355200ebf8c56bc7d03a15d4043b75943c673625aa2e071dd4e5b312d4408f4aca22f0e2347c205e2ed9a6036de1a301475930f945f57b28a2e6b996bd40d6fe0c9897dbcbdcaf215da48e5cb9a5e258845c2bdc5ceac9175d157f17e04e6690e10967313d0c3f303517218d4fcacb620651628f7ce39c73b93da15340661b13e2e0fd65b79ce09d69b7a874fa01c878342577137bf7dfe93b920951ce05de1c3dab420edef15fb99e21bfec12f38018309a354a5e105d99aa1f7024eb7c390caf99dddef4a1407a157ad8e22629e5e51815d3a02156a871aa8470a073dd930de01a7854725b965af5a1b3581fb756644642a8fb05b6dab521b561035d8f466b7019c59670101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff04028b0100ffffffff02705c2d01000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875ac1c574b000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000
|
|
@ -329,6 +329,10 @@ lazy_static! {
|
|||
pub static ref BLOCK_MAINNET_202_BYTES: Vec<u8> =
|
||||
<Vec<u8>>::from_hex(include_str!("block-main-0-000-202.txt").trim())
|
||||
.expect("Block bytes are in valid hex representation");
|
||||
// zcash-cli getblock 395 0 > block-main-0-000-395.txt
|
||||
pub static ref BLOCK_MAINNET_395_BYTES: Vec<u8> =
|
||||
<Vec<u8>>::from_hex(include_str!("block-main-0-000-395.txt").trim())
|
||||
.expect("Block bytes are in valid hex representation");
|
||||
// zcash-cli getblock 396 0 > block-main-0-000-396.txt
|
||||
pub static ref BLOCK_MAINNET_396_BYTES: Vec<u8> =
|
||||
<Vec<u8>>::from_hex(include_str!("block-main-0-000-396.txt").trim())
|
||||
|
|
Loading…
Reference in New Issue