diff --git a/Cargo.lock b/Cargo.lock index e79753c..3fb9195 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2668,6 +2668,7 @@ version = "0.2.0" dependencies = [ "anyhow", "axum", + "base64 0.21.4", "bytes", "color-eyre", "config", diff --git a/Cargo.toml b/Cargo.toml index bdf9724..6adb66d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] anyhow = "1.0.69" axum = "0.6.20" +base64 = "0.21.4" bytes = "1.4.0" color-eyre = "0.6" config = "*" diff --git a/config.toml b/config.toml index 99c1dfd..c785f55 100644 --- a/config.toml +++ b/config.toml @@ -55,17 +55,3 @@ rpc_url = "https://archival-rpc.testnet.near.org" wallet_url = "https://wallet.testnet.near.org" explorer_transaction_url = "https://explorer.testnet.near.org/transactions/" rpc_api_key = "" - -# betanet -# network = "betanet" -# rpc_url = "https://rpc.betanet.near.org" -# wallet_url = "https://wallet.betanet.near.org" -# explorer_transaction_url = "https://explorer.betanet.near.org/transactions/" -# rpc_api_key = "" - -# shardnet -# network = "shardnet" -# rpc_url = "https://rpc.shardnet.near.org" -# wallet_url = "https://wallet.shardnet.near.org" -# explorer_transaction_url = "https://explorer.shardnet.near.org/transactions/" -# rpc_api_key = "" diff --git a/src/main.rs b/src/main.rs index 61417ef..614c9c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,62 +1,67 @@ mod error; +mod redis_fns; mod rpc_conf; mod shared_storage; -mod redis_fns; +#[cfg(test)] +use axum::body::{BoxBody, HttpBody}; +#[cfg(test)] +use axum::response::Response; use axum::{ extract::Json, http::StatusCode, response::IntoResponse, + routing::{get, post}, Router, - routing::{get, post} }; +#[cfg(test)] +use base64::{engine::general_purpose::URL_SAFE_NO_PAD as BASE64_ENGINE, Engine as _}; +#[cfg(test)] +use bytes::BytesMut; use config::{Config, File}; use near_crypto::InMemorySigner; #[cfg(test)] use near_crypto::{KeyType, PublicKey, Signature}; use near_fetch::signer::KeyRotatingSigner; +use near_primitives::borsh::BorshDeserialize; #[cfg(test)] use near_primitives::borsh::BorshSerialize; -use near_primitives::borsh::BorshDeserialize; +use near_primitives::delegate_action::SignedDelegateAction; #[cfg(test)] use near_primitives::delegate_action::{DelegateAction, NonDelegateAction}; -use near_primitives::delegate_action::SignedDelegateAction; #[cfg(test)] use near_primitives::transaction::TransferAction; use near_primitives::transaction::{Action, FunctionCallAction}; +use near_primitives::types::AccountId; #[cfg(test)] use near_primitives::types::{BlockHeight, Nonce}; -use near_primitives::types::AccountId; use once_cell::sync::Lazy; use r2d2::{Pool, PooledConnection}; use r2d2_redis::redis::{Commands, ErrorKind::IoError, RedisError}; use r2d2_redis::RedisConnectionManager; use serde::Deserialize; -use serde_json::json; -use std::{fmt, path::Path}; +use serde_json::{json, Value}; use std::fmt::{Debug, Display, Formatter}; use std::net::SocketAddr; use std::str::FromStr; use std::string::ToString; -#[cfg(test)] -use axum::body::{BoxBody, HttpBody}; -#[cfg(test)] -use axum::response::Response; -#[cfg(test)] -use bytes::BytesMut; +use std::{fmt, path::Path}; use tower_http::trace::TraceLayer; -use tracing::{debug, info, warn}; use tracing::log::error; +use tracing::{debug, info, warn}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use utoipa::{OpenApi, ToSchema}; use utoipa_rapidoc::RapiDoc; use utoipa_swagger_ui::SwaggerUi; use crate::error::RelayError; +use crate::redis_fns::{ + calculate_total_gas_burned, get_oauth_token_in_redis, get_remaining_allowance, + set_account_and_allowance_in_redis, set_oauth_token_in_redis, update_remaining_allowance, +}; use crate::rpc_conf::NetworkConfig; use crate::shared_storage::SharedStoragePoolManager; - // transaction cost in Gas (10^21yN or 10Tgas or 0.001N) const TXN_GAS_ALLOWANCE: u64 = 10_000_000_000_000; const YN_TO_GAS: u128 = 1_000_000_000; @@ -68,7 +73,7 @@ static LOCAL_CONF: Lazy = Lazy::new(|| { .build() .unwrap() }); -static NETWORK_ENV: Lazy = Lazy::new(|| { LOCAL_CONF.get("network").unwrap() }); +static NETWORK_ENV: Lazy = Lazy::new(|| LOCAL_CONF.get("network").unwrap()); static RPC_CLIENT: Lazy = Lazy::new(|| { let network_config = NetworkConfig { rpc_url: LOCAL_CONF.get("rpc_url").unwrap(), @@ -78,58 +83,54 @@ static RPC_CLIENT: Lazy = Lazy::new(|| { }; network_config.rpc_client() }); -static IP_ADDRESS: Lazy<[u8; 4]> = Lazy::new(|| { LOCAL_CONF.get("ip_address").unwrap() }); -static PORT: Lazy = Lazy::new(|| { LOCAL_CONF.get("port").unwrap() }); -static RELAYER_ACCOUNT_ID: Lazy = Lazy::new(|| { - LOCAL_CONF.get("relayer_account_id").unwrap() -}); -static SHARED_STORAGE_ACCOUNT_ID: Lazy = Lazy::new(|| { - LOCAL_CONF.get("shared_storage_account_id").unwrap() -}); +static IP_ADDRESS: Lazy<[u8; 4]> = Lazy::new(|| LOCAL_CONF.get("ip_address").unwrap()); +static PORT: Lazy = Lazy::new(|| LOCAL_CONF.get("port").unwrap()); +static RELAYER_ACCOUNT_ID: Lazy = + Lazy::new(|| LOCAL_CONF.get("relayer_account_id").unwrap()); +static SHARED_STORAGE_ACCOUNT_ID: Lazy = + Lazy::new(|| LOCAL_CONF.get("shared_storage_account_id").unwrap()); static SIGNER: Lazy = Lazy::new(|| { - let paths = LOCAL_CONF.get::>("keys_filenames") + let paths = LOCAL_CONF + .get::>("keys_filenames") .expect("Failed to read 'keys_filenames' from config"); KeyRotatingSigner::from_signers(paths.iter().map(|path| { InMemorySigner::from_file(Path::new(path)) .unwrap_or_else(|err| panic!("failed to read signing keys from {path}: {err:?}")) })) }); -static SHARED_STORAGE_KEYS_FILENAME: Lazy = Lazy::new(|| { - LOCAL_CONF.get("shared_storage_keys_filename").unwrap() -}); -static WHITELISTED_CONTRACTS: Lazy> = Lazy::new(|| { - LOCAL_CONF.get("whitelisted_contracts").unwrap() -}); +static SHARED_STORAGE_KEYS_FILENAME: Lazy = + Lazy::new(|| LOCAL_CONF.get("shared_storage_keys_filename").unwrap()); +static WHITELISTED_CONTRACTS: Lazy> = + Lazy::new(|| LOCAL_CONF.get("whitelisted_contracts").unwrap()); static USE_WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS: Lazy = Lazy::new(|| { - LOCAL_CONF.get("use_whitelisted_delegate_action_receiver_ids").unwrap_or(false) + LOCAL_CONF + .get("use_whitelisted_delegate_action_receiver_ids") + .unwrap_or(false) }); static WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS: Lazy> = Lazy::new(|| { - LOCAL_CONF.get("whitelisted_delegate_action_receiver_ids").unwrap() -}); -static USE_REDIS: Lazy = Lazy::new(|| { - LOCAL_CONF.get("use_redis").unwrap_or(false) + LOCAL_CONF + .get("whitelisted_delegate_action_receiver_ids") + .unwrap() }); +static USE_REDIS: Lazy = Lazy::new(|| LOCAL_CONF.get("use_redis").unwrap_or(false)); static REDIS_POOL: Lazy> = Lazy::new(|| { let redis_cnxn_url: String = LOCAL_CONF.get("redis_url").unwrap(); let manager = RedisConnectionManager::new(redis_cnxn_url).unwrap(); Pool::builder().build(manager).unwrap() }); -static USE_FASTAUTH_FEATURES: Lazy = Lazy::new(|| { - LOCAL_CONF.get("use_fastauth_features").unwrap_or(false) -}); -static USE_PAY_WITH_FT: Lazy = Lazy::new(|| { - LOCAL_CONF.get("use_pay_with_ft").unwrap_or(false) -}); -static BURN_ADDRESS: Lazy = Lazy::new(||{ - LOCAL_CONF.get("burn_address").unwrap() -}); -static USE_SHARED_STORAGE: Lazy = Lazy::new(|| { - LOCAL_CONF.get("use_shared_storage").unwrap_or(false) -}); +static USE_FASTAUTH_FEATURES: Lazy = + Lazy::new(|| LOCAL_CONF.get("use_fastauth_features").unwrap_or(false)); +static USE_PAY_WITH_FT: Lazy = + Lazy::new(|| LOCAL_CONF.get("use_pay_with_ft").unwrap_or(false)); +static BURN_ADDRESS: Lazy = Lazy::new(|| LOCAL_CONF.get("burn_address").unwrap()); +static USE_SHARED_STORAGE: Lazy = + Lazy::new(|| LOCAL_CONF.get("use_shared_storage").unwrap_or(false)); static SHARED_STORAGE_POOL: Lazy = Lazy::new(|| { let social_db_id: String = LOCAL_CONF.get("social_db_contract_id").unwrap(); let signer = InMemorySigner::from_file(Path::new(SHARED_STORAGE_KEYS_FILENAME.as_str())) - .unwrap_or_else(|err| panic!("failed to get signing keys={SHARED_STORAGE_KEYS_FILENAME:?}: {err:?}")); + .unwrap_or_else(|err| { + panic!("failed to get signing keys={SHARED_STORAGE_KEYS_FILENAME:?}: {err:?}") + }); SharedStoragePoolManager::new( signer, &RPC_CLIENT, @@ -144,10 +145,14 @@ struct AccountIdAllowanceOauthSDAJson { account_id: String, #[schema(example = 900000000)] allowance: u64, - #[schema(example = "https://securetoken.google.com/pagoda-oboarding-dev:Op4h13AQozM4CikngfHiFVC2xhf2")] + #[schema( + example = "https://securetoken.google.com/pagoda-oboarding-dev:Op4h13AQozM4CikngfHiFVC2xhf2" + )] oauth_token: String, // NOTE: imported SignedDelegateAction itself doesn't have a corresponding schema in the OpenAPI document - #[schema(example = "{\"delegate_action\": {\"actions\": [{\"Transfer\": {\"deposit\": \"1\" }}], \"max_block_height\": 922790412, \"nonce\": 103066617000686, \"public_key\": \"ed25519:98GtfFzez3opomVpwa7i4m2nptHtc8Ha405XHMWszQtL\", \"receiver_id\": \"relayer.example.testnet\", \"sender_id\": \"example.testnet\" }, \"signature\": \"ed25519:4uJu8KapH98h8cQm4btE0DKnbiFXSZNT7McDw4LHy7pdAt4Mz8DfuyQZadGgFExo77or9152iwcw2q12rnFWa6bg\" }")] + #[schema( + example = "{\"delegate_action\": {\"actions\": [{\"Transfer\": {\"deposit\": \"1\" }}], \"max_block_height\": 922790412, \"nonce\": 103066617000686, \"public_key\": \"ed25519:98GtfFzez3opomVpwa7i4m2nptHtc8Ha405XHMWszQtL\", \"receiver_id\": \"relayer.example.testnet\", \"sender_id\": \"example.testnet\" }, \"signature\": \"ed25519:4uJu8KapH98h8cQm4btE0DKnbiFXSZNT7McDw4LHy7pdAt4Mz8DfuyQZadGgFExo77or9152iwcw2q12rnFWa6bg\" }" + )] signed_delegate_action: SignedDelegateAction, } impl Display for AccountIdAllowanceOauthSDAJson { @@ -156,7 +161,7 @@ impl Display for AccountIdAllowanceOauthSDAJson { f, "account_id: {}, allowance in Gas: {}, oauth_token: {}, signed_delegate_action signature: {}", self.account_id, self.allowance, self.oauth_token, self.signed_delegate_action.signature - ) // SignedDelegateAction doesn't implement display, so just displaying signature + ) // SignedDelegateAction doesn't implement display, so just displaying signature } } @@ -166,7 +171,9 @@ struct AccountIdAllowanceOauthJson { account_id: String, #[schema(example = 900000000)] allowance: u64, - #[schema(example = "https://securetoken.google.com/pagoda-oboarding-dev:Op4h13AQozM4CikngfHiFVC2xhf2")] + #[schema( + example = "https://securetoken.google.com/pagoda-oboarding-dev:Op4h13AQozM4CikngfHiFVC2xhf2" + )] oauth_token: String, } impl Display for AccountIdAllowanceOauthJson { @@ -208,7 +215,8 @@ impl Display for AccountIdJson { } #[derive(Clone, Debug, Deserialize, ToSchema)] -struct AllowanceJson { // TODO: LP use for return type of GET get_allowance +struct AllowanceJson { + // TODO: LP use for return type of GET get_allowance #[schema(example = 900000000)] allowance_in_gas: u64, } @@ -218,11 +226,32 @@ impl Display for AllowanceJson { } } +async fn init_senders_infinite_allowance_fastauth() { + let max_allowance = u64::MAX; + for whitelisted_sender in WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS.clone() { + let redis_result = + set_account_and_allowance_in_redis(&whitelisted_sender, &max_allowance).await; + if let Err(err) = redis_result { + error!( + "Error setting allowance for account_id {} with allowance {} in Relayer DB: {:?}", + whitelisted_sender, max_allowance, err, + ); + } else { + info!( + "Set allowance for account_id {} with allowance {} in Relayer DB", + whitelisted_sender.clone().as_str(), + max_allowance, + ); + } + } +} #[tokio::main] async fn main() { // initialize tracing (aka logging) - tracing_subscriber::registry().with(tracing_subscriber::fmt::layer()).init(); + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .init(); // initialize our shared storage pool manager if using fastauth features or using shared storage if USE_FASTAUTH_FEATURES.clone() || USE_SHARED_STORAGE.clone() { @@ -276,10 +305,14 @@ async fn main() { )] struct ApiDoc; + // if fastauth enabled, initialize whitelisted senders with "infinite" allowance in relayer DB + if *USE_FASTAUTH_FEATURES { + init_senders_infinite_allowance_fastauth().await; + } + // build our application with a route let app = Router::new() - .merge(SwaggerUi::new("/swagger-ui") - .url("/api-docs/openapi.json", ApiDoc::openapi())) + .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) // There is no need to create `RapiDoc::with_openapi` because the OpenApi is served // via SwaggerUi instead we only make rapidoc to point to the existing doc. .merge(RapiDoc::new("/api-docs/openapi.json").path("/rapidoc")) @@ -320,20 +353,25 @@ async fn main() { async fn get_allowance(account_id_json: Json) -> impl IntoResponse { // convert str account_id val from json to AccountId so I can reuse get_remaining_allowance fn let Ok(account_id_val) = AccountId::from_str(&account_id_json.account_id) else { - return (StatusCode::FORBIDDEN, format!("Invalid account_id: {}", account_id_json.account_id)).into_response(); + return ( + StatusCode::FORBIDDEN, + format!("Invalid account_id: {}", account_id_json.account_id), + ) + .into_response(); }; - match redis_fns::get_remaining_allowance(&account_id_val).await { + match get_remaining_allowance(&account_id_val).await { Ok(allowance) => ( StatusCode::OK, - allowance.to_string() // TODO: LP return in json format - // AllowanceJson { - // allowance_in_gas: allowance - // } - ).into_response(), + allowance.to_string(), // TODO: LP return in json format + // AllowanceJson { + // allowance_in_gas: allowance + // } + ) + .into_response(), Err(err) => { let err_msg = format!( "Error getting allowance for account_id {} in Relayer DB: {:?}", - account_id_val.clone().as_str(), err + account_id_val, err ); error!("{err_msg}"); (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response() @@ -341,7 +379,6 @@ async fn get_allowance(account_id_json: Json) -> impl IntoRespons } } - // TODO: LP how to get multiple 500 status messages to show up #[utoipa::path( post, @@ -360,7 +397,7 @@ async fn get_allowance(account_id_json: Json) -> impl IntoRespons ), )] async fn create_account_atomic( - account_id_allowance_oauth_sda: Json + account_id_allowance_oauth_sda: Json, ) -> impl IntoResponse { /* This function atomically creates an account, both in our systems (redis) @@ -375,17 +412,19 @@ async fn create_account_atomic( let account_id: &String = &account_id_allowance_oauth_sda.account_id; let allowance_in_gas: &u64 = &account_id_allowance_oauth_sda.allowance; let oauth_token: &String = &account_id_allowance_oauth_sda.oauth_token; - let sda: SignedDelegateAction = account_id_allowance_oauth_sda.signed_delegate_action.clone(); + let sda: SignedDelegateAction = account_id_allowance_oauth_sda + .signed_delegate_action + .clone(); /* - do logic similar to register_account_and_allowance fn - without updating redis or allocating shared storage - if that fails, return error - if it succeeds, then continue - */ + do logic similar to register_account_and_allowance fn + without updating redis or allocating shared storage + if that fails, return error + if it succeeds, then continue + */ // check if the oauth_token has already been used and is a key in Redis - match redis_fns::get_oauth_token_in_redis(oauth_token).await { + match get_oauth_token_in_redis(oauth_token).await { Ok(is_oauth_token_in_redis) => { if is_oauth_token_in_redis { let err_msg = format!( @@ -404,10 +443,7 @@ async fn create_account_atomic( return (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response(); } } - let redis_result = redis_fns::set_account_and_allowance_in_redis( - account_id, - allowance_in_gas, - ).await; + let redis_result = set_account_and_allowance_in_redis(account_id, allowance_in_gas).await; let Ok(_) = redis_result else { let err_msg = format!( @@ -418,11 +454,11 @@ async fn create_account_atomic( }; /* - call process_signed_delegate_action fn - if there's an error, then return error - if it succeeds, then add oauth token to redis and allocate shared storage - after updated redis and adding shared storage, finally return success msg - */ + call process_signed_delegate_action fn + if there's an error, then return error + if it succeeds, then add oauth token to redis and allocate shared storage + after updated redis and adding shared storage, finally return success msg + */ let create_account_sda_result = process_signed_delegate_action(sda).await; if create_account_sda_result.is_err() { let err: RelayError = create_account_sda_result.err().unwrap(); @@ -436,7 +472,10 @@ async fn create_account_atomic( // allocate shared storage for account_id if shared storage is being used if USE_FASTAUTH_FEATURES.clone() || USE_SHARED_STORAGE.clone() { - if let Err(err) = SHARED_STORAGE_POOL.allocate_default(account_id.clone()).await { + if let Err(err) = SHARED_STORAGE_POOL + .allocate_default(account_id.clone()) + .await + { let err_msg = format!("Error allocating storage for account {account_id}: {err:?}"); error!("{err_msg}"); return (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response(); @@ -444,7 +483,7 @@ async fn create_account_atomic( } // add oauth token to redis (key: oauth_token, val: true) - match redis_fns::set_oauth_token_in_redis(oauth_token.clone()).await { + match set_oauth_token_in_redis(oauth_token.clone()).await { Ok(_) => { let ok_msg = format!( "Added Oauth token {oauth_token:?} for account_id {account_id:?} \ @@ -452,22 +491,17 @@ async fn create_account_atomic( Near onchain account creation response: {create_account_sda_result:?}" ); info!("{ok_msg}"); - ( - StatusCode::CREATED, - ok_msg, - ).into_response() + (StatusCode::CREATED, ok_msg).into_response() } Err(err) => { - let err_msg = format!( - "Error creating oauth token {oauth_token:?} in Relayer DB:\n{err:?}", - ); + let err_msg = + format!("Error creating oauth token {oauth_token:?} in Relayer DB:\n{err:?}",); error!("{err_msg}"); (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response() } } } - #[utoipa::path( post, path = "/update_allowance", @@ -478,16 +512,11 @@ post, \n{db_result:?}", body = String), ), )] -async fn update_allowance( - account_id_allowance: Json -) -> impl IntoResponse { +async fn update_allowance(account_id_allowance: Json) -> impl IntoResponse { let account_id: &String = &account_id_allowance.account_id; let allowance_in_gas: &u64 = &account_id_allowance.allowance; - let redis_result = redis_fns::set_account_and_allowance_in_redis( - account_id, - allowance_in_gas, - ).await; + let redis_result = set_account_and_allowance_in_redis(account_id, allowance_in_gas).await; let Ok(_) = redis_result else { let err_msg = format!( @@ -499,13 +528,9 @@ async fn update_allowance( }; let success_msg: String = format!("Relayer DB updated for {account_id_allowance:?}"); info!("success_msg"); - ( - StatusCode::CREATED, - success_msg - ).into_response() + (StatusCode::CREATED, success_msg).into_response() } - #[utoipa::path( post, path = "/update_all_allowances", @@ -515,9 +540,7 @@ async fn update_allowance( (status = 500, description = "Error updating allowance for key example.near: err_msg", body = String), ), )] -async fn update_all_allowances( - Json(allowance_json): Json, -) -> impl IntoResponse { +async fn update_all_allowances(Json(allowance_json): Json) -> impl IntoResponse { let allowance_in_gas = allowance_json.allowance_in_gas; let redis_response = update_all_allowances_in_redis(allowance_in_gas).await; match redis_response { @@ -526,7 +549,6 @@ async fn update_all_allowances( } } - #[utoipa::path( post, path = "/register_account_and_allowance", @@ -538,13 +560,13 @@ async fn update_all_allowances( ), )] async fn register_account_and_allowance( - account_id_allowance_oauth: Json + account_id_allowance_oauth: Json, ) -> impl IntoResponse { let account_id: &String = &account_id_allowance_oauth.account_id; let allowance_in_gas: &u64 = &account_id_allowance_oauth.allowance; let oauth_token: &String = &account_id_allowance_oauth.oauth_token; // check if the oauth_token has already been used and is a key in Relayer DB - match redis_fns::get_oauth_token_in_redis(oauth_token).await { + match get_oauth_token_in_redis(oauth_token).await { Ok(is_oauth_token_in_redis) => { if is_oauth_token_in_redis { let err_msg = format!( @@ -564,10 +586,7 @@ async fn register_account_and_allowance( return (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response(); } } - let redis_result = redis_fns::set_account_and_allowance_in_redis( - account_id, - allowance_in_gas, - ).await; + let redis_result = set_account_and_allowance_in_redis(account_id, allowance_in_gas).await; let Ok(_) = redis_result else { let err_msg = format!( @@ -582,26 +601,25 @@ async fn register_account_and_allowance( warn!("{err_msg}"); return (StatusCode::BAD_REQUEST, err_msg).into_response(); }; - if let Err(err) = SHARED_STORAGE_POOL.allocate_default(account_id.clone()).await { + if let Err(err) = SHARED_STORAGE_POOL + .allocate_default(account_id.clone()) + .await + { let err_msg = format!("Error allocating storage for account {account_id}: {err:?}"); error!("{err_msg}"); return (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response(); } // add oauth token to redis (key: oauth_token, val: true) - match redis_fns::set_oauth_token_in_redis(oauth_token.clone()).await { + match set_oauth_token_in_redis(oauth_token.clone()).await { Ok(_) => { let ok_msg = format!("Added Oauth token {account_id_allowance_oauth:?} to Relayer DB"); info!("{ok_msg}"); - ( - StatusCode::CREATED, - ok_msg, - ).into_response() + (StatusCode::CREATED, ok_msg).into_response() } Err(err) => { - let err_msg = format!( - "Error creating oauth token {oauth_token:?} in Relayer DB:\n{err:?}", - ); + let err_msg = + format!("Error creating oauth token {oauth_token:?} in Relayer DB:\n{err:?}",); error!("{err_msg}"); (StatusCode::INTERNAL_SERVER_ERROR, err_msg).into_response() } @@ -618,24 +636,24 @@ async fn register_account_and_allowance( (status = 500, description = "Error signing transaction: ...", body = String), ), )] -async fn relay( - data: Json>, -) -> impl IntoResponse { +async fn relay(data: Json>) -> impl IntoResponse { // deserialize SignedDelegateAction using borsh match SignedDelegateAction::try_from_slice(&data.0) { - Ok(signed_delegate_action) => match process_signed_delegate_action( - signed_delegate_action, - ).await { - Ok(response) => response.into_response(), - Err(err) => (err.status_code, err.message).into_response(), - }, + Ok(signed_delegate_action) => { + match process_signed_delegate_action(signed_delegate_action).await { + Ok(response) => response.into_response(), + Err(err) => (err.status_code, err.message).into_response(), + } + } Err(e) => { let err_msg = format!( - "{}: {:?}", "Error deserializing payload data object", e.to_string(), + "{}: {:?}", + "Error deserializing payload data object", + e.to_string(), ); warn!("{err_msg}"); (StatusCode::BAD_REQUEST, err_msg).into_response() - }, + } } } @@ -649,13 +667,12 @@ async fn relay( (status = 500, description = "Error signing transaction: ...", body = String), ), )] -async fn send_meta_tx( - data: Json, -) -> impl IntoResponse { +async fn send_meta_tx(data: Json) -> impl IntoResponse { let relayer_response = process_signed_delegate_action( // deserialize SignedDelegateAction using serde json data.0, - ).await; + ) + .await; match relayer_response { Ok(response) => response.into_response(), Err(err) => (err.status_code, err.message).into_response(), @@ -665,7 +682,10 @@ async fn send_meta_tx( async fn process_signed_delegate_action( signed_delegate_action: SignedDelegateAction, ) -> Result { - debug!("Deserialized SignedDelegateAction object: {:#?}", signed_delegate_action); + debug!( + "Deserialized SignedDelegateAction object: {:#?}", + signed_delegate_action + ); // create Transaction from SignedDelegateAction let signer_account_id: AccountId = RELAYER_ACCOUNT_ID.as_str().parse().unwrap(); @@ -674,15 +694,15 @@ async fn process_signed_delegate_action( let da_receiver_id = signed_delegate_action.delegate_action.receiver_id.clone(); // check that the delegate action receiver_id is in the whitelisted_contracts - let is_whitelisted_da_receiver = WHITELISTED_CONTRACTS.iter().any( - |s| s == da_receiver_id.as_str() - ); + let is_whitelisted_da_receiver = WHITELISTED_CONTRACTS + .iter() + .any(|s| s == da_receiver_id.as_str()); // check the sender_id in whitelist if applicable if USE_WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS.clone() && !USE_FASTAUTH_FEATURES.clone() { // check if the delegate action receiver_id (account sender_id) if a whitelisted delegate action receiver - let is_whitelisted_sender = WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS.iter().any( - |s| s == receiver_id.as_str() - ); + let is_whitelisted_sender = WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS + .iter() + .any(|s| s == receiver_id.as_str()); if !is_whitelisted_sender { let err_msg = format!( "Delegate Action receiver_id {} or sender_id {} is not whitelisted", @@ -692,29 +712,32 @@ async fn process_signed_delegate_action( warn!("{err_msg}"); return Err(RelayError { status_code: StatusCode::BAD_REQUEST, - message: err_msg + message: err_msg, }); } } if !is_whitelisted_da_receiver && USE_FASTAUTH_FEATURES.clone() { // check if sender id and receiver id are the same AND (AddKey or DeleteKey action) - let non_delegate_action = signed_delegate_action.delegate_action.actions.get(0).ok_or_else(|| { - let err_msg = format!( - "DelegateAction must have at least one NonDelegateAction" - ); - warn!("{err_msg}"); - RelayError { - status_code: StatusCode::BAD_REQUEST, - message: err_msg, - } - })?; + let non_delegate_action = signed_delegate_action + .delegate_action + .actions + .get(0) + .ok_or_else(|| { + let err_msg = format!("DelegateAction must have at least one NonDelegateAction"); + warn!("{err_msg}"); + RelayError { + status_code: StatusCode::BAD_REQUEST, + message: err_msg, + } + })?; let contains_key_action = matches!( - (*non_delegate_action).clone().into(), Action::AddKey(_) | Action::DeleteKey(_) + (*non_delegate_action).clone().into(), + Action::AddKey(_) | Action::DeleteKey(_) ); // check if the receiver_id (delegate action sender_id) if a whitelisted delegate action receiver - let is_whitelisted_sender = WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS.iter().any( - |s| s == receiver_id.as_str() - ); + let is_whitelisted_sender = WHITELISTED_DELEGATE_ACTION_RECEIVER_IDS + .iter() + .any(|s| s == receiver_id.as_str()); if (receiver_id != da_receiver_id || !contains_key_action) && !is_whitelisted_sender { let err_msg = format!( "Delegate Action receiver_id {} or sender_id {} is not whitelisted OR \ @@ -725,7 +748,7 @@ async fn process_signed_delegate_action( warn!("{err_msg}"); return Err(RelayError { status_code: StatusCode::BAD_REQUEST, - message: err_msg + message: err_msg, }); } } @@ -734,15 +757,43 @@ async fn process_signed_delegate_action( if USE_PAY_WITH_FT.clone() { let non_delegate_actions = signed_delegate_action.delegate_action.get_actions(); let treasury_payments: Vec = non_delegate_actions - .into_iter() - .filter(|x| matches!( - x, - Action::FunctionCall(FunctionCallAction { args, .. } - ) if String::from_utf8_lossy(args).contains(&BURN_ADDRESS.to_string())) - ) + .iter() + .filter_map(|action| { + if let Action::FunctionCall(FunctionCallAction { args, .. }) = action { + debug!("args: {:?}", args); + + // convert to ascii lowercase + let args_ascii = args.to_ascii_lowercase(); + debug!("args_ascii: {:?}", args_ascii); + + // Convert to UTF-8 string + let args_str = String::from_utf8_lossy(&args_ascii); + debug!("args_str: {:?}", args_str); + + // Parse to JSON (assuming args are serialized as JSON) + let args_json: Value = serde_json::from_str(&args_str).unwrap_or_else(|err| { + error!("Failed to parse JSON: {}", err); + // Provide a default Value + Value::Null + }); + debug!("args_json: {:?}", args_json); + + // get the receiver_id from the json without the escape chars + let receiver_id = args_json["receiver_id"].as_str().unwrap_or_default(); + debug!("receiver_id: {receiver_id}"); + debug!("BURN_ADDRESS.to_string(): {:?}", BURN_ADDRESS.to_string()); + + // Check if receiver_id in args contain BURN_ADDRESS + if receiver_id == BURN_ADDRESS.to_string() { + debug!("SignedDelegateAction contains the BURN_ADDRESS MATCH"); + return Some(action.clone()); + } + } + None + }) .collect(); if treasury_payments.is_empty() { - let err_msg = format!("No treasury payment found in this transaction", ); + let err_msg = format!("No treasury payment found in this transaction",); warn!("{err_msg}"); return Err(RelayError { status_code: StatusCode::BAD_REQUEST, @@ -754,7 +805,7 @@ async fn process_signed_delegate_action( if USE_REDIS.clone() { // Check the sender's remaining gas allowance in Redis let end_user_account: &AccountId = &signed_delegate_action.delegate_action.sender_id; - let remaining_allowance: u64 = redis_fns::get_remaining_allowance(end_user_account).await.unwrap_or(0); + let remaining_allowance: u64 = get_remaining_allowance(end_user_account).await.unwrap_or(0); if remaining_allowance < TXN_GAS_ALLOWANCE { let err_msg = format!( "AccountId {} does not have enough remaining gas allowance.", @@ -768,7 +819,8 @@ async fn process_signed_delegate_action( } let actions = vec![Action::Delegate(signed_delegate_action)]; - let execution = RPC_CLIENT.send_tx(&*SIGNER, &receiver_id, actions) + let execution = RPC_CLIENT + .send_tx(&*SIGNER, &receiver_id, actions) .await .map_err(|err| { let err_msg = format!("Error signing transaction: {err:?}"); @@ -796,27 +848,21 @@ async fn process_signed_delegate_action( "Receipts Outcome": &execution.receipts_outcome, }); - let gas_used_in_yn = redis_fns::calculate_total_gas_burned( - execution.transaction_outcome, - execution.receipts_outcome, - ); + let gas_used_in_yn = + calculate_total_gas_burned(execution.transaction_outcome, execution.receipts_outcome); debug!("total gas burnt in yN: {}", gas_used_in_yn); - let new_allowance = redis_fns::update_remaining_allowance( - &signer_account_id, - gas_used_in_yn, - remaining_allowance - ) - .await - .map_err(|err| { - let err_msg = format!( - "Updating redis remaining allowance errored out: {err:?}" - ); - error!("{err_msg}"); - RelayError { - status_code: StatusCode::INTERNAL_SERVER_ERROR, - message: err_msg, - } - })?; + let new_allowance = + update_remaining_allowance(&signer_account_id, gas_used_in_yn, remaining_allowance) + .await + .map_err(|err| { + let err_msg = + format!("Updating redis remaining allowance errored out: {err:?}"); + error!("{err_msg}"); + RelayError { + status_code: StatusCode::INTERNAL_SERVER_ERROR, + message: err_msg, + } + })?; info!("Updated remaining allowance for account {signer_account_id}: {new_allowance}",); match status { @@ -832,10 +878,10 @@ async fn process_signed_delegate_action( Ok(status_msg.to_string()) } } - } else { let actions = vec![Action::Delegate(signed_delegate_action)]; - let execution = RPC_CLIENT.send_tx(&*SIGNER, &receiver_id, actions) + let execution = RPC_CLIENT + .send_tx(&*SIGNER, &receiver_id, actions) .await .map_err(|err| { let err_msg = format!("Error signing transaction: {err:?}"); @@ -905,7 +951,7 @@ pub async fn update_all_allowances_in_redis(allowance_in_gas: u64) -> Result Result Result Result> { +async fn read_body_to_string( + mut body: BoxBody, +) -> Result> { // helper fn to convert the awful BoxBody dtype into a String so I can view the darn msg let mut bytes = BytesMut::new(); while let Some(chunk) = body.data().await { @@ -991,10 +1039,27 @@ async fn read_body_to_string(mut body: BoxBody) -> Result