openethereum/secret_store/src/key_server_cluster/admin_sessions/share_change_session.rs

385 lines
14 KiB
Rust

// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity 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.
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use std::collections::{BTreeSet, BTreeMap};
use ethkey::Secret;
use key_server_cluster::{Error, NodeId, SessionId, KeyStorage};
use key_server_cluster::cluster::Cluster;
use key_server_cluster::cluster_sessions::ClusterSession;
use key_server_cluster::math;
use key_server_cluster::jobs::servers_set_change_access_job::ServersSetChangeAccessRequest;
use key_server_cluster::jobs::job_session::JobTransport;
use key_server_cluster::message::{Message, ServersSetChangeMessage, ServersSetChangeShareAddMessage, ServersSetChangeShareMoveMessage,
ServersSetChangeShareRemoveMessage};
use key_server_cluster::share_add_session::{SessionTransport as ShareAddSessionTransport,
SessionImpl as ShareAddSessionImpl, SessionParams as ShareAddSessionParams};
use key_server_cluster::share_move_session::{SessionTransport as ShareMoveSessionTransport,
SessionImpl as ShareMoveSessionImpl, SessionParams as ShareMoveSessionParams};
use key_server_cluster::share_remove_session::{SessionTransport as ShareRemoveSessionTransport,
SessionImpl as ShareRemoveSessionImpl, SessionParams as ShareRemoveSessionParams};
use key_server_cluster::message::{ShareAddMessage, ShareMoveMessage, ShareRemoveMessage};
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
/// Single session meta-change session. Brief overview:
/// 1) new shares are added to the session
/// 2) shares are moved between nodes
/// 3) shares are removed from nodes
pub struct ShareChangeSession {
/// Servers set change session id.
session_id: SessionId,
/// Session nonce.
nonce: u64,
/// Share change session meta.
meta: ShareChangeSessionMeta,
/// Cluster.
cluster: Arc<Cluster>,
/// Key storage.
key_storage: Arc<KeyStorage>,
/// Old nodes set.
old_nodes_set: BTreeSet<NodeId>,
/// Nodes to add shares for.
nodes_to_add: Option<BTreeMap<NodeId, Secret>>,
/// Nodes to move shares from/to.
nodes_to_move: Option<BTreeMap<NodeId, NodeId>>,
/// Nodes to remove shares from.
nodes_to_remove: Option<BTreeSet<NodeId>>,
/// Share add session.
share_add_session: Option<ShareAddSessionImpl<ShareChangeTransport>>,
/// Share move session.
share_move_session: Option<ShareMoveSessionImpl<ShareChangeTransport>>,
/// Share remove session.
share_remove_session: Option<ShareRemoveSessionImpl<ShareChangeTransport>>,
/// Is finished.
is_finished: bool,
}
/// Share change session plan.
pub struct ShareChangeSessionPlan {
/// Nodes to add shares for.
pub nodes_to_add: BTreeMap<NodeId, Secret>,
/// Nodes to move shares from/to (keys = target nodes, values = source nodes).
pub nodes_to_move: BTreeMap<NodeId, NodeId>,
/// Nodes to remove shares from.
pub nodes_to_remove: BTreeSet<NodeId>,
}
/// Session parameters.
pub struct ShareChangeSessionParams {
/// Servers set change session id.
pub session_id: SessionId,
/// Session nonce.
pub nonce: u64,
/// Share change session meta.
pub meta: ShareChangeSessionMeta,
/// Cluster.
pub cluster: Arc<Cluster>,
/// Keys storage.
pub key_storage: Arc<KeyStorage>,
/// Old nodes set.
pub old_nodes_set: BTreeSet<NodeId>,
/// Session plan.
pub plan: ShareChangeSessionPlan,
}
/// Share add session transport.
#[derive(Clone)]
pub struct ShareChangeTransport {
/// Servers set change session id.
session_id: SessionId,
/// Session nonce.
nonce: u64,
/// Cluster.
cluster: Arc<Cluster>,
}
impl ShareChangeSession {
/// Create new share change session.
pub fn new(params: ShareChangeSessionParams) -> Result<Self, Error> {
// we can't create sessions right now, because key share is read when session is created, but it can change in previous session
let nodes_to_add = if !params.plan.nodes_to_add.is_empty() { Some(params.plan.nodes_to_add) } else { None };
let nodes_to_remove = if !params.plan.nodes_to_remove.is_empty() { Some(params.plan.nodes_to_remove) } else { None };
let nodes_to_move = if !params.plan.nodes_to_move.is_empty() { Some(params.plan.nodes_to_move) } else { None };
debug_assert!(nodes_to_add.is_some() || nodes_to_move.is_some() || nodes_to_remove.is_some());
Ok(ShareChangeSession {
session_id: params.session_id,
nonce: params.nonce,
meta: params.meta,
cluster: params.cluster,
key_storage: params.key_storage,
old_nodes_set: params.old_nodes_set,
nodes_to_add: nodes_to_add,
nodes_to_remove: nodes_to_remove,
nodes_to_move: nodes_to_move,
share_add_session: None,
share_move_session: None,
share_remove_session: None,
is_finished: false,
})
}
/// Is finished?.
pub fn is_finished(&self) -> bool {
self.is_finished
}
/// Is master node?.
pub fn is_master(&self) -> bool {
self.meta.self_node_id == self.meta.master_node_id
}
/// Initialize session (on master node).
pub fn initialize(&mut self) -> Result<(), Error> {
self.proceed_to_next_state()
}
/// When share-add message is received.
pub fn on_share_add_message(&mut self, sender: &NodeId, message: &ShareAddMessage) -> Result<(), Error> {
if self.share_add_session.is_none() {
self.create_share_add_session()?;
}
let change_state_needed = self.share_add_session.as_ref()
.map(|share_add_session| {
let was_finished = share_add_session.is_finished();
share_add_session.process_message(sender, message)
.map(|_| share_add_session.is_finished() && !was_finished)
})
.unwrap_or(Err(Error::InvalidMessage))?;
if change_state_needed {
self.proceed_to_next_state()?;
}
Ok(())
}
/// When share-move message is received.
pub fn on_share_move_message(&mut self, sender: &NodeId, message: &ShareMoveMessage) -> Result<(), Error> {
if self.share_move_session.is_none() {
self.create_share_move_session()?;
}
let change_state_needed = self.share_move_session.as_ref()
.map(|share_move_session| {
let was_finished = share_move_session.is_finished();
share_move_session.process_message(sender, message)
.map(|_| share_move_session.is_finished() && !was_finished)
})
.unwrap_or(Err(Error::InvalidMessage))?;
if change_state_needed {
self.proceed_to_next_state()?;
}
Ok(())
}
/// When share-remove message is received.
pub fn on_share_remove_message(&mut self, sender: &NodeId, message: &ShareRemoveMessage) -> Result<(), Error> {
if self.share_remove_session.is_none() {
self.create_share_remove_session()?;
}
let change_state_needed = self.share_remove_session.as_ref()
.map(|share_remove_session| {
let was_finished = share_remove_session.is_finished();
share_remove_session.process_message(sender, message)
.map(|_| share_remove_session.is_finished() && !was_finished)
})
.unwrap_or(Err(Error::InvalidMessage))?;
if change_state_needed {
self.proceed_to_next_state()?;
}
Ok(())
}
/// Create new share add session.
fn create_share_add_session(&mut self) -> Result<(), Error> {
let nodes_to_add = self.nodes_to_add.take().ok_or(Error::InvalidStateForRequest)?;
let new_nodes_set = self.old_nodes_set.iter().map(|n| (n.clone(), None))
.chain(nodes_to_add.clone().into_iter().map(|(k, v)| (k, Some(v))))
.collect();
let share_add_session = ShareAddSessionImpl::new(ShareAddSessionParams {
meta: self.meta.clone(),
nonce: self.nonce,
transport: ShareChangeTransport::new(self.session_id, self.nonce, self.cluster.clone()),
key_storage: self.key_storage.clone(),
admin_public: None,
})?;
share_add_session.set_consensus_output(self.old_nodes_set.clone(), new_nodes_set)?;
self.share_add_session = Some(share_add_session);
Ok(())
}
/// Create new share move session.
fn create_share_move_session(&mut self) -> Result<(), Error> {
let nodes_to_move = self.nodes_to_move.take().ok_or(Error::InvalidStateForRequest)?;
let share_move_session = ShareMoveSessionImpl::new(ShareMoveSessionParams {
meta: self.meta.clone(),
nonce: self.nonce,
transport: ShareChangeTransport::new(self.session_id, self.nonce, self.cluster.clone()),
key_storage: self.key_storage.clone(),
admin_public: None,
})?;
share_move_session.set_consensus_output(nodes_to_move)?;
self.share_move_session = Some(share_move_session);
Ok(())
}
/// Create new share remove session.
fn create_share_remove_session(&mut self) -> Result<(), Error> {
let nodes_to_remove = self.nodes_to_remove.take().ok_or(Error::InvalidStateForRequest)?;
let share_remove_session = ShareRemoveSessionImpl::new(ShareRemoveSessionParams {
meta: self.meta.clone(),
nonce: self.nonce,
transport: ShareChangeTransport::new(self.session_id, self.nonce, self.cluster.clone()),
key_storage: self.key_storage.clone(),
admin_public: None,
})?;
share_remove_session.set_consensus_output(nodes_to_remove)?;
self.share_remove_session = Some(share_remove_session);
Ok(())
}
/// Proceed to the next state.
fn proceed_to_next_state(&mut self) -> Result<(), Error> {
if self.meta.self_node_id != self.meta.master_node_id {
if self.nodes_to_add.is_none() && self.nodes_to_move.is_none() && self.nodes_to_remove.is_none() {
self.is_finished = true;
}
return Ok(());
}
if self.nodes_to_add.is_some() {
self.create_share_add_session()?;
return self.share_add_session.as_ref()
.expect("either create_share_add_session fails, or session is created; qed")
.initialize(None, None, None);
}
if self.nodes_to_move.is_some() {
self.create_share_move_session()?;
return self.share_move_session.as_ref()
.expect("either create_share_move_session fails, or session is created; qed")
.initialize(None, None, None);
}
if self.nodes_to_remove.is_some() {
self.create_share_remove_session()?;
return self.share_remove_session.as_ref()
.expect("either create_share_remove_session fails, or session is created; qed")
.initialize(None, None, None);
}
self.is_finished = true;
Ok(())
}
}
impl ShareChangeTransport {
pub fn new(session_id: SessionId, nonce: u64, cluster: Arc<Cluster>) -> Self {
ShareChangeTransport {
session_id: session_id,
nonce: nonce,
cluster: cluster,
}
}
}
impl JobTransport for ShareChangeTransport {
type PartialJobRequest = ServersSetChangeAccessRequest;
type PartialJobResponse = bool;
fn send_partial_request(&self, _node: &NodeId, _request: ServersSetChangeAccessRequest) -> Result<(), Error> {
unreachable!("only called when establishing consensus; this transport is never used for establishing consensus; qed")
}
fn send_partial_response(&self, _node: &NodeId, _response: bool) -> Result<(), Error> {
unreachable!("only called when establishing consensus; this transport is never used for establishing consensus; qed")
}
}
impl ShareAddSessionTransport for ShareChangeTransport {
fn set_id_numbers(&mut self, _id_numbers: BTreeMap<NodeId, Secret>) {
unreachable!("only called when establishing consensus; this transport is never used for establishing consensus; qed")
}
fn send(&self, node: &NodeId, message: ShareAddMessage) -> Result<(), Error> {
self.cluster.send(node, Message::ServersSetChange(ServersSetChangeMessage::ServersSetChangeShareAddMessage(ServersSetChangeShareAddMessage {
session: self.session_id.clone().into(),
session_nonce: self.nonce,
message: message,
})))
}
}
impl ShareMoveSessionTransport for ShareChangeTransport {
fn set_shares_to_move_reversed(&mut self, _shares_to_move: BTreeMap<NodeId, NodeId>) {
unreachable!("only called when establishing consensus; this transport is never used for establishing consensus; qed")
}
fn send(&self, node: &NodeId, message: ShareMoveMessage) -> Result<(), Error> {
self.cluster.send(node, Message::ServersSetChange(ServersSetChangeMessage::ServersSetChangeShareMoveMessage(ServersSetChangeShareMoveMessage {
session: self.session_id.clone().into(),
session_nonce: self.nonce,
message: message,
})))
}
}
impl ShareRemoveSessionTransport for ShareChangeTransport {
fn send(&self, node: &NodeId, message: ShareRemoveMessage) -> Result<(), Error> {
self.cluster.send(node, Message::ServersSetChange(ServersSetChangeMessage::ServersSetChangeShareRemoveMessage(ServersSetChangeShareRemoveMessage {
session: self.session_id.clone().into(),
session_nonce: self.nonce,
message: message,
})))
}
}
/// Prepare share change plan for moving from old `session_nodes` to `new_nodes_set`.
pub fn prepare_share_change_session_plan(session_nodes: &BTreeSet<NodeId>, new_nodes_set: &BTreeSet<NodeId>) -> Result<ShareChangeSessionPlan, Error> {
let mut nodes_to_add: BTreeSet<_> = new_nodes_set.difference(&session_nodes).cloned().collect();
let mut nodes_to_move = BTreeMap::new();
let mut nodes_to_remove: BTreeSet<_> = session_nodes.difference(&new_nodes_set).cloned().collect();
while !nodes_to_remove.is_empty() && !nodes_to_add.is_empty() {
let source_node = nodes_to_remove.iter().cloned().nth(0).expect("nodes_to_remove.is_empty is checked in while condition; qed");
let target_node = nodes_to_add.iter().cloned().nth(0).expect("nodes_to_add.is_empty is checked in while condition; qed");
nodes_to_remove.remove(&source_node);
nodes_to_add.remove(&target_node);
nodes_to_move.insert(target_node, source_node);
}
Ok(ShareChangeSessionPlan {
nodes_to_add: nodes_to_add.into_iter()
.map(|n| math::generate_random_scalar().map(|s| (n, s)))
.collect::<Result<BTreeMap<_, _>, _>>()?,
nodes_to_move: nodes_to_move,
nodes_to_remove: nodes_to_remove,
})
}
impl ShareChangeSessionPlan {
/// Is empty (nothing-to-do) plan?
pub fn is_empty(&self) -> bool {
self.nodes_to_add.is_empty()
&& self.nodes_to_move.is_empty()
&& self.nodes_to_remove.is_empty()
}
}