Batch signature verification (#5023)

* create parallel tasks extension

* make type system happy

* basic externalities

* test for dynamic extensions

* batching test

* remove premature verify_batch

* shnschnorrkel batch

* alter test

* shnschnorrkel test

* executive batching

* some docs

* also multi/any signatgures

* error propagation

* styling

* make verification extension optional

* experimental ed25519 parallelization

* some merge fallout

* utilize task executor

* merge fallout

* utilize task executor more

* another merge fallout

* feature-gate sp-io

* arrange toml

* fix no-std

* sr25519 batching and refactoring

* add docs

* fix name

* add newline

* fix block import test

* long sr25519 test

* blocking instead of parking

* move everything in crypto

* return batch_verify to check :)

* use condvars

* use multi-threaded executor for benches

* don't call via host interface

* try no spawning

* add true

* cleanup

* straighten batching

* remove signature check from this test (?)

* remove now pointless test

* remove another now useless test

* fix warnings

* Revert "remove another now useless test"

This reverts commit bbdec24bb67ed4373072daef7c863e1a8825bd8b.

* rethink the sp-io-part

* Revert "remove now pointless test"

This reverts commit 4d553066322e65782264caa6053d4cd5538df977.

* fix wording

* add  wording

* add todo and fix

* return check and fix

* add logging in sp-io

* Update primitives/io/src/batch_verifier.rs

Co-Authored-By: cheme <emericchevalier.pro@gmail.com>

* address review and use std condvar

* account for early exit

* address reivew

* address review

* more suggestions

* add docs for batch verification

* remove unused

* more review suggestions

* move to sp-runtime

* add expects

* remove blocks

* use entry

* Update primitives/io/src/batch_verifier.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update primitives/externalities/src/extensions.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* update overlooked note

* remove stupid return

* Update primitives/io/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update primitives/io/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* fix wording

* bump spec_version

Co-authored-by: cheme <emericchevalier.pro@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Nikolay Volf
2020-04-16 22:40:04 +03:00
committed by GitHub
parent 36243068bd
commit 372f8b2c7e
21 changed files with 711 additions and 47 deletions
@@ -0,0 +1,163 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Batch/parallel verification.
use sp_core::{ed25519, sr25519, crypto::Pair, traits::CloneableSpawn};
use std::sync::{Arc, atomic::{AtomicBool, Ordering as AtomicOrdering}};
use futures::{future::FutureExt, task::FutureObj, channel::oneshot};
#[derive(Debug, Clone)]
struct Sr25519BatchItem {
signature: sr25519::Signature,
pub_key: sr25519::Public,
message: Vec<u8>,
}
/// Batch verifier.
///
/// Used to parallel-verify signatures for runtime host. Provide task executor and
/// just push (`push_ed25519`, `push_sr25519`) as many signature as you need. At the end,
/// call `verify_and_clear to get a result. After that, batch verifier is ready for the
/// next batching job.
pub struct BatchVerifier {
scheduler: Box<dyn CloneableSpawn>,
sr25519_items: Vec<Sr25519BatchItem>,
invalid: Arc<AtomicBool>,
pending_tasks: Vec<oneshot::Receiver<()>>,
}
impl BatchVerifier {
pub fn new(scheduler: Box<dyn CloneableSpawn>) -> Self {
BatchVerifier {
scheduler,
sr25519_items: Default::default(),
invalid: Arc::new(false.into()),
pending_tasks: vec![],
}
}
fn spawn_verification_task(
&mut self, f: impl FnOnce() -> bool + Send + 'static,
) -> Result<(), ()> {
// there is already invalid transaction encountered
if self.invalid.load(AtomicOrdering::Relaxed) { return Err(()); }
let invalid_clone = self.invalid.clone();
let (sender, receiver) = oneshot::channel();
self.pending_tasks.push(receiver);
self.scheduler.spawn_obj(FutureObj::new(async move {
if !f() {
invalid_clone.store(true, AtomicOrdering::Relaxed);
}
if sender.send(()).is_err() {
// sanity
log::warn!("Verification halted while result was pending");
invalid_clone.store(true, AtomicOrdering::Relaxed);
}
}.boxed())).map_err(drop)
}
/// Push ed25519 signature to verify.
///
/// Returns false if some of the pushed signatures before already failed the check
/// (in this case it won't verify anything else)
pub fn push_ed25519(
&mut self,
signature: ed25519::Signature,
pub_key: ed25519::Public,
message: Vec<u8>,
) -> bool {
if self.invalid.load(AtomicOrdering::Relaxed) { return false; }
if self.spawn_verification_task(move || ed25519::Pair::verify(&signature, &message, &pub_key)).is_err() {
log::debug!(
target: "runtime",
"Batch-verification returns false because failed to spawn background task.",
);
return false;
}
true
}
/// Push sr25519 signature to verify.
///
/// Returns false if some of the pushed signatures before already failed the check.
/// (in this case it won't verify anything else)
pub fn push_sr25519(
&mut self,
signature: sr25519::Signature,
pub_key: sr25519::Public,
message: Vec<u8>,
) -> bool {
if self.invalid.load(AtomicOrdering::Relaxed) { return false; }
self.sr25519_items.push(Sr25519BatchItem { signature, pub_key, message });
true
}
/// Verify all previously pushed signatures since last call and return
/// aggregated result.
#[must_use]
pub fn verify_and_clear(&mut self) -> bool {
use std::sync::{Mutex, Condvar};
let pending = std::mem::replace(&mut self.pending_tasks, vec![]);
log::trace!(
target: "runtime",
"Batch-verification: {} pending tasks, {} sr25519 signatures",
pending.len(),
self.sr25519_items.len(),
);
let messages = self.sr25519_items.iter().map(|item| &item.message[..]).collect();
let signatures = self.sr25519_items.iter().map(|item| &item.signature).collect();
let pub_keys = self.sr25519_items.iter().map(|item| &item.pub_key).collect();
if !sr25519::verify_batch(messages, signatures, pub_keys) {
self.sr25519_items.clear();
return false;
}
self.sr25519_items.clear();
if pending.len() > 0 {
let pair = Arc::new((Mutex::new(()), Condvar::new()));
let pair_clone = pair.clone();
if self.scheduler.spawn_obj(FutureObj::new(async move {
futures::future::join_all(pending).await;
pair_clone.1.notify_all();
}.boxed())).is_err() {
log::debug!(
target: "runtime",
"Batch-verification returns false because failed to spawn background task.",
);
return false;
}
let (mtx, cond_var) = &*pair;
let mtx = mtx.lock().expect("Locking can only fail when the mutex is poisoned; qed");
let _ = cond_var.wait(mtx).expect("Waiting can only fail when the mutex waited on is poisoned; qed");
}
!self.invalid.swap(false, AtomicOrdering::Relaxed)
}
}