fix(operator): bind sign-transaction to operator-approved client set
This commit is contained in:
@@ -94,6 +94,13 @@ impl FlowCoordinator {
|
|||||||
self.client_ids.contains_key(&client_id)
|
self.client_ids.contains_key(&client_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the DB client_ids of all currently connected SDK clients.
|
||||||
|
/// Used by operator sessions on startup to seed their approved-client set.
|
||||||
|
#[message]
|
||||||
|
pub fn get_connected_client_ids(&self) -> Vec<i32> {
|
||||||
|
self.client_ids.keys().copied().collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[message(ctx)]
|
#[message(ctx)]
|
||||||
pub async fn request_client_approval(
|
pub async fn request_client_approval(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
actors::flow_coordinator::{IsClientConnected, client_connect_approval::ClientApprovalAnswer},
|
actors::flow_coordinator::{IsClientConnected, client_connect_approval::ClientApprovalAnswer},
|
||||||
actors::vault::VaultState,
|
actors::vault::VaultState,
|
||||||
db::models::{EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata},
|
db::{
|
||||||
|
models::{EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata},
|
||||||
|
schema::program_client,
|
||||||
|
},
|
||||||
evm::policies::{Grant, SpecificGrant},
|
evm::policies::{Grant, SpecificGrant},
|
||||||
};
|
};
|
||||||
use arbiter_crypto::authn;
|
use arbiter_crypto::authn;
|
||||||
@@ -144,6 +147,14 @@ impl OperatorSession {
|
|||||||
wallet_address: Address,
|
wallet_address: Address,
|
||||||
transaction: TxEip1559,
|
transaction: TxEip1559,
|
||||||
) -> Result<Signature, SignTransactionError> {
|
) -> Result<Signature, SignTransactionError> {
|
||||||
|
if !self.approved_client_ids.contains(&client_id) {
|
||||||
|
warn!(
|
||||||
|
client_id,
|
||||||
|
"operator attempted to sign for client not in its approved set"
|
||||||
|
);
|
||||||
|
return Err(SignTransactionError::ClientNotConnected);
|
||||||
|
}
|
||||||
|
|
||||||
let connected = self
|
let connected = self
|
||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
@@ -153,6 +164,7 @@ impl OperatorSession {
|
|||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if !connected {
|
if !connected {
|
||||||
|
self.approved_client_ids.remove(&client_id);
|
||||||
warn!(client_id, "operator attempted to sign for disconnected client");
|
warn!(client_id, "operator attempted to sign for disconnected client");
|
||||||
return Err(SignTransactionError::ClientNotConnected);
|
return Err(SignTransactionError::ClientNotConnected);
|
||||||
}
|
}
|
||||||
@@ -267,6 +279,30 @@ impl OperatorSession {
|
|||||||
|
|
||||||
ctx.actor_ref().unlink(&pending_approval.controller).await;
|
ctx.actor_ref().unlink(&pending_approval.controller).await;
|
||||||
|
|
||||||
|
if approved {
|
||||||
|
let pubkey_bytes = pending_approval.pubkey.to_bytes();
|
||||||
|
match self.props.db.get().await {
|
||||||
|
Ok(mut conn) => {
|
||||||
|
match program_client::table
|
||||||
|
.filter(program_client::public_key.eq(pubkey_bytes.as_slice()))
|
||||||
|
.select(program_client::id)
|
||||||
|
.first::<i32>(&mut conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(client_id) => {
|
||||||
|
self.approved_client_ids.insert(client_id);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "Failed to look up client_id for approved pubkey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "DB pool error after client approval");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::{OutOfBand, OperatorConnection};
|
use super::{OutOfBand, OperatorConnection};
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
flow_coordinator::client_connect_approval::ClientApprovalController,
|
flow_coordinator::{GetConnectedClientIds, client_connect_approval::ClientApprovalController},
|
||||||
operator_registry::ConnectOperator,
|
operator_registry::ConnectOperator,
|
||||||
},
|
},
|
||||||
peers::client::ClientProfile,
|
peers::client::ClientProfile,
|
||||||
@@ -10,7 +10,7 @@ use arbiter_crypto::authn;
|
|||||||
use arbiter_proto::transport::Sender;
|
use arbiter_proto::transport::Sender;
|
||||||
|
|
||||||
use kameo::{Actor, actor::ActorRef, messages};
|
use kameo::{Actor, actor::ActorRef, messages};
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::{HashMap, HashSet}};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
@@ -54,6 +54,10 @@ pub struct OperatorSession {
|
|||||||
sender: Box<dyn Sender<OutOfBand>>,
|
sender: Box<dyn Sender<OutOfBand>>,
|
||||||
|
|
||||||
pending_client_approvals: HashMap<Vec<u8>, PendingClientApproval>,
|
pending_client_approvals: HashMap<Vec<u8>, PendingClientApproval>,
|
||||||
|
/// DB client_ids this operator session is allowed to sign for.
|
||||||
|
/// Seeded from currently-connected clients on start, then updated as
|
||||||
|
/// approvals are granted or denied during the session lifetime.
|
||||||
|
approved_client_ids: HashSet<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
@@ -64,6 +68,7 @@ impl OperatorSession {
|
|||||||
props,
|
props,
|
||||||
sender,
|
sender,
|
||||||
pending_client_approvals: HashMap::default(),
|
pending_client_approvals: HashMap::default(),
|
||||||
|
approved_client_ids: HashSet::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +111,7 @@ impl Actor for OperatorSession {
|
|||||||
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
async fn on_start(args: Self::Args, this: ActorRef<Self>) -> Result<Self, Self::Error> {
|
async fn on_start(mut args: Self::Args, this: ActorRef<Self>) -> Result<Self, Self::Error> {
|
||||||
args.props
|
args.props
|
||||||
.actors
|
.actors
|
||||||
.operator_registry
|
.operator_registry
|
||||||
@@ -121,6 +126,16 @@ impl Actor for OperatorSession {
|
|||||||
);
|
);
|
||||||
Error::internal("Failed to register operator connection with operator registry")
|
Error::internal("Failed to register operator connection with operator registry")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Seed approved set with clients already connected when this session starts.
|
||||||
|
// New clients will be added via handle_new_client_approve as they are approved.
|
||||||
|
match args.props.actors.flow_coordinator.ask(GetConnectedClientIds {}).await {
|
||||||
|
Ok(ids) => args.approved_client_ids.extend(ids),
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "Failed to fetch connected client IDs on operator session start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user