Merge pull request #6073 from str4d/cache-proof-and-sig-validity
Cache Sapling and Orchard bundle validation
This commit is contained in:
commit
0260b7e40b
|
@ -47,16 +47,19 @@ endif
|
||||||
# TODO: Figure out how to avoid an explicit file list.
|
# TODO: Figure out how to avoid an explicit file list.
|
||||||
CXXBRIDGE_RS = \
|
CXXBRIDGE_RS = \
|
||||||
rust/src/blake2b.rs \
|
rust/src/blake2b.rs \
|
||||||
|
rust/src/bundlecache.rs \
|
||||||
rust/src/equihash.rs \
|
rust/src/equihash.rs \
|
||||||
rust/src/orchard_bundle.rs \
|
rust/src/orchard_bundle.rs \
|
||||||
rust/src/sapling.rs
|
rust/src/sapling.rs
|
||||||
CXXBRIDGE_H = \
|
CXXBRIDGE_H = \
|
||||||
rust/gen/include/rust/blake2b.h \
|
rust/gen/include/rust/blake2b.h \
|
||||||
|
rust/gen/include/rust/bundlecache.h \
|
||||||
rust/gen/include/rust/equihash.h \
|
rust/gen/include/rust/equihash.h \
|
||||||
rust/gen/include/rust/orchard_bundle.h \
|
rust/gen/include/rust/orchard_bundle.h \
|
||||||
rust/gen/include/rust/sapling.h
|
rust/gen/include/rust/sapling.h
|
||||||
CXXBRIDGE_CPP = \
|
CXXBRIDGE_CPP = \
|
||||||
rust/gen/src/blake2b.cpp \
|
rust/gen/src/blake2b.cpp \
|
||||||
|
rust/gen/src/bundlecache.cpp \
|
||||||
rust/gen/src/equihash.cpp \
|
rust/gen/src/equihash.cpp \
|
||||||
rust/gen/src/orchard_bundle.cpp \
|
rust/gen/src/orchard_bundle.cpp \
|
||||||
rust/gen/src/sapling.cpp
|
rust/gen/src/sapling.cpp
|
||||||
|
@ -174,6 +177,7 @@ LIBZCASH_H = \
|
||||||
zcash/address/sprout.hpp \
|
zcash/address/sprout.hpp \
|
||||||
zcash/address/unified.h \
|
zcash/address/unified.h \
|
||||||
zcash/address/zip32.h \
|
zcash/address/zip32.h \
|
||||||
|
zcash/cache.h \
|
||||||
zcash/History.hpp \
|
zcash/History.hpp \
|
||||||
zcash/JoinSplit.hpp \
|
zcash/JoinSplit.hpp \
|
||||||
zcash/Note.hpp \
|
zcash/Note.hpp \
|
||||||
|
@ -613,6 +617,7 @@ libzcash_a_SOURCES = \
|
||||||
zcash/address/sprout.cpp \
|
zcash/address/sprout.cpp \
|
||||||
zcash/address/unified.cpp \
|
zcash/address/unified.cpp \
|
||||||
zcash/address/zip32.cpp \
|
zcash/address/zip32.cpp \
|
||||||
|
zcash/cache.cpp \
|
||||||
zcash/History.cpp \
|
zcash/History.cpp \
|
||||||
zcash/JoinSplit.cpp \
|
zcash/JoinSplit.cpp \
|
||||||
zcash/Note.cpp \
|
zcash/Note.cpp \
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
#include <tracing.h>
|
#include <tracing.h>
|
||||||
|
|
||||||
|
#include <rust/bundlecache.h>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
|
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
|
||||||
|
@ -66,7 +68,8 @@ public:
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
assert(sodium_init() != -1);
|
assert(sodium_init() != -1);
|
||||||
ECC_Start();
|
ECC_Start();
|
||||||
InitSignatureCache();
|
InitSignatureCache(DEFAULT_MAX_SIG_CACHE_SIZE * ((size_t) 1 << 20));
|
||||||
|
bundlecache::init(DEFAULT_MAX_SIG_CACHE_SIZE * ((size_t) 1 << 20));
|
||||||
|
|
||||||
// Log all errors to a common test file.
|
// Log all errors to a common test file.
|
||||||
fs::path tmpPath = fs::temp_directory_path();
|
fs::path tmpPath = fs::temp_directory_path();
|
||||||
|
|
|
@ -1325,7 +1325,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
||||||
// Coinbase transaction should pass contextual checks.
|
// Coinbase transaction should pass contextual checks.
|
||||||
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
|
||||||
|
|
||||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator();
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator(false);
|
||||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||||
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
|
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
|
||||||
|
|
||||||
|
|
16
src/init.cpp
16
src/init.cpp
|
@ -66,6 +66,7 @@
|
||||||
#include "zmq/zmqnotificationinterface.h"
|
#include "zmq/zmqnotificationinterface.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <rust/bundlecache.h>
|
||||||
#include <rust/init.h>
|
#include <rust/init.h>
|
||||||
#include <rust/metrics.h>
|
#include <rust/metrics.h>
|
||||||
|
|
||||||
|
@ -465,7 +466,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||||
{
|
{
|
||||||
strUsage += HelpMessageOpt("-limitfreerelay=<n>", strprintf("Continuously rate-limit free transactions to <n>*1000 bytes per minute (default: %u)", DEFAULT_LIMITFREERELAY));
|
strUsage += HelpMessageOpt("-limitfreerelay=<n>", strprintf("Continuously rate-limit free transactions to <n>*1000 bytes per minute (default: %u)", DEFAULT_LIMITFREERELAY));
|
||||||
strUsage += HelpMessageOpt("-relaypriority", strprintf("Require high priority for relaying free or low-fee transactions (default: %u)", DEFAULT_RELAYPRIORITY));
|
strUsage += HelpMessageOpt("-relaypriority", strprintf("Require high priority for relaying free or low-fee transactions (default: %u)", DEFAULT_RELAYPRIORITY));
|
||||||
strUsage += HelpMessageOpt("-maxsigcachesize=<n>", strprintf("Limit size of signature cache to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE));
|
strUsage += HelpMessageOpt("-maxsigcachesize=<n>", strprintf("Limit total size of signature and bundle caches to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE));
|
||||||
strUsage += HelpMessageOpt("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE));
|
strUsage += HelpMessageOpt("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE));
|
||||||
}
|
}
|
||||||
strUsage += HelpMessageOpt("-minrelaytxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)"),
|
strUsage += HelpMessageOpt("-minrelaytxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)"),
|
||||||
|
@ -1410,7 +1411,18 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD);
|
LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD);
|
||||||
std::ostringstream strErrors;
|
std::ostringstream strErrors;
|
||||||
|
|
||||||
InitSignatureCache();
|
// Initialize the validity caches. We currently have three:
|
||||||
|
// - Transparent signature validity.
|
||||||
|
// - Sapling bundle validity.
|
||||||
|
// - Orchard bundle validity.
|
||||||
|
// Assign half of the cap to transparent signatures, and split the rest
|
||||||
|
// between Sapling and Orchard bundles.
|
||||||
|
size_t nMaxCacheSize = GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
|
||||||
|
if (nMaxCacheSize <= 0) {
|
||||||
|
return InitError(strprintf(_("-maxsigcachesize must be at least 1")));
|
||||||
|
}
|
||||||
|
InitSignatureCache(nMaxCacheSize / 2);
|
||||||
|
bundlecache::init(nMaxCacheSize / 4);
|
||||||
|
|
||||||
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
|
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
|
||||||
if (nScriptCheckThreads) {
|
if (nScriptCheckThreads) {
|
||||||
|
|
12
src/main.cpp
12
src/main.cpp
|
@ -1994,11 +1994,11 @@ bool AcceptToMemoryPool(
|
||||||
// This will be a single-transaction batch, which will be more efficient
|
// This will be a single-transaction batch, which will be more efficient
|
||||||
// than unbatched if the transaction contains at least one Sapling Spend
|
// than unbatched if the transaction contains at least one Sapling Spend
|
||||||
// or at least two Sapling Outputs.
|
// or at least two Sapling Outputs.
|
||||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator();
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator(true);
|
||||||
|
|
||||||
// This will be a single-transaction batch, which is still more efficient as every
|
// This will be a single-transaction batch, which is still more efficient as every
|
||||||
// Orchard bundle contains at least two signatures.
|
// Orchard bundle contains at least two signatures.
|
||||||
std::optional<orchard::AuthValidator> orchardAuth = orchard::AuthValidator::Batch();
|
std::optional<orchard::AuthValidator> orchardAuth = orchard::AuthValidator::Batch(true);
|
||||||
|
|
||||||
// Check shielded input signatures.
|
// Check shielded input signatures.
|
||||||
if (!ContextualCheckShieldedInputs(
|
if (!ContextualCheckShieldedInputs(
|
||||||
|
@ -3094,14 +3094,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
fExpensiveChecks = false;
|
fExpensiveChecks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't cache results if we're actually connecting blocks (still consult the cache, though).
|
||||||
|
bool fCacheResults = fJustCheck;
|
||||||
|
|
||||||
// proof verification is expensive, disable if possible
|
// proof verification is expensive, disable if possible
|
||||||
auto verifier = fExpensiveChecks ? ProofVerifier::Strict() : ProofVerifier::Disabled();
|
auto verifier = fExpensiveChecks ? ProofVerifier::Strict() : ProofVerifier::Disabled();
|
||||||
|
|
||||||
// Disable Sapling and Orchard batch validation if possible.
|
// Disable Sapling and Orchard batch validation if possible.
|
||||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = fExpensiveChecks ?
|
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = fExpensiveChecks ?
|
||||||
std::optional(sapling::init_batch_validator()) : std::nullopt;
|
std::optional(sapling::init_batch_validator(fCacheResults)) : std::nullopt;
|
||||||
std::optional<orchard::AuthValidator> orchardAuth = fExpensiveChecks ?
|
std::optional<orchard::AuthValidator> orchardAuth = fExpensiveChecks ?
|
||||||
orchard::AuthValidator::Batch() : orchard::AuthValidator::Disabled();
|
orchard::AuthValidator::Batch(fCacheResults) : orchard::AuthValidator::Disabled();
|
||||||
|
|
||||||
// If in initial block download, and this block is an ancestor of a checkpoint,
|
// If in initial block download, and this block is an ancestor of a checkpoint,
|
||||||
// and -ibdskiptxverification is set, disable all transaction checks.
|
// and -ibdskiptxverification is set, disable all transaction checks.
|
||||||
|
@ -3329,7 +3332,6 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
nFees += view.GetValueIn(tx)-tx.GetValueOut();
|
nFees += view.GetValueIn(tx)-tx.GetValueOut();
|
||||||
|
|
||||||
std::vector<CScriptCheck> vChecks;
|
std::vector<CScriptCheck> vChecks;
|
||||||
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
|
|
||||||
if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, fCacheResults, txdata.back(), chainparams.GetConsensus(), consensusBranchId, nScriptCheckThreads ? &vChecks : NULL))
|
if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, fCacheResults, txdata.back(), chainparams.GetConsensus(), consensusBranchId, nScriptCheckThreads ? &vChecks : NULL))
|
||||||
return error("ConnectBlock(): CheckInputs on %s failed with %s",
|
return error("ConnectBlock(): CheckInputs on %s failed with %s",
|
||||||
tx.GetHash().ToString(), FormatStateMessage(state));
|
tx.GetHash().ToString(), FormatStateMessage(state));
|
||||||
|
|
|
@ -80,7 +80,7 @@ bool orchard_bundle_anchor(
|
||||||
///
|
///
|
||||||
/// Please free this with `orchard_batch_validation_free` when you are done with
|
/// Please free this with `orchard_batch_validation_free` when you are done with
|
||||||
/// it.
|
/// it.
|
||||||
OrchardBatchValidatorPtr* orchard_batch_validation_init();
|
OrchardBatchValidatorPtr* orchard_batch_validation_init(bool cache_store);
|
||||||
|
|
||||||
/// Frees a batch validator returned from `orchard_batch_validation_init`.
|
/// Frees a batch validator returned from `orchard_batch_validation_init`.
|
||||||
void orchard_batch_validation_free(OrchardBatchValidatorPtr* batch);
|
void orchard_batch_validation_free(OrchardBatchValidatorPtr* batch);
|
||||||
|
@ -153,9 +153,9 @@ public:
|
||||||
|
|
||||||
/// Creates a validation context that batch-validates Orchard proofs and
|
/// Creates a validation context that batch-validates Orchard proofs and
|
||||||
/// signatures.
|
/// signatures.
|
||||||
static AuthValidator Batch() {
|
static AuthValidator Batch(bool cacheResult) {
|
||||||
auto batch = AuthValidator();
|
auto batch = AuthValidator();
|
||||||
batch.inner.reset(orchard_batch_validation_init());
|
batch.inner.reset(orchard_batch_validation_init(cacheResult));
|
||||||
return batch;
|
return batch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
use std::{
|
||||||
|
convert::TryInto,
|
||||||
|
sync::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand_core::{OsRng, RngCore};
|
||||||
|
|
||||||
|
#[cxx::bridge]
|
||||||
|
mod ffi {
|
||||||
|
#[namespace = "libzcash"]
|
||||||
|
unsafe extern "C++" {
|
||||||
|
include!("zcash/cache.h");
|
||||||
|
|
||||||
|
type BundleValidityCache;
|
||||||
|
|
||||||
|
fn NewBundleValidityCache(kind: &str, bytes: usize) -> UniquePtr<BundleValidityCache>;
|
||||||
|
fn insert(self: Pin<&mut BundleValidityCache>, entry: [u8; 32]);
|
||||||
|
fn contains(&self, entry: &[u8; 32], erase: bool) -> bool;
|
||||||
|
}
|
||||||
|
#[namespace = "bundlecache"]
|
||||||
|
extern "Rust" {
|
||||||
|
fn init(cache_bytes: usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CacheEntry([u8; 32]);
|
||||||
|
|
||||||
|
pub(crate) enum CacheEntries {
|
||||||
|
Storing(Vec<CacheEntry>),
|
||||||
|
NotStoring,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CacheEntries {
|
||||||
|
pub(crate) fn new(cache_store: bool) -> Self {
|
||||||
|
if cache_store {
|
||||||
|
CacheEntries::Storing(vec![])
|
||||||
|
} else {
|
||||||
|
CacheEntries::NotStoring
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct BundleValidityCache {
|
||||||
|
hasher: blake2b_simd::State,
|
||||||
|
cache: cxx::UniquePtr<ffi::BundleValidityCache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BundleValidityCache {
|
||||||
|
fn new(kind: &'static str, personalization: &[u8; 16], cache_bytes: usize) -> Self {
|
||||||
|
// Use BLAKE2b to produce entries from bundles. It has a block size of 128 bytes,
|
||||||
|
// into which we put:
|
||||||
|
// - 32 byte nonce
|
||||||
|
// - 64 bytes of bundle commitments
|
||||||
|
// - 32 byte sighash
|
||||||
|
let mut hasher = blake2b_simd::Params::new()
|
||||||
|
.hash_length(32)
|
||||||
|
.personal(personalization)
|
||||||
|
.to_state();
|
||||||
|
|
||||||
|
// Pre-load the hasher with a per-instance nonce. This ensures that cache entries
|
||||||
|
// are deterministic but also unique per node.
|
||||||
|
let mut nonce = [0; 32];
|
||||||
|
OsRng.fill_bytes(&mut nonce);
|
||||||
|
hasher.update(&nonce);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
hasher,
|
||||||
|
cache: ffi::NewBundleValidityCache(kind, cache_bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compute_entry(
|
||||||
|
&self,
|
||||||
|
bundle_commitment: &[u8; 32],
|
||||||
|
bundle_authorizing_commitment: &[u8; 32],
|
||||||
|
sighash: &[u8; 32],
|
||||||
|
) -> CacheEntry {
|
||||||
|
self.hasher
|
||||||
|
.clone()
|
||||||
|
.update(bundle_commitment)
|
||||||
|
.update(bundle_authorizing_commitment)
|
||||||
|
.update(sighash)
|
||||||
|
.finalize()
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.map(CacheEntry)
|
||||||
|
.expect("BLAKE2b configured with hash length of 32 so conversion cannot fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert(&mut self, queued_entries: CacheEntries) {
|
||||||
|
if let CacheEntries::Storing(cache_entries) = queued_entries {
|
||||||
|
for cache_entry in cache_entries {
|
||||||
|
self.cache.pin_mut().insert(cache_entry.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains(&self, entry: CacheEntry, queued_entries: &mut CacheEntries) -> bool {
|
||||||
|
if self
|
||||||
|
.cache
|
||||||
|
.contains(&entry.0, matches!(queued_entries, CacheEntries::NotStoring))
|
||||||
|
{
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
if let CacheEntries::Storing(cache_entries) = queued_entries {
|
||||||
|
cache_entries.push(entry);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static BUNDLE_CACHES_LOADED: Once = Once::new();
|
||||||
|
static mut SAPLING_BUNDLE_VALIDITY_CACHE: Option<RwLock<BundleValidityCache>> = None;
|
||||||
|
static mut ORCHARD_BUNDLE_VALIDITY_CACHE: Option<RwLock<BundleValidityCache>> = None;
|
||||||
|
|
||||||
|
fn init(cache_bytes: usize) {
|
||||||
|
BUNDLE_CACHES_LOADED.call_once(|| unsafe {
|
||||||
|
SAPLING_BUNDLE_VALIDITY_CACHE = Some(RwLock::new(BundleValidityCache::new(
|
||||||
|
"Sapling",
|
||||||
|
b"SaplingVeriCache",
|
||||||
|
cache_bytes,
|
||||||
|
)));
|
||||||
|
ORCHARD_BUNDLE_VALIDITY_CACHE = Some(RwLock::new(BundleValidityCache::new(
|
||||||
|
"Orchard",
|
||||||
|
b"OrchardVeriCache",
|
||||||
|
cache_bytes,
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sapling_bundle_validity_cache() -> RwLockReadGuard<'static, BundleValidityCache> {
|
||||||
|
unsafe { SAPLING_BUNDLE_VALIDITY_CACHE.as_ref() }
|
||||||
|
.expect("bundlecache::init() should have been called")
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sapling_bundle_validity_cache_mut() -> RwLockWriteGuard<'static, BundleValidityCache>
|
||||||
|
{
|
||||||
|
unsafe { SAPLING_BUNDLE_VALIDITY_CACHE.as_mut() }
|
||||||
|
.expect("bundlecache::init() should have been called")
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn orchard_bundle_validity_cache() -> RwLockReadGuard<'static, BundleValidityCache> {
|
||||||
|
unsafe { ORCHARD_BUNDLE_VALIDITY_CACHE.as_ref() }
|
||||||
|
.expect("bundlecache::init() should have been called")
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn orchard_bundle_validity_cache_mut() -> RwLockWriteGuard<'static, BundleValidityCache>
|
||||||
|
{
|
||||||
|
unsafe { ORCHARD_BUNDLE_VALIDITY_CACHE.as_mut() }
|
||||||
|
.expect("bundlecache::init() should have been called")
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
use std::{mem, ptr};
|
use std::{convert::TryInto, mem, ptr};
|
||||||
|
|
||||||
use libc::size_t;
|
use libc::size_t;
|
||||||
use memuse::DynamicUsage;
|
use memuse::DynamicUsage;
|
||||||
use orchard::{
|
use orchard::{
|
||||||
bundle::{Authorized, BatchValidator},
|
bundle::Authorized, keys::OutgoingViewingKey, note_encryption::OrchardDomain, Bundle,
|
||||||
keys::OutgoingViewingKey,
|
|
||||||
note_encryption::OrchardDomain,
|
|
||||||
Bundle,
|
|
||||||
};
|
};
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
use zcash_note_encryption::try_output_recovery_with_ovk;
|
use zcash_note_encryption::try_output_recovery_with_ovk;
|
||||||
use zcash_primitives::transaction::components::{orchard as orchard_serialization, Amount};
|
use zcash_primitives::transaction::components::{orchard as orchard_serialization, Amount};
|
||||||
|
|
||||||
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
|
use crate::{
|
||||||
|
bundlecache::{orchard_bundle_validity_cache, orchard_bundle_validity_cache_mut, CacheEntries},
|
||||||
|
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
|
||||||
|
};
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_bundle_clone(
|
pub extern "C" fn orchard_bundle_clone(
|
||||||
|
@ -145,12 +145,20 @@ pub extern "C" fn orchard_bundle_anchor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BatchValidator {
|
||||||
|
validator: orchard::bundle::BatchValidator,
|
||||||
|
queued_entries: CacheEntries,
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates an Orchard bundle batch validation context.
|
/// Creates an Orchard bundle batch validation context.
|
||||||
///
|
///
|
||||||
/// Please free this when you're done.
|
/// Please free this when you're done.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_batch_validation_init() -> *mut BatchValidator {
|
pub extern "C" fn orchard_batch_validation_init(cache_store: bool) -> *mut BatchValidator {
|
||||||
let ctx = Box::new(BatchValidator::new());
|
let ctx = Box::new(BatchValidator {
|
||||||
|
validator: orchard::bundle::BatchValidator::new(),
|
||||||
|
queued_entries: CacheEntries::new(cache_store),
|
||||||
|
});
|
||||||
Box::into_raw(ctx)
|
Box::into_raw(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +183,31 @@ pub extern "C" fn orchard_batch_add_bundle(
|
||||||
let sighash = unsafe { sighash.as_ref() };
|
let sighash = unsafe { sighash.as_ref() };
|
||||||
|
|
||||||
match (batch, bundle, sighash) {
|
match (batch, bundle, sighash) {
|
||||||
(Some(batch), Some(bundle), Some(sighash)) => batch.add_bundle(bundle, *sighash),
|
(Some(batch), Some(bundle), Some(sighash)) => {
|
||||||
|
let cache = orchard_bundle_validity_cache();
|
||||||
|
|
||||||
|
// Compute the cache entry for this bundle.
|
||||||
|
let cache_entry = {
|
||||||
|
let bundle_commitment = bundle.commitment();
|
||||||
|
let bundle_authorizing_commitment = bundle.authorizing_commitment();
|
||||||
|
cache.compute_entry(
|
||||||
|
bundle_commitment.0.as_bytes().try_into().unwrap(),
|
||||||
|
bundle_authorizing_commitment
|
||||||
|
.0
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
sighash,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if this bundle's validation result exists in the cache.
|
||||||
|
if !cache.contains(cache_entry, &mut batch.queued_entries) {
|
||||||
|
// The bundle has been added to `inner.queued_entries` because it was not
|
||||||
|
// in the cache. We now add its authorization to the validation batch.
|
||||||
|
batch.validator.add_bundle(bundle, *sighash);
|
||||||
|
}
|
||||||
|
}
|
||||||
(_, _, None) => error!("orchard_batch_add_bundle() called without sighash!"),
|
(_, _, None) => error!("orchard_batch_add_bundle() called without sighash!"),
|
||||||
(Some(_), None, Some(_)) => debug!("Tx has no Orchard component"),
|
(Some(_), None, Some(_)) => debug!("Tx has no Orchard component"),
|
||||||
(None, Some(_), _) => debug!("Orchard BatchValidator not provided, assuming disabled."),
|
(None, Some(_), _) => debug!("Orchard BatchValidator not provided, assuming disabled."),
|
||||||
|
@ -205,7 +237,16 @@ pub extern "C" fn orchard_batch_validate(batch: *mut BatchValidator) -> bool {
|
||||||
let batch = unsafe { Box::from_raw(batch) };
|
let batch = unsafe { Box::from_raw(batch) };
|
||||||
let vk =
|
let vk =
|
||||||
unsafe { crate::ORCHARD_VK.as_ref() }.expect("ORCHARD_VK should have been initialized");
|
unsafe { crate::ORCHARD_VK.as_ref() }.expect("ORCHARD_VK should have been initialized");
|
||||||
batch.validate(vk, OsRng)
|
if batch.validator.validate(vk, OsRng) {
|
||||||
|
// `BatchValidator::validate()` is only called if every
|
||||||
|
// `BatchValidator::check_bundle()` returned `true`, so at this point
|
||||||
|
// every bundle that was added to `inner.queued_entries` has valid
|
||||||
|
// authorization.
|
||||||
|
orchard_bundle_validity_cache_mut().insert(batch.queued_entries);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// The orchard::BatchValidator C++ class uses null to represent a disabled batch
|
// The orchard::BatchValidator C++ class uses null to represent a disabled batch
|
||||||
// validator.
|
// validator.
|
||||||
|
|
|
@ -69,6 +69,7 @@ mod zcashd_orchard;
|
||||||
|
|
||||||
mod address_ffi;
|
mod address_ffi;
|
||||||
mod builder_ffi;
|
mod builder_ffi;
|
||||||
|
mod bundlecache;
|
||||||
mod history_ffi;
|
mod history_ffi;
|
||||||
mod incremental_merkle_tree;
|
mod incremental_merkle_tree;
|
||||||
mod incremental_merkle_tree_ffi;
|
mod incremental_merkle_tree_ffi;
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
// on the entire module.
|
// on the entire module.
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use bellman::groth16::{prepare_verifying_key, Proof};
|
use bellman::groth16::{prepare_verifying_key, Proof};
|
||||||
use group::GroupEncoding;
|
use group::GroupEncoding;
|
||||||
|
|
||||||
|
@ -17,12 +19,19 @@ use zcash_primitives::{
|
||||||
redjubjub::{self, Signature},
|
redjubjub::{self, Signature},
|
||||||
Nullifier,
|
Nullifier,
|
||||||
},
|
},
|
||||||
transaction::components::{sapling, Amount},
|
transaction::{
|
||||||
|
components::{sapling, Amount},
|
||||||
|
txid::{BlockTxCommitmentDigester, TxIdDigester},
|
||||||
|
Authorized, TransactionDigest,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use zcash_proofs::sapling::{self as sapling_proofs, SaplingVerificationContext};
|
use zcash_proofs::sapling::{self as sapling_proofs, SaplingVerificationContext};
|
||||||
|
|
||||||
use super::GROTH_PROOF_SIZE;
|
use super::GROTH_PROOF_SIZE;
|
||||||
use super::{de_ct, SAPLING_OUTPUT_VK, SAPLING_SPEND_VK};
|
use super::{de_ct, SAPLING_OUTPUT_VK, SAPLING_SPEND_VK};
|
||||||
|
use crate::bundlecache::{
|
||||||
|
sapling_bundle_validity_cache, sapling_bundle_validity_cache_mut, CacheEntries,
|
||||||
|
};
|
||||||
|
|
||||||
#[cxx::bridge(namespace = "sapling")]
|
#[cxx::bridge(namespace = "sapling")]
|
||||||
mod ffi {
|
mod ffi {
|
||||||
|
@ -82,7 +91,7 @@ mod ffi {
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
|
||||||
type BatchValidator;
|
type BatchValidator;
|
||||||
fn init_batch_validator() -> Box<BatchValidator>;
|
fn init_batch_validator(cache_store: bool) -> Box<BatchValidator>;
|
||||||
fn check_bundle(self: &mut BatchValidator, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool;
|
fn check_bundle(self: &mut BatchValidator, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool;
|
||||||
fn validate(self: &mut BatchValidator) -> bool;
|
fn validate(self: &mut BatchValidator) -> bool;
|
||||||
}
|
}
|
||||||
|
@ -90,6 +99,12 @@ mod ffi {
|
||||||
|
|
||||||
struct Bundle(sapling::Bundle<sapling::Authorized>);
|
struct Bundle(sapling::Bundle<sapling::Authorized>);
|
||||||
|
|
||||||
|
impl Bundle {
|
||||||
|
fn commitment<D: TransactionDigest<Authorized>>(&self, digester: D) -> D::SaplingDigest {
|
||||||
|
digester.digest_sapling(Some(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct BundleAssembler {
|
struct BundleAssembler {
|
||||||
shielded_spends: Vec<sapling::SpendDescription<sapling::Authorized>>,
|
shielded_spends: Vec<sapling::SpendDescription<sapling::Authorized>>,
|
||||||
shielded_outputs: Vec<sapling::OutputDescription<[u8; 192]>>, // GROTH_PROOF_SIZE
|
shielded_outputs: Vec<sapling::OutputDescription<[u8; 192]>>, // GROTH_PROOF_SIZE
|
||||||
|
@ -325,10 +340,18 @@ impl Verifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BatchValidator(Option<sapling_proofs::BatchValidator>);
|
struct BatchValidatorInner {
|
||||||
|
validator: sapling_proofs::BatchValidator,
|
||||||
|
queued_entries: CacheEntries,
|
||||||
|
}
|
||||||
|
|
||||||
fn init_batch_validator() -> Box<BatchValidator> {
|
struct BatchValidator(Option<BatchValidatorInner>);
|
||||||
Box::new(BatchValidator(Some(sapling_proofs::BatchValidator::new())))
|
|
||||||
|
fn init_batch_validator(cache_store: bool) -> Box<BatchValidator> {
|
||||||
|
Box::new(BatchValidator(Some(BatchValidatorInner {
|
||||||
|
validator: sapling_proofs::BatchValidator::new(),
|
||||||
|
queued_entries: CacheEntries::new(cache_store),
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatchValidator {
|
impl BatchValidator {
|
||||||
|
@ -341,23 +364,70 @@ impl BatchValidator {
|
||||||
/// it fails other consensus rules.
|
/// it fails other consensus rules.
|
||||||
///
|
///
|
||||||
/// `sighash` must be for the transaction this bundle is within.
|
/// `sighash` must be for the transaction this bundle is within.
|
||||||
|
///
|
||||||
|
/// If this batch was configured to not cache the results, then if the bundle was in
|
||||||
|
/// the global bundle validity cache, it will have been removed (and this method will
|
||||||
|
/// return `true`).
|
||||||
#[allow(clippy::boxed_local)]
|
#[allow(clippy::boxed_local)]
|
||||||
fn check_bundle(&mut self, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool {
|
fn check_bundle(&mut self, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool {
|
||||||
if let Some(validator) = &mut self.0 {
|
if let Some(inner) = &mut self.0 {
|
||||||
validator.check_bundle(bundle.0, sighash)
|
let cache = sapling_bundle_validity_cache();
|
||||||
|
|
||||||
|
// Compute the cache entry for this bundle.
|
||||||
|
let cache_entry = {
|
||||||
|
let bundle_commitment = bundle.commitment(TxIdDigester).unwrap();
|
||||||
|
let bundle_authorizing_commitment = bundle.commitment(BlockTxCommitmentDigester);
|
||||||
|
cache.compute_entry(
|
||||||
|
bundle_commitment.as_bytes().try_into().unwrap(),
|
||||||
|
bundle_authorizing_commitment.as_bytes().try_into().unwrap(),
|
||||||
|
&sighash,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if this bundle's validation result exists in the cache.
|
||||||
|
if cache.contains(cache_entry, &mut inner.queued_entries) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// The bundle has been added to `inner.queued_entries` because it was not
|
||||||
|
// in the cache. We now check the bundle against the Sapling-specific
|
||||||
|
// consensus rules, and add its authorization to the validation batch.
|
||||||
|
inner.validator.check_bundle(bundle.0, sighash)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::error!("sapling::BatchValidator has already been used");
|
tracing::error!("sapling::BatchValidator has already been used");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Batch-validates the accumulated bundles.
|
||||||
|
///
|
||||||
|
/// Returns `true` if every proof and signature in every bundle added to the batch
|
||||||
|
/// validator is valid, or `false` if one or more are invalid. No attempt is made to
|
||||||
|
/// figure out which of the accumulated bundles might be invalid; if that information
|
||||||
|
/// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles.
|
||||||
|
///
|
||||||
|
/// This method MUST NOT be called if any prior call to `Self::check_bundle` returned
|
||||||
|
/// `false`.
|
||||||
|
///
|
||||||
|
/// If this batch was configured to cache the results, then if this method returns
|
||||||
|
/// `true` every bundle added to the batch will have also been added to the global
|
||||||
|
/// bundle validity cache.
|
||||||
fn validate(&mut self) -> bool {
|
fn validate(&mut self) -> bool {
|
||||||
if let Some(validator) = self.0.take() {
|
if let Some(inner) = self.0.take() {
|
||||||
validator.validate(
|
if inner.validator.validate(
|
||||||
unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(),
|
unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(),
|
||||||
unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap(),
|
unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap(),
|
||||||
OsRng,
|
OsRng,
|
||||||
)
|
) {
|
||||||
|
// `Self::validate()` is only called if every `Self::check_bundle()`
|
||||||
|
// returned `true`, so at this point every bundle that was added to
|
||||||
|
// `inner.queued_entries` has valid authorization and satisfies the
|
||||||
|
// Sapling-specific consensus rules.
|
||||||
|
sapling_bundle_validity_cache_mut().insert(inner.queued_entries);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::error!("sapling::BatchValidator has already been used");
|
tracing::error!("sapling::BatchValidator has already been used");
|
||||||
false
|
false
|
||||||
|
|
|
@ -70,9 +70,8 @@ static CSignatureCache signatureCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To be called once in AppInit2/TestingSetup to initialize the signatureCache
|
// To be called once in AppInit2/TestingSetup to initialize the signatureCache
|
||||||
void InitSignatureCache()
|
void InitSignatureCache(size_t nMaxCacheSize)
|
||||||
{
|
{
|
||||||
size_t nMaxCacheSize = GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
|
|
||||||
if (nMaxCacheSize <= 0) return;
|
if (nMaxCacheSize <= 0) return;
|
||||||
size_t nElems = signatureCache.setup_bytes(nMaxCacheSize);
|
size_t nElems = signatureCache.setup_bytes(nMaxCacheSize);
|
||||||
LogPrintf("Using %zu MiB out of %zu requested for signature cache, able to store %zu elements\n",
|
LogPrintf("Using %zu MiB out of %zu requested for signature cache, able to store %zu elements\n",
|
||||||
|
|
|
@ -50,6 +50,6 @@ public:
|
||||||
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
void InitSignatureCache();
|
void InitSignatureCache(size_t nMaxCacheSize);
|
||||||
|
|
||||||
#endif // BITCOIN_SCRIPT_SIGCACHE_H
|
#endif // BITCOIN_SCRIPT_SIGCACHE_H
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
|
|
||||||
#include "librustzcash.h"
|
#include "librustzcash.h"
|
||||||
|
|
||||||
|
#include <rust/bundlecache.h>
|
||||||
|
|
||||||
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
|
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
|
||||||
|
|
||||||
CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h
|
CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h
|
||||||
|
@ -50,7 +52,8 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName)
|
||||||
ECC_Start();
|
ECC_Start();
|
||||||
SetupEnvironment();
|
SetupEnvironment();
|
||||||
SetupNetworking();
|
SetupNetworking();
|
||||||
InitSignatureCache();
|
InitSignatureCache(DEFAULT_MAX_SIG_CACHE_SIZE * ((size_t) 1 << 20));
|
||||||
|
bundlecache::init(DEFAULT_MAX_SIG_CACHE_SIZE * ((size_t) 1 << 20));
|
||||||
|
|
||||||
// Uncomment this to log all errors to stdout so we see them in test output.
|
// Uncomment this to log all errors to stdout so we see them in test output.
|
||||||
// We don't enable this by default because several tests intentionally cause
|
// We don't enable this by default because several tests intentionally cause
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright (c) 2022 The Zcash developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "zcash/cache.h"
|
||||||
|
#include "util/system.h"
|
||||||
|
|
||||||
|
namespace libzcash
|
||||||
|
{
|
||||||
|
std::unique_ptr<BundleValidityCache> NewBundleValidityCache(rust::Str kind, size_t nMaxCacheSize)
|
||||||
|
{
|
||||||
|
auto cache = std::unique_ptr<BundleValidityCache>(new BundleValidityCache());
|
||||||
|
size_t nElems = cache->setup_bytes(nMaxCacheSize);
|
||||||
|
LogPrintf("Using %zu MiB out of %zu requested for %s bundle cache, able to store %zu elements\n",
|
||||||
|
(nElems * sizeof(BundleCacheEntry)) >> 20, nMaxCacheSize >> 20, kind, nElems);
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
} // namespace libzcash
|
||||||
|
|
||||||
|
// Explicit instantiations for libzcash::BundleValidityCache
|
||||||
|
template void libzcash::BundleValidityCache::insert(libzcash::BundleCacheEntry e);
|
||||||
|
template bool libzcash::BundleValidityCache::contains(const libzcash::BundleCacheEntry& e, const bool erase) const;
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) 2022 The Zcash developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef ZCASH_ZCASH_CACHE_H
|
||||||
|
#define ZCASH_ZCASH_CACHE_H
|
||||||
|
|
||||||
|
#include "cuckoocache.h"
|
||||||
|
|
||||||
|
#include <rust/cxx.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace libzcash
|
||||||
|
{
|
||||||
|
typedef std::array<uint8_t, 32> BundleCacheEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We're hashing a nonce into the entries themselves, so we don't need extra
|
||||||
|
* blinding in the set hash computation.
|
||||||
|
*
|
||||||
|
* This may exhibit platform endian dependent behavior but because these are
|
||||||
|
* nonced hashes (random) and this state is only ever used locally it is safe.
|
||||||
|
* All that matters is local consistency.
|
||||||
|
*/
|
||||||
|
class BundleCacheHasher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <uint8_t hash_select>
|
||||||
|
uint32_t operator()(const BundleCacheEntry& key) const
|
||||||
|
{
|
||||||
|
static_assert(hash_select < 8, "BundleCacheHasher only has 8 hashes available.");
|
||||||
|
uint32_t u;
|
||||||
|
std::memcpy(&u, key.begin() + 4 * hash_select, 4);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef CuckooCache::cache<BundleCacheEntry, BundleCacheHasher> BundleValidityCache;
|
||||||
|
|
||||||
|
std::unique_ptr<BundleValidityCache> NewBundleValidityCache(rust::Str kind, size_t nMaxCacheSize);
|
||||||
|
} // namespace libzcash
|
||||||
|
|
||||||
|
#endif // ZCASH_ZCASH_CACHE_H
|
Loading…
Reference in New Issue