refactor(proposal): use id newtypes across the governance path

This commit is contained in:
CleverWild
2026-08-27 13:42:14 +02:00
parent 8421a09d9f
commit 71081b6ee7
12 changed files with 127 additions and 84 deletions

View File

@@ -8,7 +8,8 @@ use crate::{
self, self,
models::{ models::{
NewProposal, NewProposalVote, NewRecoveryProposalVote, NewRecoveryWakeupRequest, NewProposal, NewProposalVote, NewRecoveryProposalVote, NewRecoveryWakeupRequest,
Proposal, ProposalStatus, SqliteTimestamp, OperatorIdentityId, Proposal, ProposalId, ProposalStatus, RecoveryOperatorIdentityId,
SqliteTimestamp,
}, },
proposal::{ProposalKind, ProposalKindTag, one_off_transaction, persistent_grant}, proposal::{ProposalKind, ProposalKindTag, one_off_transaction, persistent_grant},
schema, schema,
@@ -65,9 +66,9 @@ pub enum Error {
#[derive(Debug)] #[derive(Debug)]
pub struct ProposalSummary { pub struct ProposalSummary {
pub id: i32, pub id: ProposalId,
pub kind: ProposalKindTag, pub kind: ProposalKindTag,
pub initiator_id: i32, pub initiator_id: OperatorIdentityId,
pub expires_at: SqliteTimestamp, pub expires_at: SqliteTimestamp,
pub approve_count: i64, pub approve_count: i64,
pub reject_count: i64, pub reject_count: i64,
@@ -103,9 +104,9 @@ impl ProposalManager {
pub async fn create_proposal( pub async fn create_proposal(
&mut self, &mut self,
kind: ProposalKind, kind: ProposalKind,
initiator_id: i32, initiator_id: OperatorIdentityId,
ttl_secs: Option<u32>, ttl_secs: Option<u32>,
) -> Result<i32, Error> { ) -> Result<ProposalId, Error> {
let ttl = ttl_secs.unwrap_or(DEFAULT_TTL_SECS); let ttl = ttl_secs.unwrap_or(DEFAULT_TTL_SECS);
if ttl > MAX_TTL_SECS { if ttl > MAX_TTL_SECS {
return Err(Error::TtlTooLong); return Err(Error::TtlTooLong);
@@ -113,12 +114,12 @@ impl ProposalManager {
let expires_at = let expires_at =
SqliteTimestamp::from(Utc::now() + chrono::Duration::seconds(i64::from(ttl))); SqliteTimestamp::from(Utc::now() + chrono::Duration::seconds(i64::from(ttl)));
let id: i32 = self let id: ProposalId = self
.db .db
.get() .get()
.await? .await?
.transaction(async |conn| { .transaction(async |conn| {
let id: i32 = diesel::insert_into(schema::proposal::table) let id: ProposalId = diesel::insert_into(schema::proposal::table)
.values(&NewProposal { .values(&NewProposal {
kind: kind.discriminant(), kind: kind.discriminant(),
initiator_id, initiator_id,
@@ -136,7 +137,7 @@ impl ProposalManager {
} }
#[message] #[message]
pub async fn query_pending(&mut self, operator_id: i32) -> Vec<ProposalSummary> { pub async fn query_pending(&mut self, operator_id: OperatorIdentityId) -> Vec<ProposalSummary> {
#[expect( #[expect(
clippy::cast_possible_truncation, clippy::cast_possible_truncation,
clippy::as_conversions, clippy::as_conversions,
@@ -149,7 +150,7 @@ impl ProposalManager {
return vec![]; return vec![];
}; };
let voted_ids: Vec<i32> = schema::proposal_vote::table let voted_ids: Vec<ProposalId> = schema::proposal_vote::table
.filter(schema::proposal_vote::operator_id.eq(operator_id)) .filter(schema::proposal_vote::operator_id.eq(operator_id))
.select(schema::proposal_vote::proposal_id) .select(schema::proposal_vote::proposal_id)
.load(&mut conn) .load(&mut conn)
@@ -195,8 +196,8 @@ impl ProposalManager {
#[message] #[message]
pub async fn cast_vote( pub async fn cast_vote(
&mut self, &mut self,
proposal_id: i32, proposal_id: ProposalId,
operator_id: i32, operator_id: OperatorIdentityId,
approve: bool, approve: bool,
signature: Vec<u8>, signature: Vec<u8>,
) -> Result<VoteOutcome, Error> { ) -> Result<VoteOutcome, Error> {
@@ -249,7 +250,7 @@ impl ProposalManager {
// Canonical vote message: proposal_id (i64 big-endian) || approve (u8) // Canonical vote message: proposal_id (i64 big-endian) || approve (u8)
let mut vote_msg = Vec::with_capacity(9); 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)); vote_msg.push(u8::from(approve));
let auth_sig = authn::Signature::try_from(signature.as_slice()) 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. /// §3.6: Any ordinary operator may request recovery wake-up.
/// Fails if a wake-up is already pending or active. /// Fails if a wake-up is already pending or active.
#[message] #[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?; let mut conn = self.db.get().await?;
if Self::has_uncancelled_wakeup(&mut conn).await? { if Self::has_uncancelled_wakeup(&mut conn).await? {
return Err(Error::WakeupAlreadyPending); return Err(Error::WakeupAlreadyPending);
@@ -375,7 +379,10 @@ impl ProposalManager {
/// §3.6: Any ordinary operator may cancel a pending wake-up request. /// §3.6: Any ordinary operator may cancel a pending wake-up request.
/// Fails if there is no uncancelled request. /// Fails if there is no uncancelled request.
#[message] #[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 mut conn = self.db.get().await?;
let rows_updated = diesel::update(schema::recovery_wakeup_request::table) let rows_updated = diesel::update(schema::recovery_wakeup_request::table)
.filter(schema::recovery_wakeup_request::cancelled_at.is_null()) .filter(schema::recovery_wakeup_request::cancelled_at.is_null())
@@ -396,8 +403,8 @@ impl ProposalManager {
#[message] #[message]
pub async fn cast_recovery_vote( pub async fn cast_recovery_vote(
&mut self, &mut self,
proposal_id: i32, proposal_id: ProposalId,
recovery_operator_id: i32, recovery_operator_id: RecoveryOperatorIdentityId,
approve: bool, approve: bool,
signature: Vec<u8>, signature: Vec<u8>,
) -> Result<VoteOutcome, Error> { ) -> Result<VoteOutcome, Error> {
@@ -454,7 +461,7 @@ impl ProposalManager {
.map_err(|()| Error::InvalidSignature)?; .map_err(|()| Error::InvalidSignature)?;
let mut vote_msg = Vec::with_capacity(9); 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)); vote_msg.push(u8::from(approve));
let auth_sig = authn::Signature::try_from(signature.as_slice()) 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). /// removes their old Shamir share, then begins a coordinated re-key (§3.3).
async fn execute_replace_operator( async fn execute_replace_operator(
&self, &self,
old_operator_id: i32, old_operator_id: OperatorIdentityId,
new_pubkey: Vec<u8>, new_pubkey: Vec<u8>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?; let mut conn = self.db.get().await.map_err(Error::DatabaseConnection)?;
@@ -657,7 +664,7 @@ impl ProposalManager {
async fn execute_approve_one_off_transaction( async fn execute_approve_one_off_transaction(
&self, &self,
proposal_id: i32, proposal_id: ProposalId,
tx: one_off_transaction::Settings, tx: one_off_transaction::Settings,
) -> Result<(), Error> { ) -> Result<(), Error> {
use crate::actors::evm::ClientSignTransaction; use crate::actors::evm::ClientSignTransaction;

View File

@@ -143,6 +143,8 @@ pub mod types {
declare_id!(TlsHistoryId); declare_id!(TlsHistoryId);
declare_id!(EvmWalletId); declare_id!(EvmWalletId);
declare_id!(ClientId); declare_id!(ClientId);
declare_id!(ProposalId);
declare_id!(RecoveryOperatorIdentityId);
#[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow)] #[derive(Debug, Clone, PartialEq, Eq, AsExpression, FromSqlRow)]
#[diesel(sql_type = Text)] #[diesel(sql_type = Text)]
@@ -478,9 +480,9 @@ pub struct IntegrityEnvelope {
#[derive(Debug, Queryable, Selectable, Identifiable)] #[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = schema::proposal, check_for_backend(Sqlite))] #[diesel(table_name = schema::proposal, check_for_backend(Sqlite))]
pub struct Proposal { pub struct Proposal {
pub id: i32, pub id: ProposalId,
pub kind: ProposalKindTag, pub kind: ProposalKindTag,
pub initiator_id: i32, pub initiator_id: OperatorIdentityId,
pub created_at: SqliteTimestamp, pub created_at: SqliteTimestamp,
pub expires_at: SqliteTimestamp, pub expires_at: SqliteTimestamp,
pub status: ProposalStatus, pub status: ProposalStatus,
@@ -490,7 +492,7 @@ pub struct Proposal {
#[diesel(table_name = schema::proposal, check_for_backend(Sqlite))] #[diesel(table_name = schema::proposal, check_for_backend(Sqlite))]
pub struct NewProposal { pub struct NewProposal {
pub kind: ProposalKindTag, pub kind: ProposalKindTag,
pub initiator_id: i32, pub initiator_id: OperatorIdentityId,
// status defaults to 'pending' at the DB layer // status defaults to 'pending' at the DB layer
pub expires_at: SqliteTimestamp, pub expires_at: SqliteTimestamp,
} }
@@ -499,8 +501,8 @@ pub struct NewProposal {
#[diesel(table_name = schema::proposal_vote, check_for_backend(Sqlite))] #[diesel(table_name = schema::proposal_vote, check_for_backend(Sqlite))]
pub struct ProposalVote { pub struct ProposalVote {
pub id: i32, pub id: i32,
pub proposal_id: i32, pub proposal_id: ProposalId,
pub operator_id: i32, pub operator_id: OperatorIdentityId,
pub approve: bool, pub approve: bool,
pub signature: Vec<u8>, pub signature: Vec<u8>,
pub voted_at: SqliteTimestamp, pub voted_at: SqliteTimestamp,
@@ -509,8 +511,8 @@ pub struct ProposalVote {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = schema::proposal_vote, check_for_backend(Sqlite))] #[diesel(table_name = schema::proposal_vote, check_for_backend(Sqlite))]
pub struct NewProposalVote { pub struct NewProposalVote {
pub proposal_id: i32, pub proposal_id: ProposalId,
pub operator_id: i32, pub operator_id: OperatorIdentityId,
pub approve: bool, pub approve: bool,
pub signature: Vec<u8>, pub signature: Vec<u8>,
} }
@@ -518,8 +520,8 @@ pub struct NewProposalVote {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = schema::recovery_proposal_vote, check_for_backend(Sqlite))] #[diesel(table_name = schema::recovery_proposal_vote, check_for_backend(Sqlite))]
pub struct NewRecoveryProposalVote { pub struct NewRecoveryProposalVote {
pub proposal_id: i32, pub proposal_id: ProposalId,
pub recovery_operator_id: i32, pub recovery_operator_id: RecoveryOperatorIdentityId,
pub approve: bool, pub approve: bool,
pub signature: Vec<u8>, pub signature: Vec<u8>,
} }
@@ -527,5 +529,5 @@ pub struct NewRecoveryProposalVote {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = schema::recovery_wakeup_request, check_for_backend(Sqlite))] #[diesel(table_name = schema::recovery_wakeup_request, check_for_backend(Sqlite))]
pub struct NewRecoveryWakeupRequest { pub struct NewRecoveryWakeupRequest {
pub requested_by: i32, pub requested_by: OperatorIdentityId,
} }

View File

@@ -1,7 +1,9 @@
//! Approving an SDK client so it may authenticate against the vault. //! Approving an SDK client so it may authenticate against the vault.
use super::{Proposal, ProposalKindTag}; 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::{ use diesel::{
ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable,
SelectableHelper as _, sqlite::Sqlite, SelectableHelper as _, sqlite::Sqlite,
@@ -22,7 +24,7 @@ impl Proposal for ApproveSdkClient {
type Settings = Settings; type Settings = Settings;
async fn insert( async fn insert(
proposal_id: i32, proposal_id: ProposalId,
settings: &Self::Settings, settings: &Self::Settings,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {
@@ -33,7 +35,10 @@ impl Proposal for ApproveSdkClient {
.map(drop) .map(drop)
} }
async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult<Self::Settings> { async fn load(
proposal_id: ProposalId,
conn: &mut DatabaseConnection,
) -> QueryResult<Self::Settings> {
table::table table::table
.find(proposal_id) .find(proposal_id)
.select(Settings::as_select()) .select(Settings::as_select())

View File

@@ -1,7 +1,9 @@
//! Granting an SDK client visibility of a wallet. //! Granting an SDK client visibility of a wallet.
use super::{Proposal, ProposalKindTag}; 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::{ use diesel::{
ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable,
SelectableHelper as _, sqlite::Sqlite, SelectableHelper as _, sqlite::Sqlite,
@@ -23,7 +25,7 @@ impl Proposal for GrantWalletAccess {
type Settings = Settings; type Settings = Settings;
async fn insert( async fn insert(
proposal_id: i32, proposal_id: ProposalId,
settings: &Self::Settings, settings: &Self::Settings,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {
@@ -34,7 +36,10 @@ impl Proposal for GrantWalletAccess {
.map(drop) .map(drop)
} }
async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult<Self::Settings> { async fn load(
proposal_id: ProposalId,
conn: &mut DatabaseConnection,
) -> QueryResult<Self::Settings> {
table::table table::table
.find(proposal_id) .find(proposal_id)
.select(Settings::as_select()) .select(Settings::as_select())

View File

@@ -6,7 +6,7 @@
//! new kind is a new module plus one arm in each dispatcher -- nothing else in the //! new kind is a new module plus one arm in each dispatcher -- nothing else in the
//! codebase has to learn about it. //! codebase has to learn about it.
use crate::db::DatabaseConnection; use crate::db::{DatabaseConnection, models::ProposalId};
use diesel::{ use diesel::{
QueryResult, QueryResult,
backend::Backend, backend::Backend,
@@ -42,7 +42,7 @@ pub trait Proposal: Sized {
/// Writes the child row carrying `settings`. /// Writes the child row carrying `settings`.
fn insert( fn insert(
proposal_id: i32, proposal_id: ProposalId,
settings: &Self::Settings, settings: &Self::Settings,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> impl Future<Output = QueryResult<()>> + Send; ) -> impl Future<Output = QueryResult<()>> + Send;
@@ -50,7 +50,7 @@ pub trait Proposal: Sized {
/// Reads the child row back. A missing row surfaces as [`diesel::result::Error::NotFound`], /// Reads the child row back. A missing row surfaces as [`diesel::result::Error::NotFound`],
/// which is what a proposal without its parameters is. /// which is what a proposal without its parameters is.
fn load( fn load(
proposal_id: i32, proposal_id: ProposalId,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> impl Future<Output = QueryResult<Self::Settings>> + Send; ) -> impl Future<Output = QueryResult<Self::Settings>> + Send;
} }
@@ -126,7 +126,7 @@ const _: () = {
/// to the implementation that owns the table. /// to the implementation that owns the table.
pub async fn insert_kind( pub async fn insert_kind(
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
proposal_id: i32, proposal_id: ProposalId,
kind: &ProposalKind, kind: &ProposalKind,
) -> QueryResult<()> { ) -> QueryResult<()> {
match kind { 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. /// Reads the parameters back for a `proposal.kind` that is only known at runtime.
pub async fn load_kind( pub async fn load_kind(
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
proposal_id: i32, proposal_id: ProposalId,
tag: ProposalKindTag, tag: ProposalKindTag,
) -> QueryResult<ProposalKind> { ) -> QueryResult<ProposalKind> {
Ok(match tag { Ok(match tag {

View File

@@ -3,6 +3,7 @@
use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed}; use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed};
use crate::db::{ use crate::db::{
DatabaseConnection, DatabaseConnection,
models::ProposalId,
schema::{proposal_one_off_transaction, proposal_one_off_transaction_result}, schema::{proposal_one_off_transaction, proposal_one_off_transaction_result},
}; };
use diesel::{ use diesel::{
@@ -28,7 +29,7 @@ pub struct Settings {
#[derive(Debug, Queryable, Selectable, Insertable)] #[derive(Debug, Queryable, Selectable, Insertable)]
#[diesel(table_name = proposal_one_off_transaction, check_for_backend(Sqlite))] #[diesel(table_name = proposal_one_off_transaction, check_for_backend(Sqlite))]
struct Row { struct Row {
proposal_id: i32, proposal_id: ProposalId,
client_id: i32, client_id: i32,
wallet_address: Vec<u8>, wallet_address: Vec<u8>,
chain_id: i64, chain_id: i64,
@@ -42,7 +43,7 @@ struct Row {
} }
impl Row { impl Row {
fn new(proposal_id: i32, settings: &Settings) -> QueryResult<Self> { fn new(proposal_id: ProposalId, settings: &Settings) -> QueryResult<Self> {
Ok(Self { Ok(Self {
proposal_id, proposal_id,
client_id: settings.client_id, client_id: settings.client_id,
@@ -82,7 +83,7 @@ impl Proposal for OneOffTransaction {
type Settings = Settings; type Settings = Settings;
async fn insert( async fn insert(
proposal_id: i32, proposal_id: ProposalId,
settings: &Self::Settings, settings: &Self::Settings,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {
@@ -93,7 +94,10 @@ impl Proposal for OneOffTransaction {
.map(drop) .map(drop)
} }
async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult<Self::Settings> { async fn load(
proposal_id: ProposalId,
conn: &mut DatabaseConnection,
) -> QueryResult<Self::Settings> {
let row: Row = proposal_one_off_transaction::table let row: Row = proposal_one_off_transaction::table
.find(proposal_id) .find(proposal_id)
.select(Row::as_select()) .select(Row::as_select())
@@ -108,7 +112,7 @@ impl Proposal for OneOffTransaction {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = proposal_one_off_transaction_result, check_for_backend(Sqlite))] #[diesel(table_name = proposal_one_off_transaction_result, check_for_backend(Sqlite))]
struct SignatureRow { struct SignatureRow {
proposal_id: i32, proposal_id: ProposalId,
r: Vec<u8>, r: Vec<u8>,
s: Vec<u8>, s: Vec<u8>,
y_parity: i32, y_parity: i32,
@@ -117,7 +121,7 @@ struct SignatureRow {
/// Records the signature produced for an approved transaction, by component, so what /// Records the signature produced for an approved transaction, by component, so what
/// came back is as readable as what was signed. /// came back is as readable as what was signed.
pub async fn store_signature( pub async fn store_signature(
proposal_id: i32, proposal_id: ProposalId,
signature: &alloy::signers::Signature, signature: &alloy::signers::Signature,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {

View File

@@ -2,6 +2,7 @@
use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed}; use super::{Proposal, ProposalKindTag, as_i64, as_u64, fixed};
use crate::db::{ use crate::db::{
DatabaseConnection, DatabaseConnection,
models::ProposalId,
schema::{ schema::{
proposal_persistent_grant, proposal_persistent_grant_ether, proposal_persistent_grant, proposal_persistent_grant_ether,
proposal_persistent_grant_ether_target, proposal_persistent_grant_token, proposal_persistent_grant_ether_target, proposal_persistent_grant_token,
@@ -55,7 +56,7 @@ pub enum Specific {
#[derive(Debug, Queryable, Selectable, Insertable)] #[derive(Debug, Queryable, Selectable, Insertable)]
#[diesel(table_name = proposal_persistent_grant, check_for_backend(Sqlite))] #[diesel(table_name = proposal_persistent_grant, check_for_backend(Sqlite))]
struct BaseRow { struct BaseRow {
proposal_id: i32, proposal_id: ProposalId,
wallet_access_id: i32, wallet_access_id: i32,
chain_id: i64, chain_id: i64,
valid_from: Option<i64>, valid_from: Option<i64>,
@@ -69,7 +70,7 @@ struct BaseRow {
#[derive(Debug, Queryable, Selectable, Insertable)] #[derive(Debug, Queryable, Selectable, Insertable)]
#[diesel(table_name = proposal_persistent_grant_ether, check_for_backend(Sqlite))] #[diesel(table_name = proposal_persistent_grant_ether, check_for_backend(Sqlite))]
struct EtherRow { struct EtherRow {
proposal_id: i32, proposal_id: ProposalId,
window_secs: i64, window_secs: i64,
max_volume: Vec<u8>, max_volume: Vec<u8>,
} }
@@ -77,14 +78,14 @@ struct EtherRow {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = proposal_persistent_grant_ether_target, check_for_backend(Sqlite))] #[diesel(table_name = proposal_persistent_grant_ether_target, check_for_backend(Sqlite))]
struct NewEtherTarget { struct NewEtherTarget {
proposal_id: i32, proposal_id: ProposalId,
address: Vec<u8>, address: Vec<u8>,
} }
#[derive(Debug, Queryable, Selectable, Insertable)] #[derive(Debug, Queryable, Selectable, Insertable)]
#[diesel(table_name = proposal_persistent_grant_token, check_for_backend(Sqlite))] #[diesel(table_name = proposal_persistent_grant_token, check_for_backend(Sqlite))]
struct TokenRow { struct TokenRow {
proposal_id: i32, proposal_id: ProposalId,
token_contract: Vec<u8>, token_contract: Vec<u8>,
receiver: Option<Vec<u8>>, receiver: Option<Vec<u8>>,
} }
@@ -92,13 +93,13 @@ struct TokenRow {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = proposal_persistent_grant_token_limit, check_for_backend(Sqlite))] #[diesel(table_name = proposal_persistent_grant_token_limit, check_for_backend(Sqlite))]
struct NewTokenLimit { struct NewTokenLimit {
proposal_id: i32, proposal_id: ProposalId,
window_secs: i64, window_secs: i64,
max_volume: Vec<u8>, max_volume: Vec<u8>,
} }
impl BaseRow { impl BaseRow {
fn new(proposal_id: i32, settings: &Settings) -> QueryResult<Self> { fn new(proposal_id: ProposalId, settings: &Settings) -> QueryResult<Self> {
Ok(Self { Ok(Self {
proposal_id, proposal_id,
wallet_access_id: settings.wallet_access_id, wallet_access_id: settings.wallet_access_id,
@@ -141,7 +142,7 @@ impl Proposal for PersistentGrant {
type Settings = Settings; type Settings = Settings;
async fn insert( async fn insert(
proposal_id: i32, proposal_id: ProposalId,
settings: &Self::Settings, settings: &Self::Settings,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {
@@ -201,7 +202,10 @@ impl Proposal for PersistentGrant {
Ok(()) Ok(())
} }
async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult<Self::Settings> { async fn load(
proposal_id: ProposalId,
conn: &mut DatabaseConnection,
) -> QueryResult<Self::Settings> {
let base: BaseRow = proposal_persistent_grant::table let base: BaseRow = proposal_persistent_grant::table
.find(proposal_id) .find(proposal_id)
.select(BaseRow::as_select()) .select(BaseRow::as_select())

View File

@@ -1,7 +1,11 @@
//! Replacing an operator's key, which also triggers a Shamir re-key (§3.3). //! Replacing an operator's key, which also triggers a Shamir re-key (§3.3).
use super::{Proposal, ProposalKindTag}; 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::{ use diesel::{
ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable, ExpressionMethods as _, Insertable, QueryDsl as _, QueryResult, Queryable, Selectable,
SelectableHelper as _, sqlite::Sqlite, SelectableHelper as _, sqlite::Sqlite,
@@ -11,7 +15,7 @@ use diesel_async::RunQueryDsl as _;
#[derive(Debug, Clone, PartialEq, Eq, Queryable, Selectable, Insertable)] #[derive(Debug, Clone, PartialEq, Eq, Queryable, Selectable, Insertable)]
#[diesel(table_name = table, check_for_backend(Sqlite))] #[diesel(table_name = table, check_for_backend(Sqlite))]
pub struct Settings { pub struct Settings {
pub old_operator_id: i32, pub old_operator_id: OperatorIdentityId,
pub new_pubkey: Vec<u8>, pub new_pubkey: Vec<u8>,
} }
@@ -23,7 +27,7 @@ impl Proposal for ReplaceOperator {
type Settings = Settings; type Settings = Settings;
async fn insert( async fn insert(
proposal_id: i32, proposal_id: ProposalId,
settings: &Self::Settings, settings: &Self::Settings,
conn: &mut DatabaseConnection, conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {
@@ -34,7 +38,10 @@ impl Proposal for ReplaceOperator {
.map(drop) .map(drop)
} }
async fn load(proposal_id: i32, conn: &mut DatabaseConnection) -> QueryResult<Self::Settings> { async fn load(
proposal_id: ProposalId,
conn: &mut DatabaseConnection,
) -> QueryResult<Self::Settings> {
table::table table::table
.find(proposal_id) .find(proposal_id)
.select(Settings::as_select()) .select(Settings::as_select())

View File

@@ -1,7 +1,7 @@
//! A Shamir re-key over the current operator set (§3.3). //! A Shamir re-key over the current operator set (§3.3).
use super::{Proposal, ProposalKindTag}; use super::{Proposal, ProposalKindTag};
use crate::db::DatabaseConnection; use crate::db::{DatabaseConnection, models::ProposalId};
use diesel::QueryResult; use diesel::QueryResult;
pub struct TriggerRekey; pub struct TriggerRekey;
@@ -12,7 +12,7 @@ impl Proposal for TriggerRekey {
type Settings = (); type Settings = ();
async fn insert( async fn insert(
_proposal_id: i32, _proposal_id: ProposalId,
_settings: &Self::Settings, _settings: &Self::Settings,
_conn: &mut DatabaseConnection, _conn: &mut DatabaseConnection,
) -> QueryResult<()> { ) -> QueryResult<()> {
@@ -20,7 +20,7 @@ impl Proposal for TriggerRekey {
} }
async fn load( async fn load(
_proposal_id: i32, _proposal_id: ProposalId,
_conn: &mut DatabaseConnection, _conn: &mut DatabaseConnection,
) -> QueryResult<Self::Settings> { ) -> QueryResult<Self::Settings> {
Ok(()) Ok(())

View File

@@ -1,5 +1,6 @@
use crate::{ use crate::{
actors::proposal_manager::{Error as ProposalError, VoteOutcome}, actors::proposal_manager::{Error as ProposalError, VoteOutcome},
db::models::{OperatorIdentityId, ProposalId},
db::proposal::{ db::proposal::{
ProposalKind, approve_sdk_client, grant_wallet_access, one_off_transaction, ProposalKind, approve_sdk_client, grant_wallet_access, one_off_transaction,
persistent_grant, replace_operator, persistent_grant, replace_operator,
@@ -62,7 +63,7 @@ async fn handle_create(
} }
Some(ProtoKind::ReplaceOperator(p)) => { Some(ProtoKind::ReplaceOperator(p)) => {
ProposalKind::ReplaceOperator(replace_operator::Settings { 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, new_pubkey: p.new_pubkey,
}) })
} }
@@ -87,7 +88,9 @@ async fn handle_create(
})?; })?;
Ok(Some(wrap(GovResponsePayload::Created( 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<Option<OperatorResponsePayload>, Status> { ) -> Result<Option<OperatorResponsePayload>, Status> {
let result = actor let result = actor
.ask(HandleCastVote { .ask(HandleCastVote {
proposal_id: req.proposal_id, proposal_id: ProposalId::from_raw(req.proposal_id),
approve: req.approve, approve: req.approve,
signature: req.signature, signature: req.signature,
}) })
@@ -235,9 +238,9 @@ async fn handle_query(
let proposals = summaries let proposals = summaries
.into_iter() .into_iter()
.map(|s| proto_gov::ProposalSummary { .map(|s| proto_gov::ProposalSummary {
id: s.id, id: s.id.to_raw(),
kind: <&'static str>::from(s.kind).to_owned(), 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(), expires_at: s.expires_at.0.timestamp(),
approve_count: s.approve_count, approve_count: s.approve_count,
reject_count: s.reject_count, reject_count: s.reject_count,

View File

@@ -1,4 +1,5 @@
use super::{Error, OperatorSession}; use super::{Error, OperatorSession};
use crate::db::models::{OperatorIdentityId, ProposalId};
use crate::{ use crate::{
actors::{ actors::{
evm::{ evm::{
@@ -287,9 +288,9 @@ impl OperatorSession {
&mut self, &mut self,
kind: crate::db::proposal::ProposalKind, kind: crate::db::proposal::ProposalKind,
ttl_secs: Option<u32>, ttl_secs: Option<u32>,
) -> Result<i32, Error> { ) -> Result<ProposalId, Error> {
use crate::actors::proposal_manager::CreateProposal; use crate::actors::proposal_manager::CreateProposal;
let initiator_id = self.credentials.id; let initiator_id = OperatorIdentityId::from_raw(self.credentials.id);
self.props self.props
.actors .actors
.proposal_manager .proposal_manager
@@ -304,12 +305,12 @@ impl OperatorSession {
#[message] #[message]
pub(crate) async fn handle_cast_vote( pub(crate) async fn handle_cast_vote(
&mut self, &mut self,
proposal_id: i32, proposal_id: ProposalId,
approve: bool, approve: bool,
signature: Vec<u8>, signature: Vec<u8>,
) -> Result<crate::actors::proposal_manager::VoteOutcome, crate::actors::proposal_manager::Error> { ) -> Result<crate::actors::proposal_manager::VoteOutcome, crate::actors::proposal_manager::Error> {
use crate::actors::proposal_manager::CastVote; use crate::actors::proposal_manager::CastVote;
let operator_id = self.credentials.id; let operator_id = OperatorIdentityId::from_raw(self.credentials.id);
self.props self.props
.actors .actors
.proposal_manager .proposal_manager
@@ -326,7 +327,7 @@ impl OperatorSession {
&mut self, &mut self,
) -> Vec<crate::actors::proposal_manager::ProposalSummary> { ) -> Vec<crate::actors::proposal_manager::ProposalSummary> {
use crate::actors::proposal_manager::QueryPending; use crate::actors::proposal_manager::QueryPending;
let operator_id = self.credentials.id; let operator_id = OperatorIdentityId::from_raw(self.credentials.id);
self.props self.props
.actors .actors
.proposal_manager .proposal_manager

View File

@@ -10,6 +10,7 @@ use arbiter_server::{
crypto::KeyCell, crypto::KeyCell,
db::{ db::{
self, self,
models::{OperatorIdentityId, ProposalId, RecoveryOperatorIdentityId},
proposal::{ proposal::{
ProposalKind, approve_sdk_client, grant_wallet_access, one_off_transaction, ProposalKind, approve_sdk_client, grant_wallet_access, one_off_transaction,
persistent_grant, replace_operator, persistent_grant, replace_operator,
@@ -24,41 +25,45 @@ use arbiter_server::db::schema::{
use diesel::{ExpressionMethods, QueryDsl, insert_into}; use diesel::{ExpressionMethods, QueryDsl, insert_into};
use diesel_async::RunQueryDsl; 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(); let mut conn = db.get().await.unwrap();
insert_into(operator_identity::table) insert_into(operator_identity::table)
.values(operator_identity::public_key.eq(pubkey.to_bytes())) .values(operator_identity::public_key.eq(pubkey.to_bytes()))
.returning(operator_identity::id) .returning(operator_identity::id)
.get_result::<i32>(&mut conn) .get_result::<OperatorIdentityId>(&mut conn)
.await .await
.unwrap() .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(); let mut conn = db.get().await.unwrap();
insert_into(recovery_operator_identity::table) insert_into(recovery_operator_identity::table)
.values(recovery_operator_identity::public_key.eq(pubkey.to_bytes())) .values(recovery_operator_identity::public_key.eq(pubkey.to_bytes()))
.returning(recovery_operator_identity::id) .returning(recovery_operator_identity::id)
.get_result::<i32>(&mut conn) .get_result::<RecoveryOperatorIdentityId>(&mut conn)
.await .await
.unwrap() .unwrap()
} }
/// Backdates a wakeup request so it appears to have passed the 14-day window. /// 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(); let mut conn = db.get().await.unwrap();
diesel::sql_query(format!( diesel::sql_query(format!(
"INSERT INTO recovery_wakeup_request (requested_by, requested_at) \ "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) .execute(&mut conn)
.await .await
.unwrap(); .unwrap();
} }
fn make_vote_message(proposal_id: i32, approve: bool) -> Vec<u8> { fn make_vote_message(proposal_id: ProposalId, approve: bool) -> Vec<u8> {
let mut msg = Vec::with_capacity(9); 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.push(u8::from(approve));
msg msg
} }
@@ -128,13 +133,13 @@ async fn create_proposal_returns_id() {
.proposal_manager .proposal_manager
.ask(CreateProposal { .ask(CreateProposal {
kind: ProposalKind::ApproveSdkClient(approve_sdk_client::Settings { client_id: 42 }), kind: ProposalKind::ApproveSdkClient(approve_sdk_client::Settings { client_id: 42 }),
initiator_id: 1, initiator_id: OperatorIdentityId::from_raw(1),
ttl_secs: None, ttl_secs: None,
}) })
.await .await
.unwrap(); .unwrap();
assert!(proposal_id > 0); assert!(proposal_id.to_raw() > 0);
} }
#[tokio::test] #[tokio::test]
@@ -935,7 +940,7 @@ async fn key_rotation_requires_full_quorum() {
.proposal_manager .proposal_manager
.ask(CreateProposal { .ask(CreateProposal {
kind: ProposalKind::ReplaceOperator(replace_operator::Settings { kind: ProposalKind::ReplaceOperator(replace_operator::Settings {
old_operator_id: 1, old_operator_id: OperatorIdentityId::from_raw(1),
new_pubkey, new_pubkey,
}), }),
initiator_id: op1, initiator_id: op1,
@@ -983,7 +988,7 @@ async fn recovery_vote_rejected_when_sleeping() {
.proposal_manager .proposal_manager
.ask(CreateProposal { .ask(CreateProposal {
kind: ProposalKind::ReplaceOperator(replace_operator::Settings { kind: ProposalKind::ReplaceOperator(replace_operator::Settings {
old_operator_id: 1, old_operator_id: OperatorIdentityId::from_raw(1),
new_pubkey, new_pubkey,
}), }),
initiator_id: op_id, initiator_id: op_id,
@@ -1137,7 +1142,7 @@ async fn recovery_operator_vote_contributes_to_replace_quorum() {
.proposal_manager .proposal_manager
.ask(CreateProposal { .ask(CreateProposal {
kind: ProposalKind::ReplaceOperator(replace_operator::Settings { kind: ProposalKind::ReplaceOperator(replace_operator::Settings {
old_operator_id: 1, old_operator_id: OperatorIdentityId::from_raw(1),
new_pubkey, new_pubkey,
}), }),
initiator_id: op_id, initiator_id: op_id,