From 71081b6ee7110c3c9760b52d3ef079701cc12312 Mon Sep 17 00:00:00 2001 From: CleverWild Date: Thu, 27 Aug 2026 13:42:14 +0200 Subject: [PATCH] refactor(proposal): use id newtypes across the governance path --- .../src/actors/proposal_manager.rs | 45 +++++++++++-------- server/crates/arbiter-server/src/db/models.rs | 22 ++++----- .../src/db/proposal/approve_sdk_client.rs | 11 +++-- .../src/db/proposal/grant_wallet_access.rs | 11 +++-- .../arbiter-server/src/db/proposal/mod.rs | 10 ++--- .../src/db/proposal/one_off_transaction.rs | 16 ++++--- .../src/db/proposal/persistent_grant.rs | 20 +++++---- .../src/db/proposal/replace_operator.rs | 15 +++++-- .../src/db/proposal/trigger_rekey.rs | 6 +-- .../src/grpc/operator/governance.rs | 13 +++--- .../src/peers/operator/session/handlers.rs | 11 ++--- .../crates/arbiter-server/tests/governance.rs | 31 +++++++------ 12 files changed, 127 insertions(+), 84 deletions(-) diff --git a/server/crates/arbiter-server/src/actors/proposal_manager.rs b/server/crates/arbiter-server/src/actors/proposal_manager.rs index e7ab118..1be894d 100644 --- a/server/crates/arbiter-server/src/actors/proposal_manager.rs +++ b/server/crates/arbiter-server/src/actors/proposal_manager.rs @@ -8,7 +8,8 @@ use crate::{ self, models::{ NewProposal, NewProposalVote, NewRecoveryProposalVote, NewRecoveryWakeupRequest, - Proposal, ProposalStatus, SqliteTimestamp, + OperatorIdentityId, Proposal, ProposalId, ProposalStatus, RecoveryOperatorIdentityId, + SqliteTimestamp, }, proposal::{ProposalKind, ProposalKindTag, one_off_transaction, persistent_grant}, schema, @@ -65,9 +66,9 @@ pub enum Error { #[derive(Debug)] pub struct ProposalSummary { - pub id: i32, + pub id: ProposalId, pub kind: ProposalKindTag, - pub initiator_id: i32, + pub initiator_id: OperatorIdentityId, pub expires_at: SqliteTimestamp, pub approve_count: i64, pub reject_count: i64, @@ -103,9 +104,9 @@ impl ProposalManager { pub async fn create_proposal( &mut self, kind: ProposalKind, - initiator_id: i32, + initiator_id: OperatorIdentityId, ttl_secs: Option, - ) -> Result { + ) -> Result { let ttl = ttl_secs.unwrap_or(DEFAULT_TTL_SECS); if ttl > MAX_TTL_SECS { return Err(Error::TtlTooLong); @@ -113,12 +114,12 @@ impl ProposalManager { let expires_at = SqliteTimestamp::from(Utc::now() + chrono::Duration::seconds(i64::from(ttl))); - let id: i32 = self + let id: ProposalId = self .db .get() .await? .transaction(async |conn| { - let id: i32 = diesel::insert_into(schema::proposal::table) + let id: ProposalId = diesel::insert_into(schema::proposal::table) .values(&NewProposal { kind: kind.discriminant(), initiator_id, @@ -136,7 +137,7 @@ impl ProposalManager { } #[message] - pub async fn query_pending(&mut self, operator_id: i32) -> Vec { + pub async fn query_pending(&mut self, operator_id: OperatorIdentityId) -> Vec { #[expect( clippy::cast_possible_truncation, clippy::as_conversions, @@ -149,7 +150,7 @@ impl ProposalManager { return vec![]; }; - let voted_ids: Vec = schema::proposal_vote::table + let voted_ids: Vec = schema::proposal_vote::table .filter(schema::proposal_vote::operator_id.eq(operator_id)) .select(schema::proposal_vote::proposal_id) .load(&mut conn) @@ -195,8 +196,8 @@ impl ProposalManager { #[message] pub async fn cast_vote( &mut self, - proposal_id: i32, - operator_id: i32, + proposal_id: ProposalId, + operator_id: OperatorIdentityId, approve: bool, signature: Vec, ) -> Result { @@ -249,7 +250,7 @@ impl ProposalManager { // Canonical vote message: proposal_id (i64 big-endian) || approve (u8) let mut vote_msg = Vec::with_capacity(9); - vote_msg.extend_from_slice(&i64::from(proposal_id).to_be_bytes()); + vote_msg.extend_from_slice(&i64::from(proposal_id.to_raw()).to_be_bytes()); vote_msg.push(u8::from(approve)); let auth_sig = authn::Signature::try_from(signature.as_slice()) @@ -358,7 +359,10 @@ impl ProposalManager { /// §3.6: Any ordinary operator may request recovery wake-up. /// Fails if a wake-up is already pending or active. #[message] - pub async fn request_recovery_wakeup(&mut self, operator_id: i32) -> Result<(), Error> { + pub async fn request_recovery_wakeup( + &mut self, + operator_id: OperatorIdentityId, + ) -> Result<(), Error> { let mut conn = self.db.get().await?; if Self::has_uncancelled_wakeup(&mut conn).await? { return Err(Error::WakeupAlreadyPending); @@ -375,7 +379,10 @@ impl ProposalManager { /// §3.6: Any ordinary operator may cancel a pending wake-up request. /// Fails if there is no uncancelled request. #[message] - pub async fn cancel_recovery_wakeup(&mut self, operator_id: i32) -> Result<(), Error> { + pub async fn cancel_recovery_wakeup( + &mut self, + operator_id: OperatorIdentityId, + ) -> Result<(), Error> { let mut conn = self.db.get().await?; let rows_updated = diesel::update(schema::recovery_wakeup_request::table) .filter(schema::recovery_wakeup_request::cancelled_at.is_null()) @@ -396,8 +403,8 @@ impl ProposalManager { #[message] pub async fn cast_recovery_vote( &mut self, - proposal_id: i32, - recovery_operator_id: i32, + proposal_id: ProposalId, + recovery_operator_id: RecoveryOperatorIdentityId, approve: bool, signature: Vec, ) -> Result { @@ -454,7 +461,7 @@ impl ProposalManager { .map_err(|()| Error::InvalidSignature)?; let mut vote_msg = Vec::with_capacity(9); - vote_msg.extend_from_slice(&i64::from(proposal_id).to_be_bytes()); + vote_msg.extend_from_slice(&i64::from(proposal_id.to_raw()).to_be_bytes()); vote_msg.push(u8::from(approve)); let auth_sig = authn::Signature::try_from(signature.as_slice()) @@ -617,7 +624,7 @@ impl ProposalManager { /// removes their old Shamir share, then begins a coordinated re-key (§3.3). async fn execute_replace_operator( &self, - old_operator_id: i32, + old_operator_id: OperatorIdentityId, new_pubkey: Vec, ) -> Result<(), Error> { let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?; @@ -657,7 +664,7 @@ impl ProposalManager { async fn execute_approve_one_off_transaction( &self, - proposal_id: i32, + proposal_id: ProposalId, tx: one_off_transaction::Settings, ) -> Result<(), Error> { use crate::actors::evm::ClientSignTransaction; diff --git a/server/crates/arbiter-server/src/db/models.rs b/server/crates/arbiter-server/src/db/models.rs index fd749d1..4eae389 100644 --- a/server/crates/arbiter-server/src/db/models.rs +++ b/server/crates/arbiter-server/src/db/models.rs @@ -143,6 +143,8 @@ pub mod types { declare_id!(TlsHistoryId); declare_id!(EvmWalletId); declare_id!(ClientId); + declare_id!(ProposalId); + declare_id!(RecoveryOperatorIdentityId); #[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow)] #[diesel(sql_type = Text)] @@ -478,9 +480,9 @@ pub struct IntegrityEnvelope { #[derive(Debug, Queryable, Selectable, Identifiable)] #[diesel(table_name = schema::proposal, check_for_backend(Sqlite))] pub struct Proposal { - pub id: i32, + pub id: ProposalId, pub kind: ProposalKindTag, - pub initiator_id: i32, + pub initiator_id: OperatorIdentityId, pub created_at: SqliteTimestamp, pub expires_at: SqliteTimestamp, pub status: ProposalStatus, @@ -490,7 +492,7 @@ pub struct Proposal { #[diesel(table_name = schema::proposal, check_for_backend(Sqlite))] pub struct NewProposal { pub kind: ProposalKindTag, - pub initiator_id: i32, + pub initiator_id: OperatorIdentityId, // status defaults to 'pending' at the DB layer pub expires_at: SqliteTimestamp, } @@ -499,8 +501,8 @@ pub struct NewProposal { #[diesel(table_name = schema::proposal_vote, check_for_backend(Sqlite))] pub struct ProposalVote { pub id: i32, - pub proposal_id: i32, - pub operator_id: i32, + pub proposal_id: ProposalId, + pub operator_id: OperatorIdentityId, pub approve: bool, pub signature: Vec, pub voted_at: SqliteTimestamp, @@ -509,8 +511,8 @@ pub struct ProposalVote { #[derive(Debug, Insertable)] #[diesel(table_name = schema::proposal_vote, check_for_backend(Sqlite))] pub struct NewProposalVote { - pub proposal_id: i32, - pub operator_id: i32, + pub proposal_id: ProposalId, + pub operator_id: OperatorIdentityId, pub approve: bool, pub signature: Vec, } @@ -518,8 +520,8 @@ pub struct NewProposalVote { #[derive(Debug, Insertable)] #[diesel(table_name = schema::recovery_proposal_vote, check_for_backend(Sqlite))] pub struct NewRecoveryProposalVote { - pub proposal_id: i32, - pub recovery_operator_id: i32, + pub proposal_id: ProposalId, + pub recovery_operator_id: RecoveryOperatorIdentityId, pub approve: bool, pub signature: Vec, } @@ -527,5 +529,5 @@ pub struct NewRecoveryProposalVote { #[derive(Debug, Insertable)] #[diesel(table_name = schema::recovery_wakeup_request, check_for_backend(Sqlite))] pub struct NewRecoveryWakeupRequest { - pub requested_by: i32, + pub requested_by: OperatorIdentityId, } diff --git a/server/crates/arbiter-server/src/db/proposal/approve_sdk_client.rs b/server/crates/arbiter-server/src/db/proposal/approve_sdk_client.rs index d623010..cf4ef59 100644 --- a/server/crates/arbiter-server/src/db/proposal/approve_sdk_client.rs +++ b/server/crates/arbiter-server/src/db/proposal/approve_sdk_client.rs @@ -1,7 +1,9 @@ //! Approving an SDK client so it may authenticate against the vault. use super::{Proposal, ProposalKindTag}; -use crate::db::{DatabaseConnection, schema::proposal_approve_sdk_client as table}; +use crate::db::{ + DatabaseConnection, models::ProposalId, schema::proposal_approve_sdk_client as table, +}; use diesel::{ ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, SelectableHelper as _, sqlite::Sqlite, @@ -22,7 +24,7 @@ impl Proposal for ApproveSdkClient { type Settings = Settings; async fn insert( - proposal_id: i32, + proposal_id: ProposalId, settings: &Self::Settings, conn: &mut DatabaseConnection, ) -> QueryResult<()> { @@ -33,7 +35,10 @@ impl Proposal for ApproveSdkClient { .map(drop) } - async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult { + async fn load( + proposal_id: ProposalId, + conn: &mut DatabaseConnection, + ) -> QueryResult { table::table .find(proposal_id) .select(Settings::as_select()) diff --git a/server/crates/arbiter-server/src/db/proposal/grant_wallet_access.rs b/server/crates/arbiter-server/src/db/proposal/grant_wallet_access.rs index 5156b9b..b54b1e3 100644 --- a/server/crates/arbiter-server/src/db/proposal/grant_wallet_access.rs +++ b/server/crates/arbiter-server/src/db/proposal/grant_wallet_access.rs @@ -1,7 +1,9 @@ //! Granting an SDK client visibility of a wallet. use super::{Proposal, ProposalKindTag}; -use crate::db::{DatabaseConnection, schema::proposal_grant_wallet_access as table}; +use crate::db::{ + DatabaseConnection, models::ProposalId, schema::proposal_grant_wallet_access as table, +}; use diesel::{ ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, SelectableHelper as _, sqlite::Sqlite, @@ -23,7 +25,7 @@ impl Proposal for GrantWalletAccess { type Settings = Settings; async fn insert( - proposal_id: i32, + proposal_id: ProposalId, settings: &Self::Settings, conn: &mut DatabaseConnection, ) -> QueryResult<()> { @@ -34,7 +36,10 @@ impl Proposal for GrantWalletAccess { .map(drop) } - async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult { + async fn load( + proposal_id: ProposalId, + conn: &mut DatabaseConnection, + ) -> QueryResult { table::table .find(proposal_id) .select(Settings::as_select()) diff --git a/server/crates/arbiter-server/src/db/proposal/mod.rs b/server/crates/arbiter-server/src/db/proposal/mod.rs index b70ac73..a8e756a 100644 --- a/server/crates/arbiter-server/src/db/proposal/mod.rs +++ b/server/crates/arbiter-server/src/db/proposal/mod.rs @@ -6,7 +6,7 @@ //! new kind is a new module plus one arm in each dispatcher -- nothing else in the //! codebase has to learn about it. -use crate::db::DatabaseConnection; +use crate::db::{DatabaseConnection, models::ProposalId}; use diesel::{ QueryResult, backend::Backend, @@ -42,7 +42,7 @@ pub trait Proposal: Sized { /// Writes the child row carrying `settings`. fn insert( - proposal_id: i32, + proposal_id: ProposalId, settings: &Self::Settings, conn: &mut DatabaseConnection, ) -> impl Future> + Send; @@ -50,7 +50,7 @@ pub trait Proposal: Sized { /// Reads the child row back. A missing row surfaces as [`diesel::result::Error::NotFound`], /// which is what a proposal without its parameters is. fn load( - proposal_id: i32, + proposal_id: ProposalId, conn: &mut DatabaseConnection, ) -> impl Future> + Send; } @@ -126,7 +126,7 @@ const _: () = { /// to the implementation that owns the table. pub async fn insert_kind( conn: &mut DatabaseConnection, - proposal_id: i32, + proposal_id: ProposalId, kind: &ProposalKind, ) -> QueryResult<()> { match kind { @@ -146,7 +146,7 @@ pub async fn insert_kind( /// Reads the parameters back for a `proposal.kind` that is only known at runtime. pub async fn load_kind( conn: &mut DatabaseConnection, - proposal_id: i32, + proposal_id: ProposalId, tag: ProposalKindTag, ) -> QueryResult { Ok(match tag { diff --git a/server/crates/arbiter-server/src/db/proposal/one_off_transaction.rs b/server/crates/arbiter-server/src/db/proposal/one_off_transaction.rs index 4f2ec97..4c694c6 100644 --- a/server/crates/arbiter-server/src/db/proposal/one_off_transaction.rs +++ b/server/crates/arbiter-server/src/db/proposal/one_off_transaction.rs @@ -3,6 +3,7 @@ use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed}; use crate::db::{ DatabaseConnection, + models::ProposalId, schema::{proposal_one_off_transaction, proposal_one_off_transaction_result}, }; use diesel::{ @@ -28,7 +29,7 @@ pub struct Settings { #[derive(Debug, Queryable, Selectable, Insertable)] #[diesel(table_name = proposal_one_off_transaction, check_for_backend(Sqlite))] struct Row { - proposal_id: i32, + proposal_id: ProposalId, client_id: i32, wallet_address: Vec, chain_id: i64, @@ -42,7 +43,7 @@ struct Row { } impl Row { - fn new(proposal_id: i32, settings: &Settings) -> QueryResult { + fn new(proposal_id: ProposalId, settings: &Settings) -> QueryResult { Ok(Self { proposal_id, client_id: settings.client_id, @@ -82,7 +83,7 @@ impl Proposal for OneOffTransaction { type Settings = Settings; async fn insert( - proposal_id: i32, + proposal_id: ProposalId, settings: &Self::Settings, conn: &mut DatabaseConnection, ) -> QueryResult<()> { @@ -93,7 +94,10 @@ impl Proposal for OneOffTransaction { .map(drop) } - async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult { + async fn load( + proposal_id: ProposalId, + conn: &mut DatabaseConnection, + ) -> QueryResult { let row: Row = proposal_one_off_transaction::table .find(proposal_id) .select(Row::as_select()) @@ -108,7 +112,7 @@ impl Proposal for OneOffTransaction { #[derive(Debug, Insertable)] #[diesel(table_name = proposal_one_off_transaction_result, check_for_backend(Sqlite))] struct SignatureRow { - proposal_id: i32, + proposal_id: ProposalId, r: Vec, s: Vec, y_parity: i32, @@ -117,7 +121,7 @@ struct SignatureRow { /// Records the signature produced for an approved transaction, by component, so what /// came back is as readable as what was signed. pub async fn store_signature( - proposal_id: i32, + proposal_id: ProposalId, signature: &alloy::signers::Signature, conn: &mut DatabaseConnection, ) -> QueryResult<()> { diff --git a/server/crates/arbiter-server/src/db/proposal/persistent_grant.rs b/server/crates/arbiter-server/src/db/proposal/persistent_grant.rs index 495a349..57f3376 100644 --- a/server/crates/arbiter-server/src/db/proposal/persistent_grant.rs +++ b/server/crates/arbiter-server/src/db/proposal/persistent_grant.rs @@ -2,6 +2,7 @@ use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed}; use crate::db::{ DatabaseConnection, + models::ProposalId, schema::{ proposal_persistent_grant, proposal_persistent_grant_ether, proposal_persistent_grant_ether_target, proposal_persistent_grant_token, @@ -55,7 +56,7 @@ pub enum Specific { #[derive(Debug, Queryable, Selectable, Insertable)] #[diesel(table_name = proposal_persistent_grant, check_for_backend(Sqlite))] struct BaseRow { - proposal_id: i32, + proposal_id: ProposalId, wallet_access_id: i32, chain_id: i64, valid_from: Option, @@ -69,7 +70,7 @@ struct BaseRow { #[derive(Debug, Queryable, Selectable, Insertable)] #[diesel(table_name = proposal_persistent_grant_ether, check_for_backend(Sqlite))] struct EtherRow { - proposal_id: i32, + proposal_id: ProposalId, window_secs: i64, max_volume: Vec, } @@ -77,14 +78,14 @@ struct EtherRow { #[derive(Debug, Insertable)] #[diesel(table_name = proposal_persistent_grant_ether_target, check_for_backend(Sqlite))] struct NewEtherTarget { - proposal_id: i32, + proposal_id: ProposalId, address: Vec, } #[derive(Debug, Queryable, Selectable, Insertable)] #[diesel(table_name = proposal_persistent_grant_token, check_for_backend(Sqlite))] struct TokenRow { - proposal_id: i32, + proposal_id: ProposalId, token_contract: Vec, receiver: Option>, } @@ -92,13 +93,13 @@ struct TokenRow { #[derive(Debug, Insertable)] #[diesel(table_name = proposal_persistent_grant_token_limit, check_for_backend(Sqlite))] struct NewTokenLimit { - proposal_id: i32, + proposal_id: ProposalId, window_secs: i64, max_volume: Vec, } impl BaseRow { - fn new(proposal_id: i32, settings: &Settings) -> QueryResult { + fn new(proposal_id: ProposalId, settings: &Settings) -> QueryResult { Ok(Self { proposal_id, wallet_access_id: settings.wallet_access_id, @@ -141,7 +142,7 @@ impl Proposal for PersistentGrant { type Settings = Settings; async fn insert( - proposal_id: i32, + proposal_id: ProposalId, settings: &Self::Settings, conn: &mut DatabaseConnection, ) -> QueryResult<()> { @@ -201,7 +202,10 @@ impl Proposal for PersistentGrant { Ok(()) } - async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult { + async fn load( + proposal_id: ProposalId, + conn: &mut DatabaseConnection, + ) -> QueryResult { let base: BaseRow = proposal_persistent_grant::table .find(proposal_id) .select(BaseRow::as_select()) diff --git a/server/crates/arbiter-server/src/db/proposal/replace_operator.rs b/server/crates/arbiter-server/src/db/proposal/replace_operator.rs index d9e448a..1074084 100644 --- a/server/crates/arbiter-server/src/db/proposal/replace_operator.rs +++ b/server/crates/arbiter-server/src/db/proposal/replace_operator.rs @@ -1,7 +1,11 @@ //! Replacing an operator's key, which also triggers a Shamir re-key (§3.3). use super::{Proposal, ProposalKindTag}; -use crate::db::{DatabaseConnection, schema::proposal_replace_operator as table}; +use crate::db::{ + DatabaseConnection, + models::{OperatorIdentityId, ProposalId}, + schema::proposal_replace_operator as table, +}; use diesel::{ ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, SelectableHelper as _, sqlite::Sqlite, @@ -11,7 +15,7 @@ use diesel_async::RunQueryDsl as _; #[derive(Debug, Clone, PartialEq, Eq, Queryable, Selectable, Insertable)] #[diesel(table_name = table, check_for_backend(Sqlite))] pub struct Settings { - pub old_operator_id: i32, + pub old_operator_id: OperatorIdentityId, pub new_pubkey: Vec, } @@ -23,7 +27,7 @@ impl Proposal for ReplaceOperator { type Settings = Settings; async fn insert( - proposal_id: i32, + proposal_id: ProposalId, settings: &Self::Settings, conn: &mut DatabaseConnection, ) -> QueryResult<()> { @@ -34,7 +38,10 @@ impl Proposal for ReplaceOperator { .map(drop) } - async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult { + async fn load( + proposal_id: ProposalId, + conn: &mut DatabaseConnection, + ) -> QueryResult { table::table .find(proposal_id) .select(Settings::as_select()) diff --git a/server/crates/arbiter-server/src/db/proposal/trigger_rekey.rs b/server/crates/arbiter-server/src/db/proposal/trigger_rekey.rs index 6cf23bc..b132f74 100644 --- a/server/crates/arbiter-server/src/db/proposal/trigger_rekey.rs +++ b/server/crates/arbiter-server/src/db/proposal/trigger_rekey.rs @@ -1,7 +1,7 @@ //! A Shamir re-key over the current operator set (§3.3). use super::{Proposal, ProposalKindTag}; -use crate::db::DatabaseConnection; +use crate::db::{DatabaseConnection, models::ProposalId}; use diesel::QueryResult; pub struct TriggerRekey; @@ -12,7 +12,7 @@ impl Proposal for TriggerRekey { type Settings = (); async fn insert( - _proposal_id: i32, + _proposal_id: ProposalId, _settings: &Self::Settings, _conn: &mut DatabaseConnection, ) -> QueryResult<()> { @@ -20,7 +20,7 @@ impl Proposal for TriggerRekey { } async fn load( - _proposal_id: i32, + _proposal_id: ProposalId, _conn: &mut DatabaseConnection, ) -> QueryResult { Ok(()) diff --git a/server/crates/arbiter-server/src/grpc/operator/governance.rs b/server/crates/arbiter-server/src/grpc/operator/governance.rs index 987ab94..f1f1475 100644 --- a/server/crates/arbiter-server/src/grpc/operator/governance.rs +++ b/server/crates/arbiter-server/src/grpc/operator/governance.rs @@ -1,5 +1,6 @@ use crate::{ actors::proposal_manager::{Error as ProposalError, VoteOutcome}, + db::models::{OperatorIdentityId, ProposalId}, db::proposal::{ ProposalKind, approve_sdk_client, grant_wallet_access, one_off_transaction, persistent_grant, replace_operator, @@ -62,7 +63,7 @@ async fn handle_create( } Some(ProtoKind::ReplaceOperator(p)) => { ProposalKind::ReplaceOperator(replace_operator::Settings { - old_operator_id: p.old_operator_id, + old_operator_id: OperatorIdentityId::from_raw(p.old_operator_id), new_pubkey: p.new_pubkey, }) } @@ -87,7 +88,9 @@ async fn handle_create( })?; Ok(Some(wrap(GovResponsePayload::Created( - proto_gov::CreateProposalResponse { proposal_id }, + proto_gov::CreateProposalResponse { + proposal_id: proposal_id.to_raw(), + }, )))) } @@ -195,7 +198,7 @@ async fn handle_vote( ) -> Result, Status> { let result = actor .ask(HandleCastVote { - proposal_id: req.proposal_id, + proposal_id: ProposalId::from_raw(req.proposal_id), approve: req.approve, signature: req.signature, }) @@ -235,9 +238,9 @@ async fn handle_query( let proposals = summaries .into_iter() .map(|s| proto_gov::ProposalSummary { - id: s.id, + id: s.id.to_raw(), kind: <&'static str>::from(s.kind).to_owned(), - initiator_id: s.initiator_id, + initiator_id: s.initiator_id.to_raw(), expires_at: s.expires_at.0.timestamp(), approve_count: s.approve_count, reject_count: s.reject_count, diff --git a/server/crates/arbiter-server/src/peers/operator/session/handlers.rs b/server/crates/arbiter-server/src/peers/operator/session/handlers.rs index bc83e85..258ea2c 100644 --- a/server/crates/arbiter-server/src/peers/operator/session/handlers.rs +++ b/server/crates/arbiter-server/src/peers/operator/session/handlers.rs @@ -1,4 +1,5 @@ use super::{Error, OperatorSession}; +use crate::db::models::{OperatorIdentityId, ProposalId}; use crate::{ actors::{ evm::{ @@ -287,9 +288,9 @@ impl OperatorSession { &mut self, kind: crate::db::proposal::ProposalKind, ttl_secs: Option, - ) -> Result { + ) -> Result { use crate::actors::proposal_manager::CreateProposal; - let initiator_id = self.credentials.id; + let initiator_id = OperatorIdentityId::from_raw(self.credentials.id); self.props .actors .proposal_manager @@ -304,12 +305,12 @@ impl OperatorSession { #[message] pub(crate) async fn handle_cast_vote( &mut self, - proposal_id: i32, + proposal_id: ProposalId, approve: bool, signature: Vec, ) -> Result { use crate::actors::proposal_manager::CastVote; - let operator_id = self.credentials.id; + let operator_id = OperatorIdentityId::from_raw(self.credentials.id); self.props .actors .proposal_manager @@ -326,7 +327,7 @@ impl OperatorSession { &mut self, ) -> Vec { use crate::actors::proposal_manager::QueryPending; - let operator_id = self.credentials.id; + let operator_id = OperatorIdentityId::from_raw(self.credentials.id); self.props .actors .proposal_manager diff --git a/server/crates/arbiter-server/tests/governance.rs b/server/crates/arbiter-server/tests/governance.rs index 426af2d..55e2c51 100644 --- a/server/crates/arbiter-server/tests/governance.rs +++ b/server/crates/arbiter-server/tests/governance.rs @@ -10,6 +10,7 @@ use arbiter_server::{ crypto::KeyCell, db::{ self, + models::{OperatorIdentityId, ProposalId, RecoveryOperatorIdentityId}, proposal::{ ProposalKind, approve_sdk_client, grant_wallet_access, one_off_transaction, persistent_grant, replace_operator, @@ -24,41 +25,45 @@ use arbiter_server::db::schema::{ use diesel::{ExpressionMethods, QueryDsl, insert_into}; use diesel_async::RunQueryDsl; -async fn register_operator(db: &db::DatabasePool, pubkey: &authn::PublicKey) -> i32 { +async fn register_operator(db: &db::DatabasePool, pubkey: &authn::PublicKey) -> OperatorIdentityId { let mut conn = db.get().await.unwrap(); insert_into(operator_identity::table) .values(operator_identity::public_key.eq(pubkey.to_bytes())) .returning(operator_identity::id) - .get_result::(&mut conn) + .get_result::(&mut conn) .await .unwrap() } -async fn register_recovery_operator(db: &db::DatabasePool, pubkey: &authn::PublicKey) -> i32 { +async fn register_recovery_operator( + db: &db::DatabasePool, + pubkey: &authn::PublicKey, +) -> RecoveryOperatorIdentityId { let mut conn = db.get().await.unwrap(); insert_into(recovery_operator_identity::table) .values(recovery_operator_identity::public_key.eq(pubkey.to_bytes())) .returning(recovery_operator_identity::id) - .get_result::(&mut conn) + .get_result::(&mut conn) .await .unwrap() } /// Backdates a wakeup request so it appears to have passed the 14-day window. -async fn insert_active_wakeup(db: &db::DatabasePool, operator_id: i32) { +async fn insert_active_wakeup(db: &db::DatabasePool, operator_id: OperatorIdentityId) { let mut conn = db.get().await.unwrap(); diesel::sql_query(format!( "INSERT INTO recovery_wakeup_request (requested_by, requested_at) \ - VALUES ({operator_id}, unixepoch('now') - 14*24*3600 - 1)" + VALUES ({}, unixepoch('now') - 14*24*3600 - 1)", + operator_id.to_raw() )) .execute(&mut conn) .await .unwrap(); } -fn make_vote_message(proposal_id: i32, approve: bool) -> Vec { +fn make_vote_message(proposal_id: ProposalId, approve: bool) -> Vec { let mut msg = Vec::with_capacity(9); - msg.extend_from_slice(&(proposal_id as i64).to_be_bytes()); + msg.extend_from_slice(&i64::from(proposal_id.to_raw()).to_be_bytes()); msg.push(u8::from(approve)); msg } @@ -128,13 +133,13 @@ async fn create_proposal_returns_id() { .proposal_manager .ask(CreateProposal { kind: ProposalKind::ApproveSdkClient(approve_sdk_client::Settings { client_id: 42 }), - initiator_id: 1, + initiator_id: OperatorIdentityId::from_raw(1), ttl_secs: None, }) .await .unwrap(); - assert!(proposal_id > 0); + assert!(proposal_id.to_raw() > 0); } #[tokio::test] @@ -935,7 +940,7 @@ async fn key_rotation_requires_full_quorum() { .proposal_manager .ask(CreateProposal { kind: ProposalKind::ReplaceOperator(replace_operator::Settings { - old_operator_id: 1, + old_operator_id: OperatorIdentityId::from_raw(1), new_pubkey, }), initiator_id: op1, @@ -983,7 +988,7 @@ async fn recovery_vote_rejected_when_sleeping() { .proposal_manager .ask(CreateProposal { kind: ProposalKind::ReplaceOperator(replace_operator::Settings { - old_operator_id: 1, + old_operator_id: OperatorIdentityId::from_raw(1), new_pubkey, }), initiator_id: op_id, @@ -1137,7 +1142,7 @@ async fn recovery_operator_vote_contributes_to_replace_quorum() { .proposal_manager .ask(CreateProposal { kind: ProposalKind::ReplaceOperator(replace_operator::Settings { - old_operator_id: 1, + old_operator_id: OperatorIdentityId::from_raw(1), new_pubkey, }), initiator_id: op_id,