cache transaction outputs during block accept

This commit is contained in:
Svyatoslav Nikolsky 2019-04-05 10:45:29 +03:00
parent 0f6ff605d7
commit d0b0249ca2
4 changed files with 131 additions and 27 deletions

View File

@ -42,7 +42,9 @@ pub use duplex_store::{DuplexTransactionOutputProvider, NoopStore};
pub use error::Error;
pub use store::{AsSubstore, Store, SharedStore, CanonStore};
pub use transaction_meta::TransactionMeta;
pub use transaction_provider::{TransactionProvider, TransactionOutputProvider, TransactionMetaProvider};
pub use transaction_provider::{
TransactionProvider, TransactionOutputProvider, TransactionMetaProvider, CachedTransactionOutputProvider,
};
pub use nullifier_tracker::NullifierTracker;
pub use tree_state::{TreeState, H32 as H32TreeDim, Dim as TreeDim, SproutTreeState, SaplingTreeState};
pub use tree_state_provider::TreeStateProvider;

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use parking_lot::RwLock;
use hash::H256;
use bytes::Bytes;
use chain::{IndexedTransaction, OutPoint, TransactionOutput};
@ -32,3 +34,40 @@ pub trait TransactionMetaProvider: Send + Sync {
/// Otherwise returns transaction meta object
fn transaction_meta(&self, hash: &H256) -> Option<TransactionMeta>;
}
/// Transaction output provider that caches all read outputs.
///
/// Not intended for long-lasting life, because it never clears its internal
/// cache. The backing storage is considered readonly for the cache lifetime.
pub struct CachedTransactionOutputProvider<'a> {
backend: &'a TransactionOutputProvider,
cached_outputs: RwLock<HashMap<OutPoint, Option<TransactionOutput>>>,
}
impl<'a> CachedTransactionOutputProvider<'a> {
/// Create new cached tx output provider backed by passed provider.
pub fn new(backend: &'a TransactionOutputProvider) -> Self {
CachedTransactionOutputProvider {
backend,
cached_outputs: RwLock::new(HashMap::new()),
}
}
}
impl<'a> TransactionOutputProvider for CachedTransactionOutputProvider<'a> {
fn transaction_output(&self, outpoint: &OutPoint, transaction_index: usize) -> Option<TransactionOutput> {
let cached_value = self.cached_outputs.read().get(outpoint).cloned();
match cached_value {
Some(cached_value) => cached_value,
None => {
let value_from_backend = self.backend.transaction_output(outpoint, transaction_index);
self.cached_outputs.write().insert(outpoint.clone(), value_from_backend.clone());
value_from_backend
},
}
}
fn is_spent(&self, outpoint: &OutPoint) -> bool {
self.backend.is_spent(outpoint)
}
}

View File

@ -1,5 +1,8 @@
use rayon::prelude::{IntoParallelRefIterator, IndexedParallelIterator, ParallelIterator};
use storage::{DuplexTransactionOutputProvider, Store};
use storage::{
DuplexTransactionOutputProvider, TransactionOutputProvider, TransactionMetaProvider,
BlockHeaderProvider, TreeStateProvider, NullifierTracker,
};
use network::ConsensusParams;
use error::Error;
use canon::CanonBlock;
@ -16,29 +19,40 @@ pub struct ChainAcceptor<'a> {
}
impl<'a> ChainAcceptor<'a> {
pub fn new(store: &'a Store, consensus: &'a ConsensusParams, verification_level: VerificationLevel, block: CanonBlock<'a>, height: u32, time: u32, deployments: &'a BlockDeployments) -> Self {
pub fn new(
tx_out_provider: &'a TransactionOutputProvider,
tx_meta_provider: &'a TransactionMetaProvider,
header_provider: &'a BlockHeaderProvider,
tree_state_provider: &'a TreeStateProvider,
nullifier_tracker: &'a NullifierTracker,
consensus: &'a ConsensusParams,
verification_level: VerificationLevel,
block: CanonBlock<'a>,
height: u32,
time: u32,
deployments: &'a BlockDeployments,
) -> Self {
trace!(target: "verification", "Block verification {}", block.hash().to_reversed_str());
let output_store = DuplexTransactionOutputProvider::new(store.as_transaction_output_provider(), block.raw());
let headers = store.as_block_header_provider();
let output_store = DuplexTransactionOutputProvider::new(tx_out_provider, block.raw());
ChainAcceptor {
block: BlockAcceptor::new(
store.as_transaction_output_provider(),
store.as_tree_state_provider(),
tx_out_provider,
tree_state_provider,
consensus,
block,
height,
deployments,
headers,
header_provider,
),
header: HeaderAcceptor::new(headers, consensus, block.header(), height, time, deployments),
header: HeaderAcceptor::new(header_provider, consensus, block.header(), height, time, deployments),
transactions: block.transactions()
.into_iter()
.enumerate()
.map(|(tx_index, tx)| TransactionAcceptor::new(
store.as_transaction_meta_provider(),
tx_meta_provider,
output_store,
store.as_nullifier_tracker(),
nullifier_tracker,
consensus,
tx,
verification_level,

View File

@ -2,7 +2,7 @@
use chain::{IndexedBlock, IndexedBlockHeader, IndexedTransaction};
use storage::{SharedStore, TransactionOutputProvider, BlockHeaderProvider, BlockOrigin,
DuplexTransactionOutputProvider, NoopStore};
DuplexTransactionOutputProvider, NoopStore, CachedTransactionOutputProvider};
use network::ConsensusParams;
use error::{Error, TransactionError};
use canon::{CanonBlock, CanonTransaction};
@ -41,42 +41,91 @@ impl BackwardsCompatibleChainVerifier {
assert_eq!(Some(self.store.best_block().hash), self.store.block_hash(self.store.best_block().number));
let block_origin = self.store.block_origin(&block.header)?;
trace!(target: "verification", "verify_block: {:?} best_block: {:?} block_origin: {:?}", block.hash().reversed(), self.store.best_block(), block_origin);
trace!(
target: "verification",
"verify_block: {:?} best_block: {:?} block_origin: {:?}",
block.hash().reversed(),
self.store.best_block(),
block_origin,
);
let canon_block = CanonBlock::new(block);
match block_origin {
BlockOrigin::KnownBlock => {
// there should be no known blocks at this point
unreachable!();
},
BlockOrigin::CanonChain { block_number } => {
let tx_out_provider = CachedTransactionOutputProvider::new(self.store.as_store().as_transaction_output_provider());
let tx_meta_provider = self.store.as_store().as_transaction_meta_provider();
let header_provider = self.store.as_store().as_block_header_provider();
let tree_state_provider = self.store.as_store().as_tree_state_provider();
let nullifier_tracker = self.store.as_store().as_nullifier_tracker();
let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus);
let canon_block = CanonBlock::new(block);
let chain_acceptor = ChainAcceptor::new(self.store.as_store(), &self.consensus, verification_level,
canon_block, block_number, block.header.raw.time, &deployments);
let chain_acceptor = ChainAcceptor::new(
&tx_out_provider,
tx_meta_provider,
header_provider,
tree_state_provider,
nullifier_tracker,
&self.consensus,
verification_level,
canon_block,
block_number,
block.header.raw.time,
&deployments,
);
chain_acceptor.check()?;
},
BlockOrigin::SideChain(origin) => {
let block_number = origin.block_number;
let header_provider = self.store.as_store().as_block_header_provider();
let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus);
let fork = self.store.fork(origin)?;
let canon_block = CanonBlock::new(block);
let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block,
block_number, block.header.raw.time, &deployments);
let tx_out_provider = CachedTransactionOutputProvider::new(fork.store().as_transaction_output_provider());
let tx_meta_provider = fork.store().as_transaction_meta_provider();
let header_provider = fork.store().as_block_header_provider();
let tree_state_provider = fork.store().as_tree_state_provider();
let nullifier_tracker = fork.store().as_nullifier_tracker();
let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus);
let chain_acceptor = ChainAcceptor::new(
&tx_out_provider,
tx_meta_provider,
header_provider,
tree_state_provider,
nullifier_tracker,
&self.consensus,
verification_level,
canon_block,
block_number,
block.header.raw.time,
&deployments,
);
chain_acceptor.check()?;
},
BlockOrigin::SideChainBecomingCanonChain(origin) => {
let block_number = origin.block_number;
let header_provider = self.store.as_store().as_block_header_provider();
let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus);
let fork = self.store.fork(origin)?;
let canon_block = CanonBlock::new(block);
let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block,
block_number, block.header.raw.time, &deployments);
let tx_out_provider = CachedTransactionOutputProvider::new(fork.store().as_transaction_output_provider());
let tx_meta_provider = fork.store().as_transaction_meta_provider();
let header_provider = fork.store().as_block_header_provider();
let tree_state_provider = fork.store().as_tree_state_provider();
let nullifier_tracker = fork.store().as_nullifier_tracker();
let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus);
let chain_acceptor = ChainAcceptor::new(
&tx_out_provider,
tx_meta_provider,
header_provider,
tree_state_provider,
nullifier_tracker,
&self.consensus,
verification_level,
canon_block,
block_number,
block.header.raw.time,
&deployments,
);
chain_acceptor.check()?;
},
}
};
assert_eq!(Some(self.store.best_block().hash), self.store.block_hash(self.store.best_block().number));
Ok(())