From aa5f4fa1599a5a9673dc4c91af3bf7c7ef704eee Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 5 Aug 2024 19:17:35 +0530 Subject: [PATCH] Service option to exclude polls from being voted on which belong to certain tracks (#33) `--exclude-tracks` is a comma separated list of track IDs. Vote requests for such tracks return with a `TrackNotAllowed` bad request error. If a track is excluded and there are already vote requests for the track, then a warning is issued but the requests will be mixed. This is to honour the `/vote` request having previously returned HTTP OK. However, further vote requests will not be allowed. --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- service/src/lib.rs | 1 + service/src/main.rs | 46 ++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84d9f35..1644432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1234,7 +1234,7 @@ checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "client" -version = "0.0.8" +version = "0.0.9" dependencies = [ "anyhow", "bigdecimal", @@ -1257,7 +1257,7 @@ dependencies = [ [[package]] name = "client-interface" -version = "0.0.8" +version = "0.0.9" dependencies = [ "anyhow", "common", @@ -1286,7 +1286,7 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "common" -version = "0.0.8" +version = "0.0.9" dependencies = [ "aws-nitro-enclaves-cose", "aws-nitro-enclaves-nsm-api", @@ -1822,7 +1822,7 @@ dependencies = [ [[package]] name = "enclave" -version = "0.0.8" +version = "0.0.9" dependencies = [ "anyhow", "aws-nitro-enclaves-nsm-api", @@ -1844,7 +1844,7 @@ dependencies = [ [[package]] name = "enclave-interface" -version = "0.0.8" +version = "0.0.9" dependencies = [ "common", "nix 0.27.1", @@ -4700,7 +4700,7 @@ dependencies = [ [[package]] name = "service" -version = "0.0.8" +version = "0.0.9" dependencies = [ "anyhow", "aws-config", @@ -5610,7 +5610,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stress-tool" -version = "0.0.8" +version = "0.0.9" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 85edce1..c9ed0b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ resolver = "2" [workspace.package] homepage = "https://projectglove.io/" repository = "https://github.com/projectglove/glove-monorepo/" -version = "0.0.8" +version = "0.0.9" [workspace.dependencies] anyhow = "1.0.86" diff --git a/service/src/lib.rs b/service/src/lib.rs index 556a841..30f812c 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -114,6 +114,7 @@ pub struct GloveContext { pub enclave_handle: EnclaveHandle, pub attestation_bundle: AttestationBundle, pub network: CallableSubstrateNetwork, + pub exclude_tracks: HashSet, pub regular_mix_enabled: bool, pub state: GloveState, } diff --git a/service/src/main.rs b/service/src/main.rs index 3de9ab6..d235bc0 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::io; use std::sync::Arc; use std::time::Duration; -use anyhow::{bail, Context}; +use anyhow::{anyhow, bail, Context}; use aws_sdk_dynamodb::error::SdkError; use aws_sdk_dynamodb::operation::delete_item::DeleteItemError; use aws_sdk_dynamodb::operation::put_item::PutItemError; @@ -86,6 +86,13 @@ struct Args { #[arg(long, value_enum, verbatim_doc_comment, default_value_t = EnclaveMode::Nitro)] enclave_mode: EnclaveMode, + /// List of track IDs, seperated by comma, for which polls are not allowed to be voted on. + /// + /// See https://wiki.polkadot.network/docs/learn-polkadot-opengov-origins#origins-and-tracks-info + /// for list of track IDs. + #[arg(long, verbatim_doc_comment, value_delimiter = ',')] + exclude_tracks: Vec, + /// If specified, will mix vote requests at a regular interval. /// /// This is only useful for testing and development purposes. Otherwise, in production this @@ -195,10 +202,13 @@ async fn main() -> anyhow::Result<()> { enclave_handle, attestation_bundle, network, + exclude_tracks: args.exclude_tracks.into_iter().collect(), regular_mix_enabled: args.regular_mix, state: GloveState::default() }); + check_excluded_tracks(&glove_context).await?; + 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."); @@ -220,6 +230,30 @@ async fn main() -> anyhow::Result<()> { Ok(()) } +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) + .ok_or_else(|| anyhow!("Excluded track {} not found", exclude_track_id))?; + excluded_track_infos.insert(exclude_track_id, &track_info.name); + } + + info!("Excluded tracks: {:?}", excluded_track_infos); + + for poll_index in context.storage.get_poll_indices().await? { + let Some(poll_info) = context.network.get_ongoing_poll(poll_index).await? else { + 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); + } + } + + Ok(()) +} + async fn initialize_enclave(enclave_mode: EnclaveMode) -> io::Result { match enclave_mode { EnclaveMode::Nitro => { @@ -395,8 +429,11 @@ async fn vote( if request.genesis_hash != network.api.genesis_hash() { return Err(BadVoteRequestError::ChainMismatch.into()); } - if network.get_ongoing_poll(request.poll_index).await?.is_none() { + let Some(poll_info) = network.get_ongoing_poll(request.poll_index).await? else { return Err(BadVoteRequestError::PollNotOngoing.into()); + }; + 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 { return Err(BadVoteRequestError::VotedOutsideGlove.into()); @@ -670,6 +707,8 @@ enum BadVoteRequestError { VotedOutsideGlove, #[error("Poll is not ongoing or does not exist")] PollNotOngoing, + #[error("Poll belongs to a track which is not allowed for voting")] + TrackNotAllowed, #[error("Votes for poll has already been mixed and submitted on-chain")] PollAlreadyMixed, #[error("Insufficient account balance for vote")] @@ -684,6 +723,7 @@ impl IntoResponse for BadVoteRequestError { BadVoteRequestError::NotMember => "NotMember", BadVoteRequestError::VotedOutsideGlove => "VotedOutsideGlove", BadVoteRequestError::PollNotOngoing => "PollNotOngoing", + BadVoteRequestError::TrackNotAllowed => "TrackNotAllowed", BadVoteRequestError::PollAlreadyMixed => "PollAlreadyMixed", BadVoteRequestError::InsufficientBalance => "InsufficientBalance" }.to_string();