It's essential to be able to separate the lifetime of the batch item from the
lifetime of associated data (in this case, the message). The previous API did
this mixed in to the Tower implementation, but it was removed along with that
code.
Making the `queue` function take `I: Into<Item>` means that users who don't
care about lifetimes just need to wrap the function arguments in an extra
tuple.
These are better names than secret and public keys, because they concisely
describe the functional *role* of the key material, not just whether or not the
key is revealed.
The futures-based batch verification design is still good, but it turns out
that things like the latency bounds and control of when to flush the batch
should be common across different kinds of batchable verification. This should
be done by the forthcoming `tower-batch` middleware currently in the Zebra
repo.
This changes the `VerificationRequest` type alias (now called `batch::Request`)
to an enum containing either a verification request or a request to flush the
batch. This allows both automatic and manual control of batch sizes, either by
setting a low batch limit on service creation or by setting a high limit and
manually sending flush commands.
To keep things ergonomic, the `Request` enum now has a `impl From` the previous
tuple, so to send a request, all that's necessary is to assemble a
pubkey-sig-message tuple and call `.into()` on it.
This will hopefully allow things like building a Tower layer with a timeout and
a retry policy that retries timed out requests (not a big enough concurrent
batch) with singleton verification, or retries a failed batch by falling back
to singleton verification to detect which element of a batch failed.
However, there are still some rough spots in the API, and it's not clear that
manually dropping the service is an adequate way to flush requests (see comment).
This commit incidentally includes an optimization for batch verification that
improves performance when verifying multiple signatures from the same public
key.
I'm not totally happy with a few things about this API, however. Currently,
the actual batch computation is performed only when the inherent `finalize()`
method is called on the service. However, this doesn't work well with `tower`
layering, because once the service is wrapped, the inherent method is no longer
available. Another option would be for the batching service to be created with
a batch size parameter, automatically resolving the batch computation whenever
the batch size was reached. This improves latency but does not solve the
problem of finalizing the batch, since unless there's guaranteed to be a
constant stream of verification requests, a tail of requests may be left
unresolved. A third option would be for the service to return some kind of
control handle on creation that would allow shutdown via a channel or
something, but this is also unsatisfying because it means that the service has
to be listening for a shutdown signal. A fourth option would be to have a
batch size parameter but customize the `Drop` impl to finalize all pending
requests on drop; this would potentially allow "flushing" pending requests by
manually dropping the service in a way that would still be possible when using
tower wrappers.