use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use tokio::{
sync::{mpsc, oneshot},
time,
};
use zcash_primitives::{
block::{Block, BlockHeader},
transaction::Transaction,
};
use zcash_protocol::consensus::{self, BlockHeight};
use crate::scanning::{
ScanningKeys,
full::{BatchResult, BatchRunners, DEFAULT_BATCH_SIZE_THRESHOLD},
};
const DEFAULT_QUEUE_SIZE: usize = 1000;
const DEFAULT_BATCH_START_DELAY: Duration = Duration::from_millis(500);
pub fn new() -> Builder {
Builder {
queue_size: DEFAULT_QUEUE_SIZE,
sapling_batch_size_threshold: DEFAULT_BATCH_SIZE_THRESHOLD,
#[cfg(feature = "orchard")]
orchard_batch_size_threshold: DEFAULT_BATCH_SIZE_THRESHOLD,
batch_start_delay: DEFAULT_BATCH_START_DELAY,
}
}
pub struct Builder {
queue_size: usize,
sapling_batch_size_threshold: usize,
#[cfg(feature = "orchard")]
orchard_batch_size_threshold: usize,
batch_start_delay: Duration,
}
impl Builder {
pub fn queue_size(mut self, queue_size: usize) -> Self {
self.queue_size = queue_size;
self
}
pub fn batch_size_threshold(self, batch_size_threshold: usize) -> Self {
let this = self.sapling_batch_size_threshold(batch_size_threshold);
#[cfg(feature = "orchard")]
let this = this.orchard_batch_size_threshold(batch_size_threshold);
this
}
pub fn sapling_batch_size_threshold(mut self, batch_size_threshold: usize) -> Self {
self.sapling_batch_size_threshold = batch_size_threshold;
self
}
#[cfg(feature = "orchard")]
pub fn orchard_batch_size_threshold(mut self, batch_size_threshold: usize) -> Self {
self.orchard_batch_size_threshold = batch_size_threshold;
self
}
pub fn batch_start_delay(mut self, batch_start_delay: Duration) -> Self {
self.batch_start_delay = batch_start_delay;
self
}
pub fn build<AccountId, IvkTag>(
self,
) -> (Handle<AccountId, IvkTag>, Engine<AccountId, IvkTag>) {
let (handle, queue) = mpsc::channel(self.queue_size);
(
Handle { handle },
Engine {
queue,
sapling_batch_size_threshold: self.sapling_batch_size_threshold,
#[cfg(feature = "orchard")]
orchard_batch_size_threshold: self.orchard_batch_size_threshold,
batch_start_delay: self.batch_start_delay,
},
)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum TryQueueError<T> {
Shutdown(Box<T>),
Full(Box<T>),
}
impl<T> TryQueueError<T> {
pub fn into_inner(self) -> T {
match self {
TryQueueError::Shutdown(payload) | TryQueueError::Full(payload) => *payload,
}
}
}
impl<T> fmt::Display for TryQueueError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TryQueueError::Shutdown(_) => f.write_str("the batch decryptor has shut down"),
TryQueueError::Full(_) => f.write_str("the batch decryptor queue is full"),
}
}
}
impl<T: fmt::Debug> std::error::Error for TryQueueError<T> {}
#[derive(Clone)]
pub struct Handle<AccountId, IvkTag> {
handle: mpsc::Sender<DecryptRequest<AccountId, IvkTag>>,
}
impl<AccountId, IvkTag> Handle<AccountId, IvkTag> {
pub async fn queue_block(
&self,
block: Block,
) -> Option<oneshot::Receiver<BlockDecryptResult<AccountId, IvkTag>>> {
let (on_complete, rx) = oneshot::channel();
match self
.handle
.send(DecryptRequest::Block { block, on_complete })
.await
{
Ok(()) => Some(rx),
Err(_) => None,
}
}
pub fn try_queue_block(
&self,
block: Block,
) -> Result<oneshot::Receiver<BlockDecryptResult<AccountId, IvkTag>>, TryQueueError<Block>>
{
let (on_complete, rx) = oneshot::channel();
match self
.handle
.try_send(DecryptRequest::Block { block, on_complete })
{
Ok(()) => Ok(rx),
Err(mpsc::error::TrySendError::Full(DecryptRequest::Block { block, .. })) => {
Err(TryQueueError::Full(Box::new(block)))
}
Err(mpsc::error::TrySendError::Closed(DecryptRequest::Block { block, .. })) => {
Err(TryQueueError::Shutdown(Box::new(block)))
}
_ => unreachable!(),
}
}
pub async fn queue_tx(
&self,
tx: Transaction,
mempool_height: BlockHeight,
) -> Option<oneshot::Receiver<BatchResult<IvkTag>>> {
let (on_complete, rx) = oneshot::channel();
match self
.handle
.send(DecryptRequest::Tx {
tx,
mempool_height,
on_complete,
})
.await
{
Ok(()) => Some(rx),
Err(_) => None,
}
}
pub fn try_queue_tx(
&self,
tx: Transaction,
mempool_height: BlockHeight,
) -> Result<oneshot::Receiver<BatchResult<IvkTag>>, TryQueueError<Transaction>> {
let (on_complete, rx) = oneshot::channel();
match self.handle.try_send(DecryptRequest::Tx {
tx,
mempool_height,
on_complete,
}) {
Ok(()) => Ok(rx),
Err(mpsc::error::TrySendError::Full(DecryptRequest::Tx { tx, .. })) => {
Err(TryQueueError::Full(Box::new(tx)))
}
Err(mpsc::error::TrySendError::Closed(DecryptRequest::Tx { tx, .. })) => {
Err(TryQueueError::Shutdown(Box::new(tx)))
}
_ => unreachable!(),
}
}
pub async fn reload_keys(&self) -> Option<oneshot::Receiver<()>> {
let (on_complete, rx) = oneshot::channel();
match self
.handle
.send(DecryptRequest::ReloadKeys { on_complete })
.await
{
Ok(()) => Some(rx),
Err(_) => None,
}
}
}
#[allow(clippy::large_enum_variant)]
enum DecryptRequest<AccountId, IvkTag> {
Block {
block: Block,
on_complete: oneshot::Sender<BlockDecryptResult<AccountId, IvkTag>>,
},
Tx {
tx: Transaction,
mempool_height: BlockHeight,
on_complete: oneshot::Sender<BatchResult<IvkTag>>,
},
ReloadKeys {
on_complete: oneshot::Sender<()>,
},
}
type BlockDecryptResult<AccountId, IvkTag> = (
Arc<ScanningKeys<AccountId, IvkTag>>,
BlockHeader,
Vec<BatchResult<IvkTag>>,
);
pub struct Engine<AccountId, IvkTag> {
queue: mpsc::Receiver<DecryptRequest<AccountId, IvkTag>>,
sapling_batch_size_threshold: usize,
#[cfg(feature = "orchard")]
orchard_batch_size_threshold: usize,
batch_start_delay: Duration,
}
impl<AccountId, IvkTag> Engine<AccountId, IvkTag>
where
AccountId: 'static,
IvkTag: Copy + Send + Sync + 'static,
{
pub async fn run<P, E>(
mut self,
params: P,
mut reload_keys: impl FnMut() -> Result<ScanningKeys<AccountId, IvkTag>, E>,
) -> Result<(), E>
where
P: consensus::Parameters + Send + 'static,
{
let mut scanning_keys = Arc::new(reload_keys()?);
let mut runners = BatchRunners::<_, (), (), ()>::for_keys(
self.sapling_batch_size_threshold,
#[cfg(feature = "orchard")]
self.orchard_batch_size_threshold,
#[cfg(feature = "orchard")]
self.orchard_batch_size_threshold,
&scanning_keys,
);
let mut idle_flush_pending = false;
let idle_flush = time::sleep(self.batch_start_delay);
tokio::pin!(idle_flush);
loop {
tokio::select! {
request = self.queue.recv() => match request {
Some(DecryptRequest::Block { block, on_complete }) => {
let mined_height = block.claimed_height();
let (header, vtx) = block.into_parts();
let batches = vtx
.into_iter()
.map(|tx| runners.process_transaction(¶ms, mined_height, tx))
.collect::<Vec<_>>();
let scanning_keys = scanning_keys.clone();
crate::spawn!("Block decryption", async move {
let mut vtx = Vec::with_capacity(batches.len());
for batch in batches {
vtx.push(batch.wait_async().await);
}
let _ = on_complete.send((scanning_keys, header, vtx));
});
if !idle_flush_pending {
idle_flush
.as_mut()
.reset(time::Instant::now() + self.batch_start_delay);
idle_flush_pending = true;
}
}
Some(DecryptRequest::Tx {
tx,
mempool_height,
on_complete,
}) => {
let batch = runners.process_transaction(¶ms, mempool_height, tx);
crate::spawn!("Mempool decryption", async move {
let _ = on_complete.send(batch.wait_async().await);
});
if !idle_flush_pending {
idle_flush
.as_mut()
.reset(time::Instant::now() + self.batch_start_delay);
idle_flush_pending = true;
}
}
Some(DecryptRequest::ReloadKeys { on_complete }) => {
runners.flush();
scanning_keys = Arc::new(reload_keys()?);
runners = BatchRunners::for_keys(
self.sapling_batch_size_threshold,
#[cfg(feature = "orchard")]
self.orchard_batch_size_threshold,
#[cfg(feature = "orchard")]
self.orchard_batch_size_threshold,
&scanning_keys,
);
let _ = on_complete.send(());
idle_flush_pending = false;
}
None => return Ok(()),
},
_ = &mut idle_flush, if idle_flush_pending => {
runners.flush();
idle_flush_pending = false;
}
}
}
}
}