From ec94f9982955ce6b5bf2c3ba56eb4c1305cbf7e0 Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 16 Sep 2024 14:27:56 -0500 Subject: [PATCH 1/4] cargo fmt --- client-interface/src/lib.rs | 167 +++++++++----- client-interface/src/subscan.rs | 147 +++++++------ client/src/lib.rs | 190 ++++++++++------ client/src/verify.rs | 136 ++++++++---- common/src/attestation.rs | 117 ++++++---- common/src/lib.rs | 98 ++++++--- common/src/nitro.rs | 40 ++-- enclave-interface/src/lib.rs | 18 +- enclave/src/lib.rs | 168 +++++++++----- enclave/src/main.rs | 29 ++- service/src/dynamodb.rs | 131 +++++++---- service/src/enclave.rs | 45 ++-- service/src/lib.rs | 110 ++++++---- service/src/main.rs | 377 ++++++++++++++++++++------------ service/src/mixing.rs | 24 +- service/src/storage.rs | 25 ++- stress-tool/src/main.rs | 41 ++-- 17 files changed, 1196 insertions(+), 667 deletions(-) diff --git a/client-interface/src/lib.rs b/client-interface/src/lib.rs index 782bcde..955e0b7 100644 --- a/client-interface/src/lib.rs +++ b/client-interface/src/lib.rs @@ -54,7 +54,7 @@ pub type ReferendumStatus = metadata::runtime_types::pallet_referenda::types::Re u128, Tally, SubxtAccountId32, - (u32, u32) + (u32, u32), >; #[derive(Clone)] @@ -72,10 +72,13 @@ impl SubstrateNetwork { .max_delay(Duration::from_secs(10)) .take(5), ) - .build(url).await?; - let api = OnlineClient::from_rpc_client(rpc_client).await + .build(url) + .await?; + let api = OnlineClient::from_rpc_client(rpc_client) + .await .context("Unable to connect to network endpoint")?; - let ss58_address_format = api.constants() + let ss58_address_format = api + .constants() .at(&metadata::constants().system().ss58_prefix()) .map(Ss58AddressFormat::custom)?; let mut network_name = ss58_address_format.to_string(); @@ -88,8 +91,15 @@ impl SubstrateNetwork { .tokens() .first() .map(|token_registry| Token::from(*token_registry)) - .unwrap_or_else(|| Token { name: "ROC", decimals: 12 }); - Ok(Self { api, network_name, token }) + .unwrap_or_else(|| Token { + name: "ROC", + decimals: 12, + }); + Ok(Self { + api, + network_name, + token, + }) } /// This is the equivalent to calling [subxt::error::DispatchError::decode_from] followed by @@ -102,7 +112,7 @@ impl SubstrateNetwork { module_error.error[0], module_error.error[1], module_error.error[2], - module_error.error[3] + module_error.error[3], ]; let metadata = self.api.metadata(); // Taken from ModuleError::as_root_error @@ -110,7 +120,8 @@ impl SubstrateNetwork { &mut &bytes[..], metadata.outer_enums().error_enum_ty(), metadata.types(), - ).ok() + ) + .ok() } else { None } @@ -121,14 +132,15 @@ impl SubstrateNetwork { for (batch_index, proxy_executed) in events.find::().enumerate() { match proxy_executed { Ok(ProxyExecuted { result: Ok(_) }) => continue, - Ok(ProxyExecuted { result: Err(dispatch_error) }) => { - return self.extract_runtime_error(&dispatch_error) - .map_or_else( - || Err(ProxyError::Dispatch(batch_index, dispatch_error)), - |runtime_error| Err(ProxyError::Module(batch_index, runtime_error)) - ) - }, - Err(subxt_error) => return Err(subxt_error.into()) + Ok(ProxyExecuted { + result: Err(dispatch_error), + }) => { + return self.extract_runtime_error(&dispatch_error).map_or_else( + || Err(ProxyError::Dispatch(batch_index, dispatch_error)), + |runtime_error| Err(ProxyError::Module(batch_index, runtime_error)), + ) + } + Err(subxt_error) => return Err(subxt_error.into()), } } Ok(()) @@ -137,7 +149,7 @@ impl SubstrateNetwork { pub async fn subscribe_successful_extrinsics(&self, f: F) -> Result<(), SubxtError> where Fut: Future>, - F: Fn(ExtrinsicDetails, ExtrinsicEvents) -> Fut + F: Fn(ExtrinsicDetails, ExtrinsicEvents) -> Fut, { let mut blocks_sub = self.api.blocks().subscribe_finalized().await?; while let Some(block) = blocks_sub.next().await { @@ -163,17 +175,20 @@ impl SubstrateNetwork { } pub async fn account_balance(&self, account: AccountId32) -> Result { - let balance = self.api + let balance = self + .api .storage() - .at_latest().await? - .fetch(&storage().system().account(core_to_subxt(account))).await? + .at_latest() + .await? + .fetch(&storage().system().account(core_to_subxt(account))) + .await? .map_or(0, |account| account.data.free); Ok(balance) } pub async fn get_ongoing_poll( &self, - poll_index: u32 + poll_index: u32, ) -> Result, SubxtError> { match self.get_poll(poll_index).await? { Some(ReferendumInfoFor::Ongoing(status)) => Ok(Some(status)), @@ -184,12 +199,20 @@ impl SubstrateNetwork { pub async fn get_poll(&self, poll_index: u32) -> Result, SubxtError> { self.api .storage() - .at_latest().await? - .fetch(&storage().referenda().referendum_info_for(poll_index).unvalidated()).await + .at_latest() + .await? + .fetch( + &storage() + .referenda() + .referendum_info_for(poll_index) + .unvalidated(), + ) + .await } pub fn get_tracks(&self) -> Result, SubxtError> { - let tracks = self.api + let tracks = self + .api .constants() .at(&metadata::constants().referenda().tracks())? .into_iter() @@ -198,19 +221,25 @@ impl SubstrateNetwork { } pub async fn current_block_number(&self) -> Result { - let current_block_number = self.api + let current_block_number = self + .api .storage() - .at_latest().await? - .fetch(&storage().system().number()).await? + .at_latest() + .await? + .fetch(&storage().system().number()) + .await? .ok_or_else(|| SubxtError::Other("Current block number not available".into()))?; Ok(current_block_number) } pub async fn current_time(&self) -> Result { - let current_time = self.api + let current_time = self + .api .storage() - .at_latest().await? - .fetch(&storage().timestamp().now()).await? + .at_latest() + .await? + .fetch(&storage().timestamp().now()) + .await? .ok_or_else(|| SubxtError::Other("Current time not available".into()))?; Ok(current_time) } @@ -229,13 +258,17 @@ impl Debug for SubstrateNetwork { pub struct CallableSubstrateNetwork { pub network: SubstrateNetwork, pub account_key: sr25519::Keypair, - submit_lock: Arc> + submit_lock: Arc>, } impl CallableSubstrateNetwork { pub async fn connect(url: String, account_key: sr25519::Keypair) -> Result { let network = SubstrateNetwork::connect(url).await?; - Ok(Self { network, account_key, submit_lock: Arc::default() }) + Ok(Self { + network, + account_key, + submit_lock: Arc::default(), + }) } pub fn account(&self) -> AccountId32 { @@ -244,19 +277,28 @@ impl CallableSubstrateNetwork { pub async fn call_extrinsic( &self, - payload: &Call + payload: &Call, ) -> Result<(ExtrinsicEvents, ExtrinsicLocation), SubxtError> { // Submitting concurrent extrinsics causes problems with the nonce let guard = self.submit_lock.lock().await; - let tx_in_block = self.api.tx() - .sign_and_submit_then_watch_default(payload, &self.account_key).await? - .wait_for_finalized().await?; + let tx_in_block = self + .api + .tx() + .sign_and_submit_then_watch_default(payload, &self.account_key) + .await? + .wait_for_finalized() + .await?; // Unlock here as it's now OK for another thread to submit an extrinsic drop(guard); let events = tx_in_block.wait_for_success().await?; let location = ExtrinsicLocation { - block_number: self.api.blocks().at(tx_in_block.block_hash()).await?.number(), - extrinsic_index: events.extrinsic_index() + block_number: self + .api + .blocks() + .at(tx_in_block.block_hash()) + .await? + .number(), + extrinsic_index: events.extrinsic_index(), }; Ok((events, location)) } @@ -267,10 +309,13 @@ impl CallableSubstrateNetwork { if let Some(batch_interrupted) = events.find_first::()? { let runtime_error = self.extract_runtime_error(&batch_interrupted.error); return if let Some(runtime_error) = runtime_error { - Err(BatchError::Module(batch_interrupted.index as usize, runtime_error)) + Err(BatchError::Module( + batch_interrupted.index as usize, + runtime_error, + )) } else { Err(BatchError::Dispatch(batch_interrupted)) - } + }; } Ok(events) } @@ -313,7 +358,7 @@ pub enum ProxyError { #[error("Batch error: {0}")] Batch(#[from] BatchError), #[error("Internal Subxt error: {0}")] - Subxt(#[from] SubxtError) + Subxt(#[from] SubxtError), } impl ProxyError { @@ -322,9 +367,10 @@ impl ProxyError { ProxyError::Module(batch_index, _) => Some(*batch_index), ProxyError::Dispatch(batch_index, _) => Some(*batch_index), ProxyError::Batch(BatchError::Module(batch_index, _)) => Some(*batch_index), - ProxyError::Batch(BatchError::Dispatch(batch_interrupted)) => - Some(batch_interrupted.index as usize), - _ => None + ProxyError::Batch(BatchError::Dispatch(batch_interrupted)) => { + Some(batch_interrupted.index as usize) + } + _ => None, } } } @@ -332,22 +378,25 @@ impl ProxyError { pub async fn is_glove_member( network: &SubstrateNetwork, client_account: AccountId32, - glove_account: AccountId32 + glove_account: AccountId32, ) -> Result { let proxies_query = storage() .proxy() .proxies(core_to_subxt(client_account)) .unvalidated(); - let result = network.api.storage().at_latest().await?.fetch(&proxies_query).await?; + let result = network + .api + .storage() + .at_latest() + .await? + .fetch(&proxies_query) + .await?; if let Some(proxies) = result { let glove_account = core_to_subxt(glove_account); - Ok(proxies.0.0 - .iter() - .any(|proxy| { - matches!(proxy.proxy_type, ProxyType::Any | ProxyType::Governance) && - proxy.delegate == glove_account - }) - ) + Ok(proxies.0 .0.iter().any(|proxy| { + matches!(proxy.proxy_type, ProxyType::Any | ProxyType::Governance) + && proxy.delegate == glove_account + })) } else { Ok(false) } @@ -369,7 +418,7 @@ pub struct ServiceInfo { pub network_name: String, #[serde(with = "common::serde_over_hex_scale")] pub attestation_bundle: AttestationBundle, - pub version: String + pub version: String, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, MaxEncodedLen)] @@ -377,7 +426,7 @@ pub struct SignedRemoveVoteRequest { #[serde(with = "common::serde_over_hex_scale")] pub request: RemoveVoteRequest, #[serde(with = "common::serde_over_hex_scale")] - pub signature: MultiSignature + pub signature: MultiSignature, } impl SignedRemoveVoteRequest { @@ -389,7 +438,7 @@ impl SignedRemoveVoteRequest { #[derive(Debug, Clone, PartialEq, Encode, Decode, MaxEncodedLen)] pub struct RemoveVoteRequest { pub account: AccountId32, - pub poll_index: u32 + pub poll_index: u32, } pub fn parse_secret_phrase(str: &str) -> Result { @@ -418,11 +467,11 @@ mod tests { attested_data: AttestedData { genesis_hash: random::<[u8; 32]>().into(), signing_key: ed25519::Pair::generate().0.public(), - version: "1.0.0".to_string() + version: "1.0.0".to_string(), }, - attestation: Attestation::Mock + attestation: Attestation::Mock, }, - version: "1.0.0".to_string() + version: "1.0.0".to_string(), }; let json = serde_json::to_string(&service_info).unwrap(); diff --git a/client-interface/src/subscan.rs b/client-interface/src/subscan.rs index d9d37c9..210f36c 100644 --- a/client-interface/src/subscan.rs +++ b/client-interface/src/subscan.rs @@ -33,13 +33,16 @@ impl Subscan { pub async fn get_polls(&self, status: PollStatus) -> Result, Error> { let mut all_polls = Vec::new(); - let mut request = PollsRequest { status, page: 0, row: 100 }; + let mut request = PollsRequest { + status, + page: 0, + row: 100, + }; loop { - let (headers, polls_response) = self.api_call_for::( - "scan/referenda/referendums", - &request - ).await?; + let (headers, polls_response) = self + .api_call_for::("scan/referenda/referendums", &request) + .await?; let Some(data) = polls_response.data else { handle_error(headers, &polls_response.api_response, &request).await?; continue; @@ -57,7 +60,7 @@ impl Subscan { pub async fn get_votes( &self, poll_index: u32, - account: Option + account: Option, ) -> Result, Error> { let mut all_votes = Vec::new(); @@ -70,10 +73,9 @@ impl Subscan { }; loop { - let (headers, votes_response) = self.api_call_for::( - "scan/referenda/votes", - &request - ).await?; + let (headers, votes_response) = self + .api_call_for::("scan/referenda/votes", &request) + .await?; let Some(data) = votes_response.data else { handle_error(headers, &votes_response.api_response, &request).await?; continue; @@ -89,12 +91,14 @@ impl Subscan { } pub async fn get_block(&self, block_number: u32) -> Result, Error> { - let request = BlockRequest { block_num: block_number, only_head: true }; + let request = BlockRequest { + block_num: block_number, + only_head: true, + }; loop { - let (headers, block_response) = self.api_call_for::( - "scan/block", - &request - ).await?; + let (headers, block_response) = self + .api_call_for::("scan/block", &request) + .await?; if let Some(block) = block_response.data { return Ok(Some(block)); }; @@ -107,7 +111,7 @@ impl Subscan { pub async fn get_extrinsic( &self, - extrinsic_location: ExtrinsicLocation + extrinsic_location: ExtrinsicLocation, ) -> Result, Error> { let request = ExtrinsicRequest { events_limit: 1, @@ -115,10 +119,9 @@ impl Subscan { only_extrinsic_event: true, }; loop { - let (headers, extrinsic_response) = self.api_call_for::( - "scan/extrinsic", - &request - ).await?; + let (headers, extrinsic_response) = self + .api_call_for::("scan/extrinsic", &request) + .await?; if extrinsic_response.api_response.code != 0 { handle_error(headers, &extrinsic_response.api_response, &request).await?; continue; @@ -128,12 +131,13 @@ impl Subscan { } pub async fn get_token(&self) -> Result { - let request = TokenRequest { include_extends: false }; + let request = TokenRequest { + include_extends: false, + }; loop { - let (headers, token_response) = self.api_call_for::( - "v2/scan/token/native", - &request - ).await?; + let (headers, token_response) = self + .api_call_for::("v2/scan/token/native", &request) + .await?; let Some(token) = token_response.data.and_then(|data| data.token) else { handle_error(headers, &token_response.api_response, &request).await?; continue; @@ -145,17 +149,17 @@ impl Subscan { async fn api_call_for( &self, path: &str, - request: &(impl Serialize + ?Sized) + request: &(impl Serialize + ?Sized), ) -> Result<(HeaderMap, Resp), Error> { - let request_builder = self.http_client - .post(format!("https://{}.api.subscan.io/api/{}", self.network, path)); + let request_builder = self.http_client.post(format!( + "https://{}.api.subscan.io/api/{}", + self.network, path + )); let request_builder = match &self.api_key { Some(api_key) => request_builder.header("X-API-Key", api_key), - None => request_builder + None => request_builder, }; - let http_response = request_builder - .json(&request) - .send().await?; + let http_response = request_builder.json(&request).send().await?; let headers = http_response.headers().clone(); let response = http_response.json::().await?; Ok((headers, response)) @@ -165,7 +169,7 @@ impl Subscan { async fn handle_error( headers: HeaderMap, api_response: &ApiResponse, - request: &impl Debug + request: &impl Debug, ) -> Result<(), Error> { let retry_after = headers .get(RETRY_AFTER) @@ -175,7 +179,10 @@ async fn handle_error( code: api_response.code, message: api_response.message.clone(), })?; - warn!("Rate limited, retrying after {} seconds ({:?})", retry_after, request); + warn!( + "Rate limited, retrying after {} seconds ({:?})", + retry_after, request + ); sleep(Duration::from_secs(retry_after)).await; Ok(()) } @@ -183,7 +190,7 @@ async fn handle_error( #[derive(Debug, Clone, Deserialize)] struct ApiResponse { code: i64, - message: String + message: String, } #[derive(Debug, Clone, Serialize)] @@ -214,7 +221,7 @@ struct PollsData { #[derive(Debug, Clone, Deserialize)] pub struct Poll { - pub referendum_index: u32 + pub referendum_index: u32, } #[derive(Debug, Clone, Serialize)] @@ -255,7 +262,7 @@ pub struct ConvictionVote { #[derive(Debug, Clone, Serialize)] struct BlockRequest { block_num: u32, - only_head: bool + only_head: bool, } #[derive(Debug, Clone, Deserialize)] @@ -270,7 +277,7 @@ pub struct Block { pub block_num: u32, pub block_timestamp: u64, pub finalized: bool, - pub hash: HexString + pub hash: HexString, } #[serde_as] @@ -279,7 +286,7 @@ struct ExtrinsicRequest { events_limit: u32, #[serde_as(as = "DisplayFromStr")] extrinsic_index: ExtrinsicLocation, - only_extrinsic_event: bool + only_extrinsic_event: bool, } #[derive(Debug, Clone, Deserialize)] @@ -299,17 +306,19 @@ pub struct ExtrinsicDetail { pub account_display: Option, pub call_module: String, pub call_module_function: String, - pub params: Vec + pub params: Vec, } impl ExtrinsicDetail { pub fn account_address(&self) -> Option { - self.account_display.as_ref().map(|account| account.address.clone()) + self.account_display + .as_ref() + .map(|account| account.address.clone()) } pub fn is_extrinsic(&self, call_module: &str, call_module_function: &str) -> bool { - self.call_module.to_ascii_lowercase() == call_module && - self.call_module_function.to_ascii_lowercase() == call_module_function + self.call_module.to_ascii_lowercase() == call_module + && self.call_module_function.to_ascii_lowercase() == call_module_function } pub fn get_param(&self, name: &str) -> Option { @@ -317,14 +326,15 @@ impl ExtrinsicDetail { } pub fn get_param_as(&self, name: &str) -> Option { - self.get_param(name).and_then(|param| param.value_as::().ok()) + self.get_param(name) + .and_then(|param| param.value_as::().ok()) } } #[derive(Debug, Clone, Deserialize)] pub struct ExtrinsicParam { pub name: String, - pub value: serde_json::Value + pub value: serde_json::Value, } impl ExtrinsicParam { @@ -335,7 +345,7 @@ impl ExtrinsicParam { #[derive(Debug, Clone, Serialize)] struct TokenRequest { - include_extends: bool + include_extends: bool, } #[derive(Debug, Clone, Deserialize)] @@ -372,7 +382,7 @@ impl Token { #[serde(transparent)] pub struct HexString { #[serde(deserialize_with = "hex_deserialize")] - pub value: Vec + pub value: Vec, } impl Deref for HexString { @@ -391,7 +401,7 @@ impl AsRef<[u8]> for HexString { #[derive(Debug, PartialEq, Deserialize)] pub enum MultiAddress { - Id(AccountId32Ext) + Id(AccountId32Ext), } #[serde_as] @@ -399,7 +409,7 @@ pub enum MultiAddress { #[serde(transparent)] pub struct AccountId32Ext { #[serde_as(as = "DisplayFromStr")] - pub value: AccountId32 + pub value: AccountId32, } impl Deref for AccountId32Ext { @@ -420,13 +430,13 @@ impl From for AccountId32Ext { pub struct RuntimeCall { pub call_module: String, pub call_name: String, - pub params: Vec + pub params: Vec, } impl RuntimeCall { pub fn is_extrinsic(&self, call_module: &str, call_module_function: &str) -> bool { - self.call_module.to_ascii_lowercase() == call_module && - self.call_name.to_ascii_lowercase() == call_module_function + self.call_module.to_ascii_lowercase() == call_module + && self.call_name.to_ascii_lowercase() == call_module_function } pub fn get_param(&self, name: &str) -> Option { @@ -434,14 +444,15 @@ impl RuntimeCall { } pub fn get_param_as(&self, name: &str) -> Option { - self.get_param(name).and_then(|param| param.value_as::().ok()) + self.get_param(name) + .and_then(|param| param.value_as::().ok()) } } #[derive(Debug, PartialEq, Deserialize)] pub enum AccountVote { Standard(StandardAccountVote), - SplitAbstain(SplitAbstainAccountVote) + SplitAbstain(SplitAbstainAccountVote), } #[serde_as] @@ -449,7 +460,7 @@ pub enum AccountVote { pub struct StandardAccountVote { #[serde_as(as = "DisplayFromStr")] pub balance: u128, - pub vote: u8 + pub vote: u8, } #[serde_as] @@ -460,7 +471,7 @@ pub struct SplitAbstainAccountVote { #[serde_as(as = "DisplayFromStr")] pub nay: u128, #[serde_as(as = "DisplayFromStr")] - pub abstain: u128 + pub abstain: u128, } fn hex_deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { @@ -472,10 +483,7 @@ pub enum Error { #[error("Reqwest error: {0}")] Reqwest(#[from] reqwest::Error), #[error("API error: ({code}) {message}")] - Api { - code: i64, - message: String, - } + Api { code: i64, message: String }, } #[cfg(test)] @@ -515,7 +523,16 @@ mod tests { let extrinsic_param = serde_json::from_str::(json).unwrap(); assert_eq!(extrinsic_param.name, "real"); let multi_address = extrinsic_param.value_as::().unwrap(); - assert_eq!(multi_address, MultiAddress::Id(AccountId32::from_str("0xf40f4316f0adec098c14637d132a827ce6f36c930aca32a56a2cc65f7177be2b").unwrap().into())); + assert_eq!( + multi_address, + MultiAddress::Id( + AccountId32::from_str( + "0xf40f4316f0adec098c14637d132a827ce6f36c930aca32a56a2cc65f7177be2b" + ) + .unwrap() + .into() + ) + ); } #[test] @@ -535,6 +552,12 @@ mod tests { let extrinsic_param = serde_json::from_str::(json).unwrap(); assert_eq!(extrinsic_param.name, "vote"); let account_vote = extrinsic_param.value_as::().unwrap(); - assert_eq!(account_vote, AccountVote::Standard(StandardAccountVote { balance: 420964038408, vote: 2 })); + assert_eq!( + account_vote, + AccountVote::Standard(StandardAccountVote { + balance: 420964038408, + vote: 2 + }) + ); } } diff --git a/client/src/lib.rs b/client/src/lib.rs index 3f320b2..2a2f117 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -26,7 +26,10 @@ use client_interface::metadata::runtime_types::polkadot_runtime::{ProxyType, Run use client_interface::subscan::{PollStatus, Subscan}; use client_interface::RemoveVoteRequest; use client_interface::ServiceInfo; -use client_interface::{account_to_subxt_multi_address, is_glove_member, CallableSubstrateNetwork, SignedRemoveVoteRequest}; +use client_interface::{ + account_to_subxt_multi_address, is_glove_member, CallableSubstrateNetwork, + SignedRemoveVoteRequest, +}; use common::attestation::{Attestation, EnclaveInfo}; use common::{attestation, Conviction, SignedVoteRequest, VoteRequest}; use verify::try_verify_glove_result; @@ -38,7 +41,7 @@ pub mod verify; pub async fn run(input: I) -> Result where I: IntoIterator, - T: Into + Clone + T: Into + Clone, { let args = Args::parse_from(input); @@ -53,9 +56,11 @@ where let service_info = http_client .get(url_with_path(&args.glove_url, "info")) - .send().await? + .send() + .await? .error_for_status()? - .json::().await?; + .json::() + .await?; match args.command { JoinGlove(cmd) => join_glove(service_info, cmd).await, @@ -63,18 +68,28 @@ where RemoveVote(cmd) => remove_vote(cmd, args.glove_url, http_client).await, VerifyVote(cmd) => verify_vote(service_info, cmd).await, LeaveGlove(cmd) => leave_glove(service_info, cmd).await, - Info => info(service_info) + Info => info(service_info), } } async fn join_glove(service_info: ServiceInfo, cmd: JoinCmd) -> Result { let network = connect_to_network(cmd.secret_phrase_args, cmd.node_endpoint_args).await?; - if is_glove_member(&network, network.account(), service_info.proxy_account.clone()).await? { + if is_glove_member( + &network, + network.account(), + service_info.proxy_account.clone(), + ) + .await? + { return Ok(SuccessOutput::AlreadyGloveMember); } let add_proxy_call = client_interface::metadata::tx() .proxy() - .add_proxy(account_to_subxt_multi_address(service_info.proxy_account.clone()), ProxyType::Governance, 0) + .add_proxy( + account_to_subxt_multi_address(service_info.proxy_account.clone()), + ProxyType::Governance, + 0, + ) .unvalidated(); match network.call_extrinsic(&add_proxy_call).await { Ok(_) => Ok(SuccessOutput::JoinedGlove), @@ -82,10 +97,10 @@ async fn join_glove(service_info: ServiceInfo, cmd: JoinCmd) -> Result() { // Unlikely, but just in case Ok(Proxy(Duplicate)) => Ok(SuccessOutput::AlreadyGloveMember), - _ => Err(Runtime(Module(module_error)).into()) + _ => Err(Runtime(Module(module_error)).into()), } - }, - Err(e) => Err(e.into()) + } + Err(e) => Err(e.into()), } } @@ -95,7 +110,10 @@ async fn vote( glove_url: Url, http_client: ClientWithMiddleware, ) -> Result { - let subscan = Subscan::new(service_info.network_name.clone(), cmd.subscan_api_key.clone()); + let subscan = Subscan::new( + service_info.network_name.clone(), + cmd.subscan_api_key.clone(), + ); let token = subscan.get_token().await?; let keypair = cmd.secret_phrase_args.secret_phrase.clone(); let balance = (&cmd.balance * 10u128.pow(token.decimals as u32)) @@ -107,7 +125,7 @@ async fn vote( cmd.poll_index, cmd.aye, balance, - cmd.parse_conviction()? + cmd.parse_conviction()?, ); let nonce = request.nonce; @@ -119,7 +137,8 @@ async fn vote( let response = http_client .post(url_with_path(&glove_url, "vote")) .json(&signed_request) - .send().await + .send() + .await .context("Unable to send vote request")?; if response.status() != StatusCode::OK { bail!(response.text().await?) @@ -135,7 +154,7 @@ async fn remove_vote( let keypair = cmd.secret_phrase_args.secret_phrase; let request = RemoveVoteRequest { account: keypair.public_key().0.into(), - poll_index: cmd.poll_index + poll_index: cmd.poll_index, }; let signature = MultiSignature::Sr25519(keypair.sign(&request.encode()).0.into()); let signed_request = SignedRemoveVoteRequest { request, signature }; @@ -145,7 +164,8 @@ async fn remove_vote( let response = http_client .post(url_with_path(&glove_url, "remove-vote")) .json(&signed_request) - .send().await + .send() + .await .context("Unable to send remove vote request")?; if response.status() == StatusCode::OK { Ok(SuccessOutput::VoteRemoved) @@ -157,9 +177,14 @@ async fn remove_vote( async fn verify_vote(service_info: ServiceInfo, cmd: VerifyVoteCmd) -> Result { let subscan = Subscan::new(service_info.network_name.clone(), cmd.subscan_api_key); let active_polls = subscan.get_polls(PollStatus::Active).await?; - let votes = subscan.get_votes(cmd.poll_index, Some(cmd.account.clone())).await?; + let votes = subscan + .get_votes(cmd.poll_index, Some(cmd.account.clone())) + .await?; let Some(vote) = votes.first() else { - if active_polls.iter().any(|poll| poll.referendum_index == cmd.poll_index) { + if active_polls + .iter() + .any(|poll| poll.referendum_index == cmd.poll_index) + { bail!("Glove proxy has not yet voted") } else { bail!("Poll is no longer active and Glove proxy did not vote") @@ -169,57 +194,75 @@ async fn verify_vote(service_info: ServiceInfo, cmd: VerifyVoteCmd) -> Result verified_glove_proof, Ok(None) => bail!("Vote was not cast by Glove proxy"), - Err(error) => bail!("Glove proof failed verification: {}", error) + Err(error) => bail!("Glove proof failed verification: {}", error), }; let assigned_balance = verified_glove_proof .get_assigned_balance(&cmd.account) .ok_or_else(|| anyhow!("Account is not in Glove proof"))?; let image_measurement = match verified_glove_proof.enclave_info { Some(EnclaveInfo::Nitro(nitro_enclave_info)) => nitro_enclave_info.image_measurement, - None => bail!("INSECURE enclave was used to mix votes, so result cannot be trusted") + None => bail!("INSECURE enclave was used to mix votes, so result cannot be trusted"), }; if let Some(nonce) = cmd.nonce { if nonce != assigned_balance.nonce { - bail!("Nonce in Glove proof ({}) does not match expected value. \ - Glove proxy has used an older vote request.", assigned_balance.nonce) + bail!( + "Nonce in Glove proof ({}) does not match expected value. \ + Glove proxy has used an older vote request.", + assigned_balance.nonce + ) } } else { - eprintln!("Nonce was not provided so cannot check if most recent vote request was used by \ - Glove proxy"); + eprintln!( + "Nonce was not provided so cannot check if most recent vote request was used by \ + Glove proxy" + ); } let token = subscan.get_token().await?; if !cmd.enclave_measurement.is_empty() { - let enclave_match = cmd.enclave_measurement + let enclave_match = cmd + .enclave_measurement .iter() .any(|str| from_hex(str).ok() == Some(image_measurement.clone())); if !enclave_match { - bail!("Unknown enclave encountered in Glove proof ({})", - to_hex(&image_measurement)) + bail!( + "Unknown enclave encountered in Glove proof ({})", + to_hex(&image_measurement) + ) } - println!("Vote mixed by VERIFIED Glove enclave: {:?} using {} with conviction {:?}", - verified_glove_proof.result.direction, - token.display_amount(assigned_balance.balance), - assigned_balance.conviction); + println!( + "Vote mixed by VERIFIED Glove enclave: {:?} using {} with conviction {:?}", + verified_glove_proof.result.direction, + token.display_amount(assigned_balance.balance), + assigned_balance.conviction + ); } else { - println!("Vote mixed by POSSIBLE Glove enclave: {:?} using {} with conviction {:?}", - verified_glove_proof.result.direction, - token.display_amount(assigned_balance.balance), - assigned_balance.conviction); + println!( + "Vote mixed by POSSIBLE Glove enclave: {:?} using {} with conviction {:?}", + verified_glove_proof.result.direction, + token.display_amount(assigned_balance.balance), + assigned_balance.conviction + ); println!(); println!("To verify this is a Glove enclave, first audit the code:"); - println!("git clone --depth 1 --branch v{} {}", - verified_glove_proof.attested_data.version, - env!("CARGO_PKG_REPOSITORY")); + println!( + "git clone --depth 1 --branch v{} {}", + verified_glove_proof.attested_data.version, + env!("CARGO_PKG_REPOSITORY") + ); println!(); - println!("And then check 'PCR0' output is '{}' by running:", to_hex(&image_measurement)); + println!( + "And then check 'PCR0' output is '{}' by running:", + to_hex(&image_measurement) + ); println!("./build.sh"); } @@ -229,12 +272,22 @@ async fn verify_vote(service_info: ServiceInfo, cmd: VerifyVoteCmd) -> Result Result { let network = connect_to_network(cmd.secret_phrase_args, cmd.node_endpoint_args).await?; - if !is_glove_member(&network, network.account(), service_info.proxy_account.clone()).await? { + if !is_glove_member( + &network, + network.account(), + service_info.proxy_account.clone(), + ) + .await? + { return Ok(SuccessOutput::NotGloveMember); } let add_proxy_call = client_interface::metadata::tx() .proxy() - .remove_proxy(account_to_subxt_multi_address(service_info.proxy_account.clone()), ProxyType::Governance, 0) + .remove_proxy( + account_to_subxt_multi_address(service_info.proxy_account.clone()), + ProxyType::Governance, + 0, + ) .unvalidated(); match network.call_extrinsic(&add_proxy_call).await { Ok(_) => Ok(SuccessOutput::LeftGlove), @@ -242,30 +295,34 @@ async fn leave_glove(service_info: ServiceInfo, cmd: LeaveCmd) -> Result() { // Unlikely, but just in case Ok(Proxy(NotFound)) => Ok(SuccessOutput::NotGloveMember), - _ => Err(Runtime(Module(module_error)).into()) + _ => Err(Runtime(Module(module_error)).into()), } - }, - Err(e) => Err(e.into()) + } + Err(e) => Err(e.into()), } } fn info(service_info: ServiceInfo) -> Result { let ab = &service_info.attestation_bundle; let enclave_info = match ab.verify() { - Ok(EnclaveInfo::Nitro(enclave_info)) => { - &format!("AWS Nitro Enclave ({})", to_hex(enclave_info.image_measurement)) - }, + Ok(EnclaveInfo::Nitro(enclave_info)) => &format!( + "AWS Nitro Enclave ({})", + to_hex(enclave_info.image_measurement) + ), Err(attestation::Error::InsecureMode) => match ab.attestation { Attestation::Nitro(_) => "Debug AWS Nitro Enclave (INSECURE)", - Attestation::Mock => "Mock (INSECURE)" + Attestation::Mock => "Mock (INSECURE)", }, - Err(attestation_error) => &format!("Error verifying attestation: {}", attestation_error) + Err(attestation_error) => &format!("Error verifying attestation: {}", attestation_error), }; println!("Glove proxy account: {}", service_info.proxy_account); println!("Enclave: {}", enclave_info); println!("Substrate Network: {}", service_info.network_name); - println!("Genesis hash: {}", to_hex(ab.attested_data.genesis_hash)); + println!( + "Genesis hash: {}", + to_hex(ab.attested_data.genesis_hash) + ); Ok(SuccessOutput::None) } @@ -303,7 +360,7 @@ struct SecretPhraseArgs { /// See https://wiki.polkadot.network/docs/learn-account-advanced#derivation-paths for more /// details. #[arg(long, verbatim_doc_comment, value_parser = client_interface::parse_secret_phrase)] - secret_phrase: Keypair + secret_phrase: Keypair, } #[derive(Debug, Clone, clap::Args)] @@ -312,17 +369,18 @@ struct NodeEndpointArgs { /// /// See https://wiki.polkadot.network/docs/maintain-endpoints for more information. #[arg(long, verbatim_doc_comment)] - node_endpoint: String + node_endpoint: String, } async fn connect_to_network( secret_phrase_args: SecretPhraseArgs, - node_endpoint_args: NodeEndpointArgs + node_endpoint_args: NodeEndpointArgs, ) -> Result { CallableSubstrateNetwork::connect( node_endpoint_args.node_endpoint, - secret_phrase_args.secret_phrase - ).await + secret_phrase_args.secret_phrase, + ) + .await } #[derive(Debug, Subcommand)] @@ -345,7 +403,7 @@ enum Command { LeaveGlove(LeaveCmd), /// Print information about the Glove service. #[command(verbatim_doc_comment)] - Info + Info, } #[derive(Debug, Parser)] @@ -353,7 +411,7 @@ struct JoinCmd { #[command(flatten)] secret_phrase_args: SecretPhraseArgs, #[command(flatten)] - node_endpoint_args: NodeEndpointArgs + node_endpoint_args: NodeEndpointArgs, } #[derive(Debug, Parser)] @@ -373,7 +431,7 @@ struct VoteCmd { conviction: u8, /// Optional, API key to use with Subscan #[arg(long, short, verbatim_doc_comment)] - subscan_api_key: Option + subscan_api_key: Option, } impl VoteCmd { @@ -386,7 +444,7 @@ impl VoteCmd { 4 => Ok(Conviction::Locked4x), 5 => Ok(Conviction::Locked5x), 6 => Ok(Conviction::Locked6x), - _ => bail!("Conviction must be between 0 and 6 inclusive") + _ => bail!("Conviction must be between 0 and 6 inclusive"), } } } @@ -396,7 +454,7 @@ struct RemoveVoteCmd { #[command(flatten)] secret_phrase_args: SecretPhraseArgs, #[arg(long, short)] - poll_index: u32 + poll_index: u32, } #[derive(Debug, Parser)] @@ -421,7 +479,7 @@ struct VerifyVoteCmd { nonce: Option, /// Optional, API key to use with Subscan #[arg(long, short, verbatim_doc_comment)] - subscan_api_key: Option + subscan_api_key: Option, } #[derive(Debug, Parser)] @@ -429,7 +487,7 @@ struct LeaveCmd { #[command(flatten)] secret_phrase_args: SecretPhraseArgs, #[command(flatten)] - node_endpoint_args: NodeEndpointArgs + node_endpoint_args: NodeEndpointArgs, } #[derive(Display, Debug, PartialEq)] @@ -439,14 +497,16 @@ pub enum SuccessOutput { #[strum(to_string = "Account already a member of Glove proxy")] AlreadyGloveMember, #[strum(to_string = "Vote successfully submitted ({nonce})")] - Voted { nonce: u32 }, + Voted { + nonce: u32, + }, #[strum(to_string = "Vote successfully removed")] VoteRemoved, #[strum(to_string = "Account has left Glove proxy")] LeftGlove, #[strum(to_string = "Account was not a Glove proxy member")] NotGloveMember, - None + None, } impl Termination for SuccessOutput { diff --git a/client/src/verify.rs b/client/src/verify.rs index c16367c..a010484 100644 --- a/client/src/verify.rs +++ b/client/src/verify.rs @@ -5,9 +5,16 @@ use sp_core::crypto::AccountId32; use crate::genesis_hash; use attestation::EnclaveInfo; use client_interface::subscan; -use client_interface::subscan::{ExtrinsicDetail, HexString, MultiAddress, RuntimeCall, SplitAbstainAccountVote, Subscan}; -use common::attestation::{AttestationBundle, AttestationBundleLocation, AttestedData, GloveProof, GloveProofLite}; -use common::{attestation, AssignedBalance, Conviction, ExtrinsicLocation, GloveResult, VoteDirection, BASE_AYE}; +use client_interface::subscan::{ + ExtrinsicDetail, HexString, MultiAddress, RuntimeCall, SplitAbstainAccountVote, Subscan, +}; +use common::attestation::{ + AttestationBundle, AttestationBundleLocation, AttestedData, GloveProof, GloveProofLite, +}; +use common::{ + attestation, AssignedBalance, Conviction, ExtrinsicLocation, GloveResult, VoteDirection, + BASE_AYE, +}; use subscan::AccountVote; use AttestationBundleLocation::SubstrateRemark; @@ -16,7 +23,7 @@ pub struct VerifiedGloveProof { pub result: GloveResult, /// If `None` then enclave was running in insecure mode. pub enclave_info: Option, - pub attested_data: AttestedData + pub attested_data: AttestedData, } impl VerifiedGloveProof { @@ -41,7 +48,7 @@ pub async fn try_verify_glove_result( subscan: &Subscan, vote_extrinsic_location: ExtrinsicLocation, proxy_account: AccountId32, - poll_index: u32 + poll_index: u32, ) -> Result, Error> { let Some(extrinsic) = subscan.get_extrinsic(vote_extrinsic_location).await? else { return Err(Error::ExtrinsicNotFound(vote_extrinsic_location)); @@ -66,7 +73,8 @@ pub async fn try_verify_glove_result( // Make sure each assigned balance from the Glove proof is accounted for on-chain. for assigned_balance in &glove_result.assigned_balances { - account_votes.get(&assigned_balance.account) + account_votes + .get(&assigned_balance.account) .filter(|&account_vote| { is_account_vote_consistent(account_vote, glove_result.direction, assigned_balance) }) @@ -78,8 +86,9 @@ pub async fn try_verify_glove_result( // since they can only confirm their vote request was included in the proof. let attestation_bundle = match glove_proof_lite.attestation_location { - SubstrateRemark(remark_location) => + SubstrateRemark(remark_location) => { get_attestation_bundle_from_remark(subscan, remark_location).await? + } }; if Some(attestation_bundle.attested_data.genesis_hash) != genesis_hash(&subscan).await.ok() { @@ -88,25 +97,25 @@ pub async fn try_verify_glove_result( let glove_proof = GloveProof { signed_result: glove_proof_lite.signed_result, - attestation_bundle + attestation_bundle, }; let enclave_info = match glove_proof.verify() { Ok(enclave_info) => Some(enclave_info), Err(attestation::Error::InsecureMode) => None, - Err(error) => return Err(error.into()) + Err(error) => return Err(error.into()), }; Ok(Some(VerifiedGloveProof { result: glove_proof.signed_result.result, enclave_info, - attested_data: glove_proof.attestation_bundle.attested_data + attested_data: glove_proof.attestation_bundle.attested_data, })) } fn parse_glove_proof_lite( extrinsic: ExtrinsicDetail, - proxy_account: AccountId32 + proxy_account: AccountId32, ) -> Option<(GloveProofLite, Vec)> { if extrinsic.account_address() != Some(proxy_account) { return None; @@ -116,7 +125,8 @@ fn parse_glove_proof_lite( return None; }; - let remarks = calls.iter() + let remarks = calls + .iter() .filter(|call| call.is_extrinsic("system", "remark")) .filter_map(|call| call.get_param_as::("remark")) .collect::>(); @@ -126,7 +136,9 @@ fn parse_glove_proof_lite( return None; }; - GloveProofLite::decode_envelope(&remark).map(|proof| (proof, calls)).ok() + GloveProofLite::decode_envelope(&remark) + .map(|proof| (proof, calls)) + .ok() } fn parse_and_validate_proxy_account_vote( @@ -160,17 +172,17 @@ fn parse_and_validate_proxy_account_vote( fn is_account_vote_consistent( account_vote: &AccountVote, direction: VoteDirection, - assigned_balance: &AssignedBalance + assigned_balance: &AssignedBalance, ) -> bool { match direction { VoteDirection::Aye => { - parse_standard_account_vote(account_vote) == - Some((true, assigned_balance.balance, assigned_balance.conviction)) - }, + parse_standard_account_vote(account_vote) + == Some((true, assigned_balance.balance, assigned_balance.conviction)) + } VoteDirection::Nay => { - parse_standard_account_vote(account_vote) == - Some((false, assigned_balance.balance, assigned_balance.conviction)) - }, + parse_standard_account_vote(account_vote) + == Some((false, assigned_balance.balance, assigned_balance.conviction)) + } VoteDirection::Abstain => { parse_abstain_account_vote(account_vote) == Some(assigned_balance.balance) } @@ -182,7 +194,11 @@ fn parse_standard_account_vote(vote: &AccountVote) -> Option<(bool, u128, Convic return None; }; if standard.vote >= BASE_AYE { - Some((true, standard.balance, parse_conviction(standard.vote - BASE_AYE)?)) + Some(( + true, + standard.balance, + parse_conviction(standard.vote - BASE_AYE)?, + )) } else { Some((false, standard.balance, parse_conviction(standard.vote)?)) } @@ -197,35 +213,44 @@ fn parse_conviction(offset: u8) -> Option { 4 => Some(Conviction::Locked4x), 5 => Some(Conviction::Locked5x), 6 => Some(Conviction::Locked6x), - _ => None + _ => None, } } fn parse_abstain_account_vote(account_vote: &AccountVote) -> Option { match account_vote { - AccountVote::SplitAbstain(SplitAbstainAccountVote { aye: 0, nay: 0, abstain }) => - Some(*abstain), - _ => None + AccountVote::SplitAbstain(SplitAbstainAccountVote { + aye: 0, + nay: 0, + abstain, + }) => Some(*abstain), + _ => None, } } async fn get_attestation_bundle_from_remark( subscan: &Subscan, - remark_location: ExtrinsicLocation + remark_location: ExtrinsicLocation, ) -> Result { - let extrinsic_detail = subscan.get_extrinsic(remark_location).await? + let extrinsic_detail = subscan + .get_extrinsic(remark_location) + .await? .ok_or_else(|| Error::ExtrinsicNotFound(remark_location))?; if !extrinsic_detail.is_extrinsic("system", "remark") { - return Err(Error::InvalidAttestationBundle( - format!("Extrinsic at location {:?} is not a Remark", remark_location)) - ); + return Err(Error::InvalidAttestationBundle(format!( + "Extrinsic at location {:?} is not a Remark", + remark_location + ))); } - extrinsic_detail.get_param_as::("remark") + extrinsic_detail + .get_param_as::("remark") .and_then(|hex| AttestationBundle::decode_envelope(&mut hex.as_slice()).ok()) - .ok_or_else(|| Error::InvalidAttestationBundle( - format!("Extrinsic at location {:?} does not contain a valid AttestationBundle", - remark_location) - )) + .ok_or_else(|| { + Error::InvalidAttestationBundle(format!( + "Extrinsic at location {:?} does not contain a valid AttestationBundle", + remark_location + )) + }) } #[derive(thiserror::Error, Debug)] @@ -241,7 +266,7 @@ pub enum Error { #[error("Invalid attestation bundle: {0}")] InvalidAttestationBundle(String), #[error("Invalid attestation: {0}")] - Attestation(#[from] attestation::Error) + Attestation(#[from] attestation::Error), } #[cfg(test)] @@ -257,24 +282,47 @@ mod tests { let subscan = Subscan::new("rococo".into(), None); let verification_result = try_verify_glove_result( &subscan, - ExtrinsicLocation { block_number: 11729890, extrinsic_index: 2 }, + ExtrinsicLocation { + block_number: 11729890, + extrinsic_index: 2, + }, AccountId32::from_str("5E79AhCNFdcJJ1nWXepeib7BWRbacVbpKvRhcoyv8dRwrmQ3").unwrap(), - 241 - ).await.unwrap().unwrap(); + 241, + ) + .await + .unwrap() + .unwrap(); assert_eq!(verification_result.result.assigned_balances.len(), 3); let enclave_measuremnt = match &verification_result.enclave_info { Some(EnclaveInfo::Nitro(info)) => Some(info.image_measurement.clone()), - None => None + None => None, }; assert_eq!(enclave_measuremnt, Some(from_hex("4d132e40ed8d6db60d01d0116c34a4a92914de73d668821b6e019b72ae152b1180ef7c8a378e6c1925fe2bcb31c0ec80").unwrap())); let expected_balances = vec![ - ("5CyppCnQKiuY9c22yjHbDTpCqeHzAt7GXQpFAURxycWTS8My", 33351321, 1586359369580), - ("5F3wWFE7TGhpqXhZy18soAa2VjVsfr21VC4PZP3ZuAfM8Dg5", 4072408713, 5727210208563), - ("5GdjoMMME46cTumexM7AJTzkEpPp1xxbXNguEDecNtf7kz2R", 177757542, 4525520421857) + ( + "5CyppCnQKiuY9c22yjHbDTpCqeHzAt7GXQpFAURxycWTS8My", + 33351321, + 1586359369580, + ), + ( + "5F3wWFE7TGhpqXhZy18soAa2VjVsfr21VC4PZP3ZuAfM8Dg5", + 4072408713, + 5727210208563, + ), + ( + "5GdjoMMME46cTumexM7AJTzkEpPp1xxbXNguEDecNtf7kz2R", + 177757542, + 4525520421857, + ), ]; for (account, nonce, expected_balance) in expected_balances { let account = AccountId32::from_str(account).unwrap(); - assert_eq!(verification_result.get_vote_balance(&account, nonce).unwrap(), expected_balance); + assert_eq!( + verification_result + .get_vote_balance(&account, nonce) + .unwrap(), + expected_balance + ); } } } diff --git a/common/src/attestation.rs b/common/src/attestation.rs index 0afffda..c97b137 100644 --- a/common/src/attestation.rs +++ b/common/src/attestation.rs @@ -1,22 +1,22 @@ use std::io::{Read, Write}; -use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::GzEncoder; -use parity_scale_codec::{Decode, DecodeAll, Encode}; +use flate2::Compression; use parity_scale_codec::Error as ScaleError; +use parity_scale_codec::{Decode, DecodeAll, Encode}; use serde_bytes::ByteBuf; use sha2::{Digest, Sha256}; -use sp_core::{ed25519, H256, Pair}; +use sp_core::{ed25519, Pair, H256}; -use crate::{ExtrinsicLocation, nitro, SignedGloveResult}; +use crate::{nitro, ExtrinsicLocation, SignedGloveResult}; /// Represents a Glove proof of the mixing result done by a secure enclave. The votes on-chain /// must be compared to the result in the proof to ensure the mixing was done correctly. #[derive(Debug, Clone, PartialEq, Encode, Decode)] pub struct GloveProof { pub signed_result: SignedGloveResult, - pub attestation_bundle: AttestationBundle + pub attestation_bundle: AttestationBundle, } impl GloveProof { @@ -29,7 +29,7 @@ impl GloveProof { let valid = ::verify( &self.signed_result.signature, self.signed_result.result.encode(), - &self.attestation_bundle.attested_data.signing_key + &self.attestation_bundle.attested_data.signing_key, ); valid.then_some(enclave_info).ok_or(Error::GloveProof) } @@ -42,7 +42,7 @@ impl GloveProof { #[derive(Debug, Clone, PartialEq, Encode, Decode)] pub struct AttestationBundle { pub attested_data: AttestedData, - pub attestation: Attestation + pub attestation: Attestation, } /// The current encoding version for the attestation bundle envelope. @@ -59,9 +59,10 @@ impl AttestationBundle { match &self.attestation { Attestation::Nitro(nitro_attestation) => { let attestation_doc = nitro_attestation.verify()?; - let image_measurement = attestation_doc.pcrs + let image_measurement = attestation_doc + .pcrs .get(&0) - .filter(|pcr0| pcr0.iter().any(|&byte| byte != 0)) // All zeros means debug mode + .filter(|pcr0| pcr0.iter().any(|&byte| byte != 0)) // All zeros means debug mode .map(|pcr0| pcr0.to_vec()) .ok_or(Error::InsecureMode)?; let attested_data_hash = Sha256::digest(&self.attested_data.encode()).to_vec(); @@ -69,7 +70,7 @@ impl AttestationBundle { .then(|| EnclaveInfo::Nitro(nitro::EnclaveInfo { image_measurement })) .ok_or(Error::AttestedData) } - Attestation::Mock => Err(Error::InsecureMode) + Attestation::Mock => Err(Error::InsecureMode), } } @@ -106,7 +107,7 @@ pub struct AttestedData { pub signing_key: ed25519::Public, /// The version string of enclave code. Used to pinpoint the exact enclave code in the Glove /// repository. - pub version: String + pub version: String, } /// Enum of the various attestation types. @@ -116,13 +117,13 @@ pub enum Attestation { Nitro(nitro::Attestation), /// Marker for a mock enclave. There is no hardware security in a mock enclave and is therefore /// only suitable for testing. - Mock + Mock, } /// The information about the enclave that produced the attestation. #[derive(Debug, Clone, PartialEq, Encode, Decode)] pub enum EnclaveInfo { - Nitro(nitro::EnclaveInfo) + Nitro(nitro::EnclaveInfo), } /// A version of [GloveProof] that contains a location pointer to the [AttestationBundle] instead of @@ -168,7 +169,7 @@ pub enum Error { #[error("Attested data does not match attestation")] AttestedData, #[error("Invalid Glove proof signature")] - GloveProof + GloveProof, } #[cfg(test)] @@ -179,21 +180,29 @@ mod tests { use Attestation::Nitro; - use crate::{AssignedBalance, Conviction, GloveResult, nitro, VoteDirection}; use crate::attestation::Attestation::Mock; + use crate::{nitro, AssignedBalance, Conviction, GloveResult, VoteDirection}; use super::*; - static SECURE_NITRO_ATTESTATION_BUNDLE_BYTES: &[u8] = include_bytes!("../test-resources/secure-nitro-attestation-bundle-envelope"); - static DEBUG_NITRO_ATTESTATION_BUNDLE_BYTES: &[u8] = include_bytes!("../test-resources/debug-nitro-attestation-bundle-envelope"); + static SECURE_NITRO_ATTESTATION_BUNDLE_BYTES: &[u8] = + include_bytes!("../test-resources/secure-nitro-attestation-bundle-envelope"); + static DEBUG_NITRO_ATTESTATION_BUNDLE_BYTES: &[u8] = + include_bytes!("../test-resources/debug-nitro-attestation-bundle-envelope"); static GLOVE_PROOF_LITE_BYTES: &[u8] = include_bytes!("../test-resources/glove-proof-lite"); - static RAW_NITRO_ATTESTATION_DOC_BYTES: &[u8] = include_bytes!("../test-resources/raw-aws-nitro-attestation-doc"); + static RAW_NITRO_ATTESTATION_DOC_BYTES: &[u8] = + include_bytes!("../test-resources/raw-aws-nitro-attestation-doc"); #[test] fn secure_nitro_attestation_bundle_sample() { - let attestation_bundle = AttestationBundle::decode_envelope(SECURE_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); + let attestation_bundle = + AttestationBundle::decode_envelope(SECURE_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); attestation_bundle.verify().unwrap(); - assert_eq!(attestation_bundle.attested_data.genesis_hash, H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e").unwrap()); + assert_eq!( + attestation_bundle.attested_data.genesis_hash, + H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e") + .unwrap() + ); assert!(matches!(attestation_bundle.attestation, Nitro(_))); let envelope_encoding = attestation_bundle.encode_envelope(); @@ -214,20 +223,29 @@ mod tests { direction: VoteDirection::Nay, assigned_balances: vec![ AssignedBalance { - account: AccountId32::from_str("28836d6f19d5cd8dd8b26da754c63ae337c6f938a7dc6a12e439ad8a1c69fb0d").unwrap(), + account: AccountId32::from_str( + "28836d6f19d5cd8dd8b26da754c63ae337c6f938a7dc6a12e439ad8a1c69fb0d" + ) + .unwrap(), nonce: 2085265314, balance: 1013266383298, conviction: Conviction::None }, AssignedBalance { - account: AccountId32::from_str("841f65d84a0ffa95b378923a0d879f188d2a4aa5cb0f97df84fb296788cb6e3e").unwrap(), + account: AccountId32::from_str( + "841f65d84a0ffa95b378923a0d879f188d2a4aa5cb0f97df84fb296788cb6e3e" + ) + .unwrap(), nonce: 458657513, balance: 7645384086569, conviction: Conviction::Locked1x }, AssignedBalance { - account: AccountId32::from_str("ca22927dff5da60838b78763a2b5ebdf080fa4f35bcbfc8c36b3b6c59a85cd6f").unwrap(), - nonce: 3781275530, + account: AccountId32::from_str( + "ca22927dff5da60838b78763a2b5ebdf080fa4f35bcbfc8c36b3b6c59a85cd6f" + ) + .unwrap(), + nonce: 3781275530, balance: 3180439530133, conviction: Conviction::Locked3x } @@ -243,25 +261,31 @@ mod tests { }) ); - let roundtrip = GloveProofLite::decode_envelope(&glove_proof_lite.encode_envelope()).unwrap(); + let roundtrip = + GloveProofLite::decode_envelope(&glove_proof_lite.encode_envelope()).unwrap(); assert_eq!(glove_proof_lite, roundtrip); } #[test] fn valid_glove_proof_sample() { - let attestation_bundle = AttestationBundle::decode_envelope(SECURE_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); + let attestation_bundle = + AttestationBundle::decode_envelope(SECURE_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); let glove_proof_lite = GloveProofLite::decode_envelope(GLOVE_PROOF_LITE_BYTES).unwrap(); let glove_proof = GloveProof { signed_result: glove_proof_lite.signed_result, - attestation_bundle + attestation_bundle, }; glove_proof.verify().unwrap(); } #[test] fn invalid_glove_proof() { - let attestation_bundle = AttestationBundle::decode_envelope(SECURE_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); - let original_glove_result = GloveProofLite::decode_envelope(GLOVE_PROOF_LITE_BYTES).unwrap().signed_result.result; + let attestation_bundle = + AttestationBundle::decode_envelope(SECURE_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); + let original_glove_result = GloveProofLite::decode_envelope(GLOVE_PROOF_LITE_BYTES) + .unwrap() + .signed_result + .result; let mut modified_glove_result = original_glove_result.clone(); modified_glove_result.direction = VoteDirection::Aye; @@ -269,16 +293,23 @@ mod tests { let invalid_glove_proof = GloveProof { signed_result: modified_glove_result.sign(&ed25519::Pair::generate().0), - attestation_bundle + attestation_bundle, }; - assert!(matches!(invalid_glove_proof.verify(), Err(Error::GloveProof))); + assert!(matches!( + invalid_glove_proof.verify(), + Err(Error::GloveProof) + )); } #[test] fn debug_nitro_attestation_bundle_sample() { - let attestation_bundle = AttestationBundle::decode_envelope(DEBUG_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); + let attestation_bundle = + AttestationBundle::decode_envelope(DEBUG_NITRO_ATTESTATION_BUNDLE_BYTES).unwrap(); assert!(matches!(attestation_bundle.attestation, Nitro(_))); - assert!(matches!(attestation_bundle.verify(), Err(Error::InsecureMode))); + assert!(matches!( + attestation_bundle.verify(), + Err(Error::InsecureMode) + )); } #[test] @@ -287,11 +318,14 @@ mod tests { attested_data: AttestedData { genesis_hash: Default::default(), signing_key: ed25519::Pair::generate().0.public(), - version: "1.0.0".to_string() + version: "1.0.0".to_string(), }, - attestation: Mock + attestation: Mock, }; - assert!(matches!(attestation_bundle.verify(), Err(Error::InsecureMode))); + assert!(matches!( + attestation_bundle.verify(), + Err(Error::InsecureMode) + )); } #[test] @@ -300,10 +334,15 @@ mod tests { attested_data: AttestedData { genesis_hash: Default::default(), signing_key: ed25519::Pair::generate().0.public(), - version: "1.0.0".to_string() + version: "1.0.0".to_string(), }, - attestation: Nitro(nitro::Attestation::try_from(RAW_NITRO_ATTESTATION_DOC_BYTES).unwrap()) + attestation: Nitro( + nitro::Attestation::try_from(RAW_NITRO_ATTESTATION_DOC_BYTES).unwrap(), + ), }; - assert!(matches!(attestation_bundle.verify(), Err(Error::AttestedData))); + assert!(matches!( + attestation_bundle.verify(), + Err(Error::AttestedData) + )); } } diff --git a/common/src/lib.rs b/common/src/lib.rs index 2ea0119..fa92eb7 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -6,10 +6,10 @@ use std::str::FromStr; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use rand::random; use serde::{Deserialize, Serialize}; -use sp_core::{ed25519, H256, Pair}; use sp_core::crypto::AccountId32; -use sp_runtime::MultiSignature; +use sp_core::{ed25519, Pair, H256}; use sp_runtime::traits::Verify; +use sp_runtime::MultiSignature; pub mod attestation; pub mod nitro; @@ -23,7 +23,7 @@ pub struct SignedVoteRequest { #[serde(with = "serde_over_hex_scale")] pub request: VoteRequest, #[serde(with = "serde_over_hex_scale")] - pub signature: MultiSignature + pub signature: MultiSignature, } impl SignedVoteRequest { @@ -42,7 +42,7 @@ const JS_SIGNING_POSTFIX: &[u8] = b""; pub fn verify_js_payload( signature: &MultiSignature, payload: &E, - account: &AccountId32 + account: &AccountId32, ) -> bool { let capacity = JS_SIGNING_PREFIX.len() + payload.size_hint() + JS_SIGNING_POSTFIX.len(); let mut wrapped_bytes = Vec::with_capacity(capacity); @@ -96,9 +96,17 @@ impl VoteRequest { poll_index: u32, aye: bool, balance: u128, - conviction: Conviction + conviction: Conviction, ) -> Self { - Self { account, genesis_hash, poll_index, nonce: random(), aye, balance, conviction } + Self { + account, + genesis_hash, + poll_index, + nonce: random(), + aye, + balance, + conviction, + } } } @@ -108,7 +116,7 @@ impl VoteRequest { pub struct SignedGloveResult { pub result: GloveResult, /// Signature of `result` in SCALE endoding. - pub signature: ed25519::Signature + pub signature: ed25519::Signature, } #[derive(Debug, Clone, PartialEq, Encode, Decode)] @@ -116,13 +124,16 @@ pub struct GloveResult { #[codec(compact)] pub poll_index: u32, pub direction: VoteDirection, - pub assigned_balances: Vec + pub assigned_balances: Vec, } impl GloveResult { pub fn sign(self, key: &ed25519::Pair) -> SignedGloveResult { let signature = key.sign(&self.encode()); - SignedGloveResult { result: self, signature } + SignedGloveResult { + result: self, + signature, + } } } @@ -130,7 +141,7 @@ impl GloveResult { pub enum VoteDirection { Aye, Nay, - Abstain + Abstain, } #[derive(Debug, Clone, PartialEq, Encode, Decode, MaxEncodedLen)] @@ -138,16 +149,28 @@ pub struct AssignedBalance { pub account: AccountId32, pub nonce: u32, pub balance: u128, - pub conviction: Conviction + pub conviction: Conviction, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize, - Encode, Decode, MaxEncodedLen)] +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + MaxEncodedLen, +)] pub struct ExtrinsicLocation { pub block_number: u32, /// Index of the extrinsic within the block. #[codec(compact)] - pub extrinsic_index: u32 + pub extrinsic_index: u32, } impl Display for ExtrinsicLocation { @@ -160,10 +183,17 @@ impl FromStr for ExtrinsicLocation { type Err = &'static str; fn from_str(s: &str) -> Result { - let (block_number, extrinsic_index) = s.split_once('-').ok_or("Invalid ExtrinsicLocation format")?; + let (block_number, extrinsic_index) = s + .split_once('-') + .ok_or("Invalid ExtrinsicLocation format")?; let block_number = block_number.parse().map_err(|_| "Invalid block number")?; - let extrinsic_index = extrinsic_index.parse().map_err(|_| "Invalid extrinsic index")?; - Ok(ExtrinsicLocation { block_number, extrinsic_index }) + let extrinsic_index = extrinsic_index + .parse() + .map_err(|_| "Invalid extrinsic index")?; + Ok(ExtrinsicLocation { + block_number, + extrinsic_index, + }) } } @@ -172,15 +202,15 @@ impl FromStr for ExtrinsicLocation { /// as a hex string. pub mod serde_over_hex_scale { use parity_scale_codec::{Decode, Encode}; - use serde::{Deserialize, Deserializer, Serializer}; use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; use sp_core::bytes::from_hex; use subxt::utils::to_hex; pub fn serialize(value: &T, serializer: S) -> Result where T: Encode, - S: Serializer + S: Serializer, { serializer.serialize_str(&to_hex(&value.encode())) } @@ -188,7 +218,7 @@ pub mod serde_over_hex_scale { pub fn deserialize<'de, T, D>(deserializer: D) -> Result where T: Decode, - D: Deserializer<'de> + D: Deserializer<'de>, { let bytes = from_hex(&String::deserialize(deserializer)?).map_err(Error::custom)?; T::decode(&mut bytes.as_slice()).map_err(Error::custom) @@ -200,8 +230,8 @@ mod tests { use parity_scale_codec::Encode; use rand::random; use serde_json::{json, Value}; - use sp_core::{Pair, sr25519}; use sp_core::bytes::to_hex; + use sp_core::{sr25519, Pair}; use subxt_signer::sr25519::dev; use Conviction::{Locked3x, Locked6x}; @@ -218,7 +248,7 @@ mod tests { 11, true, 100, - Locked3x + Locked3x, ); let encoded_request = request.encode(); let signature: MultiSignature = pair.sign(encoded_request.as_slice()).into(); @@ -257,7 +287,11 @@ mod tests { let request = signed_request.request; assert_eq!(request.account, dev::bob().public_key().0.into()); assert_eq!(request.poll_index, 185); - assert_eq!(request.genesis_hash, H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e").unwrap()); + assert_eq!( + request.genesis_hash, + H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e") + .unwrap() + ); assert_eq!(request.balance, 2230000000000); assert_eq!(request.aye, true); assert_eq!(request.conviction, Conviction::Locked2x); @@ -276,9 +310,16 @@ mod tests { println!("{:#?}", signed_request); assert!(signed_request.verify()); let request = signed_request.request; - assert_eq!(request.account, AccountId32::from_str("5CyppCnQKiuY9c22yjHbDTpCqeHzAt7GXQpFAURxycWTS8My").unwrap()); + assert_eq!( + request.account, + AccountId32::from_str("5CyppCnQKiuY9c22yjHbDTpCqeHzAt7GXQpFAURxycWTS8My").unwrap() + ); assert_eq!(request.poll_index, 217); - assert_eq!(request.genesis_hash, H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e").unwrap()); + assert_eq!( + request.genesis_hash, + H256::from_str("6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e") + .unwrap() + ); assert_eq!(request.balance, 3230000000000); assert_eq!(request.aye, false); assert_eq!(request.conviction, Conviction::Locked4x); @@ -295,7 +336,7 @@ mod tests { 11, true, 100, - Locked3x + Locked3x, ); let encoded_request = request.encode(); let signature: MultiSignature = pair2.sign(encoded_request.as_slice()).into(); @@ -319,7 +360,7 @@ mod tests { 11, true, 100, - Locked6x + Locked6x, ); let signature: MultiSignature = pair.sign(&original_request.encode()).into(); @@ -329,11 +370,10 @@ mod tests { modified_request.aye = !original_request.aye; modified_request }, - signature + signature, }; assert_eq!(signed_request.verify(), false); - let json = serde_json::to_string(&signed_request).unwrap(); let deserialized: SignedVoteRequest = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized, signed_request); diff --git a/common/src/nitro.rs b/common/src/nitro.rs index 23c6bde..0282680 100644 --- a/common/src/nitro.rs +++ b/common/src/nitro.rs @@ -1,14 +1,14 @@ -use aws_nitro_enclaves_cose::CoseSign1; use aws_nitro_enclaves_cose::crypto::Openssl; use aws_nitro_enclaves_cose::error::CoseError; +use aws_nitro_enclaves_cose::CoseSign1; use aws_nitro_enclaves_nsm_api::api::AttestationDoc; use openssl::error::ErrorStack; use openssl::stack::Stack; -use openssl::x509::{X509, X509StoreContext, X509VerifyResult}; use openssl::x509::store::X509StoreBuilder; use openssl::x509::verify::X509VerifyParam; -use parity_scale_codec::{Decode, Encode, Input, Output}; +use openssl::x509::{X509StoreContext, X509VerifyResult, X509}; use parity_scale_codec::Error as ScaleError; +use parity_scale_codec::{Decode, Encode, Input, Output}; static ROOT_CA_BYTES: &[u8] = include_bytes!("../../assets/aws-nitro-root.pem"); @@ -35,8 +35,8 @@ impl Encode for Attestation { impl Decode for Attestation { fn decode(input: &mut I) -> Result { let bytes = Vec::::decode(input)?; - let cose_sign_1 = CoseSign1::from_bytes(&bytes) - .map_err(|_| ScaleError::from("Not a valid CoseSign1"))?; + let cose_sign_1 = + CoseSign1::from_bytes(&bytes).map_err(|_| ScaleError::from("Not a valid CoseSign1"))?; Ok(Self(cose_sign_1)) } } @@ -63,7 +63,8 @@ impl From for CoseSign1 { impl Attestation { pub fn verify(&self) -> Result { - let encoded_attestation_doc = self.0 + let encoded_attestation_doc = self + .0 .get_payload::(None) .map_err(|e| Error::Cose(e.to_string()))?; let doc = serde_cbor::from_slice::(&encoded_attestation_doc)?; @@ -91,12 +92,9 @@ impl Attestation { // Verify the cert chain and prove the certicate public key is a valid AWS nitro signing // key. let mut cert_ctx = X509StoreContext::new()?; - let valid = cert_ctx.init( - &trust_store, - &certificate, - &cert_chain, - |ctx| ctx.verify_cert() - )?; + let valid = cert_ctx.init(&trust_store, &certificate, &cert_chain, |ctx| { + ctx.verify_cert() + })?; if !valid { return Err(cert_ctx.error().into()); } @@ -129,7 +127,7 @@ pub enum Error { #[error("X.509 verification error: {0}")] CertVerify(#[from] X509VerifyResult), #[error("Invalid signature")] - Signature + Signature, } #[cfg(test)] @@ -140,11 +138,15 @@ mod tests { use super::*; - static RAW_NITRO_ATTESTATION_BYTES: &[u8] = include_bytes!("../test-resources/raw-aws-nitro-attestation-doc"); + static RAW_NITRO_ATTESTATION_BYTES: &[u8] = + include_bytes!("../test-resources/raw-aws-nitro-attestation-doc"); #[test] fn decode_and_verify_attestation() { - let doc = Attestation::try_from(RAW_NITRO_ATTESTATION_BYTES).unwrap().verify().unwrap(); + let doc = Attestation::try_from(RAW_NITRO_ATTESTATION_BYTES) + .unwrap() + .verify() + .unwrap(); println!("{:?}", doc); assert_eq!(doc.pcrs.get(&0).unwrap().to_vec(), from_hex("dd1c94beae9a589b37f6601ecf73c297ff0bf41a8872f737fabf3c9a2a96eb3b1dcdabc8e33ba1f7654b528518b8b9ed").unwrap()); assert_eq!(doc.pcrs.get(&1).unwrap().to_vec(), from_hex("52b919754e1643f4027eeee8ec39cc4a2cb931723de0c93ce5cc8d407467dc4302e86490c01c0d755acfe10dbf657546").unwrap()); @@ -156,9 +158,13 @@ mod tests { #[test] fn attestation_scale_encoding() { - let nitro_attestation = Attestation(CoseSign1::from_bytes(RAW_NITRO_ATTESTATION_BYTES).unwrap()); + let nitro_attestation = + Attestation(CoseSign1::from_bytes(RAW_NITRO_ATTESTATION_BYTES).unwrap()); let encoded = nitro_attestation.encode(); let nitro_attestation2 = Attestation::decode(&mut &encoded[..]).unwrap(); - assert_eq!(nitro_attestation.verify().unwrap(), nitro_attestation2.verify().unwrap()); + assert_eq!( + nitro_attestation.verify().unwrap(), + nitro_attestation2.verify().unwrap() + ); } } diff --git a/enclave-interface/src/lib.rs b/enclave-interface/src/lib.rs index d420d5e..5d6ee47 100644 --- a/enclave-interface/src/lib.rs +++ b/enclave-interface/src/lib.rs @@ -20,13 +20,13 @@ pub enum EnclaveRequest { #[derive(Debug, Clone, Encode, Decode)] pub enum EnclaveResponse { GloveResult(SignedGloveResult), - Error(Error) + Error(Error), } pub enum EnclaveStream { #[cfg(target_os = "linux")] Vsock(tokio_vsock::VsockStream), - Unix(tokio::net::UnixStream) + Unix(tokio::net::UnixStream), } impl EnclaveStream { @@ -35,15 +35,15 @@ impl EnclaveStream { match self { #[cfg(target_os = "linux")] EnclaveStream::Vsock(stream) => write_len_prefix_bytes(stream, bytes).await, - EnclaveStream::Unix(stream) => write_len_prefix_bytes(stream, bytes).await + EnclaveStream::Unix(stream) => write_len_prefix_bytes(stream, bytes).await, } } pub async fn read(&mut self) -> io::Result { - let bytes = match self { + let bytes = match self { #[cfg(target_os = "linux")] EnclaveStream::Vsock(stream) => read_len_prefix_bytes(stream).await?, - EnclaveStream::Unix(stream) => read_len_prefix_bytes(stream).await? + EnclaveStream::Unix(stream) => read_len_prefix_bytes(stream).await?, }; M::decode_all(&mut bytes.as_slice()) .map_err(|scale_error| io::Error::new(ErrorKind::InvalidData, scale_error)) @@ -52,7 +52,7 @@ impl EnclaveStream { async fn write_len_prefix_bytes(writer: &mut W, bytes: &[u8]) -> io::Result<()> where - W: AsyncWriteExt + Unpin + W: AsyncWriteExt + Unpin, { writer.write_u32(bytes.len() as u32).await?; writer.write_all(bytes).await?; @@ -62,10 +62,10 @@ where async fn read_len_prefix_bytes(reader: &mut R) -> io::Result> where - R: AsyncReadExt + Unpin + R: AsyncReadExt + Unpin, { let len = reader.read_u32().await?; - let mut buffer = vec![0; len as usize]; + let mut buffer = vec![0; len as usize]; reader.read_exact(&mut buffer).await?; Ok(buffer) } @@ -75,5 +75,5 @@ pub enum Error { #[error("Scale decoding error: {0}")] Scale(String), #[error("Vote mixing error: {0}")] - Mixing(String) + Mixing(String), } diff --git a/enclave/src/lib.rs b/enclave/src/lib.rs index 26858c3..ecf41ee 100644 --- a/enclave/src/lib.rs +++ b/enclave/src/lib.rs @@ -6,13 +6,17 @@ use rand::distributions::{Distribution, Uniform}; use rand::thread_rng; use sp_core::H256; -use common::{AssignedBalance, GloveResult, VoteDirection, SignedVoteRequest}; +use common::{AssignedBalance, GloveResult, SignedVoteRequest, VoteDirection}; pub fn mix_votes( genesis_hash: H256, - signed_requests: &Vec + signed_requests: &Vec, ) -> Result { - let poll_index = signed_requests.first().ok_or(Error::Empty)?.request.poll_index; + let poll_index = signed_requests + .first() + .ok_or(Error::Empty)? + .request + .poll_index; let mut rng = thread_rng(); // Generate a random multiplier between 1x and 2x. @@ -64,7 +68,10 @@ pub fn mix_votes( // It's possible the randomized weights will lead to a value greater than the request // balance. This is more likely to happen if there are fewer requests and the random // multiplier is sufficiently bigger relative to the others. - (net_balance * weight).to_u128().unwrap().min(signed_request.request.balance) + (net_balance * weight) + .to_u128() + .unwrap() + .min(signed_request.request.balance) }) .collect::>(); @@ -73,7 +80,8 @@ pub fn mix_votes( let mut index = 0; while leftover_balance > 0 { if signed_requests[index].request.balance > net_balances[index] { - let balance_allowance = signed_requests[index].request.balance - net_balances[index]; + let balance_allowance = + signed_requests[index].request.balance - net_balances[index]; let assign_extra_balance = leftover_balance.min(balance_allowance); net_balances[index] += assign_extra_balance; leftover_balance -= assign_extra_balance; @@ -87,13 +95,11 @@ pub fn mix_votes( let assigned_balances = signed_requests .iter() .zip(net_balances) - .map(|(signed_request, balance)| { - AssignedBalance { - account: signed_request.request.account.clone(), - nonce: signed_request.request.nonce, - balance, - conviction: signed_request.request.conviction, - } + .map(|(signed_request, balance)| AssignedBalance { + account: signed_request.request.account.clone(), + nonce: signed_request.request.nonce, + balance, + conviction: signed_request.request.conviction, }) .collect::>(); @@ -106,7 +112,7 @@ pub fn mix_votes( } else { VoteDirection::Abstain }, - assigned_balances + assigned_balances, }) } @@ -130,8 +136,8 @@ mod tests { use sp_core::{ed25519, Pair}; use sp_runtime::MultiSignature; - use common::{Conviction, VoteRequest}; use common::Conviction::{Locked1x, Locked3x, Locked5x}; + use common::{Conviction, VoteRequest}; use Conviction::Locked6x; use super::*; @@ -161,46 +167,49 @@ mod tests { fn two_equal_but_opposite_votes() { let signed_requests = vec![ vote(4, true, 10, Locked5x), - vote(7, false, 10, Conviction::None) + vote(7, false, 10, Conviction::None), ]; assert_eq!( mix_votes(GENESIS_HASH, &signed_requests), Ok(GloveResult { poll_index: POLL_INDEX, direction: VoteDirection::Abstain, - assigned_balances: vec![assigned(&signed_requests[0], 1), assigned(&signed_requests[1], 1)] + assigned_balances: vec![ + assigned(&signed_requests[0], 1), + assigned(&signed_requests[1], 1) + ] }) ) } #[test] fn two_aye_votes() { - let signed_requests = vec![ - vote(1, true, 10, Locked1x), - vote(2, true, 5, Locked1x) - ]; + let signed_requests = vec![vote(1, true, 10, Locked1x), vote(2, true, 5, Locked1x)]; assert_eq!( mix_votes(GENESIS_HASH, &signed_requests), Ok(GloveResult { poll_index: POLL_INDEX, direction: VoteDirection::Aye, - assigned_balances: vec![assigned(&signed_requests[0], 10), assigned(&signed_requests[1], 5)] + assigned_balances: vec![ + assigned(&signed_requests[0], 10), + assigned(&signed_requests[1], 5) + ] }) ); } #[test] fn two_nay_votes() { - let signed_requests = vec![ - vote(3, false, 5, Locked3x), - vote(2, false, 10, Locked1x) - ]; + let signed_requests = vec![vote(3, false, 5, Locked3x), vote(2, false, 10, Locked1x)]; assert_eq!( mix_votes(GENESIS_HASH, &signed_requests), Ok(GloveResult { poll_index: POLL_INDEX, direction: VoteDirection::Nay, - assigned_balances: vec![assigned(&signed_requests[0], 5), assigned(&signed_requests[1], 10)] + assigned_balances: vec![ + assigned(&signed_requests[0], 5), + assigned(&signed_requests[1], 10) + ] }) ); } @@ -211,47 +220,85 @@ mod tests { vote(3, true, 30, Locked3x), vote(7, true, 20, Locked5x), vote(1, false, 26, Conviction::None), - vote(4, false, 4, Locked6x) + vote(4, false, 4, Locked6x), ]; let result = mix_votes(GENESIS_HASH, &signed_requests).unwrap(); println!("{:?}", result); assert_eq!(result.direction, VoteDirection::Aye); assert_eq!(result.assigned_balances.len(), 4); - assert_eq!(result.assigned_balances.iter().map(|a| a.balance).sum::(), 20); + assert_eq!( + result + .assigned_balances + .iter() + .map(|a| a.balance) + .sum::(), + 20 + ); assert(signed_requests, result.assigned_balances); } #[test] fn aye_votes_smaller_than_nye_votes() { - let signed_requests = vec![ - vote(6, false, 32, Locked1x), - vote(8, true, 15, Locked3x), - ]; + let signed_requests = vec![vote(6, false, 32, Locked1x), vote(8, true, 15, Locked3x)]; let result = mix_votes(GENESIS_HASH, &signed_requests).unwrap(); assert_eq!(result.direction, VoteDirection::Nay); assert_eq!(result.assigned_balances.len(), 2); - assert_eq!(result.assigned_balances.iter().map(|a| a.balance).sum::(), 17); + assert_eq!( + result + .assigned_balances + .iter() + .map(|a| a.balance) + .sum::(), + 17 + ); assert(signed_requests, result.assigned_balances); } #[test] fn leftovers() { - let result = mix_votes(GENESIS_HASH, &vec![ - vote(4, false, 5, Locked1x), - vote(2, true, 10, Locked1x), - ]).unwrap(); - assert_eq!(result.assigned_balances.iter().map(|a| a.balance).sum::(), 5); + let result = mix_votes( + GENESIS_HASH, + &vec![vote(4, false, 5, Locked1x), vote(2, true, 10, Locked1x)], + ) + .unwrap(); + assert_eq!( + result + .assigned_balances + .iter() + .map(|a| a.balance) + .sum::(), + 5 + ); } #[test] fn multiple_polls() { assert_eq!( - mix_votes(GENESIS_HASH, &vec![ - custom_vote(ed25519::Pair::generate().0, GENESIS_HASH, 1, 432, true, 10, Locked1x), - custom_vote(ed25519::Pair::generate().0, GENESIS_HASH, 2, 431, false, 10, Locked3x) - ]), + mix_votes( + GENESIS_HASH, + &vec![ + custom_vote( + ed25519::Pair::generate().0, + GENESIS_HASH, + 1, + 432, + true, + 10, + Locked1x + ), + custom_vote( + ed25519::Pair::generate().0, + GENESIS_HASH, + 2, + 431, + false, + 10, + Locked3x + ) + ] + ), Err(Error::MultiplePolls) ); } @@ -260,7 +307,8 @@ mod tests { fn invalid_signature() { let signed_request = vote(1, true, 10, Locked1x); let mut invalid_signed_request = signed_request.clone(); - invalid_signed_request.signature = MultiSignature::Ed25519(ed25519::Signature::from([1; 64])); + invalid_signed_request.signature = + MultiSignature::Ed25519(ed25519::Signature::from([1; 64])); assert_eq!( mix_votes(GENESIS_HASH, &vec![invalid_signed_request]), @@ -280,22 +328,28 @@ mod tests { fn duplicate_account() { let (signing_key, _) = ed25519::Pair::generate(); assert_eq!( - mix_votes(GENESIS_HASH, &vec![ - custom_vote(signing_key, GENESIS_HASH, 1, 432, true, 10, Locked1x), - custom_vote(signing_key, GENESIS_HASH, 1, 431, false, 10, Locked3x) - ]), + mix_votes( + GENESIS_HASH, + &vec![ + custom_vote(signing_key, GENESIS_HASH, 1, 432, true, 10, Locked1x), + custom_vote(signing_key, GENESIS_HASH, 1, 431, false, 10, Locked3x) + ] + ), Err(Error::DuplicateAccount) ); } - fn vote( - nonce: u32, - aye: bool, - balance: u128, - conviction: Conviction - ) -> SignedVoteRequest { + fn vote(nonce: u32, aye: bool, balance: u128, conviction: Conviction) -> SignedVoteRequest { let (signing_key, _) = ed25519::Pair::generate(); - custom_vote(signing_key, GENESIS_HASH, POLL_INDEX, nonce, aye, balance, conviction) + custom_vote( + signing_key, + GENESIS_HASH, + POLL_INDEX, + nonce, + aye, + balance, + conviction, + ) } fn custom_vote( @@ -305,7 +359,7 @@ mod tests { nonce: u32, aye: bool, balance: u128, - conviction: Conviction + conviction: Conviction, ) -> SignedVoteRequest { let request = VoteRequest { account: signing_key.public().into(), @@ -314,7 +368,7 @@ mod tests { nonce, aye, balance, - conviction + conviction, }; let signature = MultiSignature::Ed25519(signing_key.sign(&request.encode())); SignedVoteRequest { request, signature } @@ -325,7 +379,7 @@ mod tests { account: signed_request.request.account.clone(), nonce: signed_request.request.nonce, balance, - conviction: signed_request.request.conviction + conviction: signed_request.request.conviction, } } diff --git a/enclave/src/main.rs b/enclave/src/main.rs index ce5becb..b530ee4 100644 --- a/enclave/src/main.rs +++ b/enclave/src/main.rs @@ -2,7 +2,7 @@ use std::env::args; use std::io; use cfg_if::cfg_if; -use sp_core::{ed25519, H256, Pair}; +use sp_core::{ed25519, Pair, H256}; use common::attestation::{Attestation, AttestationBundle, AttestedData}; use common::SignedVoteRequest; @@ -46,15 +46,21 @@ async fn main() -> anyhow::Result<()> { } } // Send the attestation bundle to the host as the first thing it receives. - stream.write(&AttestationBundle { attested_data, attestation }).await?; + stream + .write(&AttestationBundle { + attested_data, + attestation, + }) + .await?; // Loop, processing requests from the host. If the host termintes, it will close the stream, // break the loop and terminate the enclave as well. loop { let request = stream.read::().await?; let response = match request { - EnclaveRequest::MixVotes(vote_requests) => - process_mix_votes(&vote_requests, &signing_pair, genesis_hash), + EnclaveRequest::MixVotes(vote_requests) => { + process_mix_votes(&vote_requests, &signing_pair, genesis_hash) + } }; stream.write(&response).await?; } @@ -82,7 +88,9 @@ mod nitro { Ok(EnclaveStream::Vsock(stream)) } - pub(crate) fn create_attestation(attested_data: &AttestedData) -> Result { + pub(crate) fn create_attestation( + attested_data: &AttestedData, + ) -> Result { let attested_data_hash = Sha256::digest(attested_data.encode()).to_vec(); let request = Request::Attestation { @@ -101,7 +109,7 @@ mod nitro { .map_err(|e| anyhow!(e.to_string()))?; Ok(Attestation::Nitro(attestation)) } - _ => Err(anyhow!("Unexpected NSM response: {:?}", response)) + _ => Err(anyhow!("Unexpected NSM response: {:?}", response)), } } } @@ -113,7 +121,10 @@ mod mock { pub(crate) async fn establish_connection(socket_file: String) -> io::Result { let stream = UnixStream::connect(socket_file.clone()).await?; - println!("Connected to host as insecure mock enclave: {}", socket_file); + println!( + "Connected to host as insecure mock enclave: {}", + socket_file + ); Ok(EnclaveStream::Unix(stream)) } } @@ -121,7 +132,7 @@ mod mock { fn process_mix_votes( vote_requests: &Vec, signing_key: &ed25519::Pair, - genesis_hash: H256 + genesis_hash: H256, ) -> EnclaveResponse { println!("Received mix votes request:"); for vote_request in vote_requests { @@ -129,6 +140,6 @@ fn process_mix_votes( } match enclave::mix_votes(genesis_hash, &vote_requests) { Ok(glove_result) => EnclaveResponse::GloveResult(glove_result.sign(signing_key)), - Err(error) => EnclaveResponse::Error(Error::Mixing(error.to_string())) + Err(error) => EnclaveResponse::Error(Error::Mixing(error.to_string())), } } diff --git a/service/src/dynamodb.rs b/service/src/dynamodb.rs index f50e1c6..0571237 100644 --- a/service/src/dynamodb.rs +++ b/service/src/dynamodb.rs @@ -1,20 +1,22 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use crate::storage::Error; use anyhow::{anyhow, bail}; use aws_config::BehaviorVersion; -use aws_sdk_dynamodb::Client; use aws_sdk_dynamodb::error::SdkError; use aws_sdk_dynamodb::operation::query::QueryError; use aws_sdk_dynamodb::primitives::Blob; -use aws_sdk_dynamodb::types::{AttributeValue, KeyType, ReturnValue, ScalarAttributeType, TableDescription, TableStatus}; use aws_sdk_dynamodb::types::builders::AttributeDefinitionBuilder; +use aws_sdk_dynamodb::types::{ + AttributeValue, KeyType, ReturnValue, ScalarAttributeType, TableDescription, TableStatus, +}; +use aws_sdk_dynamodb::Client; +use common::SignedVoteRequest; use parity_scale_codec::{Decode, Encode}; use sp_runtime::AccountId32; use tokio::sync::Mutex; use tracing::warn; -use common::SignedVoteRequest; -use crate::storage::Error; #[derive(Clone)] pub struct DynamodbGloveStorage { @@ -29,11 +31,18 @@ impl DynamodbGloveStorage { pub async fn connect(table_name: String) -> anyhow::Result { let config = aws_config::load_defaults(BehaviorVersion::latest()).await; let client = Client::new(&config); - let table_description = client.describe_table().table_name(&table_name).send().await? + let table_description = client + .describe_table() + .table_name(&table_name) + .send() + .await? .table .ok_or_else(|| anyhow!("Table not found"))?; if table_description.table_status != Some(TableStatus::Active) { - bail!("Table is not in active state: {:?}", table_description.table_status); + bail!( + "Table is not in active state: {:?}", + table_description.table_status + ); } let partition_key = find_schema_element(&table_description, KeyType::Hash)?; if !is_attribute_type(&table_description, &partition_key, ScalarAttributeType::S)? { @@ -48,7 +57,7 @@ impl DynamodbGloveStorage { partition_key, sort_key, client, - cached_vote_accounts: Arc::default() + cached_vote_accounts: Arc::default(), }) } @@ -62,14 +71,27 @@ impl DynamodbGloveStorage { .item(&self.sort_key, account_vote_to_value(&request.account)) // Put the vote fields in separate attributes if ever easy access is needed .item("Account", AttributeValue::S(request.account.to_string())) - .item("GenesisHash", AttributeValue::S(format!("{:#x}", request.genesis_hash))) - .item("PollIndex", AttributeValue::N(request.poll_index.to_string())) + .item( + "GenesisHash", + AttributeValue::S(format!("{:#x}", request.genesis_hash)), + ) + .item( + "PollIndex", + AttributeValue::N(request.poll_index.to_string()), + ) .item("Nonce", AttributeValue::N(request.nonce.to_string())) .item("Aye", AttributeValue::Bool(request.aye)) .item("Balance", AttributeValue::N(request.balance.to_string())) - .item("Conviction", AttributeValue::S(format!("{:?}", request.conviction))) - .item("SignedVoteRequest", AttributeValue::B(Blob::new(signed_request.encode()))) - .send().await?; + .item( + "Conviction", + AttributeValue::S(format!("{:?}", request.conviction)), + ) + .item( + "SignedVoteRequest", + AttributeValue::B(Blob::new(signed_request.encode())), + ) + .send() + .await?; let mut cached_vote_accounts = self.cached_vote_accounts.lock().await; if let Some(cached_vote_accounts) = &mut *cached_vote_accounts { @@ -85,7 +107,7 @@ impl DynamodbGloveStorage { pub async fn remove_vote_request( &self, poll_index: u32, - account: &AccountId32 + account: &AccountId32, ) -> Result { let mut cached_vote_accounts = self.cached_vote_accounts.lock().await; if let Some(cached_vote_accounts) = &mut *cached_vote_accounts { @@ -102,19 +124,26 @@ impl DynamodbGloveStorage { } } - let delete_item_output = self.client + let delete_item_output = self + .client .delete_item() .table_name(&self.table_name) .key(&self.partition_key, poll_index_to_value(poll_index)) .key(&self.sort_key, account_vote_to_value(account)) .return_values(ReturnValue::AllOld) - .send().await?; + .send() + .await?; - Ok(delete_item_output.attributes.filter(|attrs| !attrs.is_empty()).is_some()) + Ok(delete_item_output + .attributes + .filter(|attrs| !attrs.is_empty()) + .is_some()) } pub async fn get_poll(&self, poll_index: u32) -> Result, Error> { - let signed_vote_requests = self.get_poll_items(poll_index, "SignedVoteRequest").await? + let signed_vote_requests = self + .get_poll_items(poll_index, "SignedVoteRequest") + .await? .iter() .filter_map(|item| { let value = item.get("SignedVoteRequest"); @@ -123,22 +152,18 @@ impl DynamodbGloveStorage { } value }) - .filter_map(|value| { - match value.as_b() { - Ok(blob) => Some(blob), - Err(_) => { - warn!("SignedVoteRequest is not a blob: {:?}", value); - None - } + .filter_map(|value| match value.as_b() { + Ok(blob) => Some(blob), + Err(_) => { + warn!("SignedVoteRequest is not a blob: {:?}", value); + None } }) - .filter_map(|blob| { - match SignedVoteRequest::decode(&mut blob.as_ref()) { - Ok(signed_request) => Some(signed_request), - Err(error) => { - warn!("Failed to decode SignedVoteRequest: {:?}", error); - None - } + .filter_map(|blob| match SignedVoteRequest::decode(&mut blob.as_ref()) { + Ok(signed_request) => Some(signed_request), + Err(error) => { + warn!("Failed to decode SignedVoteRequest: {:?}", error); + None } }) .filter_map(|signed_request| { @@ -153,7 +178,10 @@ impl DynamodbGloveStorage { if signed_request.request.poll_index == poll_index { Some(signed_request) } else { - warn!("SignedVoteRequest is not for poll {}: {:?}", poll_index, signed_request); + warn!( + "SignedVoteRequest is not for poll {}: {:?}", + poll_index, signed_request + ); None } }) @@ -163,7 +191,9 @@ impl DynamodbGloveStorage { pub async fn remove_poll(&self, poll_index: u32) -> Result<(), Error> { let poll_value = poll_index_to_value(poll_index); - let account_values = self.get_poll_items(poll_index, &self.sort_key).await? + let account_values = self + .get_poll_items(poll_index, &self.sort_key) + .await? .into_iter() .filter_map(|item| item.get(&self.sort_key).cloned()); for account_value in account_values { @@ -172,7 +202,8 @@ impl DynamodbGloveStorage { .table_name(&self.table_name) .key(&self.partition_key, poll_value.clone()) .key(&self.sort_key, account_value) - .send().await?; + .send() + .await?; } let mut cached_vote_accounts = self.cached_vote_accounts.lock().await; @@ -194,23 +225,25 @@ impl DynamodbGloveStorage { let mut vote_accounts: HashMap> = HashMap::new(); let mut exclusive_start_key = None; loop { - let scan_output = self.client + let scan_output = self + .client .scan() .table_name(&self.table_name) .set_exclusive_start_key(exclusive_start_key) .projection_expression("PollIndex,Account") - .send().await?; + .send() + .await?; for item in scan_output.items() { - let poll_index = item.get("PollIndex") + let poll_index = item + .get("PollIndex") .and_then(|v| v.as_n().ok()) .and_then(|s| s.parse::().ok()); - let account = item.get("Account") + let account = item + .get("Account") .and_then(|v| v.as_s().ok()) .and_then(|s| s.parse::().ok()); if let (Some(poll_index), Some(account)) = (poll_index, account) { - vote_accounts.entry(poll_index) - .or_default() - .insert(account); + vote_accounts.entry(poll_index).or_default().insert(account); } else { warn!("Invalid vote item: {:?}", item); } @@ -229,19 +262,21 @@ impl DynamodbGloveStorage { async fn get_poll_items( &self, poll_index: u32, - projection: &str + projection: &str, ) -> Result>, SdkError> { let mut items = Vec::new(); let mut exclusive_start_key = None; loop { - let query_output = self.client + let query_output = self + .client .query() .table_name(&self.table_name) .key_condition_expression(format!("{} = :poll", self.partition_key)) .expression_attribute_values(":poll", poll_index_to_value(poll_index)) .projection_expression(projection) .set_exclusive_start_key(exclusive_start_key) - .send().await?; + .send() + .await?; items.extend(query_output.items.unwrap_or_default()); exclusive_start_key = query_output.last_evaluated_key; if exclusive_start_key.is_none() { @@ -254,7 +289,7 @@ impl DynamodbGloveStorage { fn find_schema_element( table_description: &TableDescription, - key_type: KeyType + key_type: KeyType, ) -> anyhow::Result { table_description .key_schema() @@ -266,13 +301,15 @@ fn find_schema_element( fn is_attribute_type( table_description: &TableDescription, attribute_name: impl Into, - attribute_type: ScalarAttributeType + attribute_type: ScalarAttributeType, ) -> anyhow::Result { let attribute_definition = AttributeDefinitionBuilder::default() .attribute_name(attribute_name) .attribute_type(attribute_type) .build()?; - Ok(table_description.attribute_definitions().contains(&attribute_definition)) + Ok(table_description + .attribute_definitions() + .contains(&attribute_definition)) } fn poll_index_to_value(poll_index: u32) -> AttributeValue { diff --git a/service/src/enclave.rs b/service/src/enclave.rs index 48075b5..3294e3e 100644 --- a/service/src/enclave.rs +++ b/service/src/enclave.rs @@ -1,8 +1,8 @@ use io::{Error as IoError, ErrorKind}; -use std::{env, io}; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::Arc; +use std::{env, io}; use parity_scale_codec::{DecodeAll, Encode}; use tokio::sync::Mutex; @@ -12,7 +12,7 @@ use enclave_interface::EnclaveStream; #[derive(Clone)] pub struct EnclaveHandle { - stream: Arc> + stream: Arc>, } impl EnclaveHandle { @@ -47,21 +47,25 @@ pub mod nitro { debug!("AWS Nitro enclave cmd: {:?}", cmd); if debug_mode { let process = cmd.spawn()?; - debug!("Process to start AWS Nitro enclave, and capture its output, started: {}", - process.id()); + debug!( + "Process to start AWS Nitro enclave, and capture its output, started: {}", + process.id() + ); } else { let output = cmd.output()?; if !output.status.success() { return Err(IoError::new( ErrorKind::Other, - format!("Failed to start AWS Nitro enclave: {:?}", output)) - ); + format!("Failed to start AWS Nitro enclave: {:?}", output), + )); } debug!("AWS Nitro enclave started: {:?}", output); } let (stream, _) = listener.accept().await?; info!("AWS Nitro enclave connection established"); - Ok(EnclaveHandle { stream: Arc::new(Mutex::new(EnclaveStream::Vsock(stream))) }) + Ok(EnclaveHandle { + stream: Arc::new(Mutex::new(EnclaveStream::Vsock(stream))), + }) } } @@ -86,17 +90,30 @@ pub mod mock { debug!("Mock enclave process started: {}", process.id()); let (stream, _) = listener.accept().await?; info!("Mock enclave connection established"); - Ok(EnclaveHandle { stream: Arc::new(Mutex::new(EnclaveStream::Unix(stream))) }) + Ok(EnclaveHandle { + stream: Arc::new(Mutex::new(EnclaveStream::Unix(stream))), + }) } } fn local_file(file: &str) -> io::Result { env::args() .nth(0) - .map(|exe| Path::new(&exe).parent().unwrap_or(Path::new("/")).join(file).to_path_buf()) + .map(|exe| { + Path::new(&exe) + .parent() + .unwrap_or(Path::new("/")) + .join(file) + .to_path_buf() + }) .filter(|path| path.exists()) - .ok_or_else(|| IoError::new( - ErrorKind::NotFound, - format!("'{}' executable not in the same directory as the service executable", file), - )) -} \ No newline at end of file + .ok_or_else(|| { + IoError::new( + ErrorKind::NotFound, + format!( + "'{}' executable not in the same directory as the service executable", + file + ), + ) + }) +} diff --git a/service/src/lib.rs b/service/src/lib.rs index edc516a..67a9fb1 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -1,29 +1,37 @@ -use std::collections::{HashMap, HashSet}; use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; use std::error::Error; use std::future::Future; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use sp_runtime::AccountId32; use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::warn; -use client_interface::{account_to_subxt_multi_address, CallableSubstrateNetwork, ReferendumStatus, subscan, SubstrateNetwork}; use client_interface::metadata::runtime_types::pallet_conviction_voting::pallet::Call as ConvictionVotingCall; -use client_interface::metadata::runtime_types::pallet_conviction_voting::vote::{AccountVote, Vote}; +use client_interface::metadata::runtime_types::pallet_conviction_voting::vote::{ + AccountVote, Vote, +}; use client_interface::metadata::runtime_types::pallet_proxy::pallet::Call as ProxyCall; use client_interface::metadata::runtime_types::polkadot_runtime::RuntimeCall; use client_interface::subscan::Subscan; -use common::{AssignedBalance, BASE_AYE, BASE_NAY, Conviction, ExtrinsicLocation, GloveResult, SignedVoteRequest, VoteDirection}; +use client_interface::{ + account_to_subxt_multi_address, subscan, CallableSubstrateNetwork, ReferendumStatus, + SubstrateNetwork, +}; use common::attestation::{AttestationBundle, AttestationBundleLocation}; +use common::{ + AssignedBalance, Conviction, ExtrinsicLocation, GloveResult, SignedVoteRequest, VoteDirection, + BASE_AYE, BASE_NAY, +}; use crate::enclave::EnclaveHandle; use crate::storage::GloveStorage; +pub mod dynamodb; pub mod enclave; pub mod mixing; -pub mod dynamodb; pub mod storage; pub const BLOCK_TIME_SECS: u32 = 6; @@ -32,12 +40,13 @@ const GLOVE_MIX_PERIOD: u32 = (15 * 60) / BLOCK_TIME_SECS; pub async fn calculate_mixing_time( poll_status: ReferendumStatus, - network: &SubstrateNetwork + network: &SubstrateNetwork, ) -> Result { let Some(deciding_status) = poll_status.deciding else { return Ok(MixingTime::NotDeciding); }; - let decision_period = network.get_tracks()? + let decision_period = network + .get_tracks()? .get(&poll_status.track) .map(|track_info| track_info.decision_period) .ok_or_else(|| subxt::Error::Other("Track not found".into()))?; @@ -54,7 +63,7 @@ pub async fn calculate_mixing_time( pub enum MixingTime { Deciding(u32), Confirming(u32), - NotDeciding + NotDeciding, } impl MixingTime { @@ -62,7 +71,7 @@ impl MixingTime { match self { MixingTime::Deciding(block_number) => Some(*block_number), MixingTime::Confirming(block_number) => Some(*block_number), - MixingTime::NotDeciding => None + MixingTime::NotDeciding => None, } } } @@ -73,14 +82,14 @@ pub struct VoterLookup { poll_index: u32, // get_voters is called frequently, so we want to avoid looking up the same extrinsic every // time. - cache: Arc>>> + cache: Arc>>>, } impl VoterLookup { pub fn new(poll_index: u32) -> Self { Self { poll_index, - cache: Arc::default() + cache: Arc::default(), } } @@ -89,7 +98,7 @@ impl VoterLookup { /// if the vote was proxied. pub async fn get_voters( &self, - subscan: &Subscan + subscan: &Subscan, ) -> Result)>, subscan::Error> { let mut result = Vec::new(); let votes = subscan.get_votes(self.poll_index, None).await?; @@ -115,23 +124,21 @@ impl VoterLookup { // TODO Deal with mixed_balance of zero pub fn to_proxied_vote_call( result: &GloveResult, - assigned_balance: &AssignedBalance + assigned_balance: &AssignedBalance, ) -> RuntimeCall { - RuntimeCall::Proxy( - ProxyCall::proxy { - real: account_to_subxt_multi_address(assigned_balance.account.clone()), - force_proxy_type: None, - call: Box::new(RuntimeCall::ConvictionVoting(ConvictionVotingCall::vote { - poll_index: result.poll_index, - vote: to_account_vote(result.direction, assigned_balance) - })), - } - ) + RuntimeCall::Proxy(ProxyCall::proxy { + real: account_to_subxt_multi_address(assigned_balance.account.clone()), + force_proxy_type: None, + call: Box::new(RuntimeCall::ConvictionVoting(ConvictionVotingCall::vote { + poll_index: result.poll_index, + vote: to_account_vote(result.direction, assigned_balance), + })), + }) } fn to_account_vote( direction: VoteDirection, - assigned_balance: &AssignedBalance + assigned_balance: &AssignedBalance, ) -> AccountVote { let offset = match assigned_balance.conviction { Conviction::None => 0, @@ -140,13 +147,23 @@ fn to_account_vote( Conviction::Locked3x => 3, Conviction::Locked4x => 4, Conviction::Locked5x => 5, - Conviction::Locked6x => 6 + Conviction::Locked6x => 6, }; let balance = assigned_balance.balance; match direction { - VoteDirection::Aye => AccountVote::Standard { vote: Vote(BASE_AYE + offset), balance }, - VoteDirection::Nay => AccountVote::Standard { vote: Vote(BASE_NAY + offset), balance }, - VoteDirection::Abstain => AccountVote::SplitAbstain { aye: 0, nay: 0, abstain: balance } + VoteDirection::Aye => AccountVote::Standard { + vote: Vote(BASE_AYE + offset), + balance, + }, + VoteDirection::Nay => AccountVote::Standard { + vote: Vote(BASE_NAY + offset), + balance, + }, + VoteDirection::Abstain => AccountVote::SplitAbstain { + aye: 0, + nay: 0, + abstain: balance, + }, } } @@ -163,7 +180,7 @@ pub struct GloveContext { impl GloveContext { pub async fn add_vote_request( &self, - signed_request: SignedVoteRequest + signed_request: SignedVoteRequest, ) -> Result { let poll_index = signed_request.request.poll_index; let poll_state_ref = self.state.get_poll_state_ref(poll_index).await; @@ -179,14 +196,16 @@ impl GloveContext { pub async fn remove_vote_request( &self, poll_index: u32, - account: &AccountId32 + account: &AccountId32, ) -> Result { let poll_state_ref = self.state.get_poll_state_ref(poll_index).await; let poll_state = poll_state_ref.read_access().await; if poll_state.mix_finalized { return Ok(false); } - self.storage.remove_vote_request(poll_index, account).await?; + self.storage + .remove_vote_request(poll_index, account) + .await?; Ok(true) } @@ -202,13 +221,13 @@ pub struct GloveState { // There may be a non-trivial cost to storing the attestation bundle location, and so it's done // lazily on first poll mixing, rather than eagerly on startup. abl: Mutex>, - poll_states: Mutex> + poll_states: Mutex>, } impl GloveState { pub async fn attestation_bundle_location( &self, - new: impl FnOnce() -> Fut + new: impl FnOnce() -> Fut, ) -> Result where Fut: Future>, @@ -220,7 +239,7 @@ impl GloveState { *abl_holder = Some(abl.clone()); Ok(abl) } - Some(abl) => Ok(abl.clone()) + Some(abl) => Ok(abl.clone()), } } @@ -243,8 +262,10 @@ impl GloveState { } pub async fn was_vote_added(&self, poll_index: u32) -> bool { - self.get_poll_state_ref(poll_index).await - .read_access().await + self.get_poll_state_ref(poll_index) + .await + .read_access() + .await .vote_added .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) .unwrap_or_else(|val| val) @@ -252,12 +273,13 @@ impl GloveState { pub async fn get_poll_state_ref(&self, poll_index: u32) -> PollStateRef { let mut poll_states = self.poll_states.lock().await; - poll_states.entry(poll_index).or_insert_with(|| { - PollStateRef { + poll_states + .entry(poll_index) + .or_insert_with(|| PollStateRef { inner: Arc::default(), - voter_lookup: VoterLookup::new(poll_index) - } - }).clone() + voter_lookup: VoterLookup::new(poll_index), + }) + .clone() } async fn remove_poll_state(&self, poll_index: u32) { @@ -270,14 +292,14 @@ pub struct PollStateRef { // There is a read-write lock here to support concurrent addition/removal of vote requests, but // synchronized mixing of the votes. inner: Arc>, - pub voter_lookup: VoterLookup + pub voter_lookup: VoterLookup, } #[derive(Default)] pub struct PollState { pub non_glove_voters: HashSet, pub mix_finalized: bool, - vote_added: AtomicBool + vote_added: AtomicBool, } impl PollStateRef { diff --git a/service/src/main.rs b/service/src/main.rs index 6f69dd0..f25064c 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -29,6 +29,7 @@ use tracing::{debug, error, info}; use tracing_subscriber::filter::{EnvFilter, LevelFilter}; use attestation::Error::InsecureMode; +use client_interface::account_to_subxt_multi_address; use client_interface::metadata::runtime_types::frame_system::pallet::Call as SystemCall; use client_interface::metadata::runtime_types::pallet_conviction_voting::pallet::Call as ConvictionVotingCall; use client_interface::metadata::runtime_types::pallet_conviction_voting::pallet::Error::InsufficientFunds; @@ -39,14 +40,19 @@ use client_interface::metadata::runtime_types::polkadot_runtime::RuntimeError::P use client_interface::metadata::runtime_types::polkadot_runtime::{RuntimeCall, RuntimeError}; use client_interface::subscan::Subscan; use client_interface::BatchError; -use client_interface::account_to_subxt_multi_address; -use client_interface::{is_glove_member, CallableSubstrateNetwork, ProxyError, ReferendumStatus, ServiceInfo, SignedRemoveVoteRequest, SubstrateNetwork}; +use client_interface::{ + is_glove_member, CallableSubstrateNetwork, ProxyError, ReferendumStatus, ServiceInfo, + SignedRemoveVoteRequest, SubstrateNetwork, +}; use common::attestation::{AttestationBundle, AttestationBundleLocation, GloveProofLite}; use common::{attestation, SignedGloveResult, SignedVoteRequest}; use service::dynamodb::DynamodbGloveStorage; use service::enclave::EnclaveHandle; use service::storage::{GloveStorage, InMemoryGloveStorage}; -use service::{calculate_mixing_time, mixing, storage, to_proxied_vote_call, GloveContext, GloveState, MixingTime, BLOCK_TIME_SECS}; +use service::{ + calculate_mixing_time, mixing, storage, to_proxied_vote_call, GloveContext, GloveState, + MixingTime, BLOCK_TIME_SECS, +}; use storage::Error as StorageError; use RuntimeError::ConvictionVoting; @@ -100,7 +106,7 @@ struct Args { /// the results on-chain once. After this, any further vote requests for the same poll will be /// rejected. #[arg(long, verbatim_doc_comment)] - regular_mix: bool + regular_mix: bool, } #[derive(Debug, Subcommand)] @@ -114,12 +120,12 @@ enum Storage { /// The name of the DynamoDB table to use. The table must have a sort key, and both the /// partition key and sort key must be of strings. They can both have any name. #[arg(long, verbatim_doc_comment)] - table_name: String + table_name: String, }, /// Store all state in memory. This is only useful for testing and development purposes. Do not /// use in production. #[command(verbatim_doc_comment)] - InMemory + InMemory, } #[derive(Debug, Clone, ValueEnum)] @@ -135,7 +141,7 @@ enum EnclaveMode { /// purposes as an AWS Nitro instance is not required. This is INSECURE and Glove proofs will be /// marked as such. #[value(verbatim_doc_comment)] - Mock + Mock, } #[tokio::main] @@ -147,15 +153,14 @@ async fn main() -> anyhow::Result<()> { "subxt_core::events=info,hyper_util=info,reqwest::connect=info,aws=info,hyper::proto::h1=info")? // Set the base level to debug .add_directive(LevelFilter::DEBUG.into()); - tracing_subscriber::fmt() - .with_env_filter(filter) - .init(); + tracing_subscriber::fmt().with_env_filter(filter).init(); let args = Args::parse(); let storage = match args.storage { Storage::Dynamodb { table_name } => { - let storage = DynamodbGloveStorage::connect(table_name).await + let storage = DynamodbGloveStorage::connect(table_name) + .await .context("Failed to connect to DynamoDB table")?; info!("Connected to DynamoDB table"); GloveStorage::Dynamodb(storage) @@ -166,28 +171,31 @@ async fn main() -> anyhow::Result<()> { } }; - let enclave_handle = initialize_enclave(args.enclave_mode).await + let enclave_handle = initialize_enclave(args.enclave_mode) + .await .context("Unable to connect to enclave")?; - let network = CallableSubstrateNetwork::connect( - args.node_endpoint.clone(), - args.proxy_secret_phrase - ).await?; + let network = + CallableSubstrateNetwork::connect(args.node_endpoint.clone(), args.proxy_secret_phrase) + .await?; debug!("Connected: {:?}", network); - let attestation_bundle = enclave_handle.send_receive::( - &network.api.genesis_hash() - ).await?; + let attestation_bundle = enclave_handle + .send_receive::(&network.api.genesis_hash()) + .await?; if attestation_bundle.attested_data.version != version { - bail!("Version mismatch with enclave: {}", attestation_bundle.attested_data.version); + bail!( + "Version mismatch with enclave: {}", + attestation_bundle.attested_data.version + ); } // Just double-check everything is OK. A failure here prevents invalid Glove proofs from being // submitted on-chain. match attestation_bundle.verify() { Ok(_) | Err(InsecureMode) => debug!("Received attestation bundle from enclave"), - Err(error) => return Err(error.into()) + Err(error) => return Err(error.into()), } let glove_context = Arc::new(GloveContext { @@ -197,12 +205,15 @@ async fn main() -> anyhow::Result<()> { network, exclude_tracks: args.exclude_tracks.into_iter().collect(), regular_mix_enabled: args.regular_mix, - state: GloveState::default() + state: GloveState::default(), }); check_excluded_tracks(&glove_context).await?; - let subscan = Subscan::new(glove_context.network.network_name.clone(), args.subscan_api_key); + let subscan = Subscan::new( + glove_context.network.network_name.clone(), + args.subscan_api_key, + ); if args.regular_mix { warn!("Regular mixing of votes is enabled. This is not suitable for production."); } else { @@ -228,7 +239,8 @@ async fn check_excluded_tracks(context: &Arc) -> anyhow::Result<() let track_infos = context.network.get_tracks()?; let mut excluded_track_infos = HashMap::new(); for exclude_track_id in &context.exclude_tracks { - let track_info = track_infos.get(&exclude_track_id) + let track_info = track_infos + .get(&exclude_track_id) .ok_or_else(|| anyhow!("Excluded track {} not found", exclude_track_id))?; excluded_track_infos.insert(exclude_track_id, &track_info.name); } @@ -240,8 +252,11 @@ async fn check_excluded_tracks(context: &Arc) -> anyhow::Result<() continue; }; if excluded_track_infos.contains_key(&poll_info.track) { - warn!("Poll {} belongs to track {} which has been excluded. Existing vote requests \ - will be mixed, but further requests will not be accepted.", poll_index, poll_info.track); + warn!( + "Poll {} belongs to track {} which has been excluded. Existing vote requests \ + will be mixed, but further requests will not be accepted.", + poll_index, poll_info.track + ); } } @@ -284,14 +299,20 @@ async fn initialize_enclave(enclave_mode: EnclaveMode) -> io::Result, - subscan: &Subscan + subscan: &Subscan, ) -> anyhow::Result<()> { let glove_proxy = Some(context.network.account()); let poll_indices = context.storage.get_poll_indices().await?; debug!("Polls with requests: {:?}", poll_indices); for poll_index in poll_indices { - let voter_lookup = context.state.get_poll_state_ref(poll_index).await.voter_lookup; - let proxy_has_voted = voter_lookup.get_voters(&subscan).await? + let voter_lookup = context + .state + .get_poll_state_ref(poll_index) + .await + .voter_lookup; + let proxy_has_voted = voter_lookup + .get_voters(&subscan) + .await? .into_iter() .any(|(_, sender)| sender == glove_proxy); if proxy_has_voted { @@ -326,14 +347,18 @@ async fn run_background_task(context: Arc, subscan: Subscan) -> an context.remove_poll(poll_index).await?; continue; }; - let mut mix_required = regular_mix_enabled && context.state.was_vote_added(poll_index).await; + let mut mix_required = + regular_mix_enabled && context.state.was_vote_added(poll_index).await; if check_non_glove_voters(&subscan, &context, poll_index).await? { mix_required = true; } if context.state.is_mix_finalized(poll_index).await { if mix_required && !regular_mix_enabled { - warn!("Poll {} has already been mixed and submitted on-chain, but due to Glove \ - violators, it will need to be re-mixed", poll_index); + warn!( + "Poll {} has already been mixed and submitted on-chain, but due to Glove \ + violators, it will need to be re-mixed", + poll_index + ); } } else if !mix_required { if is_poll_ready_for_final_mix(poll_index, status, network).await? { @@ -351,47 +376,65 @@ async fn run_background_task(context: Arc, subscan: Subscan) -> an async fn check_non_glove_voters( subscan: &Subscan, context: &GloveContext, - poll_index: u32 -) -> anyhow::Result{ + poll_index: u32, +) -> anyhow::Result { let glove_proxy = Some(context.network.account()); let mut non_glove_voters = HashSet::new(); let mut mix_required = false; - let voter_lookup = context.state.get_poll_state_ref(poll_index).await.voter_lookup; + let voter_lookup = context + .state + .get_poll_state_ref(poll_index) + .await + .voter_lookup; for (voter, sender) in voter_lookup.get_voters(&subscan).await? { if sender == glove_proxy { continue; } - if context.storage.remove_vote_request(poll_index, &voter).await? { + if context + .storage + .remove_vote_request(poll_index, &voter) + .await? + { warn!("Vote request from {} for poll {} has been removed as they have voted outside of Glove", voter, poll_index); - mix_required = true; // Re-mix without the offender + mix_required = true; // Re-mix without the offender } non_glove_voters.insert(voter); } - context.state.set_non_glove_voters(poll_index, non_glove_voters).await; + context + .state + .set_non_glove_voters(poll_index, non_glove_voters) + .await; Ok(mix_required) } async fn is_poll_ready_for_final_mix( poll_index: u32, poll_status: ReferendumStatus, - network: &SubstrateNetwork + network: &SubstrateNetwork, ) -> Result { let now = network.current_block_number().await?; let mixing_time = calculate_mixing_time(poll_status, network).await?; - debug!("Mixing time for {} @ now={}: {:?}", poll_index, now, mixing_time); + debug!( + "Mixing time for {} @ now={}: {:?}", + poll_index, now, mixing_time + ); match mixing_time { MixingTime::Deciding(block_number) if now >= block_number => { - info!("Poll {} is nearing the end of its decision period and will be mixed", - poll_index); + info!( + "Poll {} is nearing the end of its decision period and will be mixed", + poll_index + ); Ok(true) } MixingTime::Confirming(block_number) if now >= block_number => { - info!("Poll {} is nearing the end of its confirmation period and will be mixed", - poll_index); + info!( + "Poll {} is nearing the end of its confirmation period and will be mixed", + poll_index + ); Ok(true) } - _ => Ok(false) + _ => Ok(false), } } @@ -403,7 +446,7 @@ async fn info(context: State>) -> Result, In proxy_account: context.network.account(), network_name: context.network.network_name.clone(), attestation_bundle: context.attestation_bundle.clone(), - version: env!("CARGO_PKG_VERSION").to_string() + version: env!("CARGO_PKG_VERSION").to_string(), })) } @@ -411,7 +454,7 @@ async fn info(context: State>) -> Result, In // TODO Reject if new vote request reaches max batch size limit for poll async fn vote( State(context): State>, - Json(signed_request): Json + Json(signed_request): Json, ) -> Result<(), VoteError> { let network = &context.network; let request = &signed_request.request; @@ -431,7 +474,11 @@ async fn vote( if context.exclude_tracks.contains(&poll_info.track) { return Err(BadVoteRequestError::TrackNotAllowed.into()); } - if context.state.is_non_glove_voter(request.poll_index, &request.account).await { + if context + .state + .is_non_glove_voter(request.poll_index, &request.account) + .await + { return Err(BadVoteRequestError::VotedOutsideGlove.into()); } // In a normal poll with multiple votes on both sides, the on-chain vote balance can be @@ -444,13 +491,16 @@ async fn vote( if !context.add_vote_request(signed_request.clone()).await? { return Err(BadVoteRequestError::PollAlreadyMixed.into()); } - debug!("Vote request added to storage: {:?}", signed_request.request); + debug!( + "Vote request added to storage: {:?}", + signed_request.request + ); Ok(()) } async fn remove_vote( State(context): State>, - Json(signed_request): Json + Json(signed_request): Json, ) -> Result<(), RemoveVoteError> { let network = &context.network; let account = &signed_request.request.account; @@ -474,14 +524,15 @@ async fn remove_vote( let remove_result = proxy_remove_vote( &context.network, signed_request.request.account.clone(), - poll_index - ).await; + poll_index, + ) + .await; match remove_result { Err(ProxyError::Module(_, ConvictionVoting(NotVoter))) => { debug!("Vote not found on-chain: {:?}", signed_request.request); } Err(error) => warn!("Error removing vote: {:?}", error), - Ok(_) => info!("Vote removed on-chain: {:?}", signed_request.request) + Ok(_) => info!("Vote removed on-chain: {:?}", signed_request.request), } }); } @@ -491,7 +542,7 @@ async fn remove_vote( async fn poll_info( State(context): State>, - Path(poll_index): Path + Path(poll_index): Path, ) -> Result, PollInfoError> { if context.regular_mix_enabled { return Err(PollInfoError::RegularMixEnabled); @@ -501,7 +552,8 @@ async fn poll_info( }; let current_block = context.network.current_block_number().await?; let current_time = context.network.current_time().await? / 1000; - let mixing_time = calculate_mixing_time(status, &context.network).await? + let mixing_time = calculate_mixing_time(status, &context.network) + .await? .block_number() .map(|block_number| { let timestamp = if block_number > current_block { @@ -514,22 +566,26 @@ async fn poll_info( // we're in the 15 min mixing buffer period at the end of the decision period // 3. Some test networks can have really short decision periods, smaller even than // the mixing buffer period - current_time.saturating_sub(((current_block - block_number) * BLOCK_TIME_SECS) as u64) + current_time + .saturating_sub(((current_block - block_number) * BLOCK_TIME_SECS) as u64) }; - MixingTimeJson { block_number, timestamp } + MixingTimeJson { + block_number, + timestamp, + } }); Ok(Json(PollInfo { mixing_time })) } #[derive(Debug, Clone, Serialize)] struct PollInfo { - mixing_time: Option + mixing_time: Option, } #[derive(Debug, Clone, Serialize)] struct MixingTimeJson { block_number: u32, - timestamp: u64 + timestamp: u64, } async fn mix_votes(context: &GloveContext, poll_index: u32) { @@ -557,12 +613,16 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result Result { let request = &poll_requests[batch_index].request; - warn!("Insufficient funds for {:?}. Removing it from poll and trying again", request); - let removed = context.storage.remove_vote_request(poll_index, &request.account).await?; + warn!( + "Insufficient funds for {:?}. Removing it from poll and trying again", + request + ); + let removed = context + .storage + .remove_vote_request(poll_index, &request.account) + .await?; if removed && context.state.is_mix_finalized(poll_index).await { // If the vote was already submitted on-chain, we need to remove it from there too - let remove_result = proxy_remove_vote( - &context.network, - request.account.clone(), - poll_index - ).await; + let remove_result = + proxy_remove_vote(&context.network, request.account.clone(), poll_index).await; if let Err(error) = remove_result { warn!("Error removing vote: {:?}", error); } @@ -589,15 +652,22 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result { let request = &poll_requests[batch_index].request; - warn!("Account is no longer part of Glove, removing its request and trying again: {:?}", - request); - context.storage.remove_vote_request(poll_index, &request.account).await?; + warn!( + "Account is no longer part of Glove, removing its request and trying again: {:?}", + request + ); + context + .storage + .remove_vote_request(poll_index, &request.account) + .await?; Ok(true) } proxy_error => { if let Some(batch_index) = proxy_error.batch_index() { - warn!("Error submitting mixed votes for {:?}: {:?}", - poll_requests[batch_index].request, proxy_error) + warn!( + "Error submitting mixed votes for {:?}: {:?}", + poll_requests[batch_index].request, proxy_error + ) } else { warn!("Error submitting mixed votes: {:?}", proxy_error) } @@ -609,7 +679,7 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result, - signed_glove_result: SignedGloveResult + signed_glove_result: SignedGloveResult, ) -> Result<(), ProxyError> { let mut batched_calls = Vec::with_capacity(signed_requests.len() + 1); @@ -619,18 +689,21 @@ async fn submit_glove_result_on_chain( batched_calls.push(to_proxied_vote_call(glove_result, assigned_balance)); } - let attestation_location = context.state.attestation_bundle_location(|| async { - submit_attestation_bundle_location_on_chain(&context).await - }).await?; + let attestation_location = context + .state + .attestation_bundle_location(|| async { + submit_attestation_bundle_location_on_chain(&context).await + }) + .await?; let glove_proof_lite = GloveProofLite { signed_result: signed_glove_result, - attestation_location + attestation_location, }; // Add the Glove result, along with the location of the attestation bundle, to the batch batched_calls.push(RuntimeCall::System(SystemCall::remark { - remark: glove_proof_lite.encode_envelope() + remark: glove_proof_lite.encode_envelope(), })); // We can't use `batchAll` to submit the votes atomically, because it doesn't work with the @@ -646,11 +719,13 @@ async fn submit_glove_result_on_chain( } async fn submit_attestation_bundle_location_on_chain( - context: &GloveContext + context: &GloveContext, ) -> Result { let encoded = context.attestation_bundle.encode_envelope(); - let result = context.network - .call_extrinsic(&client_interface::metadata::tx().system().remark(encoded)).await + let result = context + .network + .call_extrinsic(&client_interface::metadata::tx().system().remark(encoded)) + .await .map(|(_, location)| AttestationBundleLocation::SubstrateRemark(location)); info!("Stored attestation bundle: {:?}", result); result @@ -659,20 +734,22 @@ async fn submit_attestation_bundle_location_on_chain( async fn proxy_remove_vote( network: &CallableSubstrateNetwork, account: AccountId32, - poll_index: u32 + poll_index: u32, ) -> Result<(), ProxyError> { // This doesn't need to be a batch call, but using `batch_proxy_calls` lets us reuse the // error handling. - let events = network.batch(vec![RuntimeCall::Proxy( - ProxyCall::proxy { + let events = network + .batch(vec![RuntimeCall::Proxy(ProxyCall::proxy { real: account_to_subxt_multi_address(account), force_proxy_type: None, - call: Box::new(RuntimeCall::ConvictionVoting(ConvictionVotingCall::remove_vote { - class: None, - index: poll_index - })), - } - )]).await?; + call: Box::new(RuntimeCall::ConvictionVoting( + ConvictionVotingCall::remove_vote { + class: None, + index: poll_index, + }, + )), + })]) + .await?; network.confirm_proxy_executed(&events) } @@ -692,30 +769,34 @@ impl InternalError { return false; }; match storage_error { - StorageError::DynamodbPutItem(SdkError::ServiceError(error)) => matches!(error.err(), - PutItemError::ProvisionedThroughputExceededException(_) | - PutItemError::RequestLimitExceeded(_) + StorageError::DynamodbPutItem(SdkError::ServiceError(error)) => matches!( + error.err(), + PutItemError::ProvisionedThroughputExceededException(_) + | PutItemError::RequestLimitExceeded(_) ), - StorageError::DynamodbDeleteItem(SdkError::ServiceError(error)) => matches!(error.err(), - DeleteItemError::ProvisionedThroughputExceededException(_) | - DeleteItemError::RequestLimitExceeded(_) + StorageError::DynamodbDeleteItem(SdkError::ServiceError(error)) => matches!( + error.err(), + DeleteItemError::ProvisionedThroughputExceededException(_) + | DeleteItemError::RequestLimitExceeded(_) ), - StorageError::DynamodbQuery(SdkError::ServiceError(error)) => matches!(error.err(), - QueryError::ProvisionedThroughputExceededException(_) | - QueryError::RequestLimitExceeded(_) + StorageError::DynamodbQuery(SdkError::ServiceError(error)) => matches!( + error.err(), + QueryError::ProvisionedThroughputExceededException(_) + | QueryError::RequestLimitExceeded(_) ), - StorageError::DynamodbScan(SdkError::ServiceError(error)) => matches!(error.err(), - ScanError::ProvisionedThroughputExceededException(_) | - ScanError::RequestLimitExceeded(_) + StorageError::DynamodbScan(SdkError::ServiceError(error)) => matches!( + error.err(), + ScanError::ProvisionedThroughputExceededException(_) + | ScanError::RequestLimitExceeded(_) ), - _ => false + _ => false, } } fn is_service_unavailable(&self) -> bool { match self { InternalError::Subxt(error) => error.is_disconnected_will_reconnect(), - _ => false + _ => false, } } } @@ -726,17 +807,23 @@ impl IntoResponse for InternalError { warn!("Too many requests: {:?}", self); ( StatusCode::TOO_MANY_REQUESTS, - "Too many requests, please try again later".to_string() - ).into_response() + "Too many requests, please try again later".to_string(), + ) + .into_response() } else if self.is_service_unavailable() { warn!("Service unavailable: {:?}", self); ( StatusCode::SERVICE_UNAVAILABLE, - "Unable to service request, please try again later".to_string() - ).into_response() + "Unable to service request, please try again later".to_string(), + ) + .into_response() } else { warn!("Unable to service request: {:?}", self); - (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string()).into_response() + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + ) + .into_response() } } } @@ -744,7 +831,7 @@ impl IntoResponse for InternalError { #[derive(Serialize)] struct BadRequestResponse { error: String, - description: String + description: String, } #[derive(thiserror::Error, Debug)] @@ -764,7 +851,7 @@ enum BadVoteRequestError { #[error("Votes for poll has already been mixed and submitted on-chain")] PollAlreadyMixed, #[error("Insufficient account balance for vote")] - InsufficientBalance + InsufficientBalance, } impl IntoResponse for BadVoteRequestError { @@ -777,12 +864,17 @@ impl IntoResponse for BadVoteRequestError { BadVoteRequestError::PollNotOngoing => "PollNotOngoing", BadVoteRequestError::TrackNotAllowed => "TrackNotAllowed", BadVoteRequestError::PollAlreadyMixed => "PollAlreadyMixed", - BadVoteRequestError::InsufficientBalance => "InsufficientBalance" - }.to_string(); + BadVoteRequestError::InsufficientBalance => "InsufficientBalance", + } + .to_string(); ( StatusCode::BAD_REQUEST, - Json(BadRequestResponse { error: error_variant, description: self.to_string() }) - ).into_response() + Json(BadRequestResponse { + error: error_variant, + description: self.to_string(), + }), + ) + .into_response() } } @@ -791,14 +883,14 @@ enum VoteError { #[error("Bad request: {0}")] BadRequest(#[from] BadVoteRequestError), #[error("Internal error: {0}")] - Internal(#[from] InternalError) + Internal(#[from] InternalError), } impl IntoResponse for VoteError { fn into_response(self) -> Response { match self { VoteError::BadRequest(error) => error.into_response(), - VoteError::Internal(error) => error.into_response() + VoteError::Internal(error) => error.into_response(), } } } @@ -828,7 +920,7 @@ enum BadRemoveVoteRequestError { #[error("Glove proxy is not assigned as a Governance proxy to the account")] NotMember, #[error("Votes for poll has already been mixed and submitted on-chain")] - PollAlreadyMixed + PollAlreadyMixed, } impl IntoResponse for BadRemoveVoteRequestError { @@ -836,12 +928,17 @@ impl IntoResponse for BadRemoveVoteRequestError { let error_variant = match self { BadRemoveVoteRequestError::InvalidSignature => "InvalidSignature", BadRemoveVoteRequestError::NotMember => "NotMember", - BadRemoveVoteRequestError::PollAlreadyMixed => "PollAlreadyMixed" - }.to_string(); + BadRemoveVoteRequestError::PollAlreadyMixed => "PollAlreadyMixed", + } + .to_string(); ( StatusCode::BAD_REQUEST, - Json(BadRequestResponse { error: error_variant, description: self.to_string() }) - ).into_response() + Json(BadRequestResponse { + error: error_variant, + description: self.to_string(), + }), + ) + .into_response() } } @@ -850,14 +947,14 @@ enum RemoveVoteError { #[error("Bad request: {0}")] BadRequest(#[from] BadRemoveVoteRequestError), #[error("Internal error: {0}")] - Internal(#[from] InternalError) + Internal(#[from] InternalError), } impl IntoResponse for RemoveVoteError { fn into_response(self) -> Response { match self { RemoveVoteError::BadRequest(error) => error.into_response(), - RemoveVoteError::Internal(error) => error.into_response() + RemoveVoteError::Internal(error) => error.into_response(), } } } @@ -887,24 +984,26 @@ enum PollInfoError { #[error("Poll is not active")] PollNotActive, #[error("Internal error: {0}")] - Internal(#[from] InternalError) + Internal(#[from] InternalError), } impl IntoResponse for PollInfoError { fn into_response(self) -> Response { match self { - PollInfoError::RegularMixEnabled => - (StatusCode::NOT_FOUND, "Regular mixing is enabled".to_string()).into_response(), - PollInfoError::PollNotActive => { - ( - StatusCode::BAD_REQUEST, - Json(BadRequestResponse { - error: "PollNotActive".into(), - description: self.to_string() - }) - ).into_response() - } - PollInfoError::Internal(error) => error.into_response() + PollInfoError::RegularMixEnabled => ( + StatusCode::NOT_FOUND, + "Regular mixing is enabled".to_string(), + ) + .into_response(), + PollInfoError::PollNotActive => ( + StatusCode::BAD_REQUEST, + Json(BadRequestResponse { + error: "PollNotActive".into(), + description: self.to_string(), + }), + ) + .into_response(), + PollInfoError::Internal(error) => error.into_response(), } } } diff --git a/service/src/mixing.rs b/service/src/mixing.rs index caf49b2..685930b 100644 --- a/service/src/mixing.rs +++ b/service/src/mixing.rs @@ -3,9 +3,9 @@ use std::io; use tracing::debug; use tracing::warn; -use common::{attestation, SignedGloveResult, SignedVoteRequest}; -use common::attestation::{AttestationBundle, GloveProof}; use common::attestation::Error::InsecureMode; +use common::attestation::{AttestationBundle, GloveProof}; +use common::{attestation, SignedGloveResult, SignedVoteRequest}; use enclave_interface::{EnclaveRequest, EnclaveResponse}; use crate::enclave::EnclaveHandle; @@ -14,34 +14,38 @@ use crate::storage; pub async fn mix_votes_in_enclave( enclave_handle: &EnclaveHandle, attestation_bundle: &AttestationBundle, - vote_requests: Vec + vote_requests: Vec, ) -> Result { let request = EnclaveRequest::MixVotes(vote_requests); - let response = enclave_handle.send_receive::(&request).await?; + let response = enclave_handle + .send_receive::(&request) + .await?; match response { EnclaveResponse::GloveResult(signed_result) => { let result = &signed_result.result; - debug!("Glove result from enclave, poll: {}, direction: {:?}, signature: {:?}", - result.poll_index, result.direction, signed_result.signature); + debug!( + "Glove result from enclave, poll: {}, direction: {:?}, signature: {:?}", + result.poll_index, result.direction, signed_result.signature + ); for assigned_balance in &result.assigned_balances { debug!(" {:?}", assigned_balance); } // Double-check things all line up before committing on-chain let proof = GloveProof { signed_result: signed_result.clone(), - attestation_bundle: attestation_bundle.clone() + attestation_bundle: attestation_bundle.clone(), }; match proof.verify() { Ok(_) => debug!("Glove proof verified"), Err(InsecureMode) => warn!("Glove proof from insecure enclave"), - Err(error) => return Err(error.into()) + Err(error) => return Err(error.into()), } Ok(signed_result) } EnclaveResponse::Error(enclave_error) => { warn!("Mixing error from enclave: {:?}", enclave_error); Err(enclave_error.into()) - }, + } } } @@ -54,5 +58,5 @@ pub enum Error { #[error("Enclave attestation error: {0}")] Attestation(#[from] attestation::Error), #[error("Storage error: {0}")] - Storage(#[from] storage::Error) + Storage(#[from] storage::Error), } diff --git a/service/src/storage.rs b/service/src/storage.rs index e99dc0c..1f3f9e7 100644 --- a/service/src/storage.rs +++ b/service/src/storage.rs @@ -30,10 +30,12 @@ impl GloveStorage { pub async fn remove_vote_request( &self, poll_index: u32, - account: &AccountId32 + account: &AccountId32, ) -> Result { match self { - GloveStorage::InMemory(store) => Ok(store.remove_vote_request(poll_index, account).await), + GloveStorage::InMemory(store) => { + Ok(store.remove_vote_request(poll_index, account).await) + } GloveStorage::Dynamodb(store) => store.remove_vote_request(poll_index, account).await, } } @@ -62,25 +64,30 @@ impl GloveStorage { #[derive(Clone, Default)] pub struct InMemoryGloveStorage { - polls: Arc>>> + polls: Arc>>>, } impl InMemoryGloveStorage { async fn add_vote_request(&self, signed_request: SignedVoteRequest) { let mut polls = self.polls.write().await; - polls.entry(signed_request.request.poll_index) + polls + .entry(signed_request.request.poll_index) .or_default() .insert(signed_request.request.account.clone(), signed_request); } async fn remove_vote_request(&self, poll_index: u32, account: &AccountId32) -> bool { let mut polls = self.polls.write().await; - polls.get_mut(&poll_index).map(|poll| poll.remove(account).is_some()).unwrap_or(false) + polls + .get_mut(&poll_index) + .map(|poll| poll.remove(account).is_some()) + .unwrap_or(false) } async fn get_poll(&self, poll_index: u32) -> Vec { let polls = self.polls.read().await; - let signed_vote_requests = polls.get(&poll_index) + let signed_vote_requests = polls + .get(&poll_index) .map(|poll| poll.values().cloned().collect()) .unwrap_or_default(); signed_vote_requests @@ -106,13 +113,13 @@ pub enum Error { #[error("DynamoDB query error: {0}")] DynamodbQuery(#[from] SdkError), #[error("DynamoDB scan error: {0}")] - DynamodbScan(#[from] SdkError) + DynamodbScan(#[from] SdkError), } #[cfg(test)] mod tests { - use sp_runtime::MultiSignature; use sp_runtime::testing::sr25519; + use sp_runtime::MultiSignature; use subxt::utils::H256; use common::{Conviction, VoteRequest}; @@ -187,7 +194,7 @@ mod tests { account: AccountId32, poll_index: u32, aye: bool, - balance: u128 + balance: u128, ) -> SignedVoteRequest { let request = VoteRequest::new(account, H256::zero(), poll_index, aye, balance, Locked1x); let signature = MultiSignature::Sr25519(sr25519::Signature::default()); diff --git a/stress-tool/src/main.rs b/stress-tool/src/main.rs index 8c0ea92..ed1be31 100644 --- a/stress-tool/src/main.rs +++ b/stress-tool/src/main.rs @@ -22,7 +22,7 @@ async fn main() -> Result<()> { match args.command { Command::JoinGlove(cmd) => join_glove(args.glove_url, cmd).await?, Command::Vote(cmd) => vote(args.glove_url, cmd).await?, - Command::Extrinsic(ref cmd) => extrinsic(cmd.id, args).await? + Command::Extrinsic(ref cmd) => extrinsic(cmd.id, args).await?, } Ok(()) } @@ -52,7 +52,9 @@ async fn join_glove(glove_url: Url, cmd: JoinGloveCmd) -> Result<()> { &format!("-g={}", glove_url), "join-glove", &format!("--secret-phrase={}", secret_phrase), - ]).await.unwrap(); + ]) + .await + .unwrap(); println!("{}. {}: {}", account_index, account, output); } }); @@ -66,7 +68,7 @@ async fn join_glove(glove_url: Url, cmd: JoinGloveCmd) -> Result<()> { Ok(()) } -async fn vote(glove_url: Url, cmd: VoteCmd) -> Result<()>{ +async fn vote(glove_url: Url, cmd: VoteCmd) -> Result<()> { let parallelism = cmd.accounts_args.parallelism()?; let poll_indices = if cmd.poll_index.is_empty() { @@ -87,7 +89,7 @@ async fn vote(glove_url: Url, cmd: VoteCmd) -> Result<()>{ aye_probability: f64, account_index: u8, secret_phrase: String, - account: AccountId32 + account: AccountId32, } let mut vote_args = Vec::new(); @@ -100,7 +102,7 @@ async fn vote(glove_url: Url, cmd: VoteCmd) -> Result<()>{ aye_probability, account_index, secret_phrase, - account + account, }); } } @@ -140,9 +142,16 @@ async fn vote(glove_url: Url, cmd: VoteCmd) -> Result<()>{ args.push("--aye".to_string()); } let output = client::run(args).await.unwrap(); - println!("{}. {} poll={} balance={} conviction={} aye={}: {}", - vote_arg.account_index, vote_arg.account, vote_arg.poll_index, balance, - conviction, aye, output); + println!( + "{}. {} poll={} balance={} conviction={} aye={}: {}", + vote_arg.account_index, + vote_arg.account, + vote_arg.poll_index, + balance, + conviction, + aye, + output + ); } }); } @@ -160,14 +169,16 @@ async fn extrinsic(extrinsic_location: ExtrinsicLocation, args: Args) -> Result< let subscan = Subscan::new(service_info.network_name, None); match subscan.get_extrinsic(extrinsic_location).await? { Some(extrinsic) => println!("{:#?}", extrinsic), - None => bail!("Extrinsic not found") + None => bail!("Extrinsic not found"), } Ok(()) } async fn get_ongoing_poll_indices(service_info: &ServiceInfo) -> Result> { let subscan = Subscan::new(service_info.network_name.clone(), None); - let poll_indices = subscan.get_polls(PollStatus::Active).await? + let poll_indices = subscan + .get_polls(PollStatus::Active) + .await? .iter() .map(|poll| poll.referendum_index) .collect::>(); @@ -177,9 +188,11 @@ async fn get_ongoing_poll_indices(service_info: &ServiceInfo) -> Result async fn service_info(glove_url: &Url) -> Result { let service_info = Client::new() .get(url_with_path(&glove_url, "info")) - .send().await? + .send() + .await? .error_for_status()? - .json::().await?; + .json::() + .await?; Ok(service_info) } @@ -203,7 +216,7 @@ enum Command { #[derive(Debug, Parser)] struct JoinGloveCmd { #[command(flatten)] - accounts_args: AccountsArgs + accounts_args: AccountsArgs, } #[derive(Debug, Parser)] @@ -232,7 +245,7 @@ struct AccountsArgs { end_derivation: u8, #[arg(long, verbatim_doc_comment, default_value_t = 0)] - parallelism: u8 + parallelism: u8, } impl AccountsArgs { From e3ec846c7177f0fc0fa7663b9c88f5b9c79b7d5f Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 16 Sep 2024 14:31:14 -0500 Subject: [PATCH 2/4] cargo won't leave me alone --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e3a107..8d54606 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1234,7 +1234,7 @@ checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "client" -version = "0.0.14" +version = "0.0.15" dependencies = [ "anyhow", "bigdecimal", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "client-interface" -version = "0.0.14" +version = "0.0.15" dependencies = [ "anyhow", "common", @@ -1287,7 +1287,7 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "common" -version = "0.0.14" +version = "0.0.15" dependencies = [ "aws-nitro-enclaves-cose", "aws-nitro-enclaves-nsm-api", @@ -1823,7 +1823,7 @@ dependencies = [ [[package]] name = "enclave" -version = "0.0.14" +version = "0.0.15" dependencies = [ "anyhow", "aws-nitro-enclaves-nsm-api", @@ -1845,7 +1845,7 @@ dependencies = [ [[package]] name = "enclave-interface" -version = "0.0.14" +version = "0.0.15" dependencies = [ "common", "nix 0.27.1", @@ -4701,7 +4701,7 @@ dependencies = [ [[package]] name = "service" -version = "0.0.14" +version = "0.0.15" dependencies = [ "anyhow", "aws-config", @@ -5611,7 +5611,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stress-tool" -version = "0.0.14" +version = "0.0.15" dependencies = [ "anyhow", "clap", From 697a72edddf62d740e8cddb5b498dc8414bdeb19 Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 16 Sep 2024 14:38:29 -0500 Subject: [PATCH 3/4] clippy fixes --- client-interface/src/lib.rs | 2 +- client-interface/src/subscan.rs | 2 +- client/src/verify.rs | 11 ++++------- common/src/attestation.rs | 10 ++++------ common/src/lib.rs | 16 ++++++++-------- common/src/nitro.rs | 2 +- enclave/src/main.rs | 2 +- service/src/enclave.rs | 3 +-- service/src/main.rs | 16 +++++++--------- service/src/storage.rs | 10 ++++++++-- stress-tool/src/main.rs | 2 +- 11 files changed, 37 insertions(+), 39 deletions(-) diff --git a/client-interface/src/lib.rs b/client-interface/src/lib.rs index 955e0b7..194f83a 100644 --- a/client-interface/src/lib.rs +++ b/client-interface/src/lib.rs @@ -482,7 +482,7 @@ mod tests { json!({ "proxy_account": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "network_name": "polkadot", - "attestation_bundle": to_hex(&service_info.attestation_bundle.encode()), + "attestation_bundle": to_hex(service_info.attestation_bundle.encode()), "version": "1.0.0" }) ); diff --git a/client-interface/src/subscan.rs b/client-interface/src/subscan.rs index 210f36c..b88e980 100644 --- a/client-interface/src/subscan.rs +++ b/client-interface/src/subscan.rs @@ -475,7 +475,7 @@ pub struct SplitAbstainAccountVote { } fn hex_deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { - from_hex(&String::deserialize(deserializer)?).map_err(|e| de::Error::custom(e)) + from_hex(&String::deserialize(deserializer)?).map_err(de::Error::custom) } #[derive(thiserror::Error, Debug)] diff --git a/client/src/verify.rs b/client/src/verify.rs index a010484..acf1c05 100644 --- a/client/src/verify.rs +++ b/client/src/verify.rs @@ -91,7 +91,7 @@ pub async fn try_verify_glove_result( } }; - if Some(attestation_bundle.attested_data.genesis_hash) != genesis_hash(&subscan).await.ok() { + if Some(attestation_bundle.attested_data.genesis_hash) != genesis_hash(subscan).await.ok() { return Err(Error::ChainMismatch); } @@ -136,7 +136,7 @@ fn parse_glove_proof_lite( return None; }; - GloveProofLite::decode_envelope(&remark) + GloveProofLite::decode_envelope(remark) .map(|proof| (proof, calls)) .ok() } @@ -244,7 +244,7 @@ async fn get_attestation_bundle_from_remark( } extrinsic_detail .get_param_as::("remark") - .and_then(|hex| AttestationBundle::decode_envelope(&mut hex.as_slice()).ok()) + .and_then(|hex| AttestationBundle::decode_envelope(hex.as_slice()).ok()) .ok_or_else(|| { Error::InvalidAttestationBundle(format!( "Extrinsic at location {:?} does not contain a valid AttestationBundle", @@ -293,10 +293,7 @@ mod tests { .unwrap() .unwrap(); assert_eq!(verification_result.result.assigned_balances.len(), 3); - let enclave_measuremnt = match &verification_result.enclave_info { - Some(EnclaveInfo::Nitro(info)) => Some(info.image_measurement.clone()), - None => None, - }; + let enclave_measuremnt = verification_result.enclave_info.as_ref().map(|EnclaveInfo::Nitro(info)| info.image_measurement.clone()); assert_eq!(enclave_measuremnt, Some(from_hex("4d132e40ed8d6db60d01d0116c34a4a92914de73d668821b6e019b72ae152b1180ef7c8a378e6c1925fe2bcb31c0ec80").unwrap())); let expected_balances = vec![ ( diff --git a/common/src/attestation.rs b/common/src/attestation.rs index c97b137..71a55d9 100644 --- a/common/src/attestation.rs +++ b/common/src/attestation.rs @@ -65,9 +65,9 @@ impl AttestationBundle { .filter(|pcr0| pcr0.iter().any(|&byte| byte != 0)) // All zeros means debug mode .map(|pcr0| pcr0.to_vec()) .ok_or(Error::InsecureMode)?; - let attested_data_hash = Sha256::digest(&self.attested_data.encode()).to_vec(); + let attested_data_hash = Sha256::digest(self.attested_data.encode()).to_vec(); (attestation_doc.user_data == Some(ByteBuf::from(attested_data_hash))) - .then(|| EnclaveInfo::Nitro(nitro::EnclaveInfo { image_measurement })) + .then_some(EnclaveInfo::Nitro(nitro::EnclaveInfo { image_measurement })) .ok_or(Error::AttestedData) } Attestation::Mock => Err(Error::InsecureMode), @@ -85,8 +85,7 @@ impl AttestationBundle { } pub fn decode_envelope(bytes: &[u8]) -> Result { - let version = bytes - .get(0) + let version = bytes.first() .ok_or_else(|| ScaleError::from("Empty bytes"))?; if *version != ATTESTATION_BUNDLE_ENCODING_VERSION { return Err(ScaleError::from("Unknown encoding version")); @@ -145,8 +144,7 @@ impl GloveProofLite { } pub fn decode_envelope(bytes: &[u8]) -> Result { - let version = bytes - .get(0) + let version = bytes.first() .ok_or_else(|| ScaleError::from("Empty bytes"))?; if *version != GLOVE_PROOF_LITE_ENCODING_VERSION { return Err(ScaleError::from("Unknown encoding version")); diff --git a/common/src/lib.rs b/common/src/lib.rs index fa92eb7..2667afc 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -212,7 +212,7 @@ pub mod serde_over_hex_scale { T: Encode, S: Serializer, { - serializer.serialize_str(&to_hex(&value.encode())) + serializer.serialize_str(&to_hex(value.encode())) } pub fn deserialize<'de, T, D>(deserializer: D) -> Result @@ -254,7 +254,7 @@ mod tests { let signature: MultiSignature = pair.sign(encoded_request.as_slice()).into(); let signed_request = SignedVoteRequest { request, signature }; - assert_eq!(signed_request.verify(), true); + assert!(signed_request.verify()); let json = serde_json::to_string(&signed_request).unwrap(); println!("{}", json); @@ -293,7 +293,7 @@ mod tests { .unwrap() ); assert_eq!(request.balance, 2230000000000); - assert_eq!(request.aye, true); + assert!(request.aye); assert_eq!(request.conviction, Conviction::Locked2x); } @@ -321,7 +321,7 @@ mod tests { .unwrap() ); assert_eq!(request.balance, 3230000000000); - assert_eq!(request.aye, false); + assert!(!request.aye); assert_eq!(request.conviction, Conviction::Locked4x); } @@ -342,12 +342,12 @@ mod tests { let signature: MultiSignature = pair2.sign(encoded_request.as_slice()).into(); let signed_request = SignedVoteRequest { request, signature }; - assert_eq!(signed_request.verify(), false); + assert!(!signed_request.verify()); let json = serde_json::to_string(&signed_request).unwrap(); let deserialized_signed_request: SignedVoteRequest = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized_signed_request, signed_request); - assert_eq!(deserialized_signed_request.verify(), false); + assert!(!deserialized_signed_request.verify()); } #[test] @@ -372,11 +372,11 @@ mod tests { }, signature, }; - assert_eq!(signed_request.verify(), false); + assert!(!signed_request.verify()); let json = serde_json::to_string(&signed_request).unwrap(); let deserialized: SignedVoteRequest = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized, signed_request); - assert_eq!(deserialized.verify(), false); + assert!(!deserialized.verify()); } } diff --git a/common/src/nitro.rs b/common/src/nitro.rs index 0282680..d588ac6 100644 --- a/common/src/nitro.rs +++ b/common/src/nitro.rs @@ -73,7 +73,7 @@ impl Attestation { let mut cert_chain = Stack::new()?; for ca_cert in &doc.cabundle { - cert_chain.push(X509::from_der(&ca_cert)?)?; + cert_chain.push(X509::from_der(ca_cert)?)?; } // Create the trust store containing the root AWS nitro cert, and also configured to use diff --git a/enclave/src/main.rs b/enclave/src/main.rs index b530ee4..38ac90c 100644 --- a/enclave/src/main.rs +++ b/enclave/src/main.rs @@ -138,7 +138,7 @@ fn process_mix_votes( for vote_request in vote_requests { println!(" {:?}", vote_request); } - match enclave::mix_votes(genesis_hash, &vote_requests) { + match enclave::mix_votes(genesis_hash, vote_requests) { Ok(glove_result) => EnclaveResponse::GloveResult(glove_result.sign(signing_key)), Err(error) => EnclaveResponse::Error(Error::Mixing(error.to_string())), } diff --git a/service/src/enclave.rs b/service/src/enclave.rs index 3294e3e..e127c8a 100644 --- a/service/src/enclave.rs +++ b/service/src/enclave.rs @@ -97,8 +97,7 @@ pub mod mock { } fn local_file(file: &str) -> io::Result { - env::args() - .nth(0) + env::args().next() .map(|exe| { Path::new(&exe) .parent() diff --git a/service/src/main.rs b/service/src/main.rs index f25064c..a05aa1e 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -240,7 +240,7 @@ async fn check_excluded_tracks(context: &Arc) -> anyhow::Result<() let mut excluded_track_infos = HashMap::new(); for exclude_track_id in &context.exclude_tracks { let track_info = track_infos - .get(&exclude_track_id) + .get(exclude_track_id) .ok_or_else(|| anyhow!("Excluded track {} not found", exclude_track_id))?; excluded_track_infos.insert(exclude_track_id, &track_info.name); } @@ -311,7 +311,7 @@ async fn mark_voted_polls_as_final( .await .voter_lookup; let proxy_has_voted = voter_lookup - .get_voters(&subscan) + .get_voters(subscan) .await? .into_iter() .any(|(_, sender)| sender == glove_proxy); @@ -360,10 +360,8 @@ async fn run_background_task(context: Arc, subscan: Subscan) -> an poll_index ); } - } else if !mix_required { - if is_poll_ready_for_final_mix(poll_index, status, network).await? { - mix_required = true; - } + } else if !mix_required && is_poll_ready_for_final_mix(poll_index, status, network).await? { + mix_required = true; } if mix_required { mix_votes(&context, poll_index).await; @@ -386,7 +384,7 @@ async fn check_non_glove_voters( .get_poll_state_ref(poll_index) .await .voter_lookup; - for (voter, sender) in voter_lookup.get_voters(&subscan).await? { + for (voter, sender) in voter_lookup.get_voters(subscan).await? { if sender == glove_proxy { continue; } @@ -617,7 +615,7 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result Result<(), Error> { match self { - GloveStorage::InMemory(store) => Ok(store.add_vote_request(signed_request).await), + GloveStorage::InMemory(store) => { + store.add_vote_request(signed_request).await; + Ok(()) + }, GloveStorage::Dynamodb(store) => store.add_vote_request(signed_request).await, } } @@ -49,7 +52,10 @@ impl GloveStorage { pub async fn remove_poll(&self, poll_index: u32) -> Result<(), Error> { match self { - GloveStorage::InMemory(store) => Ok(store.remove_poll(poll_index).await), + GloveStorage::InMemory(store) => { + store.remove_poll(poll_index).await; + Ok(()) + }, GloveStorage::Dynamodb(store) => store.remove_poll(poll_index).await, } } diff --git a/stress-tool/src/main.rs b/stress-tool/src/main.rs index ed1be31..52cdfe5 100644 --- a/stress-tool/src/main.rs +++ b/stress-tool/src/main.rs @@ -187,7 +187,7 @@ async fn get_ongoing_poll_indices(service_info: &ServiceInfo) -> Result async fn service_info(glove_url: &Url) -> Result { let service_info = Client::new() - .get(url_with_path(&glove_url, "info")) + .get(url_with_path(glove_url, "info")) .send() .await? .error_for_status()? From 774553f1a6e9734259fe9d368c8a5f4e8459acf6 Mon Sep 17 00:00:00 2001 From: noah Date: Mon, 16 Sep 2024 14:45:27 -0500 Subject: [PATCH 4/4] clippy fixes --- client/src/verify.rs | 21 ++++++++------------- common/src/attestation.rs | 6 ++++-- enclave/src/lib.rs | 11 +++++------ service/src/dynamodb.rs | 1 + service/src/enclave.rs | 3 ++- service/src/main.rs | 5 ++++- service/src/storage.rs | 4 ++-- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/client/src/verify.rs b/client/src/verify.rs index acf1c05..62d5d37 100644 --- a/client/src/verify.rs +++ b/client/src/verify.rs @@ -121,9 +121,7 @@ fn parse_glove_proof_lite( return None; } - let Some(calls) = extrinsic.get_param_as::>("calls") else { - return None; - }; + let calls = extrinsic.get_param_as::>("calls")?; let remarks = calls .iter() @@ -151,18 +149,12 @@ fn parse_and_validate_proxy_account_vote( let Some(MultiAddress::Id(real)) = batched_call.get_param_as::("real") else { return None; }; - let Some(proxied_call) = batched_call.get_param_as::("call") else { - return None; - }; + let proxied_call = batched_call.get_param_as::("call")?; if !proxied_call.is_extrinsic("convictionvoting", "vote") { return None; } - let Some(poll_index) = proxied_call.get_param_as::("poll_index") else { - return None; - }; - let Some(vote) = proxied_call.get_param_as::("vote") else { - return None; - }; + let poll_index = proxied_call.get_param_as::("poll_index")?; + let vote = proxied_call.get_param_as::("vote")?; if expected_poll_index != poll_index { return None; } @@ -293,7 +285,10 @@ mod tests { .unwrap() .unwrap(); assert_eq!(verification_result.result.assigned_balances.len(), 3); - let enclave_measuremnt = verification_result.enclave_info.as_ref().map(|EnclaveInfo::Nitro(info)| info.image_measurement.clone()); + let enclave_measuremnt = verification_result + .enclave_info + .as_ref() + .map(|EnclaveInfo::Nitro(info)| info.image_measurement.clone()); assert_eq!(enclave_measuremnt, Some(from_hex("4d132e40ed8d6db60d01d0116c34a4a92914de73d668821b6e019b72ae152b1180ef7c8a378e6c1925fe2bcb31c0ec80").unwrap())); let expected_balances = vec![ ( diff --git a/common/src/attestation.rs b/common/src/attestation.rs index 71a55d9..46a9bf1 100644 --- a/common/src/attestation.rs +++ b/common/src/attestation.rs @@ -85,7 +85,8 @@ impl AttestationBundle { } pub fn decode_envelope(bytes: &[u8]) -> Result { - let version = bytes.first() + let version = bytes + .first() .ok_or_else(|| ScaleError::from("Empty bytes"))?; if *version != ATTESTATION_BUNDLE_ENCODING_VERSION { return Err(ScaleError::from("Unknown encoding version")); @@ -144,7 +145,8 @@ impl GloveProofLite { } pub fn decode_envelope(bytes: &[u8]) -> Result { - let version = bytes.first() + let version = bytes + .first() .ok_or_else(|| ScaleError::from("Empty bytes"))?; if *version != GLOVE_PROOF_LITE_ENCODING_VERSION { return Err(ScaleError::from("Unknown encoding version")); diff --git a/enclave/src/lib.rs b/enclave/src/lib.rs index ecf41ee..07e46c2 100644 --- a/enclave/src/lib.rs +++ b/enclave/src/lib.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::collections::HashSet; use std::str::FromStr; @@ -105,12 +106,10 @@ pub fn mix_votes( Ok(GloveResult { poll_index, - direction: if ayes_balance > nays_balance { - VoteDirection::Aye - } else if ayes_balance < nays_balance { - VoteDirection::Nay - } else { - VoteDirection::Abstain + direction: match ayes_balance.cmp(&nays_balance) { + Ordering::Greater => VoteDirection::Aye, + Ordering::Less => VoteDirection::Nay, + Ordering::Equal => VoteDirection::Abstain, }, assigned_balances, }) diff --git a/service/src/dynamodb.rs b/service/src/dynamodb.rs index 0571237..83fdab8 100644 --- a/service/src/dynamodb.rs +++ b/service/src/dynamodb.rs @@ -24,6 +24,7 @@ pub struct DynamodbGloveStorage { partition_key: String, sort_key: String, client: Client, + #[allow(clippy::type_complexity)] cached_vote_accounts: Arc>>>>, } diff --git a/service/src/enclave.rs b/service/src/enclave.rs index e127c8a..9ec59ea 100644 --- a/service/src/enclave.rs +++ b/service/src/enclave.rs @@ -97,7 +97,8 @@ pub mod mock { } fn local_file(file: &str) -> io::Result { - env::args().next() + env::args() + .next() .map(|exe| { Path::new(&exe) .parent() diff --git a/service/src/main.rs b/service/src/main.rs index a05aa1e..d48594f 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -526,6 +526,7 @@ async fn remove_vote( ) .await; match remove_result { + #[allow(non_snake_case)] Err(ProxyError::Module(_, ConvictionVoting(NotVoter))) => { debug!("Vote not found on-chain: {:?}", signed_request.request); } @@ -628,6 +629,7 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result { let request = &poll_requests[batch_index].request; warn!( @@ -648,6 +650,7 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result { let request = &poll_requests[batch_index].request; warn!( @@ -676,7 +679,7 @@ async fn try_mix_votes(context: &GloveContext, poll_index: u32) -> Result, + signed_requests: &[SignedVoteRequest], signed_glove_result: SignedGloveResult, ) -> Result<(), ProxyError> { let mut batched_calls = Vec::with_capacity(signed_requests.len() + 1); diff --git a/service/src/storage.rs b/service/src/storage.rs index 9df2f30..0a35135 100644 --- a/service/src/storage.rs +++ b/service/src/storage.rs @@ -25,7 +25,7 @@ impl GloveStorage { GloveStorage::InMemory(store) => { store.add_vote_request(signed_request).await; Ok(()) - }, + } GloveStorage::Dynamodb(store) => store.add_vote_request(signed_request).await, } } @@ -55,7 +55,7 @@ impl GloveStorage { GloveStorage::InMemory(store) => { store.remove_poll(poll_index).await; Ok(()) - }, + } GloveStorage::Dynamodb(store) => store.remove_poll(poll_index).await, } }