SecretStore: 'broadcast' decryption session (#7843)
This commit is contained in:
		
							parent
							
								
									90bd6bf700
								
							
						
					
					
						commit
						8579a56f71
					
				@ -337,7 +337,7 @@ impl SessionImpl {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let unknown_sessions_job = UnknownSessionsJob::new_on_master(self.core.key_storage.clone(), self.core.meta.self_node_id.clone());
 | 
			
		||||
		consensus_session.disseminate_jobs(unknown_sessions_job, self.unknown_sessions_transport())
 | 
			
		||||
		consensus_session.disseminate_jobs(unknown_sessions_job, self.unknown_sessions_transport(), false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// When unknown sessions are requested.
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ use key_server_cluster::cluster_sessions::{SessionIdWithSubSession, ClusterSessi
 | 
			
		||||
use key_server_cluster::message::{Message, DecryptionMessage, DecryptionConsensusMessage, RequestPartialDecryption,
 | 
			
		||||
	PartialDecryption, DecryptionSessionError, DecryptionSessionCompleted, ConsensusMessage, InitializeConsensusSession,
 | 
			
		||||
	ConfirmConsensusInitialization, DecryptionSessionDelegation, DecryptionSessionDelegationCompleted};
 | 
			
		||||
use key_server_cluster::jobs::job_session::JobTransport;
 | 
			
		||||
use key_server_cluster::jobs::job_session::{JobSession, JobTransport};
 | 
			
		||||
use key_server_cluster::jobs::key_access_job::KeyAccessJob;
 | 
			
		||||
use key_server_cluster::jobs::decryption_job::{PartialDecryptionRequest, PartialDecryptionResponse, DecryptionJob};
 | 
			
		||||
use key_server_cluster::jobs::consensus_session::{ConsensusSessionParams, ConsensusSessionState, ConsensusSession};
 | 
			
		||||
@ -63,6 +63,8 @@ struct SessionCore {
 | 
			
		||||
 | 
			
		||||
/// Decryption consensus session type.
 | 
			
		||||
type DecryptionConsensusSession = ConsensusSession<KeyAccessJob, DecryptionConsensusTransport, DecryptionJob, DecryptionJobTransport>;
 | 
			
		||||
/// Broadcast decryption job session type.
 | 
			
		||||
type BroadcastDecryptionJobSession = JobSession<DecryptionJob, DecryptionJobTransport>;
 | 
			
		||||
 | 
			
		||||
/// Mutable session data.
 | 
			
		||||
struct SessionData {
 | 
			
		||||
@ -70,8 +72,13 @@ struct SessionData {
 | 
			
		||||
	pub version: Option<H256>,
 | 
			
		||||
	/// Consensus-based decryption session.
 | 
			
		||||
	pub consensus_session: DecryptionConsensusSession,
 | 
			
		||||
	/// Broadcast decryption job.
 | 
			
		||||
	pub broadcast_job_session: Option<BroadcastDecryptionJobSession>,
 | 
			
		||||
	/// Is shadow decryption requested?
 | 
			
		||||
	pub is_shadow_decryption: Option<bool>,
 | 
			
		||||
	/// Decryption result must be reconstructed on all participating nodes. This is useful
 | 
			
		||||
	/// for service contract API so that all nodes from consensus group can confirm decryption.
 | 
			
		||||
	pub is_broadcast_session: Option<bool>,
 | 
			
		||||
	/// Delegation status.
 | 
			
		||||
	pub delegation_status: Option<DelegationStatus>,
 | 
			
		||||
	/// Decryption result.
 | 
			
		||||
@ -116,6 +123,10 @@ struct DecryptionJobTransport {
 | 
			
		||||
	access_key: Secret,
 | 
			
		||||
	/// Session-level nonce.
 | 
			
		||||
	nonce: u64,
 | 
			
		||||
	/// Is this a broadcast transport? If true, requests are not send and responses are sent only to non-master nodes.
 | 
			
		||||
	is_broadcast_transport: bool,
 | 
			
		||||
	/// Master node id.
 | 
			
		||||
	master_node_id: NodeId,
 | 
			
		||||
	/// Cluster.
 | 
			
		||||
	cluster: Arc<Cluster>,
 | 
			
		||||
}
 | 
			
		||||
@ -169,7 +180,9 @@ impl SessionImpl {
 | 
			
		||||
			data: Mutex::new(SessionData {
 | 
			
		||||
				version: None,
 | 
			
		||||
				consensus_session: consensus_session,
 | 
			
		||||
				broadcast_job_session: None,
 | 
			
		||||
				is_shadow_decryption: None,
 | 
			
		||||
				is_broadcast_session: None,
 | 
			
		||||
				delegation_status: None,
 | 
			
		||||
				result: None,
 | 
			
		||||
			}),
 | 
			
		||||
@ -206,7 +219,7 @@ impl SessionImpl {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Delegate session to other node.
 | 
			
		||||
	pub fn delegate(&self, master: NodeId, version: H256, is_shadow_decryption: bool) -> Result<(), Error> {
 | 
			
		||||
	pub fn delegate(&self, master: NodeId, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
 | 
			
		||||
		if self.core.meta.master_node_id != self.core.meta.self_node_id {
 | 
			
		||||
			return Err(Error::InvalidStateForRequest);
 | 
			
		||||
		}
 | 
			
		||||
@ -226,13 +239,14 @@ impl SessionImpl {
 | 
			
		||||
				.clone().into(),
 | 
			
		||||
			version: version.into(),
 | 
			
		||||
			is_shadow_decryption: is_shadow_decryption,
 | 
			
		||||
			is_broadcast_session: is_broadcast_session,
 | 
			
		||||
		})))?;
 | 
			
		||||
		data.delegation_status = Some(DelegationStatus::DelegatedTo(master));
 | 
			
		||||
		Ok(())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Initialize decryption session on master node.
 | 
			
		||||
	pub fn initialize(&self, version: H256, is_shadow_decryption: bool) -> Result<(), Error> {
 | 
			
		||||
	pub fn initialize(&self, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
 | 
			
		||||
		debug_assert_eq!(self.core.meta.self_node_id, self.core.meta.master_node_id);
 | 
			
		||||
 | 
			
		||||
		// check if version exists
 | 
			
		||||
@ -255,10 +269,11 @@ impl SessionImpl {
 | 
			
		||||
		data.consensus_session.consensus_job_mut().transport_mut().version = Some(version.clone());
 | 
			
		||||
		data.version = Some(version.clone());
 | 
			
		||||
		data.is_shadow_decryption = Some(is_shadow_decryption);
 | 
			
		||||
		data.is_broadcast_session = Some(is_broadcast_session);
 | 
			
		||||
		data.consensus_session.initialize(consensus_nodes)?;
 | 
			
		||||
 | 
			
		||||
		if data.consensus_session.state() == ConsensusSessionState::ConsensusEstablished {
 | 
			
		||||
			self.core.disseminate_jobs(&mut data.consensus_session, &version, is_shadow_decryption)?;
 | 
			
		||||
			Self::disseminate_jobs(&self.core, &mut *data, &version, is_shadow_decryption, is_broadcast_session)?;
 | 
			
		||||
 | 
			
		||||
			debug_assert!(data.consensus_session.state() == ConsensusSessionState::Finished);
 | 
			
		||||
			let result = data.consensus_session.result()?;
 | 
			
		||||
@ -307,7 +322,7 @@ impl SessionImpl {
 | 
			
		||||
			data.delegation_status = Some(DelegationStatus::DelegatedFrom(sender.clone(), message.session_nonce));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.initialize(message.version.clone().into(), message.is_shadow_decryption)
 | 
			
		||||
		self.initialize(message.version.clone().into(), message.is_shadow_decryption, message.is_broadcast_session)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// When delegated session is completed on other node.
 | 
			
		||||
@ -359,7 +374,9 @@ impl SessionImpl {
 | 
			
		||||
		let version = data.version.as_ref().ok_or(Error::InvalidMessage)?.clone();
 | 
			
		||||
		let is_shadow_decryption = data.is_shadow_decryption
 | 
			
		||||
			.expect("we are on master node; on master node is_shadow_decryption is filled in initialize(); on_consensus_message follows initialize (state check in consensus_session); qed");
 | 
			
		||||
		self.core.disseminate_jobs(&mut data.consensus_session, &version, is_shadow_decryption)
 | 
			
		||||
		let is_broadcast_session = data.is_broadcast_session
 | 
			
		||||
			.expect("we are on master node; on master node is_broadcast_session is filled in initialize(); on_consensus_message follows initialize (state check in consensus_session); qed");
 | 
			
		||||
		Self::disseminate_jobs(&self.core, &mut *data, &version, is_shadow_decryption, is_broadcast_session)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// When partial decryption is requested.
 | 
			
		||||
@ -378,13 +395,27 @@ impl SessionImpl {
 | 
			
		||||
			.map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
 | 
			
		||||
		let requester = data.consensus_session.consensus_job().executor().requester()?.ok_or(Error::InvalidStateForRequest)?.clone();
 | 
			
		||||
		let decryption_job = DecryptionJob::new_on_slave(self.core.meta.self_node_id.clone(), self.core.access_key.clone(), requester, key_share.clone(), key_version)?;
 | 
			
		||||
		let decryption_transport = self.core.decryption_transport();
 | 
			
		||||
		let decryption_transport = self.core.decryption_transport(false);
 | 
			
		||||
 | 
			
		||||
		data.consensus_session.on_job_request(&sender, PartialDecryptionRequest {
 | 
			
		||||
		// respond to request
 | 
			
		||||
		data.consensus_session.on_job_request(sender, PartialDecryptionRequest {
 | 
			
		||||
			id: message.request_id.clone().into(),
 | 
			
		||||
			is_shadow_decryption: message.is_shadow_decryption,
 | 
			
		||||
			is_broadcast_session: message.is_broadcast_session,
 | 
			
		||||
			other_nodes_ids: message.nodes.iter().cloned().map(Into::into).collect(),
 | 
			
		||||
		}, decryption_job, decryption_transport)
 | 
			
		||||
		}, decryption_job, decryption_transport)?;
 | 
			
		||||
 | 
			
		||||
		// ...and prepare decryption job session if we need to broadcast result
 | 
			
		||||
		if message.is_broadcast_session {
 | 
			
		||||
			let consensus_group: BTreeSet<_> = message.nodes.iter().cloned().map(Into::into).collect();
 | 
			
		||||
			let broadcast_decryption_job = DecryptionJob::new_on_master(self.core.meta.self_node_id.clone(),
 | 
			
		||||
				self.core.access_key.clone(), requester, key_share.clone(), key_version,
 | 
			
		||||
				message.is_shadow_decryption, message.is_broadcast_session)?;
 | 
			
		||||
			Self::create_broadcast_decryption_job(&self.core, &mut *data, consensus_group, broadcast_decryption_job,
 | 
			
		||||
				message.request_id.clone().into())?;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// When partial decryption is received.
 | 
			
		||||
@ -394,11 +425,22 @@ impl SessionImpl {
 | 
			
		||||
		debug_assert!(sender != &self.core.meta.self_node_id);
 | 
			
		||||
 | 
			
		||||
		let mut data = self.data.lock();
 | 
			
		||||
		data.consensus_session.on_job_response(sender, PartialDecryptionResponse {
 | 
			
		||||
			request_id: message.request_id.clone().into(),
 | 
			
		||||
			shadow_point: message.shadow_point.clone().into(),
 | 
			
		||||
			decrypt_shadow: message.decrypt_shadow.clone(),
 | 
			
		||||
		})?;
 | 
			
		||||
		if self.core.meta.self_node_id == self.core.meta.master_node_id {
 | 
			
		||||
			data.consensus_session.on_job_response(sender, PartialDecryptionResponse {
 | 
			
		||||
				request_id: message.request_id.clone().into(),
 | 
			
		||||
				shadow_point: message.shadow_point.clone().into(),
 | 
			
		||||
				decrypt_shadow: message.decrypt_shadow.clone(),
 | 
			
		||||
			})?;
 | 
			
		||||
		} else {
 | 
			
		||||
			match data.broadcast_job_session.as_mut() {
 | 
			
		||||
				Some(broadcast_job_session) => broadcast_job_session.on_partial_response(sender, PartialDecryptionResponse {
 | 
			
		||||
					request_id: message.request_id.clone().into(),
 | 
			
		||||
					shadow_point: message.shadow_point.clone().into(),
 | 
			
		||||
					decrypt_shadow: message.decrypt_shadow.clone(),
 | 
			
		||||
				})?,
 | 
			
		||||
				None => return Err(Error::TooEarlyForRequest),
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if data.consensus_session.state() != ConsensusSessionState::Finished {
 | 
			
		||||
			return Ok(());
 | 
			
		||||
@ -425,7 +467,24 @@ impl SessionImpl {
 | 
			
		||||
		debug_assert!(self.core.access_key == *message.sub_session);
 | 
			
		||||
		debug_assert!(sender != &self.core.meta.self_node_id);
 | 
			
		||||
 | 
			
		||||
		self.data.lock().consensus_session.on_session_completed(sender)
 | 
			
		||||
		let mut data = self.data.lock();
 | 
			
		||||
 | 
			
		||||
		// if it is a broadcast session, wait for all answers before completing the session
 | 
			
		||||
		let decryption_result = match data.broadcast_job_session.as_ref() {
 | 
			
		||||
			Some(broadcast_job_session) => {
 | 
			
		||||
				if !broadcast_job_session.is_result_ready() {
 | 
			
		||||
					return Err(Error::TooEarlyForRequest);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				Some(broadcast_job_session.result())
 | 
			
		||||
			},
 | 
			
		||||
			None => None,
 | 
			
		||||
		};
 | 
			
		||||
		if let Some(decryption_result) = decryption_result {
 | 
			
		||||
			Self::set_decryption_result(&self.core, &mut *data, decryption_result);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data.consensus_session.on_session_completed(sender)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Process error from the other node.
 | 
			
		||||
@ -447,8 +506,10 @@ impl SessionImpl {
 | 
			
		||||
			Ok(false) => Ok(()),
 | 
			
		||||
			Ok(true) => {
 | 
			
		||||
				let version = data.version.as_ref().ok_or(Error::InvalidMessage)?.clone();
 | 
			
		||||
				let is_shadow_decryption = data.is_shadow_decryption.expect("on_node_error returned true; this means that jobs must be REsent; this means that jobs already have been sent; jobs are sent when is_shadow_decryption.is_some(); qed");
 | 
			
		||||
				let disseminate_result = self.core.disseminate_jobs(&mut data.consensus_session, &version, is_shadow_decryption);
 | 
			
		||||
				let proof = "on_node_error returned true; this means that jobs must be REsent; this means that jobs already have been sent; jobs are sent when is_shadow_decryption.is_some(); qed";
 | 
			
		||||
				let is_shadow_decryption = data.is_shadow_decryption.expect(proof);
 | 
			
		||||
				let is_broadcast_session = data.is_broadcast_session.expect(proof);
 | 
			
		||||
				let disseminate_result = Self::disseminate_jobs(&self.core, &mut *data, &version, is_shadow_decryption, is_broadcast_session);
 | 
			
		||||
				match disseminate_result {
 | 
			
		||||
					Ok(()) => Ok(()),
 | 
			
		||||
					Err(err) => {
 | 
			
		||||
@ -468,6 +529,52 @@ impl SessionImpl {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Disseminate jobs on session master.
 | 
			
		||||
	fn disseminate_jobs(core: &SessionCore, data: &mut SessionData, version: &H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> {
 | 
			
		||||
		let key_share = match core.key_share.as_ref() {
 | 
			
		||||
			None => return Err(Error::InvalidMessage),
 | 
			
		||||
			Some(key_share) => key_share,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
 | 
			
		||||
		let requester = data.consensus_session.consensus_job().executor().requester()?.ok_or(Error::InvalidStateForRequest)?.clone();
 | 
			
		||||
		let consensus_group = data.consensus_session.select_consensus_group()?.clone();
 | 
			
		||||
		let decryption_job = DecryptionJob::new_on_master(core.meta.self_node_id.clone(),
 | 
			
		||||
			core.access_key.clone(), requester, key_share.clone(), key_version,
 | 
			
		||||
			is_shadow_decryption, is_broadcast_session)?;
 | 
			
		||||
		let decryption_request_id = decryption_job.request_id().clone().expect("TODO");
 | 
			
		||||
		let decryption_transport = core.decryption_transport(false);
 | 
			
		||||
		data.consensus_session.disseminate_jobs(decryption_job, decryption_transport, data.is_broadcast_session.expect("TODO"))?;
 | 
			
		||||
 | 
			
		||||
		// ...and prepare decryption job session if we need to broadcast result
 | 
			
		||||
		if data.is_broadcast_session.expect("TODO") {
 | 
			
		||||
			let broadcast_decryption_job = DecryptionJob::new_on_master(core.meta.self_node_id.clone(),
 | 
			
		||||
				core.access_key.clone(), requester, key_share.clone(), key_version, is_shadow_decryption, is_broadcast_session)?;
 | 
			
		||||
			Self::create_broadcast_decryption_job(&core, data, consensus_group, broadcast_decryption_job,
 | 
			
		||||
				decryption_request_id)?;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Create broadcast decryption job.
 | 
			
		||||
	fn create_broadcast_decryption_job(core: &SessionCore, data: &mut SessionData, mut consensus_group: BTreeSet<NodeId>, mut job: DecryptionJob, request_id: Secret) -> Result<(), Error> {
 | 
			
		||||
		consensus_group.insert(core.meta.self_node_id.clone());
 | 
			
		||||
		job.set_request_id(request_id.clone().into());
 | 
			
		||||
 | 
			
		||||
		let transport = core.decryption_transport(true);
 | 
			
		||||
		let mut job_session = JobSession::new(SessionMeta {
 | 
			
		||||
			id: core.meta.id.clone(),
 | 
			
		||||
			master_node_id: core.meta.self_node_id.clone(),
 | 
			
		||||
			self_node_id: core.meta.self_node_id.clone(),
 | 
			
		||||
			threshold: core.meta.threshold,
 | 
			
		||||
		}, job, transport);
 | 
			
		||||
		job_session.initialize(consensus_group, core.meta.self_node_id != core.meta.master_node_id)?;
 | 
			
		||||
		data.broadcast_job_session = Some(job_session);
 | 
			
		||||
 | 
			
		||||
		Ok(())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Set decryption result.
 | 
			
		||||
	fn set_decryption_result(core: &SessionCore, data: &mut SessionData, result: Result<EncryptedDocumentKeyShadow, Error>) {
 | 
			
		||||
		if let Some(DelegationStatus::DelegatedFrom(master, nonce)) = data.delegation_status.take() {
 | 
			
		||||
@ -555,26 +662,16 @@ impl ClusterSession for SessionImpl {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SessionCore {
 | 
			
		||||
	pub fn decryption_transport(&self) -> DecryptionJobTransport {
 | 
			
		||||
	pub fn decryption_transport(&self, is_broadcast_transport: bool) -> DecryptionJobTransport {
 | 
			
		||||
		DecryptionJobTransport {
 | 
			
		||||
			id: self.meta.id.clone(),
 | 
			
		||||
			access_key: self.access_key.clone(),
 | 
			
		||||
			nonce: self.nonce,
 | 
			
		||||
			cluster: self.cluster.clone()
 | 
			
		||||
			is_broadcast_transport: is_broadcast_transport,
 | 
			
		||||
			master_node_id: self.meta.master_node_id.clone(),
 | 
			
		||||
			cluster: self.cluster.clone(),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn disseminate_jobs(&self, consensus_session: &mut DecryptionConsensusSession, version: &H256, is_shadow_decryption: bool) -> Result<(), Error> {
 | 
			
		||||
		let key_share = match self.key_share.as_ref() {
 | 
			
		||||
			None => return Err(Error::InvalidMessage),
 | 
			
		||||
			Some(key_share) => key_share,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
 | 
			
		||||
		let requester = consensus_session.consensus_job().executor().requester()?.ok_or(Error::InvalidStateForRequest)?.clone();
 | 
			
		||||
		let decryption_job = DecryptionJob::new_on_master(self.meta.self_node_id.clone(), self.access_key.clone(), requester, key_share.clone(), key_version, is_shadow_decryption)?;
 | 
			
		||||
		consensus_session.disseminate_jobs(decryption_job, self.decryption_transport())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl JobTransport for DecryptionConsensusTransport {
 | 
			
		||||
@ -612,32 +709,41 @@ impl JobTransport for DecryptionJobTransport {
 | 
			
		||||
	type PartialJobResponse=PartialDecryptionResponse;
 | 
			
		||||
 | 
			
		||||
	fn send_partial_request(&self, node: &NodeId, request: PartialDecryptionRequest) -> Result<(), Error> {
 | 
			
		||||
		self.cluster.send(node, Message::Decryption(DecryptionMessage::RequestPartialDecryption(RequestPartialDecryption {
 | 
			
		||||
			session: self.id.clone().into(),
 | 
			
		||||
			sub_session: self.access_key.clone().into(),
 | 
			
		||||
			session_nonce: self.nonce,
 | 
			
		||||
			request_id: request.id.into(),
 | 
			
		||||
			is_shadow_decryption: request.is_shadow_decryption,
 | 
			
		||||
			nodes: request.other_nodes_ids.into_iter().map(Into::into).collect(),
 | 
			
		||||
		})))
 | 
			
		||||
		if !self.is_broadcast_transport {
 | 
			
		||||
			self.cluster.send(node, Message::Decryption(DecryptionMessage::RequestPartialDecryption(RequestPartialDecryption {
 | 
			
		||||
				session: self.id.clone().into(),
 | 
			
		||||
				sub_session: self.access_key.clone().into(),
 | 
			
		||||
				session_nonce: self.nonce,
 | 
			
		||||
				request_id: request.id.into(),
 | 
			
		||||
				is_shadow_decryption: request.is_shadow_decryption,
 | 
			
		||||
				is_broadcast_session: request.is_broadcast_session,
 | 
			
		||||
				nodes: request.other_nodes_ids.into_iter().map(Into::into).collect(),
 | 
			
		||||
			})))?;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn send_partial_response(&self, node: &NodeId, response: PartialDecryptionResponse) -> Result<(), Error> {
 | 
			
		||||
		self.cluster.send(node, Message::Decryption(DecryptionMessage::PartialDecryption(PartialDecryption {
 | 
			
		||||
			session: self.id.clone().into(),
 | 
			
		||||
			sub_session: self.access_key.clone().into(),
 | 
			
		||||
			session_nonce: self.nonce,
 | 
			
		||||
			request_id: response.request_id.into(),
 | 
			
		||||
			shadow_point: response.shadow_point.into(),
 | 
			
		||||
			decrypt_shadow: response.decrypt_shadow,
 | 
			
		||||
		})))
 | 
			
		||||
		if !self.is_broadcast_transport || *node != self.master_node_id {
 | 
			
		||||
			self.cluster.send(node, Message::Decryption(DecryptionMessage::PartialDecryption(PartialDecryption {
 | 
			
		||||
				session: self.id.clone().into(),
 | 
			
		||||
				sub_session: self.access_key.clone().into(),
 | 
			
		||||
				session_nonce: self.nonce,
 | 
			
		||||
				request_id: response.request_id.into(),
 | 
			
		||||
				shadow_point: response.shadow_point.into(),
 | 
			
		||||
				decrypt_shadow: response.decrypt_shadow,
 | 
			
		||||
			})))?;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
	use std::sync::Arc;
 | 
			
		||||
	use std::collections::BTreeMap;
 | 
			
		||||
	use std::collections::{BTreeMap, VecDeque};
 | 
			
		||||
	use acl_storage::DummyAclStorage;
 | 
			
		||||
	use ethkey::{self, KeyPair, Random, Generator, Public, Secret};
 | 
			
		||||
	use key_server_cluster::{NodeId, DocumentKeyShare, DocumentKeyShareVersion, SessionId, Error, EncryptedDocumentKeyShadow, SessionMeta};
 | 
			
		||||
@ -719,15 +825,36 @@ mod tests {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn do_messages_exchange_until<F>(clusters: &[Arc<DummyCluster>], sessions: &[SessionImpl], mut cond: F) -> Result<(), Error> where F: FnMut(&NodeId, &NodeId, &Message) -> bool {
 | 
			
		||||
		while let Some((from, to, message)) = clusters.iter().filter_map(|c| c.take_message().map(|(to, msg)| (c.node(), to, msg))).next() {
 | 
			
		||||
			let session = &sessions[sessions.iter().position(|s| s.node() == &to).unwrap()];
 | 
			
		||||
		let mut queue: VecDeque<(NodeId, NodeId, Message)> = VecDeque::new();
 | 
			
		||||
		while let Some((mut from, mut to, mut message)) = clusters.iter().filter_map(|c| c.take_message().map(|(to, msg)| (c.node(), to, msg))).next() {
 | 
			
		||||
			if cond(&from, &to, &message) {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			match message {
 | 
			
		||||
				Message::Decryption(message) => session.process_message(&from, &message)?,
 | 
			
		||||
				_ => unreachable!(),
 | 
			
		||||
			let mut is_queued_message = false;
 | 
			
		||||
			loop {
 | 
			
		||||
				let session = &sessions[sessions.iter().position(|s| s.node() == &to).unwrap()];
 | 
			
		||||
				match session.on_message(&from, &message) {
 | 
			
		||||
					Ok(_) => {
 | 
			
		||||
						if let Some(qmessage) = queue.pop_front() {
 | 
			
		||||
							from = qmessage.0;
 | 
			
		||||
							to = qmessage.1;
 | 
			
		||||
							message = qmessage.2;
 | 
			
		||||
							is_queued_message = true;
 | 
			
		||||
							continue;
 | 
			
		||||
						}
 | 
			
		||||
						break;
 | 
			
		||||
					},
 | 
			
		||||
					Err(Error::TooEarlyForRequest) => {
 | 
			
		||||
						if is_queued_message {
 | 
			
		||||
							queue.push_front((from, to, message));
 | 
			
		||||
						} else {
 | 
			
		||||
							queue.push_back((from, to, message));
 | 
			
		||||
						}
 | 
			
		||||
						break;
 | 
			
		||||
					},
 | 
			
		||||
					Err(err) => return Err(err),
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -784,7 +911,7 @@ mod tests {
 | 
			
		||||
			cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
 | 
			
		||||
			nonce: 0,
 | 
			
		||||
		}, Some(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap())).unwrap();
 | 
			
		||||
		assert_eq!(session.initialize(Default::default(), false), Err(Error::InvalidMessage));
 | 
			
		||||
		assert_eq!(session.initialize(Default::default(), false, false), Err(Error::InvalidMessage));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
@ -817,20 +944,20 @@ mod tests {
 | 
			
		||||
			cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
 | 
			
		||||
			nonce: 0,
 | 
			
		||||
		}, Some(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap())).unwrap();
 | 
			
		||||
		assert_eq!(session.initialize(Default::default(), false), Err(Error::ConsensusUnreachable));
 | 
			
		||||
		assert_eq!(session.initialize(Default::default(), false, false), Err(Error::ConsensusUnreachable));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn fails_to_initialize_when_already_initialized() {
 | 
			
		||||
		let (_, _, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		assert_eq!(sessions[0].initialize(Default::default(), false).unwrap(), ());
 | 
			
		||||
		assert_eq!(sessions[0].initialize(Default::default(), false).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
		assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap(), ());
 | 
			
		||||
		assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn fails_to_accept_initialization_when_already_initialized() {
 | 
			
		||||
		let (_, _, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		assert_eq!(sessions[0].initialize(Default::default(), false).unwrap(), ());
 | 
			
		||||
		assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap(), ());
 | 
			
		||||
		assert_eq!(sessions[0].on_consensus_message(sessions[1].node(), &message::DecryptionConsensusMessage {
 | 
			
		||||
				session: SessionId::default().into(),
 | 
			
		||||
				sub_session: sessions[0].access_key().clone().into(),
 | 
			
		||||
@ -860,6 +987,7 @@ mod tests {
 | 
			
		||||
			session_nonce: 0,
 | 
			
		||||
			request_id: Random.generate().unwrap().secret().clone().into(),
 | 
			
		||||
			is_shadow_decryption: false,
 | 
			
		||||
			is_broadcast_session: false,
 | 
			
		||||
			nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(),
 | 
			
		||||
		}).unwrap_err(), Error::InvalidMessage);
 | 
			
		||||
	}
 | 
			
		||||
@ -882,6 +1010,7 @@ mod tests {
 | 
			
		||||
			session_nonce: 0,
 | 
			
		||||
			request_id: Random.generate().unwrap().secret().clone().into(),
 | 
			
		||||
			is_shadow_decryption: false,
 | 
			
		||||
			is_broadcast_session: false,
 | 
			
		||||
			nodes: sessions.iter().map(|s| s.node().clone().into()).take(2).collect(),
 | 
			
		||||
		}).unwrap_err(), Error::InvalidMessage);
 | 
			
		||||
	}
 | 
			
		||||
@ -902,7 +1031,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn fails_to_accept_partial_decrypt_twice() {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		let mut pd_from = None;
 | 
			
		||||
		let mut pd_msg = None;
 | 
			
		||||
@ -930,7 +1059,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn node_is_marked_rejected_when_timed_out_during_initialization_confirmation() {
 | 
			
		||||
		let (_, _, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		// 1 node disconnects => we still can recover secret
 | 
			
		||||
		sessions[0].on_node_timeout(sessions[1].node());
 | 
			
		||||
@ -948,7 +1077,7 @@ mod tests {
 | 
			
		||||
		let key_pair = Random.generate().unwrap();
 | 
			
		||||
 | 
			
		||||
		acl_storages[1].prohibit(key_pair.public().clone(), SessionId::default());
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -960,7 +1089,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn session_does_not_fail_if_requested_node_disconnects() {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -976,7 +1105,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn session_does_not_fail_if_node_with_shadow_point_disconnects() {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults
 | 
			
		||||
			&& sessions[0].data.lock().consensus_session.computation_job().responses().len() == 2).unwrap();
 | 
			
		||||
@ -993,7 +1122,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn session_restarts_if_confirmed_node_disconnects() {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -1008,7 +1137,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn session_does_not_fail_if_non_master_node_disconnects_from_non_master_node() {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -1023,7 +1152,7 @@ mod tests {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
 | 
			
		||||
		// now let's try to do a decryption
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange(&clusters, &sessions).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -1045,7 +1174,7 @@ mod tests {
 | 
			
		||||
		let (key_pair, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
 | 
			
		||||
		// now let's try to do a decryption
 | 
			
		||||
		sessions[0].initialize(Default::default(), true).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), true, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange(&clusters, &sessions).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -1076,7 +1205,7 @@ mod tests {
 | 
			
		||||
		let (key_pair, clusters, acl_storages, sessions) = prepare_decryption_sessions();
 | 
			
		||||
 | 
			
		||||
		// now let's try to do a decryption
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		// we need 4 out of 5 nodes to agree to do a decryption
 | 
			
		||||
		// let's say that 2 of these nodes are disagree
 | 
			
		||||
@ -1099,7 +1228,7 @@ mod tests {
 | 
			
		||||
		acl_storages[0].prohibit(key_pair.public().clone(), SessionId::default());
 | 
			
		||||
 | 
			
		||||
		// now let's try to do a decryption
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
 | 
			
		||||
		do_messages_exchange(&clusters, &sessions).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -1139,7 +1268,7 @@ mod tests {
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		// now let's try to do a decryption
 | 
			
		||||
		sessions[1].delegate(sessions[0].core.meta.self_node_id.clone(), Default::default(), false).unwrap();
 | 
			
		||||
		sessions[1].delegate(sessions[0].core.meta.self_node_id.clone(), Default::default(), false, false).unwrap();
 | 
			
		||||
		do_messages_exchange(&clusters, &sessions).unwrap();
 | 
			
		||||
 | 
			
		||||
		// now check that:
 | 
			
		||||
@ -1165,7 +1294,7 @@ mod tests {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// now let's try to do a decryption
 | 
			
		||||
		sessions[0].initialize(Default::default(), false).unwrap();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, false).unwrap();
 | 
			
		||||
		do_messages_exchange(&clusters, &sessions).unwrap();
 | 
			
		||||
 | 
			
		||||
		assert_eq!(sessions[0].decrypted_secret().unwrap().unwrap(), EncryptedDocumentKeyShadow {
 | 
			
		||||
@ -1174,4 +1303,17 @@ mod tests {
 | 
			
		||||
			decrypt_shadows: None,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn decryption_result_restored_on_all_nodes_if_broadcast_session_is_completed() {
 | 
			
		||||
		let (_, clusters, _, sessions) = prepare_decryption_sessions();
 | 
			
		||||
		sessions[0].initialize(Default::default(), false, true).unwrap();
 | 
			
		||||
		do_messages_exchange(&clusters, &sessions).unwrap();
 | 
			
		||||
 | 
			
		||||
		// decryption result must be the same and available on 4 nodes
 | 
			
		||||
		let result = sessions[0].decrypted_secret();
 | 
			
		||||
		assert!(result.clone().unwrap().is_ok());
 | 
			
		||||
		assert_eq!(3, sessions.iter().skip(1).filter(|s| s.decrypted_secret() == result).count());
 | 
			
		||||
		assert_eq!(1, sessions.iter().skip(1).filter(|s| s.decrypted_secret().is_none()).count());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -734,7 +734,7 @@ impl SessionCore {
 | 
			
		||||
 | 
			
		||||
		let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone();
 | 
			
		||||
		let signing_job = SigningJob::new_on_master(self.meta.self_node_id.clone(), key_share.clone(), key_version, session_public, session_secret_share, message_hash)?;
 | 
			
		||||
		consensus_session.disseminate_jobs(signing_job, self.signing_transport())
 | 
			
		||||
		consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -474,9 +474,9 @@ impl ClusterCore {
 | 
			
		||||
					Ok((version, master)) => match session.continue_action() {
 | 
			
		||||
						Some(ContinueAction::Decrypt(session, is_shadow_decryption)) => {
 | 
			
		||||
							let initialization_error = if data.self_key_pair.public() == &master {
 | 
			
		||||
								session.initialize(version, is_shadow_decryption)
 | 
			
		||||
								session.initialize(version, is_shadow_decryption, false)
 | 
			
		||||
							} else {
 | 
			
		||||
								session.delegate(master, version, is_shadow_decryption)
 | 
			
		||||
								session.delegate(master, version, is_shadow_decryption, false)
 | 
			
		||||
							};
 | 
			
		||||
 | 
			
		||||
							if let Err(error) = initialization_error {
 | 
			
		||||
@ -920,7 +920,7 @@ impl ClusterClient for ClusterClientImpl {
 | 
			
		||||
		let session = self.data.sessions.decryption_sessions.insert(cluster, self.data.self_key_pair.public().clone(), session_id.clone(), None, false, Some(requestor_signature))?;
 | 
			
		||||
 | 
			
		||||
		let initialization_result = match version {
 | 
			
		||||
			Some(version) => session.initialize(version, is_shadow_decryption),
 | 
			
		||||
			Some(version) => session.initialize(version, is_shadow_decryption, false),
 | 
			
		||||
			None => {
 | 
			
		||||
				self.create_key_version_negotiation_session(session_id.id.clone())
 | 
			
		||||
					.map(|version_session| {
 | 
			
		||||
 | 
			
		||||
@ -141,7 +141,7 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
 | 
			
		||||
	/// Initialize session on master node.
 | 
			
		||||
	pub fn initialize(&mut self, nodes: BTreeSet<NodeId>) -> Result<(), Error> {
 | 
			
		||||
		debug_assert!(self.meta.self_node_id == self.meta.master_node_id);
 | 
			
		||||
		let initialization_result = self.consensus_job.initialize(nodes);
 | 
			
		||||
		let initialization_result = self.consensus_job.initialize(nodes, false);
 | 
			
		||||
		self.state = ConsensusSessionState::EstablishingConsensus;
 | 
			
		||||
		self.process_result(initialization_result)
 | 
			
		||||
	}
 | 
			
		||||
@ -180,12 +180,12 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Disseminate jobs from master node.
 | 
			
		||||
	pub fn disseminate_jobs(&mut self, executor: ComputationExecutor, transport: ComputationTransport) -> Result<(), Error> {
 | 
			
		||||
	pub fn disseminate_jobs(&mut self, executor: ComputationExecutor, transport: ComputationTransport, broadcast_self_response: bool) -> Result<(), Error> {
 | 
			
		||||
		let consensus_group = self.select_consensus_group()?.clone();
 | 
			
		||||
		self.consensus_group.clear();
 | 
			
		||||
 | 
			
		||||
		let mut computation_job = JobSession::new(self.meta.clone(), executor, transport);
 | 
			
		||||
		let computation_result = computation_job.initialize(consensus_group);
 | 
			
		||||
		let computation_result = computation_job.initialize(consensus_group, broadcast_self_response);
 | 
			
		||||
		self.computation_job = Some(computation_job);
 | 
			
		||||
		self.state = ConsensusSessionState::WaitingForPartialResults;
 | 
			
		||||
		self.process_result(computation_result)
 | 
			
		||||
@ -212,6 +212,7 @@ impl<ConsensusExecutor, ConsensusTransport, ComputationExecutor, ComputationTran
 | 
			
		||||
		let computation_result = self.computation_job.as_mut()
 | 
			
		||||
			.expect("WaitingForPartialResults is only set when computation_job is created; qed")
 | 
			
		||||
			.on_partial_response(node, response);
 | 
			
		||||
 | 
			
		||||
		self.process_result(computation_result)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -449,7 +450,7 @@ mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn consensus_session_job_dissemination_fails_if_consensus_is_not_reached() {
 | 
			
		||||
		let mut session = make_master_consensus_session(1, None, None);
 | 
			
		||||
		assert_eq!(session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
		assert_eq!(session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
@ -461,7 +462,7 @@ mod tests {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::Finished);
 | 
			
		||||
		assert!(session.computation_job().responses().contains_key(&NodeId::from(1)));
 | 
			
		||||
	}
 | 
			
		||||
@ -479,7 +480,7 @@ mod tests {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
		assert!(!session.computation_job().responses().contains_key(&NodeId::from(1)));
 | 
			
		||||
	}
 | 
			
		||||
@ -623,7 +624,7 @@ mod tests {
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(3), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(3)), Ok(false));
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(4)), Ok(false));
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
@ -636,7 +637,7 @@ mod tests {
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(2), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(3), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
@ -644,7 +645,7 @@ mod tests {
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(2)), Ok(true));
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(3)), Ok(false));
 | 
			
		||||
@ -658,7 +659,7 @@ mod tests {
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(2), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(2)), Err(Error::ConsensusUnreachable));
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::Failed);
 | 
			
		||||
	}
 | 
			
		||||
@ -677,7 +678,7 @@ mod tests {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(3), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
@ -686,7 +687,7 @@ mod tests {
 | 
			
		||||
		assert_eq!(session.on_session_timeout(), Ok(true));
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		assert_eq!(session.on_session_timeout(), Ok(false));
 | 
			
		||||
@ -700,7 +701,7 @@ mod tests {
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(2), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		assert_eq!(session.on_session_timeout(), Err(Error::ConsensusUnreachable));
 | 
			
		||||
@ -732,7 +733,7 @@ mod tests {
 | 
			
		||||
			is_confirmed: true,
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
		session.on_job_response(&NodeId::from(2), 16).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::Finished);
 | 
			
		||||
@ -749,7 +750,7 @@ mod tests {
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		session.on_consensus_message(&NodeId::from(3), &ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
 | 
			
		||||
@ -759,7 +760,7 @@ mod tests {
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(2)).unwrap(), true);
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		assert_eq!(session.on_node_error(&NodeId::from(3)).unwrap(), false);
 | 
			
		||||
@ -770,7 +771,7 @@ mod tests {
 | 
			
		||||
		})).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::ConsensusEstablished);
 | 
			
		||||
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default()).unwrap();
 | 
			
		||||
		session.disseminate_jobs(SquaredSumJobExecutor, DummyJobTransport::default(), false).unwrap();
 | 
			
		||||
		assert_eq!(session.state(), ConsensusSessionState::WaitingForPartialResults);
 | 
			
		||||
 | 
			
		||||
		session.on_job_response(&NodeId::from(4), 16).unwrap();
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,8 @@ pub struct DecryptionJob {
 | 
			
		||||
	request_id: Option<Secret>,
 | 
			
		||||
	/// Is shadow decryption requested.
 | 
			
		||||
	is_shadow_decryption: Option<bool>,
 | 
			
		||||
	/// Is broadcast decryption requested.
 | 
			
		||||
	is_broadcast_session: Option<bool>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Decryption job partial request.
 | 
			
		||||
@ -47,11 +49,14 @@ pub struct PartialDecryptionRequest {
 | 
			
		||||
	pub id: Secret,
 | 
			
		||||
	/// Is shadow decryption requested.
 | 
			
		||||
	pub is_shadow_decryption: bool,
 | 
			
		||||
	/// Is broadcast decryption requested.
 | 
			
		||||
	pub is_broadcast_session: bool,
 | 
			
		||||
	/// Id of other nodes, participating in decryption.
 | 
			
		||||
	pub other_nodes_ids: BTreeSet<NodeId>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Decryption job partial response.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct PartialDecryptionResponse {
 | 
			
		||||
	/// Request id.
 | 
			
		||||
	pub request_id: Secret,
 | 
			
		||||
@ -72,10 +77,11 @@ impl DecryptionJob {
 | 
			
		||||
			key_version: key_version,
 | 
			
		||||
			request_id: None,
 | 
			
		||||
			is_shadow_decryption: None,
 | 
			
		||||
			is_broadcast_session: None,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn new_on_master(self_node_id: NodeId, access_key: Secret, requester: Public, key_share: DocumentKeyShare, key_version: H256, is_shadow_decryption: bool) -> Result<Self, Error> {
 | 
			
		||||
	pub fn new_on_master(self_node_id: NodeId, access_key: Secret, requester: Public, key_share: DocumentKeyShare, key_version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<Self, Error> {
 | 
			
		||||
		debug_assert!(key_share.common_point.is_some() && key_share.encrypted_point.is_some());
 | 
			
		||||
		Ok(DecryptionJob {
 | 
			
		||||
			self_node_id: self_node_id,
 | 
			
		||||
@ -85,8 +91,17 @@ impl DecryptionJob {
 | 
			
		||||
			key_version: key_version,
 | 
			
		||||
			request_id: Some(math::generate_random_scalar()?),
 | 
			
		||||
			is_shadow_decryption: Some(is_shadow_decryption),
 | 
			
		||||
			is_broadcast_session: Some(is_broadcast_session),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn request_id(&self) -> &Option<Secret> {
 | 
			
		||||
		&self.request_id
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn set_request_id(&mut self, request_id: Secret) {
 | 
			
		||||
		self.request_id = Some(request_id);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl JobExecutor for DecryptionJob {
 | 
			
		||||
@ -101,12 +116,15 @@ impl JobExecutor for DecryptionJob {
 | 
			
		||||
			.expect("prepare_partial_request is only called on master nodes; request_id is filed in constructor on master nodes; qed");
 | 
			
		||||
		let is_shadow_decryption = self.is_shadow_decryption
 | 
			
		||||
			.expect("prepare_partial_request is only called on master nodes; is_shadow_decryption is filed in constructor on master nodes; qed");
 | 
			
		||||
		let is_broadcast_session = self.is_broadcast_session
 | 
			
		||||
			.expect("prepare_partial_request is only called on master nodes; is_broadcast_session is filed in constructor on master nodes; qed");
 | 
			
		||||
		let mut other_nodes_ids = nodes.clone();
 | 
			
		||||
		other_nodes_ids.remove(node);
 | 
			
		||||
 | 
			
		||||
		Ok(PartialDecryptionRequest {
 | 
			
		||||
			id: request_id.clone(),
 | 
			
		||||
			is_shadow_decryption: is_shadow_decryption,
 | 
			
		||||
			is_broadcast_session: is_broadcast_session,
 | 
			
		||||
			other_nodes_ids: other_nodes_ids,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,7 @@ pub enum JobPartialRequestAction<PartialJobResponse> {
 | 
			
		||||
/// Job executor.
 | 
			
		||||
pub trait JobExecutor {
 | 
			
		||||
	type PartialJobRequest;
 | 
			
		||||
	type PartialJobResponse;
 | 
			
		||||
	type PartialJobResponse: Clone;
 | 
			
		||||
	type JobResponse;
 | 
			
		||||
 | 
			
		||||
	/// Prepare job request for given node.
 | 
			
		||||
@ -175,6 +175,14 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
 | 
			
		||||
			.responses
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Returns true if enough responses are ready to compute result.
 | 
			
		||||
	pub fn is_result_ready(&self) -> bool {
 | 
			
		||||
		debug_assert!(self.meta.self_node_id == self.meta.master_node_id);
 | 
			
		||||
		self.data.active_data.as_ref()
 | 
			
		||||
			.expect("is_result_ready is only called on master nodes after initialization; on master nodes active_data is filled during initialization; qed")
 | 
			
		||||
			.responses.len() >= self.meta.threshold + 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Get job result.
 | 
			
		||||
	pub fn result(&self) -> Result<Executor::JobResponse, Error> {
 | 
			
		||||
		debug_assert!(self.meta.self_node_id == self.meta.master_node_id);
 | 
			
		||||
@ -189,7 +197,7 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// Initialize.
 | 
			
		||||
	pub fn initialize(&mut self, nodes: BTreeSet<NodeId>) -> Result<(), Error> {		
 | 
			
		||||
	pub fn initialize(&mut self, nodes: BTreeSet<NodeId>, broadcast_self_response: bool) -> Result<(), Error> {		
 | 
			
		||||
		debug_assert!(self.meta.self_node_id == self.meta.master_node_id);
 | 
			
		||||
 | 
			
		||||
		if nodes.len() < self.meta.threshold + 1 {
 | 
			
		||||
@ -213,25 +221,32 @@ impl<Executor, Transport> JobSession<Executor, Transport> where Executor: JobExe
 | 
			
		||||
		} else {
 | 
			
		||||
			None
 | 
			
		||||
		};
 | 
			
		||||
		let self_response = match self_response {
 | 
			
		||||
			Some(JobPartialRequestAction::Respond(self_response)) => Some(self_response),
 | 
			
		||||
			Some(JobPartialRequestAction::Reject(self_response)) => Some(self_response),
 | 
			
		||||
			None => None,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// update state
 | 
			
		||||
		self.data.active_data = Some(active_data);
 | 
			
		||||
		self.data.state = JobSessionState::Active;
 | 
			
		||||
 | 
			
		||||
		// if we are waiting for response from self => do it
 | 
			
		||||
		if let Some(self_response) = self_response {
 | 
			
		||||
		if let Some(self_response) = self_response.clone() {
 | 
			
		||||
			let self_node_id = self.meta.self_node_id.clone();
 | 
			
		||||
			match self_response {
 | 
			
		||||
				JobPartialRequestAction::Respond(self_response) => self.on_partial_response(&self_node_id, self_response)?,
 | 
			
		||||
				JobPartialRequestAction::Reject(self_response) => self.on_partial_response(&self_node_id, self_response)?,
 | 
			
		||||
			}
 | 
			
		||||
			self.on_partial_response(&self_node_id, self_response)?;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// send requests to save nodes. we only send requests if session is still active.
 | 
			
		||||
		if self.data.state == JobSessionState::Active {
 | 
			
		||||
			for node in nodes.iter().filter(|n| **n != self.meta.self_node_id) {
 | 
			
		||||
		for node in nodes.iter().filter(|n| **n != self.meta.self_node_id) {
 | 
			
		||||
			if self.data.state == JobSessionState::Active {
 | 
			
		||||
				self.transport.send_partial_request(node, self.executor.prepare_partial_request(node, &nodes)?)?;
 | 
			
		||||
			}
 | 
			
		||||
			if broadcast_self_response {
 | 
			
		||||
				if let Some(self_response) = self_response.clone() {
 | 
			
		||||
					self.transport.send_partial_response(node, self_response)?;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Ok(())
 | 
			
		||||
@ -372,6 +387,10 @@ pub mod tests {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	impl<T, U> DummyJobTransport<T, U> {
 | 
			
		||||
		pub fn is_empty_response(&self) -> bool {
 | 
			
		||||
			self.responses.lock().is_empty()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn response(&self) -> (NodeId, U) {
 | 
			
		||||
			self.responses.lock().pop_front().unwrap()
 | 
			
		||||
		}
 | 
			
		||||
@ -396,22 +415,23 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_initialize_fails_if_not_inactive() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1)].into_iter().collect()).unwrap();
 | 
			
		||||
		assert_eq!(job.initialize(vec![Public::from(1)].into_iter().collect()).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
		job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_initialization_leads_to_finish_if_single_node_is_required() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Finished);
 | 
			
		||||
		assert!(job.is_result_ready());
 | 
			
		||||
		assert_eq!(job.result(), Ok(4));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_initialization_does_not_leads_to_finish_if_single_other_node_is_required() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -454,7 +474,7 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_response_fails_if_comes_to_failed_state() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		job.on_session_timeout().unwrap_err();
 | 
			
		||||
		assert_eq!(job.on_partial_response(&NodeId::from(2), 2).unwrap_err(), Error::InvalidStateForRequest);
 | 
			
		||||
	}
 | 
			
		||||
@ -462,14 +482,14 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_response_fails_if_comes_from_unknown_node() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.on_partial_response(&NodeId::from(3), 2).unwrap_err(), Error::InvalidNodeForRequest);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_response_leads_to_failure_if_too_few_nodes_left() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		assert_eq!(job.on_partial_response(&NodeId::from(2), 3).unwrap_err(), Error::ConsensusUnreachable);
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Failed);
 | 
			
		||||
@ -478,16 +498,18 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_response_succeeds() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(2), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		assert!(!job.is_result_ready());
 | 
			
		||||
		job.on_partial_response(&NodeId::from(2), 2).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		assert!(!job.is_result_ready());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_response_leads_to_finish() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		job.on_partial_response(&NodeId::from(2), 2).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Finished);
 | 
			
		||||
@ -512,7 +534,7 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_node_error_ignored_when_disconnects_from_rejected() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		job.on_partial_response(&NodeId::from(2), 3).unwrap();
 | 
			
		||||
		job.on_node_error(&NodeId::from(2)).unwrap();
 | 
			
		||||
@ -522,7 +544,7 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_node_error_ignored_when_disconnects_from_unknown() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		job.on_node_error(&NodeId::from(3)).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
@ -531,7 +553,7 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_node_error_ignored_when_disconnects_from_requested_and_enough_nodes_left() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		job.on_node_error(&NodeId::from(3)).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
@ -540,9 +562,25 @@ pub mod tests {
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_node_error_leads_to_fail_when_disconnects_from_requested_and_not_enough_nodes_left() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect()).unwrap();
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		assert_eq!(job.on_node_error(&NodeId::from(2)).unwrap_err(), Error::ConsensusUnreachable);
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Failed);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_broadcasts_self_response() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), true).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		assert_eq!(job.transport().response(), (NodeId::from(2), 4));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn job_does_not_broadcasts_self_response() {
 | 
			
		||||
		let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default());
 | 
			
		||||
		job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap();
 | 
			
		||||
		assert_eq!(job.state(), JobSessionState::Active);
 | 
			
		||||
		assert!(job.transport().is_empty_response());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ pub struct PartialSigningRequest {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Signing job partial response.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct PartialSigningResponse {
 | 
			
		||||
	/// Request id.
 | 
			
		||||
	pub request_id: Secret,
 | 
			
		||||
 | 
			
		||||
@ -548,6 +548,9 @@ pub struct RequestPartialDecryption {
 | 
			
		||||
	/// Is shadow decryption requested? When true, decryption result
 | 
			
		||||
	/// will be visible to the owner of requestor public key only.
 | 
			
		||||
	pub is_shadow_decryption: bool,
 | 
			
		||||
	/// Decryption result must be reconstructed on all participating nodes. This is useful
 | 
			
		||||
	/// for service contract API so that all nodes from consensus group can confirm decryption.
 | 
			
		||||
	pub is_broadcast_session: bool,
 | 
			
		||||
	/// Nodes that are agreed to do a decryption.
 | 
			
		||||
	pub nodes: BTreeSet<MessageNodeId>,
 | 
			
		||||
}
 | 
			
		||||
@ -609,6 +612,9 @@ pub struct DecryptionSessionDelegation {
 | 
			
		||||
	/// Is shadow decryption requested? When true, decryption result
 | 
			
		||||
	/// will be visible to the owner of requestor public key only.
 | 
			
		||||
	pub is_shadow_decryption: bool,
 | 
			
		||||
	/// Decryption result must be reconstructed on all participating nodes. This is useful
 | 
			
		||||
	/// for service contract API so that all nodes from consensus group can confirm decryption.
 | 
			
		||||
	pub is_broadcast_session: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// When delegated decryption session is completed.
 | 
			
		||||
 | 
			
		||||
@ -583,7 +583,7 @@ mod tests {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn server_key_generation_is_scheduled_when_requested_key_is_unknnown() {
 | 
			
		||||
	fn server_key_generation_is_scheduled_when_requested_key_is_unknown() {
 | 
			
		||||
		let mut contract = DummyServiceContract::default();
 | 
			
		||||
		contract.logs.push(vec![Default::default(), Default::default(), Default::default()]);
 | 
			
		||||
		let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None);
 | 
			
		||||
@ -605,7 +605,7 @@ mod tests {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	#[test]
 | 
			
		||||
	fn server_key_restore_is_scheduled_when_requested_key_is_knnown() {
 | 
			
		||||
	fn server_key_restore_is_scheduled_when_requested_key_is_known() {
 | 
			
		||||
		let mut contract = DummyServiceContract::default();
 | 
			
		||||
		contract.logs.push(vec![Default::default(), Default::default(), Default::default()]);
 | 
			
		||||
		let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user