Merge pull request #361 from str4d/release-0.5-prep
Release 0.5 preparations
This commit is contained in:
commit
44e3176d5a
|
@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.7"
|
||||
bech32 = "0.8"
|
||||
bls12_381 = "0.3.1"
|
||||
bs58 = { version = "0.4", features = ["check"] }
|
||||
base64 = "0.13"
|
||||
|
|
|
@ -187,11 +187,21 @@ pub struct PrunedBlock<'a> {
|
|||
pub transactions: &'a Vec<WalletTx<Nullifier>>,
|
||||
}
|
||||
|
||||
/// A transaction that was detected during scanning of the blockchain,
|
||||
/// including its decrypted Sapling outputs.
|
||||
///
|
||||
/// The purpose of this struct is to permit atomic updates of the
|
||||
/// wallet database when transactions are successfully decrypted.
|
||||
pub struct ReceivedTransaction<'a> {
|
||||
pub tx: &'a Transaction,
|
||||
pub outputs: &'a Vec<DecryptedOutput>,
|
||||
}
|
||||
|
||||
/// A transaction that was constructed and sent by the wallet.
|
||||
///
|
||||
/// The purpose of this struct is to permit atomic updates of the
|
||||
/// wallet database when transactions are created and submitted
|
||||
/// to the network.
|
||||
pub struct SentTransaction<'a> {
|
||||
pub tx: &'a Transaction,
|
||||
pub created: time::OffsetDateTime,
|
||||
|
|
|
@ -192,8 +192,9 @@ where
|
|||
/// caller is handling rollbacks.
|
||||
///
|
||||
/// For brand-new light client databases, this function starts scanning from the Sapling
|
||||
/// activation height. This height can be fast-forwarded to a more recent block by calling
|
||||
/// [`init_blocks_table`] before this function.
|
||||
/// activation height. This height can be fast-forwarded to a more recent block by
|
||||
/// initializing the client database with a starting block (for example, calling
|
||||
/// `init_blocks_table` before this function if using `zcash_client_sqlite`).
|
||||
///
|
||||
/// Scanned blocks are required to be height-sequential. If a block is missing from the
|
||||
/// cache, an error will be returned with kind [`ChainInvalid::BlockHeightDiscontinuity`].
|
||||
|
@ -235,8 +236,6 @@ where
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`init_blocks_table`]: crate::init::init_blocks_table
|
||||
pub fn scan_cached_blocks<E, N, P, C, D>(
|
||||
params: &P,
|
||||
cache: &C,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
//! Encoding and decoding functions for Zcash key and address structs.
|
||||
//!
|
||||
//! Human-Readable Prefixes (HRPs) for Bech32 encodings are located in the [`zcash_primitives::constants`]
|
||||
//! module.
|
||||
//! Human-Readable Prefixes (HRPs) for Bech32 encodings are located in the
|
||||
//! [zcash_primitives::constants][constants] module.
|
||||
//!
|
||||
//! [`constants`]: zcash_primitives::constants
|
||||
//! [constants]: zcash_primitives::constants
|
||||
|
||||
use bech32::{self, Error, FromBase32, ToBase32};
|
||||
use bech32::{self, Error, FromBase32, ToBase32, Variant};
|
||||
use bs58::{self, decode::Error as Bs58Error};
|
||||
use std::convert::TryInto;
|
||||
use std::io::{self, Write};
|
||||
|
@ -21,18 +21,18 @@ where
|
|||
{
|
||||
let mut data: Vec<u8> = vec![];
|
||||
write(&mut data).expect("Should be able to write to a Vec");
|
||||
bech32::encode(hrp, data.to_base32()).expect("hrp is invalid")
|
||||
bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid")
|
||||
}
|
||||
|
||||
fn bech32_decode<T, F>(hrp: &str, s: &str, read: F) -> Result<Option<T>, Error>
|
||||
where
|
||||
F: Fn(Vec<u8>) -> Option<T>,
|
||||
{
|
||||
let (decoded_hrp, data) = bech32::decode(s)?;
|
||||
if decoded_hrp == hrp {
|
||||
Vec::<u8>::from_base32(&data).map(|data| read(data))
|
||||
} else {
|
||||
Ok(None)
|
||||
match bech32::decode(s)? {
|
||||
(decoded_hrp, data, Variant::Bech32) if decoded_hrp == hrp => {
|
||||
Vec::<u8>::from_base32(&data).map(|data| read(data))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,11 +52,14 @@ where
|
|||
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0);
|
||||
/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
|
||||
/// ```
|
||||
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
|
||||
pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
|
||||
bech32_encode(hrp, |w| extsk.write(w))
|
||||
}
|
||||
|
||||
/// Decodes an [`ExtendedSpendingKey`] from a Bech32-encoded string.
|
||||
///
|
||||
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
|
||||
pub fn decode_extended_spending_key(
|
||||
hrp: &str,
|
||||
s: &str,
|
||||
|
@ -82,11 +85,14 @@ pub fn decode_extended_spending_key(
|
|||
/// let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
|
||||
/// ```
|
||||
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
|
||||
pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
|
||||
bech32_encode(hrp, |w| extfvk.write(w))
|
||||
}
|
||||
|
||||
/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string.
|
||||
///
|
||||
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
|
||||
pub fn decode_extended_full_viewing_key(
|
||||
hrp: &str,
|
||||
s: &str,
|
||||
|
@ -127,6 +133,7 @@ pub fn decode_extended_full_viewing_key(
|
|||
/// "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
|
||||
/// );
|
||||
/// ```
|
||||
/// [`PaymentAddress`]: zcash_primitives::primitives::PaymentAddress
|
||||
pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String {
|
||||
bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
|
||||
}
|
||||
|
@ -167,6 +174,7 @@ pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String {
|
|||
/// Ok(Some(pa)),
|
||||
/// );
|
||||
/// ```
|
||||
/// [`PaymentAddress`]: zcash_primitives::primitives::PaymentAddress
|
||||
pub fn decode_payment_address(hrp: &str, s: &str) -> Result<Option<PaymentAddress>, Error> {
|
||||
bech32_decode(hrp, s, |data| {
|
||||
if data.len() != 43 {
|
||||
|
@ -210,6 +218,7 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<Option<PaymentAddres
|
|||
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
||||
/// );
|
||||
/// ```
|
||||
/// [`TransparentAddress`]: zcash_primitives::legacy::TransparentAddress
|
||||
pub fn encode_transparent_address(
|
||||
pubkey_version: &[u8],
|
||||
script_version: &[u8],
|
||||
|
@ -263,6 +272,7 @@ pub fn encode_transparent_address(
|
|||
/// Ok(Some(TransparentAddress::Script([0; 20]))),
|
||||
/// );
|
||||
/// ```
|
||||
/// [`TransparentAddress`]: zcash_primitives::legacy::TransparentAddress
|
||||
pub fn decode_transparent_address(
|
||||
pubkey_version: &[u8],
|
||||
script_version: &[u8],
|
||||
|
|
|
@ -17,6 +17,7 @@ use zcash_primitives::zip32::{ChildIndex, ExtendedSpendingKey};
|
|||
///
|
||||
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, 0);
|
||||
/// ```
|
||||
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
|
||||
pub fn spending_key(seed: &[u8], coin_type: u32, account: u32) -> ExtendedSpendingKey {
|
||||
if seed.len() < 32 {
|
||||
panic!("ZIP 32 seeds MUST be at least 32 bytes");
|
||||
|
|
|
@ -63,6 +63,8 @@ pub struct WalletShieldedOutput<N> {
|
|||
pub nf: N,
|
||||
}
|
||||
|
||||
/// Information about a note that is tracked by the wallet that is available for spending,
|
||||
/// with sufficient information for use in note selection.
|
||||
pub struct SpendableNote {
|
||||
pub diversifier: Diversifier,
|
||||
pub note_value: Amount,
|
||||
|
|
|
@ -23,6 +23,8 @@ use crate::wallet::{AccountId, WalletShieldedOutput, WalletShieldedSpend, Wallet
|
|||
///
|
||||
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are incremented
|
||||
/// with this output's commitment.
|
||||
///
|
||||
/// [`ScanningKey`]: crate::welding_rig::ScanningKey
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn scan_output<P: consensus::Parameters, K: ScanningKey>(
|
||||
params: &P,
|
||||
|
@ -88,10 +90,23 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
|
|||
/// A key that can be used to perform trial decryption and nullifier
|
||||
/// computation for a Sapling [`CompactOutput`]
|
||||
///
|
||||
/// The purpose of this trait is to enable [`scan_block`]
|
||||
/// and related methods to be used with either incoming viewing keys
|
||||
/// or full viewing keys, with the data returned from trial decryption
|
||||
/// being dependent upon the type of key used. In the case that an
|
||||
/// incoming viewing key is used, only the note and payment address
|
||||
/// will be returned; in the case of a full viewing key, the
|
||||
/// nullifier for the note can also be obtained.
|
||||
///
|
||||
/// [`CompactOutput`]: crate::proto::compact_formats::CompactOutput
|
||||
/// [`scan_block`]: crate::welding_rig::scan_block
|
||||
pub trait ScanningKey {
|
||||
/// The type of nullifier extracted when a note is successfully
|
||||
/// obtained by trial decryption.
|
||||
type Nf;
|
||||
|
||||
/// Attempts to decrypt a Sapling note and payment address
|
||||
/// from the specified ciphertext using this scanning key.
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
&self,
|
||||
params: &P,
|
||||
|
@ -101,9 +116,18 @@ pub trait ScanningKey {
|
|||
ct: &[u8],
|
||||
) -> Option<(Note, PaymentAddress)>;
|
||||
|
||||
/// Produces the nullifier for the specified note and witness, if possible.
|
||||
///
|
||||
/// IVK-based implementations of this trait cannot successfully derive
|
||||
/// nullifiers, in which case `Self::Nf` should be set to the unit type
|
||||
/// and this function is a no-op.
|
||||
fn nf(&self, note: &Note, witness: &IncrementalWitness<Node>) -> Self::Nf;
|
||||
}
|
||||
|
||||
/// The [`ScanningKey`] implementation for [`ExtendedFullViewingKey`]s.
|
||||
/// Nullifiers may be derived when scanning with these keys.
|
||||
///
|
||||
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
|
||||
impl ScanningKey for ExtendedFullViewingKey {
|
||||
type Nf = Nullifier;
|
||||
|
||||
|
@ -123,6 +147,10 @@ impl ScanningKey for ExtendedFullViewingKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// The [`ScanningKey`] implementation for [`SaplingIvk`]s.
|
||||
/// Nullifiers cannot be derived when scanning with these keys.
|
||||
///
|
||||
/// [`SaplingIvk`]: zcash_primitives::primitives::SaplingIvk
|
||||
impl ScanningKey for SaplingIvk {
|
||||
type Nf = ();
|
||||
|
||||
|
@ -152,16 +180,17 @@ impl ScanningKey for SaplingIvk {
|
|||
/// The implementation of [`ScanningKey`] may either support or omit the computation of
|
||||
/// the nullifiers for received notes; the implementation for [`ExtendedFullViewingKey`]
|
||||
/// will derive the nullifiers for received notes and return them as part of the resulting
|
||||
/// [`WalletShieldedOutput`]s, whereas since the implementation for [`SaplingIvk`] cannot
|
||||
/// do so and it will return the unit value in those outputs instead.
|
||||
/// [`WalletShieldedOutput`]s, whereas the implementation for [`SaplingIvk`] cannot
|
||||
/// do so and will return the unit value in those outputs instead.
|
||||
///
|
||||
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
|
||||
/// [`SaplingIvk`]: zcash_primitives::SaplingIvk
|
||||
/// [`SaplingIvk`]: zcash_primitives::primitives::SaplingIvk
|
||||
/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
|
||||
/// [`ScanningKey`]: self::ScanningKey
|
||||
/// [`ScanningKey`]: crate::welding_rig::ScanningKey
|
||||
/// [`CommitmentTree`]: zcash_primitives::merkle_tree::CommitmentTree
|
||||
/// [`IncrementalWitness`]: zcash_primitives::merkle_tree::IncrementalWitness
|
||||
/// [`WalletShieldedOutput`]: crate::wallet::WalletShieldedOutput
|
||||
/// [`WalletTx`]: crate::wallet::WalletTx
|
||||
pub fn scan_block<P: consensus::Parameters, K: ScanningKey>(
|
||||
params: &P,
|
||||
block: CompactBlock,
|
||||
|
|
|
@ -462,7 +462,7 @@ mod parse {
|
|||
Ok(payment)
|
||||
}
|
||||
|
||||
/// Parser that consumes the leading "zcash:[address]" from
|
||||
/// Parser that consumes the leading "zcash:\[address\]" from
|
||||
/// a ZIP 321 URI.
|
||||
pub fn lead_addr<'a, P: consensus::Parameters>(
|
||||
params: &'a P,
|
||||
|
|
|
@ -29,6 +29,13 @@ same as before, but have been reorganized.
|
|||
### Removed
|
||||
- `zcash_client_sqlite::address` module (moved to `zcash_client_backend`).
|
||||
|
||||
### Fixed
|
||||
- Shielded transactions created by the wallet that have no change output (fully
|
||||
spending their input notes) are now correctly detected as mined when scanning
|
||||
compact blocks.
|
||||
- Unshielding transactions created by the wallet (with a transparent recipient
|
||||
address) that have no change output no longer cause a panic.
|
||||
|
||||
## [0.2.1] - 2020-10-24
|
||||
### Fixed
|
||||
- `transact::create_to_address` now correctly reconstructs notes from the data
|
||||
|
|
|
@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.7"
|
||||
bech32 = "0.8"
|
||||
bs58 = { version = "0.4", features = ["check"] }
|
||||
ff = "0.8"
|
||||
group = "0.8"
|
||||
|
|
|
@ -477,14 +477,13 @@ pub fn get_nullifiers<P>(
|
|||
wdb: &WalletDB<P>,
|
||||
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
|
||||
// Get the nullifiers for the notes we are tracking
|
||||
let mut stmt_fetch_nullifiers = wdb
|
||||
.conn
|
||||
.prepare(
|
||||
"SELECT rn.id_note, rn.account, rn.nf, tx.block as block
|
||||
let mut stmt_fetch_nullifiers = wdb.conn.prepare(
|
||||
"SELECT rn.id_note, rn.account, rn.nf, tx.block as block
|
||||
FROM received_notes rn
|
||||
LEFT OUTER JOIN transactions tx
|
||||
ON tx.id_tx = rn.spent
|
||||
WHERE block IS NULL")?;
|
||||
WHERE block IS NULL",
|
||||
)?;
|
||||
let nullifiers = stmt_fetch_nullifiers.query_map(NO_PARAMS, |row| {
|
||||
let account = AccountId(row.get(1)?);
|
||||
let nf_bytes: Vec<u8> = row.get(2)?;
|
||||
|
|
|
@ -687,7 +687,7 @@ mod tests {
|
|||
let (cb, _) = fake_compact_block(
|
||||
sapling_activation_height(),
|
||||
BlockHash([0; 32]),
|
||||
extfvk.clone(),
|
||||
extfvk,
|
||||
value,
|
||||
);
|
||||
insert_into_cache(&db_cache, &cb);
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
//! `regtest` is a `zcashd`-specific environment used for local testing. They mostly reuse
|
||||
//! the testnet constants.
|
||||
//! These constants are defined in [the `zcashd` codebase].
|
||||
//! [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L482-L496
|
||||
//!
|
||||
//! [the `zcashd` codebase]: <https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L482-L496>
|
||||
|
||||
/// The regtest cointype reuses the testnet cointype
|
||||
pub const COIN_TYPE: u32 = 1;
|
||||
|
@ -13,7 +14,7 @@ pub const COIN_TYPE: u32 = 1;
|
|||
/// It is defined in [the `zcashd` codebase].
|
||||
///
|
||||
/// [`ExtendedSpendingKey`]: crate::zip32::ExtendedSpendingKey
|
||||
/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L496
|
||||
/// [the `zcashd` codebase]: <https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L496>
|
||||
pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest";
|
||||
|
||||
/// The HRP for a Bech32-encoded regtest [`ExtendedFullViewingKey`].
|
||||
|
@ -21,7 +22,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest
|
|||
/// It is defined in [the `zcashd` codebase].
|
||||
///
|
||||
/// [`ExtendedFullViewingKey`]: crate::zip32::ExtendedFullViewingKey
|
||||
/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L494
|
||||
/// [the `zcashd` codebase]: <https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L494>
|
||||
pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling";
|
||||
|
||||
/// The HRP for a Bech32-encoded regtest [`PaymentAddress`].
|
||||
|
@ -29,7 +30,7 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling";
|
|||
/// It is defined in [the `zcashd` codebase].
|
||||
///
|
||||
/// [`PaymentAddress`]: crate::primitives::PaymentAddress
|
||||
/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L493
|
||||
/// [the `zcashd` codebase]: <https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L493>
|
||||
pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zregtestsapling";
|
||||
|
||||
/// The prefix for a Base58Check-encoded regtest [`TransparentAddress::PublicKey`].
|
||||
|
|
|
@ -9,8 +9,8 @@ use std::str;
|
|||
|
||||
/// Format a byte array as a colon-delimited hex string.
|
||||
///
|
||||
/// Source: https://github.com/tendermint/signatory
|
||||
/// License: MIT / Apache 2.0
|
||||
/// - Source: <https://github.com/tendermint/signatory>
|
||||
/// - License: MIT / Apache 2.0
|
||||
fn fmt_colon_delimited_hex<B>(f: &mut fmt::Formatter<'_>, bytes: B) -> fmt::Result
|
||||
where
|
||||
B: AsRef<[u8]>,
|
||||
|
|
|
@ -49,7 +49,7 @@ use crate::prover::mock::MockTxProver;
|
|||
const DEFAULT_TX_EXPIRY_DELTA: u32 = 20;
|
||||
|
||||
/// If there are any shielded inputs, always have at least two shielded outputs, padding
|
||||
/// with dummy outputs if necessary. See https://github.com/zcash/zcash/issues/3615
|
||||
/// with dummy outputs if necessary. See <https://github.com/zcash/zcash/issues/3615>.
|
||||
const MIN_SHIELDED_OUTPUTS: usize = 2;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
|
@ -142,9 +142,9 @@ pub struct TzeIn {
|
|||
pub witness: tze::Witness,
|
||||
}
|
||||
|
||||
/// Transaction encoding and decoding functions conforming to ZIP-222
|
||||
/// Transaction encoding and decoding functions conforming to [ZIP 222].
|
||||
///
|
||||
/// https://zips.z.cash/zip-0222#encoding-in-transactions
|
||||
/// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions
|
||||
#[cfg(feature = "zfuture")]
|
||||
impl TzeIn {
|
||||
/// Convenience constructor
|
||||
|
|
|
@ -64,25 +64,16 @@ macro_rules! update_hash {
|
|||
}
|
||||
|
||||
fn has_overwinter_components(version: &TxVersion) -> bool {
|
||||
match version {
|
||||
TxVersion::Sprout(_) => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(version, TxVersion::Sprout(_))
|
||||
}
|
||||
|
||||
fn has_sapling_components(version: &TxVersion) -> bool {
|
||||
match version {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(version, TxVersion::Sprout(_) | TxVersion::Overwinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
fn has_tze_components(version: &TxVersion) -> bool {
|
||||
match version {
|
||||
TxVersion::ZFuture => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(version, TxVersion::ZFuture)
|
||||
}
|
||||
|
||||
fn prevout_hash(vin: &[TxIn]) -> Blake2bHash {
|
||||
|
|
Loading…
Reference in New Issue