tower_batch_control/
lib.rs

1//! Tower middleware for batch request processing
2//!
3//! This crate provides generic middleware for handling management of
4//! latency/throughput tradeoffs for batch processing. It provides a
5//! [`BatchControl<R>`](BatchControl) enum with [`Item(R)`](BatchControl::Item)
6//! and [`Flush`](BatchControl::Flush) variants, and provides a
7//! [`Batch<S>`](Batch) wrapper that wraps `S: Service<BatchControl<R>>` to
8//! provide a `Service<R>`, managing maximum request latency and batch size.
9//!
10//! ## Example: batch verification
11//!
12//! In cryptography, batch verification asks whether *all* items in some set are
13//! valid, rather than asking whether *each* of them is valid. This increases
14//! throughput by allowing computation to be shared across each item. However, it
15//! comes at the cost of higher latency (the entire batch must complete),
16//! complexity of caller code (which must assemble a batch of items to verify),
17//! and loss of the ability to easily pinpoint failing items (requiring either a
18//! retry or more sophisticated techniques).
19//!
20//! The latency-throughput tradeoff is manageable, but the second aspect poses
21//! serious practical difficulties. Conventional batch verification APIs require
22//! choosing in advance how much data to batch, and then processing the entire
23//! batch simultaneously. But for applications which require verification of
24//! heterogeneous data, this is cumbersome and difficult.
25//!
26//! For example, Zcash uses four different kinds of signatures (ECDSA signatures
27//! from Bitcoin, Ed25519 signatures for Sprout, and RedJubjub spendauth and
28//! binding signatures for Sapling) as well as three different kinds of
29//! zero-knowledge proofs (Sprout-on-BCTV14, Sprout-on-Groth16, and
30//! Sapling-on-Groth16). A single transaction can have multiple proofs or
31//! signatures of different kinds, depending on the transaction version and its
32//! structure. Verification of a transaction conventionally proceeds
33//! "depth-first", checking that the structure is appropriate and then that all
34//! the component signatures and proofs are valid.
35//!
36//! Now consider the problem of implementing batch verification in this context,
37//! using conventional batch verification APIs that require passing a list of
38//! signatures or proofs. This is quite complicated, requiring implementing a
39//! second transposed set of validation logic that proceeds "breadth-first",
40//! checking that the structure of each transaction is appropriate while
41//! assembling collections of signatures and proofs to verify. This transposed
42//! validation logic must match the untransposed logic, but there is another
43//! problem, which is that the set of transactions must be decided in advance.
44//! This is difficult because different levels of batching are required in
45//! different contexts. For instance, batching within a transaction is
46//! appropriate on receipt of a gossiped transaction, batching within a block is
47//! appropriate for block verification, and batching across blocks is appropriate
48//! when syncing the chain.
49//!
50//! ## Asynchronous batch verification
51//!
52//! To address this problem, we move from a synchronous model for signature
53//! verification to an asynchronous model. Rather than immediately returning a
54//! verification result, verification returns a future which will eventually
55//! resolve to a verification result. Verification futures can be combined with
56//! various futures combinators, expressing the logical semantics of the combined
57//! verification checks. This allows writing checks generic over the choice of
58//! singleton or batched verification. And because the batch context is distinct
59//! from the verification logic itself, the same verification logic can be reused
60//! in different batching contexts - batching within a transaction, within a
61//! block, within a chain, etc.
62//!
63//! ## Batch processing middleware
64//!
65//! Tower's [`Service`](tower::Service) interface is an attractive choice for
66//! implementing this model for two reasons. First, it makes it easy to express
67//! generic bounds on [`Service`](tower::Service)s, allowing higher-level
68//! verification services to be written generically with respect to the
69//! verification of each lower-level component.
70//!
71//! Second, Tower's design allows service combinators to easily compose
72//! behaviors. For instance, the third drawback mentioned above (failure
73//! pinpointing) can addressed fairly straightforwardly by composing a batch
74//! verification [`Service`](tower::Service) with a retry
75//! [`Layer`](tower::layer::Layer) that retries verification of that item without
76//! batching.
77//!
78//! The remaining problem to address is the latency-throughput tradeoff. The
79//! logic to manage this tradeoff is independent of the specific batching
80//! procedure, and this crate provides a generic `Batch` wrapper that does so.
81//! The wrapper makes use of a [`BatchControl<R>`](BatchControl) enum with
82//! [`Item(R)`](BatchControl::Item) and [`Flush`](BatchControl::Flush) variants.
83//! Given `S: Service<BatchControl<R>>`, the [`Batch<S>`](Batch) wrapper provides
84//! a `Service<R>`. The wrapped service does not need to implement any batch
85//! control logic, as it will receive explicit [`Flush`](BatchControl::Flush)
86//! requests from the wrapper.
87//!
88//! ## Implementation History
89//!
90//! The `tower-batch-control` code was modified from a 2019 version of:
91//! <https://github.com/tower-rs/tower/tree/master/tower/src/buffer>
92//!
93//! A modified fork of this crate is available on crates.io as `tower-batch`.
94//! It is focused on batching disk writes.
95
96pub mod error;
97pub mod future;
98mod layer;
99mod message;
100mod service;
101mod worker;
102
103type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
104
105/// Signaling mechanism for batchable services that allows explicit flushing.
106///
107/// This request type is a generic wrapper for the inner `Req` type.
108pub enum BatchControl<Req> {
109    /// A new batch item.
110    Item(Req),
111    /// The current batch should be flushed.
112    Flush,
113}
114
115impl<Req> From<Req> for BatchControl<Req> {
116    fn from(req: Req) -> BatchControl<Req> {
117        BatchControl::Item(req)
118    }
119}
120
121pub use self::layer::BatchLayer;
122pub use self::service::Batch;