refactor(client): redesign of wallet handle
This commit is contained in:
@@ -8,9 +8,12 @@ license = "Apache-2.0"
|
|||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
evm = ["dep:alloy"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arbiter-proto.path = "../arbiter-proto"
|
arbiter-proto.path = "../arbiter-proto"
|
||||||
alloy.workspace = true
|
alloy = { workspace = true, optional = true }
|
||||||
tonic.workspace = true
|
tonic.workspace = true
|
||||||
tonic.features = ["tls-aws-lc"]
|
tonic.features = ["tls-aws-lc"]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|||||||
76
server/crates/arbiter-client/src/client.rs
Normal file
76
server/crates/arbiter-client/src/client.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use arbiter_proto::{proto::arbiter_service_client::ArbiterServiceClient, url::ArbiterUrl};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::{Mutex, mpsc};
|
||||||
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
|
use tonic::transport::ClientTlsConfig;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
auth::{ConnectError, authenticate},
|
||||||
|
storage::{FileSigningKeyStorage, SigningKeyStorage},
|
||||||
|
transport::{BUFFER_LENGTH, ClientTransport},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "evm")]
|
||||||
|
use crate::wallets::evm::ArbiterEvmWallet;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ClientError {
|
||||||
|
#[error("gRPC error")]
|
||||||
|
Grpc(#[from] tonic::Status),
|
||||||
|
|
||||||
|
#[error("Connection closed by server")]
|
||||||
|
ConnectionClosed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArbiterClient {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
transport: Arc<Mutex<ClientTransport>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArbiterClient {
|
||||||
|
pub async fn connect(url: ArbiterUrl) -> Result<Self, ConnectError> {
|
||||||
|
let storage = FileSigningKeyStorage::from_default_location()?;
|
||||||
|
Self::connect_with_storage(url, &storage).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_with_storage<S: SigningKeyStorage>(
|
||||||
|
url: ArbiterUrl,
|
||||||
|
storage: &S,
|
||||||
|
) -> Result<Self, ConnectError> {
|
||||||
|
let key = storage.load_or_create()?;
|
||||||
|
Self::connect_with_key(url, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_with_key(
|
||||||
|
url: ArbiterUrl,
|
||||||
|
key: ed25519_dalek::SigningKey,
|
||||||
|
) -> Result<Self, ConnectError> {
|
||||||
|
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
||||||
|
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
||||||
|
|
||||||
|
let channel = tonic::transport::Channel::from_shared(format!("{}:{}", url.host, url.port))?
|
||||||
|
.tls_config(tls)?
|
||||||
|
.connect()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut client = ArbiterServiceClient::new(channel);
|
||||||
|
let (tx, rx) = mpsc::channel(BUFFER_LENGTH);
|
||||||
|
let response_stream = client.client(ReceiverStream::new(rx)).await?.into_inner();
|
||||||
|
|
||||||
|
let mut transport = ClientTransport {
|
||||||
|
sender: tx,
|
||||||
|
receiver: response_stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
authenticate(&mut transport, &key).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
transport: Arc::new(Mutex::new(transport)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "evm")]
|
||||||
|
pub async fn evm_wallets(&self) -> Result<Vec<ArbiterEvmWallet>, ClientError> {
|
||||||
|
todo!("fetch EVM wallet list from server")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
mod auth;
|
mod auth;
|
||||||
mod signer;
|
mod client;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod transport;
|
mod transport;
|
||||||
|
pub mod wallets;
|
||||||
|
|
||||||
pub use auth::ConnectError;
|
pub use auth::ConnectError;
|
||||||
pub use signer::ArbiterSigner;
|
pub use client::{ArbiterClient, ClientError};
|
||||||
pub use storage::{FileSigningKeyStorage, SigningKeyStorage, StorageError};
|
pub use storage::{FileSigningKeyStorage, SigningKeyStorage, StorageError};
|
||||||
|
|
||||||
|
#[cfg(feature = "evm")]
|
||||||
|
pub use wallets::evm::ArbiterEvmWallet;
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
use alloy::{
|
|
||||||
consensus::SignableTransaction,
|
|
||||||
network::TxSigner,
|
|
||||||
primitives::{Address, B256, ChainId, Signature},
|
|
||||||
signers::{Error, Result, Signer},
|
|
||||||
};
|
|
||||||
use arbiter_proto::{
|
|
||||||
proto::arbiter_service_client::ArbiterServiceClient,
|
|
||||||
url::ArbiterUrl,
|
|
||||||
};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use tokio::sync::{Mutex, mpsc};
|
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
|
||||||
use tonic::transport::ClientTlsConfig;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
auth::{ConnectError, authenticate},
|
|
||||||
storage::{FileSigningKeyStorage, SigningKeyStorage},
|
|
||||||
transport::{BUFFER_LENGTH, ClientSignError, ClientTransport},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct ArbiterSigner {
|
|
||||||
transport: Mutex<ClientTransport>,
|
|
||||||
address: Option<Address>,
|
|
||||||
chain_id: Option<ChainId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArbiterSigner {
|
|
||||||
pub async fn connect_grpc(url: ArbiterUrl) -> std::result::Result<Self, ConnectError> {
|
|
||||||
let storage = FileSigningKeyStorage::from_default_location()?;
|
|
||||||
Self::connect_grpc_with_storage(url, &storage).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_grpc_with_storage<S: SigningKeyStorage>(
|
|
||||||
url: ArbiterUrl,
|
|
||||||
storage: &S,
|
|
||||||
) -> std::result::Result<Self, ConnectError> {
|
|
||||||
let key = storage.load_or_create()?;
|
|
||||||
Self::connect_grpc_with_key(url, key).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_grpc_with_key(
|
|
||||||
url: ArbiterUrl,
|
|
||||||
key: ed25519_dalek::SigningKey,
|
|
||||||
) -> std::result::Result<Self, ConnectError> {
|
|
||||||
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
|
||||||
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
|
||||||
|
|
||||||
// NOTE: We intentionally keep the same URL construction strategy as the user-agent crate
|
|
||||||
// to avoid behavior drift between the two clients.
|
|
||||||
let channel = tonic::transport::Channel::from_shared(format!("{}:{}", url.host, url.port))?
|
|
||||||
.tls_config(tls)?
|
|
||||||
.connect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut client = ArbiterServiceClient::new(channel);
|
|
||||||
let (tx, rx) = mpsc::channel(BUFFER_LENGTH);
|
|
||||||
let response_stream = client.client(ReceiverStream::new(rx)).await?.into_inner();
|
|
||||||
|
|
||||||
let mut transport = ClientTransport {
|
|
||||||
sender: tx,
|
|
||||||
receiver: response_stream,
|
|
||||||
};
|
|
||||||
|
|
||||||
authenticate(&mut transport, &key).await?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
transport: Mutex::new(transport),
|
|
||||||
address: None,
|
|
||||||
chain_id: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wallet_address(&self) -> Option<Address> {
|
|
||||||
self.address
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_wallet_address(&mut self, address: Option<Address>) {
|
|
||||||
self.address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_wallet_address(mut self, address: Address) -> Self {
|
|
||||||
self.address = Some(address);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_chain_id(mut self, chain_id: ChainId) -> Self {
|
|
||||||
self.chain_id = Some(chain_id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_chain_id(&self, tx: &mut dyn SignableTransaction<Signature>) -> Result<()> {
|
|
||||||
if let Some(chain_id) = self.chain_id
|
|
||||||
&& !tx.set_chain_id_checked(chain_id)
|
|
||||||
{
|
|
||||||
return Err(Error::TransactionChainIdMismatch {
|
|
||||||
signer: chain_id,
|
|
||||||
tx: tx.chain_id().unwrap(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_wallet_address(&self) -> Result<Address> {
|
|
||||||
let wallet_address = self
|
|
||||||
.address
|
|
||||||
.ok_or_else(|| Error::other(ClientSignError::WalletAddressNotConfigured))?;
|
|
||||||
|
|
||||||
Ok(wallet_address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Signer for ArbiterSigner {
|
|
||||||
async fn sign_hash(&self, _hash: &B256) -> Result<Signature> {
|
|
||||||
Err(Error::other(
|
|
||||||
"hash-only signing is not supported for ArbiterSigner; use transaction signing",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn address(&self) -> Address {
|
|
||||||
self.address.unwrap_or(Address::ZERO)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chain_id(&self) -> Option<ChainId> {
|
|
||||||
self.chain_id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
|
|
||||||
self.chain_id = chain_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl TxSigner<Signature> for ArbiterSigner {
|
|
||||||
fn address(&self) -> Address {
|
|
||||||
self.address.unwrap_or(Address::ZERO)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_transaction(
|
|
||||||
&self,
|
|
||||||
tx: &mut dyn SignableTransaction<Signature>,
|
|
||||||
) -> Result<Signature> {
|
|
||||||
let _transport = self.transport.lock().await;
|
|
||||||
self.validate_chain_id(tx)?;
|
|
||||||
let _ = self.ensure_wallet_address()?;
|
|
||||||
|
|
||||||
Err(Error::other(
|
|
||||||
"transaction signing is not supported by current arbiter.client protocol",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,9 +18,6 @@ pub(crate) enum ClientSignError {
|
|||||||
|
|
||||||
#[error("Connection closed by server")]
|
#[error("Connection closed by server")]
|
||||||
ConnectionClosed,
|
ConnectionClosed,
|
||||||
|
|
||||||
#[error("Wallet address is not configured")]
|
|
||||||
WalletAddressNotConfigured,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ClientTransport {
|
pub(crate) struct ClientTransport {
|
||||||
|
|||||||
89
server/crates/arbiter-client/src/wallets/evm.rs
Normal file
89
server/crates/arbiter-client/src/wallets/evm.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use alloy::{
|
||||||
|
consensus::SignableTransaction,
|
||||||
|
network::TxSigner,
|
||||||
|
primitives::{Address, B256, ChainId, Signature},
|
||||||
|
signers::{Error, Result, Signer},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::transport::ClientTransport;
|
||||||
|
|
||||||
|
pub struct ArbiterEvmWallet {
|
||||||
|
transport: Arc<Mutex<ClientTransport>>,
|
||||||
|
address: Address,
|
||||||
|
chain_id: Option<ChainId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArbiterEvmWallet {
|
||||||
|
pub(crate) fn new(transport: Arc<Mutex<ClientTransport>>, address: Address) -> Self {
|
||||||
|
Self {
|
||||||
|
transport,
|
||||||
|
address,
|
||||||
|
chain_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self) -> Address {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_chain_id(mut self, chain_id: ChainId) -> Self {
|
||||||
|
self.chain_id = Some(chain_id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_chain_id(&self, tx: &mut dyn SignableTransaction<Signature>) -> Result<()> {
|
||||||
|
if let Some(chain_id) = self.chain_id
|
||||||
|
&& !tx.set_chain_id_checked(chain_id)
|
||||||
|
{
|
||||||
|
return Err(Error::TransactionChainIdMismatch {
|
||||||
|
signer: chain_id,
|
||||||
|
tx: tx.chain_id().unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Signer for ArbiterEvmWallet {
|
||||||
|
async fn sign_hash(&self, _hash: &B256) -> Result<Signature> {
|
||||||
|
Err(Error::other(
|
||||||
|
"hash-only signing is not supported for ArbiterEvmWallet; use transaction signing",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn address(&self) -> Address {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chain_id(&self) -> Option<ChainId> {
|
||||||
|
self.chain_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
|
||||||
|
self.chain_id = chain_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TxSigner<Signature> for ArbiterEvmWallet {
|
||||||
|
fn address(&self) -> Address {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sign_transaction(
|
||||||
|
&self,
|
||||||
|
tx: &mut dyn SignableTransaction<Signature>,
|
||||||
|
) -> Result<Signature> {
|
||||||
|
let _transport = self.transport.lock().await;
|
||||||
|
self.validate_chain_id(tx)?;
|
||||||
|
|
||||||
|
Err(Error::other(
|
||||||
|
"transaction signing is not supported by current arbiter.client protocol",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
2
server/crates/arbiter-client/src/wallets/mod.rs
Normal file
2
server/crates/arbiter-client/src/wallets/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(feature = "evm")]
|
||||||
|
pub mod evm;
|
||||||
Reference in New Issue
Block a user