feat(client): add client metadata and wallet visibility support
This commit is contained in:
@@ -5,8 +5,15 @@ package arbiter.client;
|
|||||||
import "evm.proto";
|
import "evm.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
message ClientInfo {
|
||||||
|
string name = 1;
|
||||||
|
string description = 2;
|
||||||
|
string version = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
message AuthChallengeRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
|
ClientInfo client_info = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthChallenge {
|
message AuthChallenge {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ message VolumeRateLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message SharedSettings {
|
message SharedSettings {
|
||||||
int32 wallet_id = 1;
|
int32 visibility_id = 1;
|
||||||
uint64 chain_id = 2;
|
uint64 chain_id = 2;
|
||||||
optional google.protobuf.Timestamp valid_from = 3;
|
optional google.protobuf.Timestamp valid_from = 3;
|
||||||
optional google.protobuf.Timestamp valid_until = 4;
|
optional google.protobuf.Timestamp valid_until = 4;
|
||||||
@@ -139,9 +139,8 @@ message TransactionEvalError {
|
|||||||
|
|
||||||
// --- UserAgent grant management ---
|
// --- UserAgent grant management ---
|
||||||
message EvmGrantCreateRequest {
|
message EvmGrantCreateRequest {
|
||||||
int32 client_id = 1;
|
SharedSettings shared = 1;
|
||||||
SharedSettings shared = 2;
|
SpecificGrant specific = 2;
|
||||||
SpecificGrant specific = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvmGrantCreateResponse {
|
message EvmGrantCreateResponse {
|
||||||
@@ -165,13 +164,13 @@ message EvmGrantDeleteResponse {
|
|||||||
// Basic grant info returned in grant listings
|
// Basic grant info returned in grant listings
|
||||||
message GrantEntry {
|
message GrantEntry {
|
||||||
int32 id = 1;
|
int32 id = 1;
|
||||||
int32 client_id = 2;
|
int32 visibility_id = 2;
|
||||||
SharedSettings shared = 3;
|
SharedSettings shared = 3;
|
||||||
SpecificGrant specific = 4;
|
SpecificGrant specific = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvmGrantListRequest {
|
message EvmGrantListRequest {
|
||||||
optional int32 wallet_id = 1;
|
optional int32 visibility_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvmGrantListResponse {
|
message EvmGrantListResponse {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package arbiter.user_agent;
|
package arbiter.user_agent;
|
||||||
|
|
||||||
|
import "client.proto";
|
||||||
import "evm.proto";
|
import "evm.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ enum VaultState {
|
|||||||
|
|
||||||
message ClientConnectionRequest {
|
message ClientConnectionRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
|
arbiter.client.ClientInfo info = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientConnectionResponse {
|
message ClientConnectionResponse {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ pub trait Sender<Outbound>: Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Receiver<Inbound>: Send + Sync {
|
pub trait Receiver<Inbound>: Send + Sync {
|
||||||
async fn recv(&mut self) -> Option<Inbound>;
|
async fn recv(&mut self) -> Option<Inbound>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ create table if not exists tls_history (
|
|||||||
id INTEGER not null PRIMARY KEY,
|
id INTEGER not null PRIMARY KEY,
|
||||||
cert text not null,
|
cert text not null,
|
||||||
cert_key text not null, -- PEM Encoded private key
|
cert_key text not null, -- PEM Encoded private key
|
||||||
ca_cert text not null,
|
ca_cert text not null,
|
||||||
ca_key text not null, -- PEM Encoded private key
|
ca_key text not null, -- PEM Encoded private key
|
||||||
created_at integer not null default(unixepoch ('now'))
|
created_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
@@ -40,7 +40,8 @@ create table if not exists arbiter_settings (
|
|||||||
tls_id integer references tls_history (id) on delete RESTRICT
|
tls_id integer references tls_history (id) on delete RESTRICT
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
insert into arbiter_settings (id) values (1) on conflict do nothing; -- ensure singleton row exists
|
insert into arbiter_settings (id) values (1) on conflict do nothing;
|
||||||
|
-- ensure singleton row exists
|
||||||
|
|
||||||
create table if not exists useragent_client (
|
create table if not exists useragent_client (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
@@ -50,15 +51,37 @@ create table if not exists useragent_client (
|
|||||||
created_at integer not null default(unixepoch ('now')),
|
created_at integer not null default(unixepoch ('now')),
|
||||||
updated_at integer not null default(unixepoch ('now'))
|
updated_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
create unique index if not exists uniq_useragent_client_public_key on useragent_client (public_key, key_type);
|
||||||
|
|
||||||
|
create table if not exists client_metadata (
|
||||||
|
id integer not null primary key,
|
||||||
|
name text not null, -- human-readable name for the client
|
||||||
|
description text, -- optional description for the client
|
||||||
|
version text, -- client version for tracking and debugging
|
||||||
|
created_at integer not null default(unixepoch ('now'))
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- created to track history of changes
|
||||||
|
create table if not exists client_metadata_history (
|
||||||
|
id integer not null primary key,
|
||||||
|
metadata_id integer not null references client_metadata (id) on delete cascade,
|
||||||
|
client_id integer not null references program_client (id) on delete cascade,
|
||||||
|
created_at integer not null default(unixepoch ('now'))
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
create unique index if not exists uniq_metadata_binding_client on client_metadata_history (client_id);
|
||||||
|
|
||||||
create table if not exists program_client (
|
create table if not exists program_client (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
nonce integer not null default(1), -- used for auth challenge
|
nonce integer not null default(1), -- used for auth challenge
|
||||||
public_key blob not null,
|
public_key blob not null,
|
||||||
|
metadata_id integer not null references client_metadata (id) on delete cascade,
|
||||||
created_at integer not null default(unixepoch ('now')),
|
created_at integer not null default(unixepoch ('now')),
|
||||||
updated_at integer not null default(unixepoch ('now'))
|
updated_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
|
create unique index if not exists uniq_program_client_public_key on program_client (public_key);
|
||||||
|
|
||||||
create table if not exists evm_wallet (
|
create table if not exists evm_wallet (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
address blob not null, -- 20-byte Ethereum address
|
address blob not null, -- 20-byte Ethereum address
|
||||||
@@ -67,93 +90,101 @@ create table if not exists evm_wallet (
|
|||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
create unique index if not exists uniq_evm_wallet_address on evm_wallet (address);
|
create unique index if not exists uniq_evm_wallet_address on evm_wallet (address);
|
||||||
|
|
||||||
create unique index if not exists uniq_evm_wallet_aead on evm_wallet (aead_encrypted_id);
|
create unique index if not exists uniq_evm_wallet_aead on evm_wallet (aead_encrypted_id);
|
||||||
|
|
||||||
|
create table if not exists evm_wallet_visibility (
|
||||||
|
id integer not null primary key,
|
||||||
|
wallet_id integer not null references evm_wallet (id) on delete cascade,
|
||||||
|
client_id integer not null references program_client (id) on delete cascade,
|
||||||
|
created_at integer not null default(unixepoch ('now'))
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
create unique index if not exists uniq_wallet_visibility on evm_wallet_visibility (wallet_id, client_id);
|
||||||
|
|
||||||
create table if not exists evm_ether_transfer_limit (
|
create table if not exists evm_ether_transfer_limit (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
window_secs integer not null, -- window duration in seconds
|
window_secs integer not null, -- window duration in seconds
|
||||||
max_volume blob not null -- big-endian 32-byte U256
|
max_volume blob not null -- big-endian 32-byte U256
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- Shared grant properties: client scope, timeframe, fee caps, and rate limit
|
-- Shared grant properties: client scope, timeframe, fee caps, and rate limit
|
||||||
create table if not exists evm_basic_grant (
|
create table if not exists evm_basic_grant (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
wallet_id integer not null references evm_wallet(id) on delete restrict,
|
visibility_id integer not null references evm_wallet_visibility (id) on delete restrict,
|
||||||
client_id integer not null references program_client(id) on delete restrict,
|
chain_id integer not null, -- EIP-155 chain ID
|
||||||
chain_id integer not null, -- EIP-155 chain ID
|
valid_from integer, -- unix timestamp (seconds), null = no lower bound
|
||||||
valid_from integer, -- unix timestamp (seconds), null = no lower bound
|
valid_until integer, -- unix timestamp (seconds), null = no upper bound
|
||||||
valid_until integer, -- unix timestamp (seconds), null = no upper bound
|
max_gas_fee_per_gas blob, -- big-endian 32-byte U256, null = unlimited
|
||||||
max_gas_fee_per_gas blob, -- big-endian 32-byte U256, null = unlimited
|
max_priority_fee_per_gas blob, -- big-endian 32-byte U256, null = unlimited
|
||||||
max_priority_fee_per_gas blob, -- big-endian 32-byte U256, null = unlimited
|
rate_limit_count integer, -- max transactions in window, null = unlimited
|
||||||
rate_limit_count integer, -- max transactions in window, null = unlimited
|
rate_limit_window_secs integer, -- window duration in seconds, null = unlimited
|
||||||
rate_limit_window_secs integer, -- window duration in seconds, null = unlimited
|
revoked_at integer, -- unix timestamp when revoked, null = still active
|
||||||
revoked_at integer, -- unix timestamp when revoked, null = still active
|
created_at integer not null default(unixepoch ('now'))
|
||||||
created_at integer not null default(unixepoch('now'))
|
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- Shared transaction log for all EVM grants, used for rate limit tracking and auditing
|
-- Shared transaction log for all EVM grants, used for rate limit tracking and auditing
|
||||||
create table if not exists evm_transaction_log (
|
create table if not exists evm_transaction_log (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
grant_id integer not null references evm_basic_grant(id) on delete restrict,
|
visibility_id integer not null references evm_wallet_visibility (id) on delete restrict,
|
||||||
client_id integer not null references program_client(id) on delete restrict,
|
grant_id integer not null references evm_basic_grant (id) on delete restrict,
|
||||||
wallet_id integer not null references evm_wallet(id) on delete restrict,
|
|
||||||
chain_id integer not null,
|
chain_id integer not null,
|
||||||
eth_value blob not null, -- always present on any EVM tx
|
eth_value blob not null, -- always present on any EVM tx
|
||||||
signed_at integer not null default(unixepoch('now'))
|
signed_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
create index if not exists idx_evm_basic_grant_wallet_chain on evm_basic_grant(client_id, wallet_id, chain_id);
|
create index if not exists idx_evm_basic_grant_wallet_chain on evm_basic_grant (visibility_id, chain_id);
|
||||||
|
|
||||||
-- ===============================
|
-- ===============================
|
||||||
-- ERC20 token transfer grant
|
-- ERC20 token transfer grant
|
||||||
-- ===============================
|
-- ===============================
|
||||||
create table if not exists evm_token_transfer_grant (
|
create table if not exists evm_token_transfer_grant (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
basic_grant_id integer not null unique references evm_basic_grant(id) on delete cascade,
|
basic_grant_id integer not null unique references evm_basic_grant (id) on delete cascade,
|
||||||
token_contract blob not null, -- 20-byte ERC20 contract address
|
token_contract blob not null, -- 20-byte ERC20 contract address
|
||||||
receiver blob -- 20-byte recipient address or null if every recipient allowed
|
receiver blob -- 20-byte recipient address or null if every recipient allowed
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- Per-window volume limits for token transfer grants
|
-- Per-window volume limits for token transfer grants
|
||||||
create table if not exists evm_token_transfer_volume_limit (
|
create table if not exists evm_token_transfer_volume_limit (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
grant_id integer not null references evm_token_transfer_grant(id) on delete cascade,
|
grant_id integer not null references evm_token_transfer_grant (id) on delete cascade,
|
||||||
window_secs integer not null, -- window duration in seconds
|
window_secs integer not null, -- window duration in seconds
|
||||||
max_volume blob not null -- big-endian 32-byte U256
|
max_volume blob not null -- big-endian 32-byte U256
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- Log table for token transfer grant usage
|
-- Log table for token transfer grant usage
|
||||||
create table if not exists evm_token_transfer_log (
|
create table if not exists evm_token_transfer_log (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
grant_id integer not null references evm_token_transfer_grant(id) on delete restrict,
|
grant_id integer not null references evm_token_transfer_grant (id) on delete restrict,
|
||||||
log_id integer not null references evm_transaction_log(id) on delete restrict,
|
log_id integer not null references evm_transaction_log (id) on delete restrict,
|
||||||
chain_id integer not null, -- EIP-155 chain ID
|
chain_id integer not null, -- EIP-155 chain ID
|
||||||
token_contract blob not null, -- 20-byte ERC20 contract address
|
token_contract blob not null, -- 20-byte ERC20 contract address
|
||||||
recipient_address blob not null, -- 20-byte recipient address
|
recipient_address blob not null, -- 20-byte recipient address
|
||||||
value blob not null, -- big-endian 32-byte U256
|
value blob not null, -- big-endian 32-byte U256
|
||||||
created_at integer not null default(unixepoch('now'))
|
created_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
create index if not exists idx_token_transfer_log_grant on evm_token_transfer_log(grant_id);
|
create index if not exists idx_token_transfer_log_grant on evm_token_transfer_log (grant_id);
|
||||||
create index if not exists idx_token_transfer_log_log_id on evm_token_transfer_log(log_id);
|
|
||||||
create index if not exists idx_token_transfer_log_chain on evm_token_transfer_log(chain_id);
|
|
||||||
|
|
||||||
|
create index if not exists idx_token_transfer_log_log_id on evm_token_transfer_log (log_id);
|
||||||
|
|
||||||
|
create index if not exists idx_token_transfer_log_chain on evm_token_transfer_log (chain_id);
|
||||||
|
|
||||||
-- ===============================
|
-- ===============================
|
||||||
-- Ether transfer grant (uses base log)
|
-- Ether transfer grant (uses base log)
|
||||||
-- ===============================
|
-- ===============================
|
||||||
create table if not exists evm_ether_transfer_grant (
|
create table if not exists evm_ether_transfer_grant (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
basic_grant_id integer not null unique references evm_basic_grant(id) on delete cascade,
|
basic_grant_id integer not null unique references evm_basic_grant (id) on delete cascade,
|
||||||
limit_id integer not null references evm_ether_transfer_limit(id) on delete restrict
|
limit_id integer not null references evm_ether_transfer_limit (id) on delete restrict
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- Specific recipient addresses for an ether transfer grant
|
-- Specific recipient addresses for an ether transfer grant
|
||||||
create table if not exists evm_ether_transfer_grant_target (
|
create table if not exists evm_ether_transfer_grant_target (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
grant_id integer not null references evm_ether_transfer_grant(id) on delete cascade,
|
grant_id integer not null references evm_ether_transfer_grant (id) on delete cascade,
|
||||||
address blob not null -- 20-byte recipient address
|
address blob not null -- 20-byte recipient address
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
create unique index if not exists uniq_ether_transfer_target on evm_ether_transfer_grant_target(grant_id, address);
|
create unique index if not exists uniq_ether_transfer_target on evm_ether_transfer_grant_target (grant_id, address);
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ use arbiter_proto::{
|
|||||||
format_challenge,
|
format_challenge,
|
||||||
transport::{Bi, expect_message},
|
transport::{Bi, expect_message},
|
||||||
};
|
};
|
||||||
|
use chrono::Utc;
|
||||||
use diesel::{
|
use diesel::{
|
||||||
ExpressionMethods as _, OptionalExtension as _, QueryDsl as _, dsl::insert_into, update,
|
ExpressionMethods as _, OptionalExtension as _, QueryDsl as _, SelectableHelper as _,
|
||||||
|
dsl::insert_into, update,
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl as _;
|
use diesel_async::RunQueryDsl as _;
|
||||||
use ed25519_dalek::{Signature, VerifyingKey};
|
use ed25519_dalek::{Signature, VerifyingKey};
|
||||||
@@ -15,9 +17,20 @@ use crate::{
|
|||||||
client::ClientConnection,
|
client::ClientConnection,
|
||||||
router::{self, RequestClientApproval},
|
router::{self, RequestClientApproval},
|
||||||
},
|
},
|
||||||
db::{self, schema::program_client},
|
db::{
|
||||||
|
self,
|
||||||
|
models::{ProgramClientMetadata, SqliteTimestamp},
|
||||||
|
schema::program_client,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ClientMetadata {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Database pool unavailable")]
|
#[error("Database pool unavailable")]
|
||||||
@@ -44,8 +57,13 @@ pub enum ApproveError {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Inbound {
|
pub enum Inbound {
|
||||||
AuthChallengeRequest { pubkey: VerifyingKey },
|
AuthChallengeRequest {
|
||||||
AuthChallengeSolution { signature: Signature },
|
pubkey: VerifyingKey,
|
||||||
|
metadata: ClientMetadata,
|
||||||
|
},
|
||||||
|
AuthChallengeSolution {
|
||||||
|
signature: Signature,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -118,23 +136,37 @@ async fn approve_new_client(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_client(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result<(), Error> {
|
async fn insert_client(
|
||||||
let now = std::time::SystemTime::now()
|
db: &db::DatabasePool,
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
pubkey: &VerifyingKey,
|
||||||
.unwrap_or_default()
|
metadata: &ClientMetadata,
|
||||||
.as_secs() as i32;
|
) -> Result<(), Error> {
|
||||||
|
use crate::db::schema::client_metadata;
|
||||||
|
|
||||||
let mut conn = db.get().await.map_err(|e| {
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
error!(error = ?e, "Database pool error");
|
error!(error = ?e, "Database pool error");
|
||||||
Error::DatabasePoolUnavailable
|
Error::DatabasePoolUnavailable
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let metadata_id = insert_into(client_metadata::table)
|
||||||
|
.values((
|
||||||
|
client_metadata::name.eq(&metadata.name),
|
||||||
|
client_metadata::description.eq(&metadata.description),
|
||||||
|
client_metadata::version.eq(&metadata.version),
|
||||||
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result::<i32>(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Failed to insert client metadata");
|
||||||
|
Error::DatabaseOperationFailed
|
||||||
|
})?;
|
||||||
|
|
||||||
insert_into(program_client::table)
|
insert_into(program_client::table)
|
||||||
.values((
|
.values((
|
||||||
program_client::public_key.eq(pubkey.as_bytes().to_vec()),
|
program_client::public_key.eq(pubkey.as_bytes().to_vec()),
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
program_client::nonce.eq(1), // pre-incremented; challenge uses 0
|
program_client::nonce.eq(1), // pre-incremented; challenge uses 0
|
||||||
program_client::created_at.eq(now),
|
|
||||||
program_client::updated_at.eq(now),
|
|
||||||
))
|
))
|
||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await
|
.await
|
||||||
@@ -146,6 +178,95 @@ async fn insert_client(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_client_id(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result<Option<i32>, Error> {
|
||||||
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
|
error!(error = ?e, "Database pool error");
|
||||||
|
Error::DatabasePoolUnavailable
|
||||||
|
})?;
|
||||||
|
|
||||||
|
program_client::table
|
||||||
|
.filter(program_client::public_key.eq(pubkey.as_bytes().to_vec()))
|
||||||
|
.select(program_client::id)
|
||||||
|
.first::<i32>(&mut conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Database error");
|
||||||
|
Error::DatabaseOperationFailed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_client_metadata(
|
||||||
|
db: &db::DatabasePool,
|
||||||
|
client_id: i32,
|
||||||
|
metadata: &ClientMetadata,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use crate::db::schema::{client_metadata, client_metadata_history};
|
||||||
|
|
||||||
|
let now = SqliteTimestamp(Utc::now());
|
||||||
|
|
||||||
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
|
error!(error = ?e, "Database pool error");
|
||||||
|
Error::DatabasePoolUnavailable
|
||||||
|
})?;
|
||||||
|
|
||||||
|
conn.exclusive_transaction(|conn| {
|
||||||
|
let metadata = metadata.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
let (current_metadata_id, current): (i32, ProgramClientMetadata) =
|
||||||
|
program_client::table
|
||||||
|
.find(client_id)
|
||||||
|
.inner_join(client_metadata::table)
|
||||||
|
.select((
|
||||||
|
program_client::metadata_id,
|
||||||
|
ProgramClientMetadata::as_select(),
|
||||||
|
))
|
||||||
|
.first(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let unchanged = current.name == metadata.name
|
||||||
|
&& current.description == metadata.description
|
||||||
|
&& current.version == metadata.version;
|
||||||
|
if unchanged {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_into(client_metadata_history::table)
|
||||||
|
.values((
|
||||||
|
client_metadata_history::metadata_id.eq(current_metadata_id),
|
||||||
|
client_metadata_history::client_id.eq(client_id),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let metadata_id = insert_into(client_metadata::table)
|
||||||
|
.values((
|
||||||
|
client_metadata::name.eq(&metadata.name),
|
||||||
|
client_metadata::description.eq(&metadata.description),
|
||||||
|
client_metadata::version.eq(&metadata.version),
|
||||||
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result::<i32>(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
update(program_client::table.find(client_id))
|
||||||
|
.set((
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
|
program_client::updated_at.eq(now),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok::<(), diesel::result::Error>(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Database error");
|
||||||
|
Error::DatabaseOperationFailed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn challenge_client<T>(
|
async fn challenge_client<T>(
|
||||||
transport: &mut T,
|
transport: &mut T,
|
||||||
pubkey: VerifyingKey,
|
pubkey: VerifyingKey,
|
||||||
@@ -189,7 +310,7 @@ pub async fn authenticate<T>(
|
|||||||
where
|
where
|
||||||
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
|
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
|
||||||
{
|
{
|
||||||
let Some(Inbound::AuthChallengeRequest { pubkey }) = transport.recv().await else {
|
let Some(Inbound::AuthChallengeRequest { pubkey, metadata }) = transport.recv().await else {
|
||||||
return Err(Error::Transport);
|
return Err(Error::Transport);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,11 +318,16 @@ where
|
|||||||
Some(nonce) => nonce,
|
Some(nonce) => nonce,
|
||||||
None => {
|
None => {
|
||||||
approve_new_client(&props.actors, pubkey).await?;
|
approve_new_client(&props.actors, pubkey).await?;
|
||||||
insert_client(&props.db, &pubkey).await?;
|
insert_client(&props.db, &pubkey, &metadata).await?;
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let client_id = get_client_id(&props.db, &pubkey)
|
||||||
|
.await?
|
||||||
|
.ok_or(Error::DatabaseOperationFailed)?;
|
||||||
|
sync_client_metadata(&props.db, client_id, &metadata).await?;
|
||||||
|
|
||||||
challenge_client(transport, pubkey, nonce).await?;
|
challenge_client(transport, pubkey, nonce).await?;
|
||||||
transport
|
transport
|
||||||
.send(Ok(Outbound::AuthSuccess))
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
|
|||||||
@@ -148,31 +148,24 @@ impl EvmActor {
|
|||||||
#[message]
|
#[message]
|
||||||
pub async fn useragent_create_grant(
|
pub async fn useragent_create_grant(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_id: i32,
|
|
||||||
basic: SharedGrantSettings,
|
basic: SharedGrantSettings,
|
||||||
grant: SpecificGrant,
|
grant: SpecificGrant,
|
||||||
) -> Result<i32, evm::CreationError> {
|
) -> Result<i32, evm::CreationError> {
|
||||||
match grant {
|
match grant {
|
||||||
SpecificGrant::EtherTransfer(settings) => {
|
SpecificGrant::EtherTransfer(settings) => {
|
||||||
self.engine
|
self.engine
|
||||||
.create_grant::<EtherTransfer>(
|
.create_grant::<EtherTransfer>(FullGrant {
|
||||||
client_id,
|
basic,
|
||||||
FullGrant {
|
specific: settings,
|
||||||
basic,
|
})
|
||||||
specific: settings,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
SpecificGrant::TokenTransfer(settings) => {
|
SpecificGrant::TokenTransfer(settings) => {
|
||||||
self.engine
|
self.engine
|
||||||
.create_grant::<TokenTransfer>(
|
.create_grant::<TokenTransfer>(FullGrant {
|
||||||
client_id,
|
basic,
|
||||||
FullGrant {
|
specific: settings,
|
||||||
basic,
|
})
|
||||||
specific: settings,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,16 +206,19 @@ impl EvmActor {
|
|||||||
.await
|
.await
|
||||||
.optional()?
|
.optional()?
|
||||||
.ok_or(SignTransactionError::WalletNotFound)?;
|
.ok_or(SignTransactionError::WalletNotFound)?;
|
||||||
|
let visibility = schema::evm_wallet_visibility::table
|
||||||
|
.select(models::EvmWalletVisibility::as_select())
|
||||||
|
.filter(schema::evm_wallet_visibility::wallet_id.eq(wallet.id))
|
||||||
|
.filter(schema::evm_wallet_visibility::client_id.eq(client_id))
|
||||||
|
.first(&mut conn)
|
||||||
|
.await
|
||||||
|
.optional()?
|
||||||
|
.ok_or(SignTransactionError::WalletNotFound)?;
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let meaning = self
|
let meaning = self
|
||||||
.engine
|
.engine
|
||||||
.evaluate_transaction(
|
.evaluate_transaction(visibility, transaction.clone(), RunKind::Execution)
|
||||||
wallet.id,
|
|
||||||
client_id,
|
|
||||||
transaction.clone(),
|
|
||||||
RunKind::Execution,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(meaning)
|
Ok(meaning)
|
||||||
@@ -243,6 +239,14 @@ impl EvmActor {
|
|||||||
.await
|
.await
|
||||||
.optional()?
|
.optional()?
|
||||||
.ok_or(SignTransactionError::WalletNotFound)?;
|
.ok_or(SignTransactionError::WalletNotFound)?;
|
||||||
|
let visibility = schema::evm_wallet_visibility::table
|
||||||
|
.select(models::EvmWalletVisibility::as_select())
|
||||||
|
.filter(schema::evm_wallet_visibility::wallet_id.eq(wallet.id))
|
||||||
|
.filter(schema::evm_wallet_visibility::client_id.eq(client_id))
|
||||||
|
.first(&mut conn)
|
||||||
|
.await
|
||||||
|
.optional()?
|
||||||
|
.ok_or(SignTransactionError::WalletNotFound)?;
|
||||||
drop(conn);
|
drop(conn);
|
||||||
|
|
||||||
let raw_key: SafeCell<Vec<u8>> = self
|
let raw_key: SafeCell<Vec<u8>> = self
|
||||||
@@ -256,12 +260,7 @@ impl EvmActor {
|
|||||||
let signer = safe_signer::SafeSigner::from_cell(raw_key)?;
|
let signer = safe_signer::SafeSigner::from_cell(raw_key)?;
|
||||||
|
|
||||||
self.engine
|
self.engine
|
||||||
.evaluate_transaction(
|
.evaluate_transaction(visibility, transaction.clone(), RunKind::Execution)
|
||||||
wallet.id,
|
|
||||||
client_id,
|
|
||||||
transaction.clone(),
|
|
||||||
RunKind::Execution,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
use alloy::network::TxSignerSync as _;
|
use alloy::network::TxSignerSync as _;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::GlobalActors,
|
actors::GlobalActors,
|
||||||
db::{self, models::KeyType},
|
db::{self, models::KeyType},
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ impl Error {
|
|||||||
pub struct UserAgentSession {
|
pub struct UserAgentSession {
|
||||||
props: UserAgentConnection,
|
props: UserAgentConnection,
|
||||||
state: UserAgentStateMachine<DummyContext>,
|
state: UserAgentStateMachine<DummyContext>,
|
||||||
#[allow(dead_code, reason = "The session keeps ownership of the outbound transport even before the state-machine flow starts using it directly")]
|
#[allow(
|
||||||
|
dead_code,
|
||||||
|
reason = "The session keeps ownership of the outbound transport even before the state-machine flow starts using it directly"
|
||||||
|
)]
|
||||||
sender: Box<dyn Sender<OutOfBand>>,
|
sender: Box<dyn Sender<OutOfBand>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +90,7 @@ impl UserAgentSession {
|
|||||||
pub async fn request_new_client_approval(
|
pub async fn request_new_client_approval(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_pubkey: VerifyingKey,
|
client_pubkey: VerifyingKey,
|
||||||
cancel_flag: watch::Receiver<()>,
|
cancel_flag: watch::Receiver<()>,
|
||||||
) -> Result<bool, ()> {
|
) -> Result<bool, ()> {
|
||||||
// temporary use to make clippy happy while we refactor this flow
|
// temporary use to make clippy happy while we refactor this flow
|
||||||
dbg!(client_pubkey);
|
dbg!(client_pubkey);
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
keyholder::{self, Bootstrap, TryUnseal},
|
keyholder::{self, Bootstrap, TryUnseal},
|
||||||
user_agent::session::{
|
user_agent::session::{
|
||||||
UserAgentSession,
|
UserAgentSession,
|
||||||
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
safe_cell::SafeCellHandle as _,
|
safe_cell::SafeCellHandle as _,
|
||||||
};
|
};
|
||||||
@@ -312,7 +312,6 @@ impl UserAgentSession {
|
|||||||
#[message]
|
#[message]
|
||||||
pub(crate) async fn handle_grant_create(
|
pub(crate) async fn handle_grant_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_id: i32,
|
|
||||||
basic: crate::evm::policies::SharedGrantSettings,
|
basic: crate::evm::policies::SharedGrantSettings,
|
||||||
grant: crate::evm::policies::SpecificGrant,
|
grant: crate::evm::policies::SpecificGrant,
|
||||||
) -> Result<i32, Error> {
|
) -> Result<i32, Error> {
|
||||||
@@ -320,11 +319,7 @@ impl UserAgentSession {
|
|||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
.evm
|
.evm
|
||||||
.ask(UseragentCreateGrant {
|
.ask(UseragentCreateGrant { basic, grant })
|
||||||
client_id,
|
|
||||||
basic,
|
|
||||||
grant,
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(grant_id) => Ok(grant_id),
|
Ok(grant_id) => Ok(grant_id),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub mod types {
|
|||||||
sqlite::{Sqlite, SqliteType},
|
sqlite::{Sqlite, SqliteType},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, FromSqlRow, AsExpression)]
|
#[derive(Debug, FromSqlRow, AsExpression, Clone)]
|
||||||
#[diesel(sql_type = Integer)]
|
#[diesel(sql_type = Integer)]
|
||||||
#[repr(transparent)] // hint compiler to optimize the wrapper struct away
|
#[repr(transparent)] // hint compiler to optimize the wrapper struct away
|
||||||
pub struct SqliteTimestamp(pub DateTime<Utc>);
|
pub struct SqliteTimestamp(pub DateTime<Utc>);
|
||||||
@@ -185,12 +185,41 @@ pub struct EvmWallet {
|
|||||||
pub created_at: SqliteTimestamp,
|
pub created_at: SqliteTimestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug, Insertable, Selectable)]
|
#[derive(Models, Queryable, Debug, Insertable, Selectable, Clone)]
|
||||||
|
#[diesel(table_name = schema::evm_wallet_visibility, check_for_backend(Sqlite))]
|
||||||
|
pub struct EvmWalletVisibility {
|
||||||
|
pub id: i32,
|
||||||
|
pub wallet_id: i32,
|
||||||
|
pub client_id: i32,
|
||||||
|
pub created_at: SqliteTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Models, Queryable, Debug, Insertable, Selectable)]
|
||||||
|
#[diesel(table_name = schema::client_metadata, check_for_backend(Sqlite))]
|
||||||
|
pub struct ProgramClientMetadata {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub version: Option<String>,
|
||||||
|
pub created_at: SqliteTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Models, Queryable, Debug, Insertable, Selectable)]
|
||||||
|
#[diesel(table_name = schema::client_metadata_history, check_for_backend(Sqlite))]
|
||||||
|
pub struct ProgramClientMetadataHistory {
|
||||||
|
pub id: i32,
|
||||||
|
pub metadata_id: i32,
|
||||||
|
pub client_id: i32,
|
||||||
|
pub created_at: SqliteTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Models, Queryable, Debug, Insertable, Selectable)]
|
||||||
#[diesel(table_name = schema::program_client, check_for_backend(Sqlite))]
|
#[diesel(table_name = schema::program_client, check_for_backend(Sqlite))]
|
||||||
pub struct ProgramClient {
|
pub struct ProgramClient {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub nonce: i32,
|
pub nonce: i32,
|
||||||
pub public_key: Vec<u8>,
|
pub public_key: Vec<u8>,
|
||||||
|
pub metadata_id: i32,
|
||||||
pub created_at: SqliteTimestamp,
|
pub created_at: SqliteTimestamp,
|
||||||
pub updated_at: SqliteTimestamp,
|
pub updated_at: SqliteTimestamp,
|
||||||
}
|
}
|
||||||
@@ -230,8 +259,7 @@ pub struct EvmEtherTransferLimit {
|
|||||||
)]
|
)]
|
||||||
pub struct EvmBasicGrant {
|
pub struct EvmBasicGrant {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub wallet_id: i32, // references evm_wallet.id
|
pub visibility_id: i32, // references evm_wallet_visibility.id
|
||||||
pub client_id: i32, // references program_client.id
|
|
||||||
pub chain_id: i32,
|
pub chain_id: i32,
|
||||||
pub valid_from: Option<SqliteTimestamp>,
|
pub valid_from: Option<SqliteTimestamp>,
|
||||||
pub valid_until: Option<SqliteTimestamp>,
|
pub valid_until: Option<SqliteTimestamp>,
|
||||||
@@ -254,8 +282,7 @@ pub struct EvmBasicGrant {
|
|||||||
pub struct EvmTransactionLog {
|
pub struct EvmTransactionLog {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub grant_id: i32,
|
pub grant_id: i32,
|
||||||
pub client_id: i32,
|
pub visibility_id: i32,
|
||||||
pub wallet_id: i32,
|
|
||||||
pub chain_id: i32,
|
pub chain_id: i32,
|
||||||
pub eth_value: Vec<u8>,
|
pub eth_value: Vec<u8>,
|
||||||
pub signed_at: SqliteTimestamp,
|
pub signed_at: SqliteTimestamp,
|
||||||
|
|||||||
@@ -20,11 +20,29 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
client_metadata (id) {
|
||||||
|
id -> Integer,
|
||||||
|
name -> Text,
|
||||||
|
description -> Nullable<Text>,
|
||||||
|
version -> Nullable<Text>,
|
||||||
|
created_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
client_metadata_history (id) {
|
||||||
|
id -> Integer,
|
||||||
|
metadata_id -> Integer,
|
||||||
|
client_id -> Integer,
|
||||||
|
created_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
evm_basic_grant (id) {
|
evm_basic_grant (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
wallet_id -> Integer,
|
visibility_id -> Integer,
|
||||||
client_id -> Integer,
|
|
||||||
chain_id -> Integer,
|
chain_id -> Integer,
|
||||||
valid_from -> Nullable<Integer>,
|
valid_from -> Nullable<Integer>,
|
||||||
valid_until -> Nullable<Integer>,
|
valid_until -> Nullable<Integer>,
|
||||||
@@ -95,9 +113,8 @@ diesel::table! {
|
|||||||
diesel::table! {
|
diesel::table! {
|
||||||
evm_transaction_log (id) {
|
evm_transaction_log (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
|
visibility_id -> Integer,
|
||||||
grant_id -> Integer,
|
grant_id -> Integer,
|
||||||
client_id -> Integer,
|
|
||||||
wallet_id -> Integer,
|
|
||||||
chain_id -> Integer,
|
chain_id -> Integer,
|
||||||
eth_value -> Binary,
|
eth_value -> Binary,
|
||||||
signed_at -> Integer,
|
signed_at -> Integer,
|
||||||
@@ -113,11 +130,21 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
evm_wallet_visibility (id) {
|
||||||
|
id -> Integer,
|
||||||
|
wallet_id -> Integer,
|
||||||
|
client_id -> Integer,
|
||||||
|
created_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
program_client (id) {
|
program_client (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
nonce -> Integer,
|
nonce -> Integer,
|
||||||
public_key -> Binary,
|
public_key -> Binary,
|
||||||
|
metadata_id -> Integer,
|
||||||
created_at -> Integer,
|
created_at -> Integer,
|
||||||
updated_at -> Integer,
|
updated_at -> Integer,
|
||||||
}
|
}
|
||||||
@@ -151,17 +178,18 @@ diesel::table! {
|
|||||||
id -> Integer,
|
id -> Integer,
|
||||||
nonce -> Integer,
|
nonce -> Integer,
|
||||||
public_key -> Binary,
|
public_key -> Binary,
|
||||||
|
key_type -> Integer,
|
||||||
created_at -> Integer,
|
created_at -> Integer,
|
||||||
updated_at -> Integer,
|
updated_at -> Integer,
|
||||||
key_type -> Integer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::joinable!(aead_encrypted -> root_key_history (associated_root_key_id));
|
diesel::joinable!(aead_encrypted -> root_key_history (associated_root_key_id));
|
||||||
diesel::joinable!(arbiter_settings -> root_key_history (root_key_id));
|
diesel::joinable!(arbiter_settings -> root_key_history (root_key_id));
|
||||||
diesel::joinable!(arbiter_settings -> tls_history (tls_id));
|
diesel::joinable!(arbiter_settings -> tls_history (tls_id));
|
||||||
diesel::joinable!(evm_basic_grant -> evm_wallet (wallet_id));
|
diesel::joinable!(client_metadata_history -> client_metadata (metadata_id));
|
||||||
diesel::joinable!(evm_basic_grant -> program_client (client_id));
|
diesel::joinable!(client_metadata_history -> program_client (client_id));
|
||||||
|
diesel::joinable!(evm_basic_grant -> evm_wallet_visibility (visibility_id));
|
||||||
diesel::joinable!(evm_ether_transfer_grant -> evm_basic_grant (basic_grant_id));
|
diesel::joinable!(evm_ether_transfer_grant -> evm_basic_grant (basic_grant_id));
|
||||||
diesel::joinable!(evm_ether_transfer_grant -> evm_ether_transfer_limit (limit_id));
|
diesel::joinable!(evm_ether_transfer_grant -> evm_ether_transfer_limit (limit_id));
|
||||||
diesel::joinable!(evm_ether_transfer_grant_target -> evm_ether_transfer_grant (grant_id));
|
diesel::joinable!(evm_ether_transfer_grant_target -> evm_ether_transfer_grant (grant_id));
|
||||||
@@ -169,11 +197,18 @@ diesel::joinable!(evm_token_transfer_grant -> evm_basic_grant (basic_grant_id));
|
|||||||
diesel::joinable!(evm_token_transfer_log -> evm_token_transfer_grant (grant_id));
|
diesel::joinable!(evm_token_transfer_log -> evm_token_transfer_grant (grant_id));
|
||||||
diesel::joinable!(evm_token_transfer_log -> evm_transaction_log (log_id));
|
diesel::joinable!(evm_token_transfer_log -> evm_transaction_log (log_id));
|
||||||
diesel::joinable!(evm_token_transfer_volume_limit -> evm_token_transfer_grant (grant_id));
|
diesel::joinable!(evm_token_transfer_volume_limit -> evm_token_transfer_grant (grant_id));
|
||||||
|
diesel::joinable!(evm_transaction_log -> evm_basic_grant (grant_id));
|
||||||
|
diesel::joinable!(evm_transaction_log -> evm_wallet_visibility (visibility_id));
|
||||||
diesel::joinable!(evm_wallet -> aead_encrypted (aead_encrypted_id));
|
diesel::joinable!(evm_wallet -> aead_encrypted (aead_encrypted_id));
|
||||||
|
diesel::joinable!(evm_wallet_visibility -> evm_wallet (wallet_id));
|
||||||
|
diesel::joinable!(evm_wallet_visibility -> program_client (client_id));
|
||||||
|
diesel::joinable!(program_client -> client_metadata (metadata_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
aead_encrypted,
|
aead_encrypted,
|
||||||
arbiter_settings,
|
arbiter_settings,
|
||||||
|
client_metadata,
|
||||||
|
client_metadata_history,
|
||||||
evm_basic_grant,
|
evm_basic_grant,
|
||||||
evm_ether_transfer_grant,
|
evm_ether_transfer_grant,
|
||||||
evm_ether_transfer_grant_target,
|
evm_ether_transfer_grant_target,
|
||||||
@@ -183,6 +218,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||||||
evm_token_transfer_volume_limit,
|
evm_token_transfer_volume_limit,
|
||||||
evm_transaction_log,
|
evm_transaction_log,
|
||||||
evm_wallet,
|
evm_wallet,
|
||||||
|
evm_wallet_visibility,
|
||||||
program_client,
|
program_client,
|
||||||
root_key_history,
|
root_key_history,
|
||||||
tls_history,
|
tls_history,
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ use alloy::{
|
|||||||
primitives::{TxKind, U256},
|
primitives::{TxKind, U256},
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, QueryResult, insert_into, sqlite::Sqlite};
|
use diesel::{ExpressionMethods as _, QueryDsl as _, QueryResult, insert_into, sqlite::Sqlite};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
models::{EvmBasicGrant, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp},
|
models::{
|
||||||
|
EvmBasicGrant, EvmWalletVisibility, NewEvmBasicGrant, NewEvmTransactionLog,
|
||||||
|
SqliteTimestamp,
|
||||||
|
},
|
||||||
schema::{self, evm_transaction_log},
|
schema::{self, evm_transaction_log},
|
||||||
},
|
},
|
||||||
evm::policies::{
|
evm::policies::{
|
||||||
@@ -184,8 +187,7 @@ impl Engine {
|
|||||||
let log_id: i32 = insert_into(evm_transaction_log::table)
|
let log_id: i32 = insert_into(evm_transaction_log::table)
|
||||||
.values(&NewEvmTransactionLog {
|
.values(&NewEvmTransactionLog {
|
||||||
grant_id: grant.shared_grant_id,
|
grant_id: grant.shared_grant_id,
|
||||||
client_id: context.client_id,
|
visibility_id: context.target.id,
|
||||||
wallet_id: context.wallet_id,
|
|
||||||
chain_id: context.chain as i32,
|
chain_id: context.chain as i32,
|
||||||
eth_value: utils::u256_to_bytes(context.value).to_vec(),
|
eth_value: utils::u256_to_bytes(context.value).to_vec(),
|
||||||
signed_at: Utc::now().into(),
|
signed_at: Utc::now().into(),
|
||||||
@@ -213,7 +215,6 @@ impl Engine {
|
|||||||
|
|
||||||
pub async fn create_grant<P: Policy>(
|
pub async fn create_grant<P: Policy>(
|
||||||
&self,
|
&self,
|
||||||
client_id: i32,
|
|
||||||
full_grant: FullGrant<P::Settings>,
|
full_grant: FullGrant<P::Settings>,
|
||||||
) -> Result<i32, CreationError> {
|
) -> Result<i32, CreationError> {
|
||||||
let mut conn = self.db.get().await?;
|
let mut conn = self.db.get().await?;
|
||||||
@@ -225,9 +226,8 @@ impl Engine {
|
|||||||
|
|
||||||
let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table)
|
let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table)
|
||||||
.values(&NewEvmBasicGrant {
|
.values(&NewEvmBasicGrant {
|
||||||
wallet_id: full_grant.basic.wallet_id,
|
|
||||||
chain_id: full_grant.basic.chain as i32,
|
chain_id: full_grant.basic.chain as i32,
|
||||||
client_id,
|
visibility_id: full_grant.basic.visibility_id,
|
||||||
valid_from: full_grant.basic.valid_from.map(SqliteTimestamp),
|
valid_from: full_grant.basic.valid_from.map(SqliteTimestamp),
|
||||||
valid_until: full_grant.basic.valid_until.map(SqliteTimestamp),
|
valid_until: full_grant.basic.valid_until.map(SqliteTimestamp),
|
||||||
max_gas_fee_per_gas: full_grant
|
max_gas_fee_per_gas: full_grant
|
||||||
@@ -295,8 +295,7 @@ impl Engine {
|
|||||||
|
|
||||||
pub async fn evaluate_transaction(
|
pub async fn evaluate_transaction(
|
||||||
&self,
|
&self,
|
||||||
wallet_id: i32,
|
target: EvmWalletVisibility,
|
||||||
client_id: i32,
|
|
||||||
transaction: TxEip1559,
|
transaction: TxEip1559,
|
||||||
run_kind: RunKind,
|
run_kind: RunKind,
|
||||||
) -> Result<SpecificMeaning, VetError> {
|
) -> Result<SpecificMeaning, VetError> {
|
||||||
@@ -304,8 +303,7 @@ impl Engine {
|
|||||||
return Err(VetError::ContractCreationNotSupported);
|
return Err(VetError::ContractCreationNotSupported);
|
||||||
};
|
};
|
||||||
let context = policies::EvalContext {
|
let context = policies::EvalContext {
|
||||||
wallet_id,
|
target,
|
||||||
client_id,
|
|
||||||
chain: transaction.chain_id,
|
chain: transaction.chain_id,
|
||||||
to,
|
to,
|
||||||
value: transaction.value,
|
value: transaction.value,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use miette::Diagnostic;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::models::{self, EvmBasicGrant},
|
db::models::{self, EvmBasicGrant, EvmWalletVisibility},
|
||||||
evm::utils,
|
evm::utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,9 +19,8 @@ pub mod token_transfers;
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EvalContext {
|
pub struct EvalContext {
|
||||||
// Which wallet is this transaction for
|
// Which wallet is this transaction for and who requested it
|
||||||
pub client_id: i32,
|
pub target: EvmWalletVisibility,
|
||||||
pub wallet_id: i32,
|
|
||||||
|
|
||||||
// The transaction data
|
// The transaction data
|
||||||
pub chain: ChainId,
|
pub chain: ChainId,
|
||||||
@@ -145,8 +144,7 @@ pub struct VolumeRateLimit {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct SharedGrantSettings {
|
pub struct SharedGrantSettings {
|
||||||
pub wallet_id: i32,
|
pub visibility_id: i32,
|
||||||
pub client_id: i32,
|
|
||||||
pub chain: ChainId,
|
pub chain: ChainId,
|
||||||
|
|
||||||
pub valid_from: Option<DateTime<Utc>>,
|
pub valid_from: Option<DateTime<Utc>>,
|
||||||
@@ -161,8 +159,7 @@ pub struct SharedGrantSettings {
|
|||||||
impl SharedGrantSettings {
|
impl SharedGrantSettings {
|
||||||
fn try_from_model(model: EvmBasicGrant) -> QueryResult<Self> {
|
fn try_from_model(model: EvmBasicGrant) -> QueryResult<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
wallet_id: model.wallet_id,
|
visibility_id: model.visibility_id,
|
||||||
client_id: model.client_id,
|
|
||||||
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
|
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
|
||||||
valid_from: model.valid_from.map(Into::into),
|
valid_from: model.valid_from.map(Into::into),
|
||||||
valid_until: model.valid_until.map(Into::into),
|
valid_until: model.valid_until.map(Into::into),
|
||||||
|
|||||||
@@ -196,9 +196,8 @@ impl Policy for EtherTransfer {
|
|||||||
.inner_join(evm_basic_grant::table)
|
.inner_join(evm_basic_grant::table)
|
||||||
.inner_join(evm_ether_transfer_grant_target::table)
|
.inner_join(evm_ether_transfer_grant_target::table)
|
||||||
.filter(
|
.filter(
|
||||||
evm_basic_grant::wallet_id
|
evm_basic_grant::visibility_id
|
||||||
.eq(context.wallet_id)
|
.eq(context.target.id)
|
||||||
.and(evm_basic_grant::client_id.eq(context.client_id))
|
|
||||||
.and(evm_basic_grant::revoked_at.is_null())
|
.and(evm_basic_grant::revoked_at.is_null())
|
||||||
.and(evm_ether_transfer_grant_target::address.eq(&target_bytes)),
|
.and(evm_ether_transfer_grant_target::address.eq(&target_bytes)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use diesel_async::RunQueryDsl;
|
|||||||
|
|
||||||
use crate::db::{
|
use crate::db::{
|
||||||
self, DatabaseConnection,
|
self, DatabaseConnection,
|
||||||
models::{EvmBasicGrant, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp},
|
models::{
|
||||||
|
EvmBasicGrant, EvmWalletVisibility, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp,
|
||||||
|
},
|
||||||
schema::{evm_basic_grant, evm_transaction_log},
|
schema::{evm_basic_grant, evm_transaction_log},
|
||||||
};
|
};
|
||||||
use crate::evm::{
|
use crate::evm::{
|
||||||
@@ -15,8 +17,7 @@ use crate::evm::{
|
|||||||
|
|
||||||
use super::{EtherTransfer, Settings};
|
use super::{EtherTransfer, Settings};
|
||||||
|
|
||||||
const WALLET_ID: i32 = 1;
|
const VISIBILITY_ID: i32 = 1;
|
||||||
const CLIENT_ID: i32 = 2;
|
|
||||||
const CHAIN_ID: u64 = 1;
|
const CHAIN_ID: u64 = 1;
|
||||||
|
|
||||||
const ALLOWED: Address = address!("1111111111111111111111111111111111111111");
|
const ALLOWED: Address = address!("1111111111111111111111111111111111111111");
|
||||||
@@ -24,8 +25,12 @@ const OTHER: Address = address!("2222222222222222222222222222222222222222");
|
|||||||
|
|
||||||
fn ctx(to: Address, value: U256) -> EvalContext {
|
fn ctx(to: Address, value: U256) -> EvalContext {
|
||||||
EvalContext {
|
EvalContext {
|
||||||
wallet_id: WALLET_ID,
|
target: EvmWalletVisibility {
|
||||||
client_id: CLIENT_ID,
|
id: VISIBILITY_ID,
|
||||||
|
wallet_id: 10,
|
||||||
|
client_id: 20,
|
||||||
|
created_at: SqliteTimestamp(Utc::now()),
|
||||||
|
},
|
||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
to,
|
to,
|
||||||
value,
|
value,
|
||||||
@@ -38,8 +43,7 @@ fn ctx(to: Address, value: U256) -> EvalContext {
|
|||||||
async fn insert_basic(conn: &mut DatabaseConnection, revoked: bool) -> EvmBasicGrant {
|
async fn insert_basic(conn: &mut DatabaseConnection, revoked: bool) -> EvmBasicGrant {
|
||||||
insert_into(evm_basic_grant::table)
|
insert_into(evm_basic_grant::table)
|
||||||
.values(NewEvmBasicGrant {
|
.values(NewEvmBasicGrant {
|
||||||
wallet_id: WALLET_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
client_id: CLIENT_ID,
|
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
@@ -67,14 +71,13 @@ fn make_settings(targets: Vec<Address>, max_volume: u64) -> Settings {
|
|||||||
|
|
||||||
fn shared() -> SharedGrantSettings {
|
fn shared() -> SharedGrantSettings {
|
||||||
SharedGrantSettings {
|
SharedGrantSettings {
|
||||||
wallet_id: WALLET_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
max_gas_fee_per_gas: None,
|
max_gas_fee_per_gas: None,
|
||||||
max_priority_fee_per_gas: None,
|
max_priority_fee_per_gas: None,
|
||||||
rate_limit: None,
|
rate_limit: None,
|
||||||
client_id: CLIENT_ID,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +156,7 @@ async fn evaluate_passes_when_volume_within_limit() {
|
|||||||
insert_into(evm_transaction_log::table)
|
insert_into(evm_transaction_log::table)
|
||||||
.values(NewEvmTransactionLog {
|
.values(NewEvmTransactionLog {
|
||||||
grant_id,
|
grant_id,
|
||||||
client_id: CLIENT_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
wallet_id: WALLET_ID,
|
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
eth_value: utils::u256_to_bytes(U256::from(500u64)).to_vec(),
|
eth_value: utils::u256_to_bytes(U256::from(500u64)).to_vec(),
|
||||||
signed_at: SqliteTimestamp(Utc::now()),
|
signed_at: SqliteTimestamp(Utc::now()),
|
||||||
@@ -194,8 +196,7 @@ async fn evaluate_rejects_volume_over_limit() {
|
|||||||
insert_into(evm_transaction_log::table)
|
insert_into(evm_transaction_log::table)
|
||||||
.values(NewEvmTransactionLog {
|
.values(NewEvmTransactionLog {
|
||||||
grant_id,
|
grant_id,
|
||||||
client_id: CLIENT_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
wallet_id: WALLET_ID,
|
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
eth_value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(),
|
eth_value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(),
|
||||||
signed_at: SqliteTimestamp(Utc::now()),
|
signed_at: SqliteTimestamp(Utc::now()),
|
||||||
@@ -236,8 +237,7 @@ async fn evaluate_passes_at_exactly_volume_limit() {
|
|||||||
insert_into(evm_transaction_log::table)
|
insert_into(evm_transaction_log::table)
|
||||||
.values(NewEvmTransactionLog {
|
.values(NewEvmTransactionLog {
|
||||||
grant_id,
|
grant_id,
|
||||||
client_id: CLIENT_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
wallet_id: WALLET_ID,
|
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(),
|
eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(),
|
||||||
signed_at: SqliteTimestamp(Utc::now()),
|
signed_at: SqliteTimestamp(Utc::now()),
|
||||||
|
|||||||
@@ -209,8 +209,7 @@ impl Policy for TokenTransfer {
|
|||||||
|
|
||||||
let grant: Option<(EvmBasicGrant, EvmTokenTransferGrant)> = grant_join()
|
let grant: Option<(EvmBasicGrant, EvmTokenTransferGrant)> = grant_join()
|
||||||
.filter(evm_basic_grant::revoked_at.is_null())
|
.filter(evm_basic_grant::revoked_at.is_null())
|
||||||
.filter(evm_basic_grant::wallet_id.eq(context.wallet_id))
|
.filter(evm_basic_grant::visibility_id.eq(context.target.id))
|
||||||
.filter(evm_basic_grant::client_id.eq(context.client_id))
|
|
||||||
.filter(evm_token_transfer_grant::token_contract.eq(&token_contract_bytes))
|
.filter(evm_token_transfer_grant::token_contract.eq(&token_contract_bytes))
|
||||||
.select((
|
.select((
|
||||||
EvmBasicGrant::as_select(),
|
EvmBasicGrant::as_select(),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use diesel_async::RunQueryDsl;
|
|||||||
|
|
||||||
use crate::db::{
|
use crate::db::{
|
||||||
self, DatabaseConnection,
|
self, DatabaseConnection,
|
||||||
models::{EvmBasicGrant, NewEvmBasicGrant, SqliteTimestamp},
|
models::{EvmBasicGrant, EvmWalletVisibility, NewEvmBasicGrant, SqliteTimestamp},
|
||||||
schema::evm_basic_grant,
|
schema::evm_basic_grant,
|
||||||
};
|
};
|
||||||
use crate::evm::{
|
use crate::evm::{
|
||||||
@@ -21,8 +21,7 @@ use super::{Settings, TokenTransfer};
|
|||||||
const CHAIN_ID: u64 = 1;
|
const CHAIN_ID: u64 = 1;
|
||||||
const DAI: Address = address!("6B175474E89094C44Da98b954EedeAC495271d0F");
|
const DAI: Address = address!("6B175474E89094C44Da98b954EedeAC495271d0F");
|
||||||
|
|
||||||
const WALLET_ID: i32 = 1;
|
const VISIBILITY_ID: i32 = 1;
|
||||||
const CLIENT_ID: i32 = 2;
|
|
||||||
|
|
||||||
const RECIPIENT: Address = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
const RECIPIENT: Address = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||||
const OTHER: Address = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
const OTHER: Address = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
||||||
@@ -38,8 +37,12 @@ fn transfer_calldata(to: Address, value: U256) -> Bytes {
|
|||||||
|
|
||||||
fn ctx(to: Address, calldata: Bytes) -> EvalContext {
|
fn ctx(to: Address, calldata: Bytes) -> EvalContext {
|
||||||
EvalContext {
|
EvalContext {
|
||||||
wallet_id: WALLET_ID,
|
target: EvmWalletVisibility {
|
||||||
client_id: CLIENT_ID,
|
id: VISIBILITY_ID,
|
||||||
|
wallet_id: 10,
|
||||||
|
client_id: 20,
|
||||||
|
created_at: SqliteTimestamp(Utc::now()),
|
||||||
|
},
|
||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
to,
|
to,
|
||||||
value: U256::ZERO,
|
value: U256::ZERO,
|
||||||
@@ -52,8 +55,7 @@ fn ctx(to: Address, calldata: Bytes) -> EvalContext {
|
|||||||
async fn insert_basic(conn: &mut DatabaseConnection, revoked: bool) -> EvmBasicGrant {
|
async fn insert_basic(conn: &mut DatabaseConnection, revoked: bool) -> EvmBasicGrant {
|
||||||
insert_into(evm_basic_grant::table)
|
insert_into(evm_basic_grant::table)
|
||||||
.values(NewEvmBasicGrant {
|
.values(NewEvmBasicGrant {
|
||||||
wallet_id: WALLET_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
client_id: CLIENT_ID,
|
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
@@ -86,14 +88,13 @@ fn make_settings(target: Option<Address>, max_volume: Option<u64>) -> Settings {
|
|||||||
|
|
||||||
fn shared() -> SharedGrantSettings {
|
fn shared() -> SharedGrantSettings {
|
||||||
SharedGrantSettings {
|
SharedGrantSettings {
|
||||||
wallet_id: WALLET_ID,
|
visibility_id: VISIBILITY_ID,
|
||||||
chain: CHAIN_ID,
|
chain: CHAIN_ID,
|
||||||
valid_from: None,
|
valid_from: None,
|
||||||
valid_until: None,
|
valid_until: None,
|
||||||
max_gas_fee_per_gas: None,
|
max_gas_fee_per_gas: None,
|
||||||
max_priority_fee_per_gas: None,
|
max_priority_fee_per_gas: None,
|
||||||
rate_limit: None,
|
rate_limit: None,
|
||||||
client_id: CLIENT_ID,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,9 +110,8 @@ async fn dispatch_conn_message(
|
|||||||
pub async fn start(conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientResponse>) {
|
pub async fn start(conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientResponse>) {
|
||||||
let mut conn = conn;
|
let mut conn = conn;
|
||||||
let mut request_tracker = RequestTracker::default();
|
let mut request_tracker = RequestTracker::default();
|
||||||
let mut response_id = None;
|
|
||||||
|
|
||||||
match auth::start(&mut conn, &mut bi, &mut request_tracker, &mut response_id).await {
|
match auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let actor =
|
let actor =
|
||||||
client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
|
client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
|
||||||
@@ -125,11 +124,7 @@ pub async fn start(conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientR
|
|||||||
dispatch_loop(bi, actor, request_tracker).await;
|
dispatch_loop(bi, actor, request_tracker).await;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut transport = auth::AuthTransportAdapter::new(
|
let mut transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
|
||||||
&mut bi,
|
|
||||||
&mut request_tracker,
|
|
||||||
&mut response_id,
|
|
||||||
);
|
|
||||||
let _ = transport.send(Err(e.clone())).await;
|
let _ = transport.send(Err(e.clone())).await;
|
||||||
warn!(error = ?e, "Authentication failed");
|
warn!(error = ?e, "Authentication failed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use arbiter_proto::{
|
|||||||
proto::client::{
|
proto::client::{
|
||||||
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
ClientRequest, ClientResponse, client_request::Payload as ClientRequestPayload,
|
ClientInfo as ProtoClientInfo, ClientRequest, ClientResponse,
|
||||||
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
},
|
},
|
||||||
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
@@ -19,19 +20,16 @@ use crate::{
|
|||||||
pub struct AuthTransportAdapter<'a> {
|
pub struct AuthTransportAdapter<'a> {
|
||||||
bi: &'a mut GrpcBi<ClientRequest, ClientResponse>,
|
bi: &'a mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
request_tracker: &'a mut RequestTracker,
|
request_tracker: &'a mut RequestTracker,
|
||||||
response_id: &'a mut Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AuthTransportAdapter<'a> {
|
impl<'a> AuthTransportAdapter<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bi: &'a mut GrpcBi<ClientRequest, ClientResponse>,
|
bi: &'a mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
request_tracker: &'a mut RequestTracker,
|
request_tracker: &'a mut RequestTracker,
|
||||||
response_id: &'a mut Option<i32>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bi,
|
bi,
|
||||||
request_tracker,
|
request_tracker,
|
||||||
response_id,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +70,9 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
payload: ClientResponsePayload,
|
payload: ClientResponsePayload,
|
||||||
) -> Result<(), TransportError> {
|
) -> Result<(), TransportError> {
|
||||||
let request_id = self.response_id.take();
|
|
||||||
|
|
||||||
self.bi
|
self.bi
|
||||||
.send(Ok(ClientResponse {
|
.send(Ok(ClientResponse {
|
||||||
request_id,
|
request_id: Some(self.request_tracker.current_request_id()),
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
@@ -114,19 +110,27 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let request_id = match self.request_tracker.request(request.request_id) {
|
match self.request_tracker.request(request.request_id) {
|
||||||
Ok(request_id) => request_id,
|
Ok(request_id) => request_id,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let _ = self.bi.send(Err(error)).await;
|
let _ = self.bi.send(Err(error)).await;
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*self.response_id = Some(request_id);
|
|
||||||
|
|
||||||
let payload = request.payload?;
|
let payload = request.payload?;
|
||||||
|
|
||||||
match payload {
|
match payload {
|
||||||
ClientRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest { pubkey }) => {
|
ClientRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest {
|
||||||
|
pubkey,
|
||||||
|
client_info,
|
||||||
|
}) => {
|
||||||
|
let Some(client_info) = client_info else {
|
||||||
|
let _ = self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::invalid_argument("Missing client info")))
|
||||||
|
.await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
let Ok(pubkey) = <[u8; 32]>::try_from(pubkey) else {
|
let Ok(pubkey) = <[u8; 32]>::try_from(pubkey) else {
|
||||||
let _ = self.send_auth_result(ProtoAuthResult::InvalidKey).await;
|
let _ = self.send_auth_result(ProtoAuthResult::InvalidKey).await;
|
||||||
return None;
|
return None;
|
||||||
@@ -135,7 +139,10 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
let _ = self.send_auth_result(ProtoAuthResult::InvalidKey).await;
|
let _ = self.send_auth_result(ProtoAuthResult::InvalidKey).await;
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(auth::Inbound::AuthChallengeRequest { pubkey })
|
Some(auth::Inbound::AuthChallengeRequest {
|
||||||
|
pubkey,
|
||||||
|
metadata: client_metadata_from_proto(client_info),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
ClientRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution {
|
ClientRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
signature,
|
signature,
|
||||||
@@ -151,7 +158,9 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
_ => {
|
_ => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.bi
|
.bi
|
||||||
.send(Err(Status::invalid_argument("Unsupported client auth request")))
|
.send(Err(Status::invalid_argument(
|
||||||
|
"Unsupported client auth request",
|
||||||
|
)))
|
||||||
.await;
|
.await;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -161,13 +170,20 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
|
|
||||||
impl Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {}
|
impl Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {}
|
||||||
|
|
||||||
|
fn client_metadata_from_proto(metadata: ProtoClientInfo) -> auth::ClientMetadata {
|
||||||
|
auth::ClientMetadata {
|
||||||
|
name: metadata.name,
|
||||||
|
description: (!metadata.description.is_empty()).then_some(metadata.description),
|
||||||
|
version: (!metadata.version.is_empty()).then_some(metadata.version),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start(
|
pub async fn start(
|
||||||
conn: &mut ClientConnection,
|
conn: &mut ClientConnection,
|
||||||
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
request_tracker: &mut RequestTracker,
|
request_tracker: &mut RequestTracker,
|
||||||
response_id: &mut Option<i32>,
|
|
||||||
) -> Result<(), auth::Error> {
|
) -> Result<(), auth::Error> {
|
||||||
let mut transport = AuthTransportAdapter::new(bi, request_tracker, response_id);
|
let mut transport = AuthTransportAdapter::new(bi, request_tracker);
|
||||||
client::auth::authenticate(conn, &mut transport).await?;
|
client::auth::authenticate(conn, &mut transport).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,10 @@ impl RequestTracker {
|
|||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is used to set the response id for auth responses, which need to match the request id of the auth challenge request.
|
||||||
|
// -1 offset is needed because request() increments the next_request_id after returning the current request id.
|
||||||
|
pub fn current_request_id(&self) -> i32 {
|
||||||
|
self.next_request_id - 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,11 +241,7 @@ async fn dispatch_conn_message(
|
|||||||
UserAgentRequestPayload::EvmGrantList(_) => UserAgentResponsePayload::EvmGrantList(
|
UserAgentRequestPayload::EvmGrantList(_) => UserAgentResponsePayload::EvmGrantList(
|
||||||
EvmGrantOrWallet::grant_list_response(actor.ask(HandleGrantList {}).await),
|
EvmGrantOrWallet::grant_list_response(actor.ask(HandleGrantList {}).await),
|
||||||
),
|
),
|
||||||
UserAgentRequestPayload::EvmGrantCreate(EvmGrantCreateRequest {
|
UserAgentRequestPayload::EvmGrantCreate(EvmGrantCreateRequest { shared, specific }) => {
|
||||||
client_id,
|
|
||||||
shared,
|
|
||||||
specific,
|
|
||||||
}) => {
|
|
||||||
let (basic, grant) = match parse_grant_request(shared, specific) {
|
let (basic, grant) = match parse_grant_request(shared, specific) {
|
||||||
Ok(values) => values,
|
Ok(values) => values,
|
||||||
Err(status) => {
|
Err(status) => {
|
||||||
@@ -255,13 +251,7 @@ async fn dispatch_conn_message(
|
|||||||
};
|
};
|
||||||
|
|
||||||
UserAgentResponsePayload::EvmGrantCreate(EvmGrantOrWallet::grant_create_response(
|
UserAgentResponsePayload::EvmGrantCreate(EvmGrantOrWallet::grant_create_response(
|
||||||
actor
|
actor.ask(HandleGrantCreate { basic, grant }).await,
|
||||||
.ask(HandleGrantCreate {
|
|
||||||
client_id,
|
|
||||||
basic,
|
|
||||||
grant,
|
|
||||||
})
|
|
||||||
.await,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
UserAgentRequestPayload::EvmGrantDelete(EvmGrantDeleteRequest { grant_id }) => {
|
UserAgentRequestPayload::EvmGrantDelete(EvmGrantDeleteRequest { grant_id }) => {
|
||||||
@@ -296,6 +286,7 @@ async fn send_out_of_band(
|
|||||||
OutOfBand::ClientConnectionRequest { pubkey } => {
|
OutOfBand::ClientConnectionRequest { pubkey } => {
|
||||||
UserAgentResponsePayload::ClientConnectionRequest(ClientConnectionRequest {
|
UserAgentResponsePayload::ClientConnectionRequest(ClientConnectionRequest {
|
||||||
pubkey: pubkey.to_bytes().to_vec(),
|
pubkey: pubkey.to_bytes().to_vec(),
|
||||||
|
info: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
OutOfBand::ClientConnectionCancel => {
|
OutOfBand::ClientConnectionCancel => {
|
||||||
@@ -327,8 +318,7 @@ fn parse_grant_request(
|
|||||||
|
|
||||||
fn shared_settings_from_proto(shared: ProtoSharedSettings) -> Result<SharedGrantSettings, Status> {
|
fn shared_settings_from_proto(shared: ProtoSharedSettings) -> Result<SharedGrantSettings, Status> {
|
||||||
Ok(SharedGrantSettings {
|
Ok(SharedGrantSettings {
|
||||||
wallet_id: shared.wallet_id,
|
visibility_id: shared.visibility_id,
|
||||||
client_id: 0,
|
|
||||||
chain: shared.chain_id,
|
chain: shared.chain_id,
|
||||||
valid_from: shared.valid_from.map(proto_timestamp_to_utc).transpose()?,
|
valid_from: shared.valid_from.map(proto_timestamp_to_utc).transpose()?,
|
||||||
valid_until: shared.valid_until.map(proto_timestamp_to_utc).transpose()?,
|
valid_until: shared.valid_until.map(proto_timestamp_to_utc).transpose()?,
|
||||||
@@ -412,7 +402,7 @@ fn proto_timestamp_to_utc(
|
|||||||
|
|
||||||
fn shared_settings_to_proto(shared: SharedGrantSettings) -> ProtoSharedSettings {
|
fn shared_settings_to_proto(shared: SharedGrantSettings) -> ProtoSharedSettings {
|
||||||
ProtoSharedSettings {
|
ProtoSharedSettings {
|
||||||
wallet_id: shared.wallet_id,
|
visibility_id: shared.visibility_id,
|
||||||
chain_id: shared.chain,
|
chain_id: shared.chain,
|
||||||
valid_from: shared.valid_from.map(|time| prost_types::Timestamp {
|
valid_from: shared.valid_from.map(|time| prost_types::Timestamp {
|
||||||
seconds: time.timestamp(),
|
seconds: time.timestamp(),
|
||||||
@@ -552,7 +542,7 @@ impl EvmGrantOrWallet {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|grant| GrantEntry {
|
.map(|grant| GrantEntry {
|
||||||
id: grant.id,
|
id: grant.id,
|
||||||
client_id: grant.shared.client_id,
|
visibility_id: grant.shared.visibility_id,
|
||||||
shared: Some(shared_settings_to_proto(grant.shared)),
|
shared: Some(shared_settings_to_proto(grant.shared)),
|
||||||
specific: Some(specific_grant_to_proto(grant.settings)),
|
specific: Some(specific_grant_to_proto(grant.settings)),
|
||||||
})
|
})
|
||||||
@@ -575,15 +565,8 @@ pub async fn start(
|
|||||||
mut bi: GrpcBi<UserAgentRequest, UserAgentResponse>,
|
mut bi: GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
) {
|
) {
|
||||||
let mut request_tracker = RequestTracker::default();
|
let mut request_tracker = RequestTracker::default();
|
||||||
let mut response_id = None;
|
|
||||||
|
|
||||||
let pubkey = match auth::start(
|
let pubkey = match auth::start(&mut conn, &mut bi, &mut request_tracker).await
|
||||||
&mut conn,
|
|
||||||
&mut bi,
|
|
||||||
&mut request_tracker,
|
|
||||||
&mut response_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
Ok(pubkey) => pubkey,
|
Ok(pubkey) => pubkey,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -21,19 +21,16 @@ use crate::{
|
|||||||
pub struct AuthTransportAdapter<'a> {
|
pub struct AuthTransportAdapter<'a> {
|
||||||
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
request_tracker: &'a mut RequestTracker,
|
request_tracker: &'a mut RequestTracker,
|
||||||
response_id: &'a mut Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AuthTransportAdapter<'a> {
|
impl<'a> AuthTransportAdapter<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
request_tracker: &'a mut RequestTracker,
|
request_tracker: &'a mut RequestTracker,
|
||||||
response_id: &'a mut Option<i32>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bi,
|
bi,
|
||||||
request_tracker,
|
request_tracker,
|
||||||
response_id,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,11 +38,9 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
payload: UserAgentResponsePayload,
|
payload: UserAgentResponsePayload,
|
||||||
) -> Result<(), TransportError> {
|
) -> Result<(), TransportError> {
|
||||||
let id = self.response_id.take();
|
|
||||||
|
|
||||||
self.bi
|
self.bi
|
||||||
.send(Ok(UserAgentResponse {
|
.send(Ok(UserAgentResponse {
|
||||||
id,
|
id: Some(self.request_tracker.current_request_id()),
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
@@ -75,9 +70,14 @@ impl Sender<Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {
|
|||||||
Err(Error::InvalidBootstrapToken) => {
|
Err(Error::InvalidBootstrapToken) => {
|
||||||
UserAgentResponsePayload::AuthResult(ProtoAuthResult::TokenInvalid.into())
|
UserAgentResponsePayload::AuthResult(ProtoAuthResult::TokenInvalid.into())
|
||||||
}
|
}
|
||||||
Err(Error::Internal { details }) => return self.bi.send(Err(Status::internal(details))).await,
|
Err(Error::Internal { details }) => {
|
||||||
|
return self.bi.send(Err(Status::internal(details))).await;
|
||||||
|
}
|
||||||
Err(Error::Transport) => {
|
Err(Error::Transport) => {
|
||||||
return self.bi.send(Err(Status::unavailable("transport error"))).await;
|
return self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::unavailable("transport error")))
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,14 +96,13 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let request_id = match self.request_tracker.request(request.id) {
|
match self.request_tracker.request(request.id) {
|
||||||
Ok(request_id) => request_id,
|
Ok(request_id) => request_id,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let _ = self.bi.send(Err(error)).await;
|
let _ = self.bi.send(Err(error)).await;
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*self.response_id = Some(request_id);
|
|
||||||
|
|
||||||
let Some(payload) = request.payload else {
|
let Some(payload) = request.payload else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -173,8 +172,7 @@ pub async fn start(
|
|||||||
conn: &mut UserAgentConnection,
|
conn: &mut UserAgentConnection,
|
||||||
bi: &mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
bi: &mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
request_tracker: &mut RequestTracker,
|
request_tracker: &mut RequestTracker,
|
||||||
response_id: &mut Option<i32>,
|
|
||||||
) -> Result<AuthPublicKey, auth::Error> {
|
) -> Result<AuthPublicKey, auth::Error> {
|
||||||
let transport = AuthTransportAdapter::new(bi, request_tracker, response_id);
|
let transport = AuthTransportAdapter::new(bi, request_tracker);
|
||||||
auth::authenticate(conn, transport).await
|
auth::authenticate(conn, transport).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ pub mod grpc;
|
|||||||
pub mod safe_cell;
|
pub mod safe_cell;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
#[allow(dead_code, reason = "Reserved as the shared default channel size while server wiring is still being consolidated")]
|
|
||||||
const DEFAULT_CHANNEL_SIZE: usize = 1000;
|
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
context: ServerContext,
|
context: ServerContext,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,50 @@ use arbiter_proto::transport::{Receiver, Sender};
|
|||||||
use arbiter_server::actors::GlobalActors;
|
use arbiter_server::actors::GlobalActors;
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::client::{ClientConnection, auth, connect_client},
|
actors::client::{ClientConnection, auth, connect_client},
|
||||||
db::{self, schema},
|
db,
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, insert_into};
|
use diesel::{ExpressionMethods as _, NullableExpressionMethods as _, QueryDsl as _, insert_into};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use ed25519_dalek::Signer as _;
|
use ed25519_dalek::Signer as _;
|
||||||
|
|
||||||
use super::common::ChannelTransport;
|
use super::common::ChannelTransport;
|
||||||
|
|
||||||
|
fn metadata(name: &str, description: Option<&str>, version: Option<&str>) -> auth::ClientMetadata {
|
||||||
|
auth::ClientMetadata {
|
||||||
|
name: name.to_owned(),
|
||||||
|
description: description.map(str::to_owned),
|
||||||
|
version: version.map(str::to_owned),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_registered_client(
|
||||||
|
db: &db::DatabasePool,
|
||||||
|
pubkey: Vec<u8>,
|
||||||
|
metadata: &auth::ClientMetadata,
|
||||||
|
) {
|
||||||
|
use arbiter_server::db::schema::{client_metadata, program_client};
|
||||||
|
|
||||||
|
let mut conn = db.get().await.unwrap();
|
||||||
|
let metadata_id: i32 = insert_into(client_metadata::table)
|
||||||
|
.values((
|
||||||
|
client_metadata::name.eq(&metadata.name),
|
||||||
|
client_metadata::description.eq(&metadata.description),
|
||||||
|
client_metadata::version.eq(&metadata.version),
|
||||||
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
insert_into(program_client::table)
|
||||||
|
.values((
|
||||||
|
program_client::public_key.eq(pubkey),
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_unregistered_pubkey_rejected() {
|
pub async fn test_unregistered_pubkey_rejected() {
|
||||||
@@ -28,6 +64,7 @@ pub async fn test_unregistered_pubkey_rejected() {
|
|||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: new_key.verifying_key(),
|
pubkey: new_key.verifying_key(),
|
||||||
|
metadata: metadata("client", Some("desc"), Some("1.0.0")),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -44,14 +81,12 @@ pub async fn test_challenge_auth() {
|
|||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
||||||
|
|
||||||
{
|
insert_registered_client(
|
||||||
let mut conn = db.get().await.unwrap();
|
&db,
|
||||||
insert_into(schema::program_client::table)
|
pubkey_bytes.clone(),
|
||||||
.values(schema::program_client::public_key.eq(pubkey_bytes.clone()))
|
&metadata("client", Some("desc"), Some("1.0.0")),
|
||||||
.execute(&mut conn)
|
)
|
||||||
.await
|
.await;
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
@@ -66,6 +101,7 @@ pub async fn test_challenge_auth() {
|
|||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: new_key.verifying_key(),
|
pubkey: new_key.verifying_key(),
|
||||||
|
metadata: metadata("client", Some("desc"), Some("1.0.0")),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -105,3 +141,187 @@ pub async fn test_challenge_auth() {
|
|||||||
// Auth completes, session spawned
|
// Auth completes, session spawned
|
||||||
task.await.unwrap();
|
task.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[test_log::test]
|
||||||
|
pub async fn test_metadata_unchanged_does_not_append_history() {
|
||||||
|
let db = db::create_test_pool().await;
|
||||||
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
|
|
||||||
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
|
let requested = metadata("client", Some("desc"), Some("1.0.0"));
|
||||||
|
|
||||||
|
{
|
||||||
|
use arbiter_server::db::schema::{
|
||||||
|
client_metadata, program_client,
|
||||||
|
};
|
||||||
|
let mut conn = db.get().await.unwrap();
|
||||||
|
let metadata_id: i32 = insert_into(client_metadata::table)
|
||||||
|
.values((
|
||||||
|
client_metadata::name.eq(&requested.name),
|
||||||
|
client_metadata::description.eq(&requested.description),
|
||||||
|
client_metadata::version.eq(&requested.version),
|
||||||
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
insert_into(program_client::table)
|
||||||
|
.values((
|
||||||
|
program_client::public_key.eq(new_key.verifying_key().to_bytes().to_vec()),
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
|
let task = tokio::spawn(async move {
|
||||||
|
let mut server_transport = server_transport;
|
||||||
|
connect_client(props, &mut server_transport).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
test_transport
|
||||||
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
|
pubkey: new_key.verifying_key(),
|
||||||
|
metadata: requested,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = test_transport.recv().await.unwrap().unwrap();
|
||||||
|
let (pubkey, nonce) = match response {
|
||||||
|
auth::Outbound::AuthChallenge { pubkey, nonce } => (pubkey, nonce),
|
||||||
|
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||||
|
};
|
||||||
|
let signature = new_key.sign(&arbiter_proto::format_challenge(nonce, pubkey.as_bytes()));
|
||||||
|
test_transport
|
||||||
|
.send(auth::Inbound::AuthChallengeSolution { signature })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let _ = test_transport.recv().await.unwrap();
|
||||||
|
task.await.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
use arbiter_server::db::schema::{client_metadata, client_metadata_history};
|
||||||
|
let mut conn = db.get().await.unwrap();
|
||||||
|
let metadata_count: i64 = client_metadata::table
|
||||||
|
.count()
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let history_count: i64 = client_metadata_history::table
|
||||||
|
.count()
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(metadata_count, 1);
|
||||||
|
assert_eq!(history_count, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[test_log::test]
|
||||||
|
pub async fn test_metadata_change_appends_history_and_repoints_binding() {
|
||||||
|
let db = db::create_test_pool().await;
|
||||||
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
|
|
||||||
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
|
|
||||||
|
{
|
||||||
|
use arbiter_server::db::schema::{
|
||||||
|
client_metadata, program_client,
|
||||||
|
};
|
||||||
|
let mut conn = db.get().await.unwrap();
|
||||||
|
let metadata_id: i32 = insert_into(client_metadata::table)
|
||||||
|
.values((
|
||||||
|
client_metadata::name.eq("client"),
|
||||||
|
client_metadata::description.eq(Some("old")),
|
||||||
|
client_metadata::version.eq(Some("1.0.0")),
|
||||||
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
insert_into(program_client::table)
|
||||||
|
.values((
|
||||||
|
program_client::public_key.eq(new_key.verifying_key().to_bytes().to_vec()),
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
|
let task = tokio::spawn(async move {
|
||||||
|
let mut server_transport = server_transport;
|
||||||
|
connect_client(props, &mut server_transport).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
test_transport
|
||||||
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
|
pubkey: new_key.verifying_key(),
|
||||||
|
metadata: metadata("client", Some("new"), Some("2.0.0")),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = test_transport.recv().await.unwrap().unwrap();
|
||||||
|
let (pubkey, nonce) = match response {
|
||||||
|
auth::Outbound::AuthChallenge { pubkey, nonce } => (pubkey, nonce),
|
||||||
|
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||||
|
};
|
||||||
|
let signature = new_key.sign(&arbiter_proto::format_challenge(nonce, pubkey.as_bytes()));
|
||||||
|
test_transport
|
||||||
|
.send(auth::Inbound::AuthChallengeSolution { signature })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let _ = test_transport.recv().await.unwrap();
|
||||||
|
task.await.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
use arbiter_server::db::schema::{
|
||||||
|
client_metadata, client_metadata_history, program_client,
|
||||||
|
};
|
||||||
|
let mut conn = db.get().await.unwrap();
|
||||||
|
let metadata_count: i64 = client_metadata::table
|
||||||
|
.count()
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let history_count: i64 = client_metadata_history::table
|
||||||
|
.count()
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let metadata_id = program_client::table
|
||||||
|
.select(program_client::metadata_id)
|
||||||
|
.first::<i32>(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let current = client_metadata::table
|
||||||
|
.find(metadata_id)
|
||||||
|
.select((
|
||||||
|
client_metadata::name,
|
||||||
|
client_metadata::description.nullable(),
|
||||||
|
client_metadata::version.nullable(),
|
||||||
|
))
|
||||||
|
.first::<(String, Option<String>, Option<String>)>(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(metadata_count, 2);
|
||||||
|
assert_eq!(history_count, 1);
|
||||||
|
assert_eq!(
|
||||||
|
current,
|
||||||
|
(
|
||||||
|
"client".to_owned(),
|
||||||
|
Some("new".to_owned()),
|
||||||
|
Some("2.0.0".to_owned())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
pub mod nonfungible;
|
pub mod nonfungible;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
pub mod evm;
|
pub mod evm;
|
||||||
|
|||||||
Reference in New Issue
Block a user