feat(server): initial EVM functionality impl
This commit is contained in:
93
server/crates/arbiter-server/src/actors/evm/mod.rs
Normal file
93
server/crates/arbiter-server/src/actors/evm/mod.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use alloy::primitives::Address;
|
||||
use diesel::{QueryDsl, SelectableHelper as _, dsl::insert_into};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use kameo::{Actor, actor::ActorRef, messages};
|
||||
use memsafe::MemSafe;
|
||||
use rand::{SeedableRng, rng, rngs::StdRng};
|
||||
|
||||
use crate::{
|
||||
actors::keyholder::{CreateNew, KeyHolder},
|
||||
db::{self, DatabasePool, models, schema},
|
||||
};
|
||||
|
||||
pub use crate::evm::safe_signer;
|
||||
|
||||
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||
pub enum Error {
|
||||
#[error("Keyholder error: {0}")]
|
||||
#[diagnostic(code(arbiter::evm::keyholder))]
|
||||
Keyholder(#[from] crate::actors::keyholder::Error),
|
||||
|
||||
#[error("Keyholder mailbox error")]
|
||||
#[diagnostic(code(arbiter::evm::keyholder_send))]
|
||||
KeyholderSend,
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
#[diagnostic(code(arbiter::evm::database))]
|
||||
Database(#[from] diesel::result::Error),
|
||||
|
||||
#[error("Database pool error: {0}")]
|
||||
#[diagnostic(code(arbiter::evm::database_pool))]
|
||||
DatabasePool(#[from] db::PoolError),
|
||||
}
|
||||
|
||||
#[derive(Actor)]
|
||||
pub struct EvmActor {
|
||||
pub keyholder: ActorRef<KeyHolder>,
|
||||
pub db: DatabasePool,
|
||||
pub rng: StdRng,
|
||||
}
|
||||
|
||||
impl EvmActor {
|
||||
pub fn new(keyholder: ActorRef<KeyHolder>, db: DatabasePool) -> Self {
|
||||
// is it safe to seed rng from system once?
|
||||
// todo: audit
|
||||
let rng = StdRng::from_rng(&mut rng());
|
||||
Self { keyholder, db, rng }
|
||||
}
|
||||
}
|
||||
|
||||
#[messages]
|
||||
impl EvmActor {
|
||||
#[message]
|
||||
pub async fn generate(&mut self) -> Result<Address, Error> {
|
||||
let (mut key_cell, address) = safe_signer::generate(&mut self.rng);
|
||||
|
||||
// Move raw key bytes into a Vec<u8> MemSafe for KeyHolder
|
||||
let plaintext = {
|
||||
let reader = key_cell.read().expect("MemSafe read");
|
||||
MemSafe::new(reader.to_vec()).expect("MemSafe allocation")
|
||||
};
|
||||
|
||||
let aead_id: i32 = self
|
||||
.keyholder
|
||||
.ask(CreateNew { plaintext })
|
||||
.await
|
||||
.map_err(|_| Error::KeyholderSend)?;
|
||||
|
||||
let mut conn = self.db.get().await?;
|
||||
insert_into(schema::evm_wallet::table)
|
||||
.values(&models::NewEvmWallet {
|
||||
address: address.as_slice().to_vec(),
|
||||
aead_encrypted_id: aead_id,
|
||||
})
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub async fn list_wallets(&self) -> Result<Vec<Address>, Error> {
|
||||
let mut conn = self.db.get().await?;
|
||||
let rows: Vec<models::EvmWallet> = schema::evm_wallet::table
|
||||
.select(models::EvmWallet::as_select())
|
||||
.load(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|w| Address::from_slice(&w.address))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,16 @@ use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
actors::{bootstrap::Bootstrapper, keyholder::KeyHolder, router::MessageRouter},
|
||||
actors::{bootstrap::Bootstrapper, evm::EvmActor, keyholder::KeyHolder, router::MessageRouter},
|
||||
db,
|
||||
};
|
||||
|
||||
pub mod bootstrap;
|
||||
pub mod router;
|
||||
pub mod keyholder;
|
||||
pub mod user_agent;
|
||||
pub mod client;
|
||||
mod evm;
|
||||
pub mod keyholder;
|
||||
pub mod router;
|
||||
pub mod user_agent;
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
pub enum SpawnError {
|
||||
@@ -30,13 +31,16 @@ pub struct GlobalActors {
|
||||
pub key_holder: ActorRef<KeyHolder>,
|
||||
pub bootstrapper: ActorRef<Bootstrapper>,
|
||||
pub router: ActorRef<MessageRouter>,
|
||||
pub evm: ActorRef<EvmActor>,
|
||||
}
|
||||
|
||||
impl GlobalActors {
|
||||
pub async fn spawn(db: db::DatabasePool) -> Result<Self, SpawnError> {
|
||||
let key_holder = KeyHolder::spawn(KeyHolder::new(db.clone()).await?);
|
||||
Ok(Self {
|
||||
bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?),
|
||||
key_holder: KeyHolder::spawn(KeyHolder::new(db.clone()).await?),
|
||||
evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)),
|
||||
key_holder,
|
||||
router: MessageRouter::spawn(MessageRouter::default()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
use std::{ops::DerefMut, sync::Mutex};
|
||||
|
||||
use arbiter_proto::proto::user_agent::{
|
||||
UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest,
|
||||
UserAgentResponse, user_agent_request::Payload as UserAgentRequestPayload,
|
||||
user_agent_response::Payload as UserAgentResponsePayload,
|
||||
use arbiter_proto::proto::{
|
||||
evm as evm_proto,
|
||||
user_agent::{
|
||||
UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest,
|
||||
UserAgentResponse, user_agent_request::Payload as UserAgentRequestPayload,
|
||||
user_agent_response::Payload as UserAgentResponsePayload,
|
||||
},
|
||||
};
|
||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||
use ed25519_dalek::VerifyingKey;
|
||||
use kameo::{Actor, error::SendError};
|
||||
use kameo::{
|
||||
Actor,
|
||||
error::SendError,
|
||||
};
|
||||
use memsafe::MemSafe;
|
||||
use tokio::select;
|
||||
use tracing::{error, info};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
use crate::actors::{
|
||||
evm::{Generate, ListWallets},
|
||||
keyholder::{self, TryUnseal},
|
||||
router::RegisterUserAgent,
|
||||
user_agent::{UserAgentConnection, UserAgentError},
|
||||
@@ -58,6 +65,8 @@ impl UserAgentSession {
|
||||
UserAgentRequestPayload::UnsealEncryptedKey(unseal_encrypted_key) => {
|
||||
self.handle_unseal_encrypted_key(unseal_encrypted_key).await
|
||||
}
|
||||
UserAgentRequestPayload::EvmWalletCreate(_) => self.handle_evm_wallet_create().await,
|
||||
UserAgentRequestPayload::EvmWalletList(_) => self.handle_evm_wallet_list().await,
|
||||
_ => Err(UserAgentError::UnexpectedRequestPayload),
|
||||
}
|
||||
}
|
||||
@@ -178,6 +187,64 @@ impl UserAgentSession {
|
||||
}
|
||||
}
|
||||
|
||||
impl UserAgentSession {
|
||||
async fn handle_evm_wallet_create(&mut self) -> Output {
|
||||
use evm_proto::wallet_create_response::Result as CreateResult;
|
||||
|
||||
let result = match self.props.actors.evm.ask(Generate {}).await {
|
||||
Ok(address) => CreateResult::Wallet(evm_proto::WalletEntry {
|
||||
address: address.as_slice().to_vec(),
|
||||
}),
|
||||
Err(err) => CreateResult::Error(map_evm_error("wallet create", err).into()),
|
||||
};
|
||||
|
||||
Ok(response(UserAgentResponsePayload::EvmWalletCreate(
|
||||
evm_proto::WalletCreateResponse {
|
||||
result: Some(result),
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
async fn handle_evm_wallet_list(&mut self) -> Output {
|
||||
use evm_proto::wallet_list_response::Result as ListResult;
|
||||
|
||||
let result = match self.props.actors.evm.ask(ListWallets {}).await {
|
||||
Ok(wallets) => ListResult::Wallets(evm_proto::WalletList {
|
||||
wallets: wallets
|
||||
.into_iter()
|
||||
.map(|addr| evm_proto::WalletEntry {
|
||||
address: addr.as_slice().to_vec(),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
Err(err) => ListResult::Error(map_evm_error("wallet list", err).into()),
|
||||
};
|
||||
|
||||
Ok(response(UserAgentResponsePayload::EvmWalletList(
|
||||
evm_proto::WalletListResponse {
|
||||
result: Some(result),
|
||||
},
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn map_evm_error<M>(op: &str, err: SendError<M, crate::actors::evm::Error>) -> evm_proto::EvmError {
|
||||
use crate::actors::{evm::Error as EvmError, keyholder::Error as KhError};
|
||||
match err {
|
||||
SendError::HandlerError(EvmError::Keyholder(KhError::NotBootstrapped)) => {
|
||||
evm_proto::EvmError::VaultSealed
|
||||
}
|
||||
SendError::HandlerError(err) => {
|
||||
error!(?err, "EVM {op} failed");
|
||||
evm_proto::EvmError::Internal
|
||||
}
|
||||
_ => {
|
||||
error!("EVM actor unreachable during {op}");
|
||||
evm_proto::EvmError::Internal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for UserAgentSession {
|
||||
type Args = Self;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user