Skip to content

Signer Verification Key store & adapters #197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mithril-aggregator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod entities;
mod http_server;
mod multi_signer;
mod snapshot_store;
mod store;

pub use crate::entities::Config;
pub use crate::http_server::Server;
Expand Down
12 changes: 12 additions & 0 deletions mithril-aggregator/src/store/adapters/adapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use super::AdapterError;

pub trait Adapter {
type Key;
type Record;

fn store_record(&mut self, key: Self::Key, record: Self::Record) -> Result<(), AdapterError>;

fn get_record(&self, key: &Self::Key) -> Result<Option<&Self::Record>, AdapterError>;

fn record_exists(&self, key: &Self::Key) -> Result<bool, AdapterError>;
}
18 changes: 18 additions & 0 deletions mithril-aggregator/src/store/adapters/adapter_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::{error::Error, fmt::Display};

#[derive(Debug, Clone)]
pub enum AdapterError {
InputOutputError(String),
}

impl Error for AdapterError {}

impl Display for AdapterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AdapterError::InputOutputError(msg) => {
write!(f, "communication problem encountered: {}", msg)
}
}
}
}
98 changes: 98 additions & 0 deletions mithril-aggregator/src/store/adapters/dumb_adapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#![allow(dead_code)]
use super::{Adapter, AdapterError};

// DumbAdapter stores a single value for testing purposes.
pub struct DumbAdapter<I: std::cmp::PartialEq, V> {
key: I,
value: V,
}

impl<I, V> DumbAdapter<I, V>
where
I: std::cmp::PartialEq,
{
pub fn new(key: I, value: Box<V>) -> Self {
let value = *value;

Self { key, value }
}
}

impl<I, V> Adapter for DumbAdapter<I, V>
where
I: std::cmp::PartialEq,
{
type Key = I;
type Record = V;

fn get_record(&self, key: &Self::Key) -> Result<Option<&Self::Record>, AdapterError> {
if &self.key == key {
Ok(Some(&self.value))
} else {
Ok(None)
}
}

fn record_exists(&self, key: &Self::Key) -> Result<bool, AdapterError> {
Ok(self.key == *key)
}

fn store_record(&mut self, key: Self::Key, record: Self::Record) -> Result<(), AdapterError> {
self.key = key;
self.value = record;

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn record_exists_works() {
let value = "three".to_string();
let adapter = DumbAdapter::new(3, Box::new(value));

assert!(
adapter.record_exists(&3).unwrap(),
"checking an existing key returns true"
);
assert!(
!adapter.record_exists(&0).unwrap(),
"checking a non existing key returns false"
);
}

#[test]
fn get_record_works() {
let value = "three".to_string();
let adapter = DumbAdapter::new(3, Box::new(value));

assert_eq!(
"three",
adapter.get_record(&3).unwrap().unwrap(),
"getting a record from an existing key"
);
assert!(
adapter.get_record(&0).unwrap().is_none(),
"getting a record with a non existing key should return None"
);
}

#[test]
fn store_record_works() {
let value = "three".to_string();
let mut adapter = DumbAdapter::new(3, Box::new(value));

assert!(
adapter.store_record(0, "zero".to_string()).is_ok(),
"storing a valid key should return OK"
);
assert_eq!(
"zero",
adapter.get_record(&0).unwrap().unwrap(),
"using the same key the record should be the same"
);
}
}
98 changes: 98 additions & 0 deletions mithril-aggregator/src/store/adapters/memory_adapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#![allow(dead_code)]
use std::{collections::HashMap, hash::Hash};

use super::{Adapter, AdapterError};

pub struct MemoryAdapter<I: std::cmp::PartialEq, V> {
data: HashMap<I, V>,
}

impl<I, V> MemoryAdapter<I, V>
where
I: std::cmp::PartialEq,
{
pub fn new() -> Self {
Self::new_with(HashMap::new())
}

pub fn new_with(data: HashMap<I, V>) -> Self {
Self { data }
}
}

impl<I, V> Adapter for MemoryAdapter<I, V>
where
I: std::cmp::Eq,
I: Hash,
{
type Key = I;
type Record = V;

fn get_record(&self, key: &Self::Key) -> Result<Option<&Self::Record>, AdapterError> {
Ok(self.data.get(key))
}

fn record_exists(&self, key: &Self::Key) -> Result<bool, AdapterError> {
Ok(self.data.contains_key(key))
}

fn store_record(&mut self, key: Self::Key, record: Self::Record) -> Result<(), AdapterError> {
let _old_value = self.data.insert(key, record);

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

fn instanciate_populated_store() -> MemoryAdapter<u64, String> {
let data: HashMap<u64, String> = {
let mut data: HashMap<u64, String> = HashMap::new();
data.insert(1, "one".to_string());
data.insert(2, "two".to_string());

data
};

MemoryAdapter::new_with(data)
}

#[test]
fn store_record_works() {
let mut adapter: MemoryAdapter<u64, &str> = MemoryAdapter::new();

assert!(!adapter.record_exists(&0).unwrap());
assert!(adapter.store_record(0, "zero").is_ok());
assert!(adapter.record_exists(&0).unwrap());
}

#[test]
fn record_exists_works() {
let adapter = instanciate_populated_store();

assert!(adapter.record_exists(&1).unwrap());
assert!(adapter.record_exists(&2).unwrap());
assert!(!adapter.record_exists(&3).unwrap());
}

#[test]
fn get_record_with_existing_key_work() {
let adapter = instanciate_populated_store();
let value = adapter.get_record(&1).unwrap();

assert!(value.is_some());
let value = value.unwrap();

assert_eq!(&("one".to_string()), value);
}

#[test]
fn get_record_with_non_existing_key_work() {
let adapter = instanciate_populated_store();
let value = adapter.get_record(&0).unwrap();

assert!(value.is_none());
}
}
11 changes: 11 additions & 0 deletions mithril-aggregator/src/store/adapters/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod adapter;
mod adapter_error;
mod dumb_adapter;
mod memory_adapter;

pub use adapter::Adapter;
pub use adapter_error::AdapterError;
pub use memory_adapter::MemoryAdapter;

#[cfg(test)]
pub use dumb_adapter::DumbAdapter;
102 changes: 102 additions & 0 deletions mithril-aggregator/src/store/key_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#![allow(dead_code)]
use super::adapters::Adapter;
use super::AdapterError;
use mithril_common::crypto_helper::{ProtocolPartyId, ProtocolSignerVerificationKey};
use std::sync::RwLock;

struct SignerVerificationKeyStore {
adapter:
Box<RwLock<dyn Adapter<Key = ProtocolPartyId, Record = ProtocolSignerVerificationKey>>>,
}

impl SignerVerificationKeyStore {
pub fn new(
adapter: Box<
RwLock<dyn Adapter<Key = ProtocolPartyId, Record = ProtocolSignerVerificationKey>>,
>,
) -> Self {
Self { adapter }
}

pub fn save(
&self,
party_id: ProtocolPartyId,
verification_key: ProtocolSignerVerificationKey,
) -> Result<(), AdapterError> {
let mut adapter = self
.adapter
.write()
.map_err(|e| AdapterError::InputOutputError(e.to_string()))?;

adapter.store_record(party_id, verification_key)
}

/// returns a verification key matching the given party_id
/// it actually returns a clone of the matching verification key if any this
/// is because a lock is used to allow concurrency use of this
/// store, it is necessary to release the lock before returning the value
pub fn fetch(
&self,
party_id: ProtocolPartyId,
) -> Result<Option<ProtocolSignerVerificationKey>, AdapterError> {
let adapter = self.adapter.read().unwrap();
let record = adapter
.get_record(&party_id)
.map_err(|e| AdapterError::InputOutputError(e.to_string()))?;

match record {
Some(vk) => Ok(Some(vk.clone())),
_ => Ok(None),
}
}
}

#[cfg(test)]
mod tests {
use super::super::adapters::DumbAdapter;
use super::*;
use mithril_common::crypto_helper::tests_setup::setup_signers;

fn generate_mithril_artefacts() -> (ProtocolPartyId, ProtocolSignerVerificationKey) {
let (party_id, _stake, signer, _multisig) = setup_signers(1).into_iter().nth(0).unwrap();

(party_id, signer)
}

fn instanciate_populated_store() -> (
ProtocolPartyId,
ProtocolSignerVerificationKey,
SignerVerificationKeyStore,
) {
let (party_id, signer_key) = generate_mithril_artefacts();
let store = SignerVerificationKeyStore::new(Box::new(RwLock::new(DumbAdapter::new(
party_id,
Box::new(signer_key.clone()),
))));

(party_id, signer_key, store)
}

#[test]
fn test_fetch_verification_key() {
let (party_id, signer_key, store) = instanciate_populated_store();

assert_eq!(signer_key, store.fetch(party_id).unwrap().unwrap());
}

#[test]
fn test_fetch_using_non_existent_key() {
let (party_id, _signer_key, store) = instanciate_populated_store();

assert!(store.fetch(party_id + 1).unwrap().is_none());
}

#[test]
fn test_save_verification_key() {
let (party_id, signer_key, store) = instanciate_populated_store();

assert!(store.fetch(party_id + 1).unwrap().is_none());
assert!(store.save(party_id + 1, signer_key).is_ok());
assert!(store.fetch(party_id + 1).unwrap().is_some());
}
}
4 changes: 4 additions & 0 deletions mithril-aggregator/src/store/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod adapters;
pub mod key_store;

pub use adapters::*;