From 9ae7cf2680659e3412efb347ba0abeb3e20ff4f8 Mon Sep 17 00:00:00 2001 From: Fluid <90795031+fluiderson@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:39:28 +0300 Subject: [PATCH] finish server & db definitions, add chain hashes to connected chains --- Cargo.lock | 128 +++++----------- README.md | 6 + src/arguments.rs | 31 +++- src/chain.rs | 6 +- src/chain/tracker.rs | 30 ++-- src/chain_wip.rs | 10 +- src/chain_wip/definitions.rs | 22 ++- src/database.rs | 278 +++++++++++++++++++++-------------- src/database/definitions.rs | 271 ++++++++++++++++++++++++++-------- src/error.rs | 60 ++++---- src/main.rs | 6 +- src/server/definitions.rs | 191 ++++++++++-------------- src/server/handlers/order.rs | 22 +-- src/state.rs | 2 +- 14 files changed, 598 insertions(+), 465 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59fec45..9bd25f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,7 +383,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -490,9 +490,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bbb537bb4a30b90362caddba8f360c0a56bc13d3a5570028e7197204cb54a17" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] @@ -511,9 +511,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -521,9 +521,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -856,7 +856,7 @@ checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ "curve25519-dalek", "ed25519", - "hashbrown", + "hashbrown 0.14.5", "hex", "rand_core", "sha2", @@ -1173,6 +1173,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -1236,9 +1242,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1332,12 +1338,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -2847,12 +2853,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -2993,7 +2999,7 @@ dependencies = [ "futures-io", "futures-sink", "futures-util", - "hashbrown", + "hashbrown 0.14.5", "pin-project-lite", "tokio", ] @@ -3163,9 +3169,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -3293,22 +3299,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3317,22 +3314,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -3341,46 +3323,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3393,48 +3357,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/README.md b/README.md index 9f6e4b0..8311532 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,12 @@ Environment variables: ``` ### 🕹ī¸đŸ“ Examples +Start the daemon, and create (or open existing) the config at the default path in the current directory, create (or open existing) the database at the default path in the current directory, and enable the default logging. +```sh +KALATORI_SEED="impulse idea people slow agent orphan color sugar siege laugh view amused property destroy ghost" \ +kalatori -r 5FuwwzpHSYp8k1Bmv9fC394vGNdwqAvPRCmqYqdv3KrXpXWi +``` + Start the daemon, and create (or open existing) the config at `my_config.toml` in the current directory. ```sh KALATORI_SEED="impulse idea people slow agent orphan color sugar siege laugh view amused property destroy ghost" \ diff --git a/src/arguments.rs b/src/arguments.rs index 70459fa..0155815 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -3,7 +3,7 @@ //! Contains everything related to CLI arguments, environment variables, and the config. use crate::{ - chain_wip::definitions::{H256, HEX_PREFIX}, + chain_wip::definitions::{Url, H256, HEX_PREFIX}, database::definitions::{AssetId, Timestamp}, error::{AccountParseError, ChainIntervalError, ConfigError}, logger, @@ -11,9 +11,9 @@ use crate::{ utils::PathDisplay, }; use clap::{Arg, ArgAction, Parser}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{ - borrow::Cow, + borrow::{Borrow, Cow}, env, ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter, Result as FmtResult}, @@ -320,16 +320,37 @@ impl Config { } } +#[derive(Deserialize, Clone, Hash, PartialEq, Eq, Serialize)] +pub struct ChainName(pub Arc); + +impl Borrow for ChainName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Display for ChainName { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + Display::fmt(&self.0, f) + } +} + +impl Debug for ChainName { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + Debug::fmt(&self.0, f) + } +} + #[derive(Deserialize, Clone)] pub struct Chain { - pub name: Arc, + pub name: ChainName, #[serde(flatten)] pub config: ChainConfig, } #[derive(Deserialize, Clone)] pub struct ChainConfig { - pub endpoints: Vec, + pub endpoints: Vec, #[serde(flatten)] pub inner: ChainConfigInner, } diff --git a/src/chain.rs b/src/chain.rs index 3ff5666..499e3b5 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -56,7 +56,7 @@ impl ChainManager { // start network monitors for c in chain { if c.config.endpoints.is_empty() { - return Err(Error::EmptyEndpoints(c.name.to_string())); + return Err(Error::EmptyEndpoints(c.name.0.to_string())); } let (chain_tx, chain_rx) = mpsc::channel(1024); watch_chain.insert(c.name.clone(), chain_tx.clone()); @@ -100,7 +100,7 @@ impl ChainManager { } else { let _unused = request .res - .send(Err(ChainError::InvalidChain(chain.to_string()))); + .send(Err(ChainError::InvalidChain(chain.0.to_string()))); } } else { let _unused = request @@ -116,7 +116,7 @@ impl ChainManager { } else { let _unused = request .res - .send(Err(ChainError::InvalidChain(chain.to_string()))); + .send(Err(ChainError::InvalidChain(chain.0.to_string()))); } } else { let _unused = request diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs index 3f0f61c..779ee3d 100644 --- a/src/chain/tracker.rs +++ b/src/chain/tracker.rs @@ -43,7 +43,7 @@ pub fn start_chain_watch( ) { task_tracker .clone() - .spawn(format!("Chain {} watcher", chain.name.clone()), async move { + .spawn(format!("Chain {} watcher", chain.name.clone().0), async move { let watchdog = 120000; let mut watched_accounts = HashMap::new(); let mut shutdown = false; @@ -53,16 +53,16 @@ pub fn start_chain_watch( if shutdown || cancellation_token.is_cancelled() { break; } - if let Ok(client) = WsClientBuilder::default().build(endpoint).await { + if let Ok(client) = WsClientBuilder::default().build(&endpoint.0).await { // prepare chain - let watcher = match ChainWatcher::prepare_chain(&client, chain.clone(), &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) + let watcher = match ChainWatcher::prepare_chain(&client, chain.clone(), &mut watched_accounts, &endpoint.0, chain_tx.clone(), state.interface(), task_tracker.clone()) .await { Ok(a) => a, Err(e) => { tracing::warn!( "Failed to connect to chain {}, due to {} switching RPC server...", - chain.name, + chain.name.0, e ); continue; @@ -82,14 +82,14 @@ pub fn start_chain_watch( Err(e) => { tracing::info!( "Failed to receive block in chain {}, due to {}; Switching RPC server...", - chain.name, + chain.name.0, e ); break; }, }; - tracing::debug!("Block hash {} from {}", block.0, chain.name); + tracing::debug!("Block hash {} from {}", block.0, chain.name.0); if watcher.version != runtime_version_identifier(&client, &block).await? { tracing::info!("Different runtime version reported! Restarting connection..."); @@ -140,11 +140,11 @@ pub fn start_chain_watch( } }, Err(e) => { - tracing::warn!("Events fetch error {e} at {}", chain.name); + tracing::warn!("Events fetch error {e} at {}", chain.name.0); break; }, } - tracing::debug!("Block {} from {} processed successfully", block.0, chain.name); + tracing::debug!("Block {} from {} processed successfully", block.0, chain.name.0); } ChainTrackerRequest::WatchAccount(request) => { watched_accounts.insert(request.id.clone(), Invoice::from_request(request)); @@ -156,7 +156,7 @@ pub fn start_chain_watch( let watcher_for_reaper = watcher.clone(); let signer_for_reaper = signer.clone(); task_tracker.clone().spawn(format!("Initiate payout for order {}", id.clone()), async move { - let result = payout(rpc, Invoice::from_request(request), reap_state_handle, watcher_for_reaper, signer_for_reaper).await; + let result = payout(rpc.0.as_ref().to_owned(), Invoice::from_request(request), reap_state_handle, watcher_for_reaper, signer_for_reaper).await; if let Err(er) = result { tracing::error!("{:?}", er); } @@ -172,7 +172,7 @@ pub fn start_chain_watch( } } } - Ok(format!("Chain {} monitor shut down", chain.name)) + Ok(format!("Chain {} monitor shut down", chain.name.0)) }); } @@ -202,9 +202,9 @@ impl ChainWatcher { let version = runtime_version_identifier(client, &block).await?; let metadata = metadata(&client, &block).await?; let name = >::spec_name_version(&metadata)?.spec_name; - if *name != *chain.name { + if *name != *chain.name.0 { return Err(ChainError::WrongNetwork { - expected: chain.name.to_string(), + expected: chain.name.0.to_string(), actual: name, rpc: rpc_url.to_string(), }); @@ -216,7 +216,7 @@ impl ChainWatcher { // TODO: make this verbosity less annoying tracing::info!( "chain {} requires native token {:?} and {:?}", - &chain.name, + &chain.name.0, &chain.config.inner.native, &chain.config.inner.asset ); @@ -228,7 +228,7 @@ impl ChainWatcher { .filter_map(|(name, properties)| { tracing::info!( "chain {} has token {} with properties {:?}", - &chain.name, + &chain.name.0, &name, &properties ); @@ -274,7 +274,7 @@ impl ChainWatcher { if assets.len() != chain.config.inner.asset.len() + usize::from(chain.config.inner.native.is_some()) { - return Err(ChainError::AssetsInvalid(chain.name.to_string())); + return Err(ChainError::AssetsInvalid(chain.name.0.to_string())); } // this MUST assert that assets match exactly before reporting it diff --git a/src/chain_wip.rs b/src/chain_wip.rs index 0fdd767..ae44bea 100644 --- a/src/chain_wip.rs +++ b/src/chain_wip.rs @@ -1,7 +1,7 @@ //! Everything related to the actual interaction with a blockchain. use crate::{ - arguments::{Chain, ChainIntervals}, + arguments::{Chain, ChainIntervals, ChainName}, error::{Error, TaskError}, utils::task_tracker::ShortTaskTracker, }; @@ -26,7 +26,7 @@ pub const MODULE: &str = module_path!(); pub async fn connect( chains: Vec, root_intervals: ChainIntervals, -) -> Result, ConnectedChain, RandomState>, Error> { +) -> Result, Error> { let ri_shared = Arc::new(RwLock::const_new(root_intervals)); let mut connected_chains = IndexMap::with_capacity_and_hasher(chains.len(), RandomState::new()); let (connected_chains_tx, mut connected_chains_rx) = mpsc::channel::<(_, _, OsSender<_>)>(1); @@ -69,10 +69,10 @@ pub async fn connect( .expect("this `JoinHandle` shouldn't panic")) } -#[tracing::instrument(skip_all, fields(chain = %chain.name))] +#[tracing::instrument(skip_all, fields(chain = ?chain.name))] async fn connect_to_chain( chain: Chain, - connected_chains: MpscSender<(Arc, ConnectedChain, OsSender)>, + connected_chains: MpscSender<(ChainName, ConnectedChain, OsSender)>, root_intervals: Arc>, ) -> Result<(), TaskError> { chain @@ -97,7 +97,7 @@ async fn connect_to_chain( } let client_config = WsClientBuilder::new().enable_ws_ping(PingConfig::new()); - let client = client_config.clone().build(&first_endpoint).await?; + let client = client_config.clone().build(&first_endpoint.0).await?; let genesis = api::fetch_genesis_hash(&client).await?; // let finalized_head = methods.get_finalized_head().await?; // let runtime = RuntimeApi::new(methods.get_metadata(&finalized_head).await?, methods)?; diff --git a/src/chain_wip/definitions.rs b/src/chain_wip/definitions.rs index e30157d..58c3b84 100644 --- a/src/chain_wip/definitions.rs +++ b/src/chain_wip/definitions.rs @@ -1,6 +1,9 @@ //! Common objects for the chain interaction system. -use crate::arguments::ChainConfigInner; +use crate::{ + arguments::{ChainConfigInner, ChainName}, + database::definitions::ChainHash, +}; use ahash::RandomState; use arrayvec::ArrayString; use const_hex::FromHexError; @@ -20,26 +23,31 @@ use std::{ pub const HEX_PREFIX: &str = "0x"; #[derive(Debug)] -pub struct ChainPreparator(pub Arc); +pub struct ChainPreparator(pub ChainName); impl Display for ChainPreparator { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.write_str("the ")?; - f.double(|formatter| formatter.write_str(&self.0))?; + f.double(|formatter| Display::fmt(&self.0, formatter))?; f.write_str(" chain preparator") } } pub struct ConnectedChain { - pub endpoints: (String, IndexSet), + pub endpoints: (Url, IndexSet), pub chain_config: ChainConfigInner, pub genesis: BlockHash, pub client: WsClient, pub client_config: WsClientBuilder, } +pub struct PreparedChain { + pub hash: ChainHash, + pub connected: ConnectedChain, +} + #[derive(Deserialize, Clone, Copy, Serialize)] pub struct BlockHash(pub H256); @@ -139,8 +147,7 @@ macro_rules! hash_impl { Self($inner::from_be_bytes(bytes.into())) } - #[allow(clippy::wrong_self_convention)] - pub fn to_be_bytes(&self) -> [u8; $bytes] { + pub fn to_be_bytes(self) -> [u8; $bytes] { self.0.to_be_bytes() } } @@ -206,6 +213,9 @@ impl EncaseInQuotes for Formatter<'_> { } } +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Hash)] +pub struct Url(pub Arc); + // #[derive(Serialize)] // pub struct StorageKey(pub String); diff --git a/src/database.rs b/src/database.rs index f86e338..819f958 100644 --- a/src/database.rs +++ b/src/database.rs @@ -6,19 +6,16 @@ //! guards with the async code. use crate::{ - arguments::OLD_SEED, - chain_wip::definitions::{BlockHash, ConnectedChain, H256, H64}, - error::DbError, + arguments::{ChainName, OLD_SEED}, + chain_wip::definitions::{ConnectedChain, PreparedChain, H256, H64}, + error::{DbError, OrderError}, signer::{KeyStore, Signer}, utils::PathDisplay, }; use ahash::{HashMap, HashMapExt, HashSet, HashSetExt, RandomState}; -use arguments::CreatedOrder; -use codec::{Decode, Encode}; -use indexmap::{ - map::{Entry, VacantEntry}, - IndexMap, -}; +use arguments::InsertOrder; +use codec::{DecodeAll, Encode}; +use indexmap::{map::Entry, IndexMap}; use names::{Generator, Name}; use redb::{ backends::{FileBackend, InMemoryBackend}, @@ -33,9 +30,9 @@ use std::{ pub mod definitions; use definitions::{ - ChainHash, ChainProperties, DaemonInfo, KeysTable, NonZeroU64, OrderId, OrderInfo, - OrdersPerChainTable, OrdersTable, PaymentStatus, Public, RootKey, RootTable, RootValue, - TableRead, TableTrait, TableTypes, TableWrite, Timestamp, Version, + ChainHash, ChainName as DbChainName, ChainProperties, DaemonInfo, KeysTable, NonZeroU64, + OrderId, OrderInfo, OrdersPerChainTable, OrdersTable, PaymentStatus, Public, RootKey, + RootTable, RootValue, TableRead, TableTrait, TableTypes, TableWrite, Timestamp, Version, }; pub const MODULE: &str = module_path!(); @@ -47,12 +44,19 @@ pub struct Database { } impl Database { - #[expect(clippy::too_many_lines)] + #[expect(clippy::too_many_lines, clippy::type_complexity)] pub fn new( path_option: Option>, - connected_chains: &IndexMap, ConnectedChain, RandomState>, + mut connected_chains: IndexMap, key_store: KeyStore, - ) -> Result<(Arc, Arc), DbError> { + ) -> Result< + ( + Arc, + Arc, + IndexMap, + ), + DbError, + > { let builder = Redb::builder(); let (mut database, is_new) = if let Some(path) = path_option { @@ -85,12 +89,12 @@ impl Database { let mut root = RootTable::open(&tx)?; let mut chain_hashes = HashSet::with_capacity(connected_chains.len()); let public = key_store.public(); + let mut prepared_chains = + IndexMap::with_capacity_and_hasher(connected_chains.len(), RandomState::new()); - let instance; let daemon_info; let signer; - - if is_new { + let instance = if is_new { signer = key_store.into_signer::(HashMap::new()); let mut new_db_chains = Vec::with_capacity(connected_chains.len()); @@ -98,23 +102,28 @@ impl Database { for (name, connected_chain) in connected_chains { process_new_chain( &mut chain_hashes, - (name.as_ref().to_owned(), &mut new_db_chains), - connected_chain.genesis, + name, + &mut new_db_chains, + connected_chain, + &mut prepared_chains, ); } - instance = Generator::with_naming(Name::Numbered).next().unwrap(); + let instance = Generator::with_naming(Name::Numbered).next().unwrap(); + daemon_info = DaemonInfo { chains: new_db_chains, public, old_publics_death_timestamps: vec![], - instance: instance.clone().into_bytes(), + instance: instance.clone(), }; root.insert_slot( RootKey::DbVersion, RootValue(&Version::as_bytes(&DB_VERSION)), )?; + + instance } else { let Some(encoded_db_version) = root.get_slot(RootKey::DbVersion)? else { return Err(DbError::NoVersion); @@ -134,8 +143,8 @@ impl Database { chains: db_chains, public: db_public, old_publics_death_timestamps, - instance: db_instance, - } = DaemonInfo::decode(&mut encoded_daemon_info.value().0)?; + instance, + } = DaemonInfo::decode_all(&mut encoded_daemon_info.value().0)?; #[expect(clippy::arithmetic_side_effects)] let mut new_db_chains = IndexMap::with_capacity_and_hasher( @@ -150,15 +159,20 @@ impl Database { properties, &mut chain_hashes, &mut new_db_chains, - connected_chains, + &mut connected_chains, &mut orders_per_chain, + &mut prepared_chains, )?; } for (name, connected_chain) in connected_chains { - if let Entry::Vacant(entry) = new_db_chains.entry(name.as_ref().to_owned()) { - process_new_chain(&mut chain_hashes, entry, connected_chain.genesis); - } + process_new_chain( + &mut chain_hashes, + name, + &mut new_db_chains, + connected_chain, + &mut prepared_chains, + ); } let tables = Tables::get(&tx, &chain_hashes)?; @@ -176,15 +190,15 @@ impl Database { new_db_chains_vec.extend(new_db_chains); - instance = String::from_utf8(db_instance) - .expect("should be a valid UTF-8 text since we encoded it as one"); daemon_info = DaemonInfo { chains: new_db_chains_vec, public, old_publics_death_timestamps: new_old_publics_death_timestamps, - instance: instance.clone().into_bytes(), + instance: instance.clone(), }; - } + + instance + }; root.insert_slot(&RootKey::DaemonInfo, &RootValue(&daemon_info.encode()))?; @@ -204,6 +218,7 @@ impl Database { instance, }), Arc::new(signer), + prepared_chains, )) } @@ -238,17 +253,21 @@ pub struct OrdersRead( ReadOnlyTable<::Key, ::Value>, ); -impl OrdersRead { - pub fn read( +pub trait OrdersReadable { + fn table( + &self, + ) -> &impl ReadableTable<::Key, ::Value>; + + fn read( &self, key: impl AsRef, ) -> Result::Value>, DbError> { - self.0 + self.table() .get_slot(OrderId(key.as_ref())) .map(|some| some.map(|ag| ag.value())) } - pub fn active( + fn active( &self, ) -> Result< impl Iterator< @@ -262,7 +281,7 @@ impl OrdersRead { >, DbError, > { - self.0.iter().map_err(DbError::Range).map(|range| { + self.table().iter().map_err(DbError::Range).map(|range| { range.filter_map(|result| { result .map(|(k, v)| { @@ -277,6 +296,24 @@ impl OrdersRead { } } +impl OrdersReadable for OrdersRead { + fn table( + &self, + ) -> &impl ReadableTable<::Key, ::Value> + { + &self.0 + } +} + +impl OrdersReadable for OrdersWrite<'_> { + fn table( + &self, + ) -> &impl ReadableTable<::Key, ::Value> + { + &self.0 + } +} + pub struct TxWrite(WriteTransaction); impl TxWrite { @@ -296,24 +333,26 @@ impl TxWrite { KeysTable::open(&self.0).map(KeysWrite) } - pub fn create_order( + pub fn insert_order( mut orders: OrdersWrite<'_>, mut orders_per_chain: OrdersPerChainWrite<'_>, mut keys: KeysWrite<'_>, - chain: ChainHash, - key: impl AsRef, - public: Public, - new_order: OrderInfo, - ) -> Result { - let order_id = OrderId(key.as_ref()); - let order_option = orders.0.get_slot(order_id)?.map(|ag| ag.value()); - - Ok(if let Some(order) = order_option { - if order.payment_status == PaymentStatus::Pending { - CreatedOrder::Modified(new_order) - } else { - CreatedOrder::Unchanged(order) - } + InsertOrder { + chain, + key, + public, + callback, + currency, + amount, + account_lifetime, + payment_account, + }: InsertOrder>, + ) -> Result { + let key_as_ref = key.as_ref(); + let order_option = orders.read(key_as_ref)?; + + if let Some(order) = order_option { + order.update(callback, chain, currency, amount, account_lifetime) } else { let increment = |option_ag: Option>| { NonZeroU64(option_ag.map_or(StdNonZeroU64::MIN, |ag| { @@ -323,8 +362,16 @@ impl TxWrite { .expect("database can't store more than `u64::MAX` orders") })) }; + let new = OrderInfo::new( + amount, + callback, + payment_account, + currency, + account_lifetime, + chain, + )?; - orders.0.insert_slot(order_id, &new_order)?; + orders.0.insert_slot(OrderId(key_as_ref), &new)?; let mut n = increment(orders_per_chain.0.get_slot(chain)?); @@ -334,8 +381,8 @@ impl TxWrite { keys.0.insert_slot(public, n)?; - CreatedOrder::New(new_order) - }) + Ok(new) + } } } @@ -350,12 +397,12 @@ impl OrdersWrite<'_> { ) -> Result<::Value, DbError> { let key_as_ref = key.as_ref(); - let Some(mut order) = self.0.get_slot(OrderId(key_as_ref))?.map(|ag| ag.value()) else { - return Err(DbError::OrderNotFound(key_as_ref.into())); + let Some(mut order) = self.read(key_as_ref)? else { + return Err(OrderError::NotFound.into()); }; if order.payment_status != PaymentStatus::Pending { - return Err(DbError::OrderAlreadyPaid(key_as_ref.into())); + return Err(OrderError::Paid.into()); } order.payment_status = PaymentStatus::Paid; @@ -391,6 +438,7 @@ impl<'a> Tables<'a> { for table in tx.list_tables().map_err(DbError::TableList)? { match table.name() { + // These tables were opened before a calling of `Tables::get()`. RootTable::NAME | OrdersPerChainTable::NAME => {} KeysTable::NAME => keys = Some(KeysTable::open(tx)?), OrdersTable::NAME => orders = Some(OrdersTable::open(tx)?), @@ -431,28 +479,19 @@ fn open_chain_tables( } } -trait ProcessNewChainHelper { - fn name(&self) -> &str; - fn add_to_db_chains(self, properties: ChainProperties); +trait NewDbChains { + fn add(self, name: DbChainName, properties: ChainProperties); } -impl ProcessNewChainHelper for VacantEntry<'_, String, ChainProperties> { - fn name(&self) -> &str { - self.key() - } - - fn add_to_db_chains(self, properties: ChainProperties) { - self.insert(properties); +impl NewDbChains for &mut IndexMap { + fn add(self, name: DbChainName, properties: ChainProperties) { + self.insert(name, properties); } } -impl ProcessNewChainHelper for (String, &mut Vec<(String, ChainProperties)>) { - fn name(&self) -> &str { - &self.0 - } - - fn add_to_db_chains(self, properties: ChainProperties) { - self.1.push((self.0, properties)); +impl NewDbChains for &mut Vec<(DbChainName, ChainProperties)> { + fn add(self, name: DbChainName, properties: ChainProperties) { + self.push((name, properties)); } } @@ -461,39 +500,49 @@ fn strip_array(array: [T; N1]) -> [T; N2] { array::from_fn(|_| { iterator.next().expect( - "number of elements in the returned array should be equal or less than in the given \ - array", + "number of elements in the returned array should be equal or less than in the \ + given array", ) }) } fn process_new_chain( chain_hashes: &mut HashSet, - helper: impl ProcessNewChainHelper, - genesis: BlockHash, + name: ChainName, + new_db_chains: impl NewDbChains, + chain: ConnectedChain, + prepared_chains: &mut IndexMap, ) { - let mut chain_hash = ChainHash(strip_array(genesis.0.to_be_bytes())); + let mut chain_hash = ChainHash(strip_array(chain.genesis.0.to_be_bytes())); let mut step = 1; loop { if chain_hashes.insert(chain_hash) { tracing::debug!( - "The new {:?} chain is assigned to the {:#} hash.", - helper.name(), + "The new {name:?} chain is assigned to the {:#} hash.", H64::from(chain_hash) ); - helper.add_to_db_chains(ChainProperties { - genesis: genesis.into(), - hash: chain_hash, - }); + new_db_chains.add( + (&name).into(), + ChainProperties { + genesis: chain.genesis.into(), + hash: chain_hash, + }, + ); + prepared_chains.insert( + name, + PreparedChain { + hash: chain_hash, + connected: chain, + }, + ); break; } tracing::debug!( - "Failed to assign the new {:?} chain to the {:#} hash. Probing the next slot...", - helper.name(), + "Failed to assign the new {name:?} chain to the {:#} hash. Probing the next slot...", H64::from(chain_hash) ); @@ -505,27 +554,28 @@ fn process_new_chain( } fn process_db_chain( - name: String, + db_name: DbChainName, properties: ChainProperties, chain_hashes: &mut HashSet, - new_db_chains: &mut IndexMap, - connected_chains: &IndexMap, ConnectedChain, RandomState>, + new_db_chains: &mut IndexMap, + connected_chains: &mut IndexMap, orders_per_chain: &mut Table< '_, ::Key, ::Value, >, + prepared_chains: &mut IndexMap, ) -> Result<(), DbError> { if !chain_hashes.insert(properties.hash) { tracing::debug!( - "Found the {name:?} chain with the hash duplicate {:#} in the database.", + "Found the {db_name:?} chain with the hash duplicate {:#} in the database.", H64::from(properties.hash) ); return Ok(()); } - let entry = match new_db_chains.entry(name) { + let entry = match new_db_chains.entry(db_name) { Entry::Occupied(entry) => { tracing::debug!( "Found 2 chains with same name ({:?}) in the database.", @@ -537,33 +587,40 @@ fn process_db_chain( Entry::Vacant(entry) => entry, }; - tracing::debug!(name = entry.key(), properties = ?properties); + tracing::debug!(name = ?entry.key(), properties = ?properties); - if let Some(connected_chain) = connected_chains.get(&**entry.key()) { - let given = connected_chain.genesis.into(); + if let Some((name, connected)) = connected_chains.swap_remove_entry(&*entry.key().0) { + let given = connected.genesis.into(); if given != properties.genesis { return Err(DbError::GenesisMismatch { - chain: entry.into_key(), + chain: entry.into_key().0, expected: properties.genesis, given, }); } tracing::debug!( - "The {:?} chain stored in the database is assigned to the {:?} hash.", - entry.key(), + "The {name:?} chain stored in the database is assigned to the {:?} hash.", H64::from(properties.hash), ); + + prepared_chains.insert( + name, + PreparedChain { + hash: properties.hash, + connected, + }, + ); } else if orders_per_chain.get_slot(properties.hash)?.is_some() { tracing::warn!( "The {:?} chain exists in the database but isn't present in the config.", entry.key(), ); } else { - tracing::warn!( + tracing::info!( "The {:?} chain exists in the database but isn't present in the config and has no \ - orders. It will be deleted.", + orders. It'll be deleted.", entry.key() ); @@ -654,7 +711,7 @@ fn process_keys( new_old_publics_death_timestamps.push((db_public, Timestamp::now()?)); } else { tracing::info!( - "The current key has no accounts associated with it and hence will be deleted immediately." + "The current key has no accounts associated with it and hence will be deleted." ); } } @@ -666,11 +723,16 @@ fn process_keys( } pub mod arguments { - use super::definitions::OrderInfo; - - pub enum CreatedOrder { - New(OrderInfo), - Modified(OrderInfo), - Unchanged(OrderInfo), + use super::definitions::{Account, Amount, ChainHash, CurrencyInfo, Public, Timestamp, Url}; + + pub struct InsertOrder { + pub chain: ChainHash, + pub key: K, + pub public: Public, + pub callback: Url, + pub currency: CurrencyInfo, + pub amount: Amount, + pub account_lifetime: Timestamp, + pub payment_account: Account, } } diff --git a/src/database/definitions.rs b/src/database/definitions.rs index 83c077c..314cf1c 100644 --- a/src/database/definitions.rs +++ b/src/database/definitions.rs @@ -4,21 +4,23 @@ //! stable since primitive types don't change their representation. pub use v1::{ - Amount, AmountKind, AssetId, BlockHash, Bytes, ChainHash, ChainProperties, CurrencyInfo, - DaemonInfo, FinalizedTx, KeysTable, NonZeroU64, OrderId, OrderInfo, OrdersPerChainTable, - OrdersTable, PaymentStatus, Public, RootKey, RootTable, RootValue, TableRead, TableTrait, - TableTypes, TableWrite, Timestamp, TokenKind, TransactionInfo, TxStatus, Version, - WithdrawalStatus, + Account, Amount, AmountKind, AssetId, BlockHash, Bytes, ChainHash, ChainName, ChainProperties, + CurrencyInfo, DaemonInfo, FinalizedTx, KeysTable, NonZeroU64, OrderId, OrderInfo, + OrdersPerChainTable, OrdersTable, PaymentStatus, Public, RootKey, RootTable, RootValue, + TableRead, TableTrait, TableTypes, TableWrite, Timestamp, TokenKind, TransactionInfo, TxStatus, + Url, Version, WithdrawalStatus, }; mod v1 { use crate::{ + arguments::ChainName as ArgChainName, chain_wip::definitions::{self, BlockHash as ChainBlockHash, H256, H64, HEX_PREFIX}, - error::{DbError, TimestampError}, + error::{DbError, OrderError, TimestampError}, + server::definitions::new::Decimals, }; use ahash::{HashMap, HashSet}; use arrayvec::{ArrayString, ArrayVec}; - use codec::{Decode, Encode, Error as CodecError, Input}; + use codec::{Compact, Decode, DecodeAll, Encode, Error as CodecError, Input}; use redb::{ AccessGuard, Key, ReadOnlyTable, ReadTransaction, ReadableTable, Table, TableDefinition, TableError, TypeName, Value, WriteTransaction, @@ -95,6 +97,16 @@ mod v1 { } } + macro_rules! into_h256 { + ($from:ty) => { + impl From<$from> for H256 { + fn from(value: $from) -> Self { + H256::from_be_bytes(value.0) + } + } + }; + } + macro_rules! table { ($table:ident<$key:ident, $value:ident> = $name:literal) => { pub struct $table; @@ -234,7 +246,7 @@ mod v1 { where Self: 'a, { - Self::decode(&mut data).unwrap() + Self::decode_all(&mut data).unwrap() } fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'_>) -> Self::AsBytes<'a> { @@ -405,12 +417,13 @@ mod v1 { #[derive(Debug, Decode, Encode)] pub struct OrderInfo { + pub chain: ChainHash, pub withdrawal_status: WithdrawalStatus, pub payment_status: PaymentStatus, pub amount: Amount, - pub message: String, + pub message: Option, pub currency: CurrencyInfo, - pub callback: String, + pub callback: Url, pub transactions: Vec, pub payment_account: Account, pub death: Timestamp, @@ -418,6 +431,71 @@ mod v1 { scale_slot!(OrderInfo); + impl OrderInfo { + pub fn new( + amount: Amount, + callback: Url, + payment_account: Account, + currency: CurrencyInfo, + account_lifetime: Timestamp, + chain: ChainHash, + ) -> Result { + Ok(Self { + chain, + withdrawal_status: WithdrawalStatus::Waiting, + payment_status: PaymentStatus::Pending, + amount, + message: None, + currency, + callback, + transactions: vec![], + payment_account, + death: get_death_ts(account_lifetime)?, + }) + } + + pub fn update( + self, + callback: Url, + chain: ChainHash, + currency: CurrencyInfo, + amount: Amount, + account_lifetime: Timestamp, + ) -> Result { + if self.payment_status == PaymentStatus::Paid { + return Err(OrderError::Paid.into()); + } + + if !self.transactions.is_empty() { + return Err(OrderError::NotEmptyTxs.into()); + } + + Ok(Self { + amount, + callback, + currency, + chain, + death: get_death_ts(account_lifetime)?, + ..self + }) + } + } + + fn get_death_ts(account_lifetime: Timestamp) -> Result { + Timestamp::from_millis( + Timestamp::now()? + .as_millis() + .saturating_add(account_lifetime.as_millis()), + ) + } + + ///// + + #[derive(Debug, Decode, Encode, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] + pub struct Url(pub String); + + ///// + #[derive(Debug, Decode, Encode, Serialize)] #[serde(rename_all = "lowercase")] pub enum WithdrawalStatus { @@ -427,6 +505,8 @@ mod v1 { None, } + ///// + #[derive(Debug, Decode, Encode, PartialEq, Eq, Serialize)] #[serde(rename_all = "lowercase")] pub enum PaymentStatus { @@ -435,12 +515,16 @@ mod v1 { TimedOut, } + ///// + #[derive(Debug, Decode, Encode)] pub struct CurrencyInfo { pub kind: TokenKind, pub asset_id: Option, } + ///// + #[derive(Debug, Decode, Encode, Serialize, Clone)] #[serde(rename_all = "lowercase")] pub enum TokenKind { @@ -448,6 +532,8 @@ mod v1 { Native, } + ///// + #[derive(Debug, Decode, Encode)] pub struct TransactionInfo { pub finalized_tx: Option, @@ -458,6 +544,8 @@ mod v1 { pub status: TxStatus, } + ///// + #[derive(Debug, Decode, Encode, Serialize)] pub struct FinalizedTx { pub block_number: BlockNumber, @@ -465,6 +553,8 @@ mod v1 { pub timestamp: Timestamp, } + ///// + #[derive(Debug, Decode, Encode, Serialize)] #[serde(rename_all = "lowercase")] pub enum TxStatus { @@ -473,6 +563,8 @@ mod v1 { Failed, } + ///// + #[derive(Encode, Decode, Debug)] pub struct Bytes(pub Vec); @@ -484,12 +576,6 @@ mod v1 { } } - impl AsRef<[u8]> for Bytes { - fn as_ref(&self) -> &[u8] { - self - } - } - impl<'de> Deserialize<'de> for Bytes { fn deserialize>(deserializer: D) -> Result { struct VisitorImpl; @@ -514,16 +600,20 @@ mod v1 { impl Serialize for Bytes { fn serialize(&self, serializer: S) -> Result { - serializer.serialize_str(&const_hex::encode_prefixed(self)) + serializer.serialize_str(&const_hex::encode_prefixed(&**self)) } } - #[derive(Debug, Decode, Encode)] + ///// + + #[derive(Debug, Decode, Encode, Copy, Clone)] pub enum AmountKind { Exact(Amount), All, } + ///// + #[derive(Debug)] pub struct NonZeroU64(pub StdNonZeroU64); @@ -552,6 +642,8 @@ mod v1 { } } + ///// + #[derive(Debug)] pub enum RootKey { // The database version must be stored in a separate slot. @@ -606,51 +698,102 @@ mod v1 { } } + ///// + #[derive(Debug, Encode, Decode, Serialize)] pub struct ExtrinsicIndex(pub u32); - #[derive(Debug, Encode, Decode, PartialEq, Eq, Hash, Clone, Copy)] - pub struct Public(pub [u8; 32]); #[derive(Debug, Deserialize, Serialize, Clone, Copy, Decode, Encode)] pub struct BlockNumber(u32); #[derive(Debug, Deserialize, Serialize, Clone, Copy, Decode, Encode)] pub struct AssetId(pub u32); #[derive(Debug, Clone, Copy)] pub struct OrderId<'a>(pub &'a str); - #[derive(Debug, Encode, Decode)] - pub struct Account([u8; 32]); #[derive(Debug, PartialEq)] pub struct Version(pub u64); #[derive(Debug)] pub struct RootValue<'a>(pub &'a [u8]); - #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy)] - pub struct BlockHash(pub [u8; 32]); - #[derive(Debug, Encode, Decode, PartialEq, Eq, Hash, Clone, Copy)] - pub struct ChainHash(pub [u8; 8]); - #[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy)] - pub struct Amount(pub u128); - key_slot!(Public([u8; 32])); key_slot!(BlockNumber(u32)); key_slot!(OrderId<'_>(&str)); - key_slot!(Account([u8; 32])); - key_slot!(ChainHash([u8; 8])); slot!(Version(u64)); slot!(RootValue<'_>(&[u8])); slot!(BlockHash([u8; 32])); slot!(AssetId(u32)); - impl From for AccountId32 { - fn from(value: Account) -> Self { - Self(value.0) + ///// + + #[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy)] + pub struct Amount(pub u128); + + // TODO: Implement custom parsing from a string without using intermediate lossy `f64`. + impl Amount { + pub fn parse(raw: f64, decimals: Decimals) -> Self { + let parsed_float = (raw * decimal_exponent_product(decimals)).round(); + + #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + Self(parsed_float as _) } + + pub fn format(&self, decimals: Decimals) -> f64 { + #[expect(clippy::cast_precision_loss)] + let float = self.0 as f64; + + float / decimal_exponent_product(decimals) + } + } + + pub fn decimal_exponent_product(decimals: Decimals) -> f64 { + 10f64.powi(decimals.0.into()) } + ///// + + #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy)] + pub struct BlockHash(pub [u8; 32]); + + into_h256!(BlockHash); + impl From for BlockHash { fn from(hash: ChainBlockHash) -> Self { Self(hash.0.to_be_bytes()) } } + ///// + + #[derive(Debug, Encode, Decode, PartialEq, Eq, Hash, Clone, Copy)] + pub struct Public(pub [u8; 32]); + + key_slot!(Public([u8; 32])); + + into_h256!(Public); + + ///// + + #[derive(Debug, Encode, Decode)] + pub struct Account([u8; 32]); + + impl From for Public { + fn from(public: CryptoPublic) -> Self { + Self(public.0) + } + } + + impl From for AccountId32 { + fn from(value: Account) -> Self { + Self(value.0) + } + } + + key_slot!(Account([u8; 32])); + + ///// + + #[derive(Debug, Encode, Decode, PartialEq, Eq, Hash, Clone, Copy)] + pub struct ChainHash(pub [u8; 8]); + + key_slot!(ChainHash([u8; 8])); + impl From for ChainHash { fn from(hash: H64) -> Self { Self(hash.to_be_bytes()) @@ -663,33 +806,41 @@ mod v1 { } } - impl From for Public { - fn from(public: CryptoPublic) -> Self { - Self(public.0) - } - } - - macro_rules! into_h256 { - ($from:ty) => { - impl From<$from> for H256 { - fn from(value: $from) -> Self { - H256::from_be_bytes(value.0) - } - } - }; - } - - into_h256!(Public); - into_h256!(BlockHash); + ///// #[derive(Encode, Decode)] pub struct DaemonInfo { - pub chains: Vec<(String, ChainProperties)>, + pub chains: Vec<(ChainName, ChainProperties)>, pub public: Public, pub old_publics_death_timestamps: Vec<(Public, Timestamp)>, - pub instance: Vec, + pub instance: String, } + ///// + + #[derive(Encode, Decode, PartialEq, Eq, Hash)] + pub struct ChainName(pub String); + + impl Display for ChainName { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + Display::fmt(&self.0, f) + } + } + + impl Debug for ChainName { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + Debug::fmt(&self.0, f) + } + } + + impl From<&ArgChainName> for ChainName { + fn from(value: &ArgChainName) -> Self { + Self((*value.0).to_owned()) + } + } + + ///// + impl<'de> Deserialize<'de> for Timestamp { fn deserialize>(deserializer: D) -> Result { Self::from_millis(Deserialize::deserialize(deserializer)?).map_err(DeError::custom) @@ -707,7 +858,7 @@ mod v1 { impl Decode for Timestamp { fn decode(input: &mut I) -> Result { - let millis = u64::decode(input)?; + let millis = >::decode(input)?.0; Self::from_millis(millis).map_err(|e| { const ERROR: &str = concat!("failed to decode `", stringify!(Timestamp), "`"); @@ -764,10 +915,6 @@ mod v1 { } } - fn get_system_millis() -> Result { - Ok(SystemTime::UNIX_EPOCH.elapsed()?.as_millis()) - } - impl Display for Timestamp { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let mut string = ArrayVec::<_, { Self::MAX_STRING.len() }>::new(); @@ -781,6 +928,12 @@ mod v1 { } } + fn get_system_millis() -> Result { + Ok(SystemTime::UNIX_EPOCH.elapsed()?.as_millis()) + } + + ///// + #[derive(Encode, Decode)] pub struct ChainProperties { pub genesis: BlockHash, diff --git a/src/error.rs b/src/error.rs index 62ca08e..a6bcbf1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -89,12 +89,6 @@ pub enum ConfigError { #[error("failed to parse the config")] Parse(#[from] TomlError), - - #[error("failed to parse the chain interval {0} in the config root")] - ConfigRootIntervals( - /* ChainIntervalField */ String, - #[source] ChainIntervalError, - ), } #[derive(Debug, Error)] @@ -113,7 +107,7 @@ pub enum SeedEnvError { } #[derive(Debug, Error)] -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub enum TaskError { #[error("chain has no endpoint in the config")] NoChainEndpoints, @@ -137,7 +131,7 @@ pub enum TaskError { } #[derive(Debug, Error)] -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub enum ChainIntervalError { #[error("`restart-gap` isn't set neither in the config root nor for this chain")] RestartGap, @@ -434,7 +428,7 @@ pub enum AccountParseError { } #[derive(Debug, Error)] -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub enum DbError { #[error("failed to create/open the database")] Initialization(#[from] DatabaseError), @@ -508,15 +502,34 @@ pub enum DbError { #[error("failed to create a timestamp")] Timestamp(#[from] TimestampError), - #[error("order {0:?} isn't found")] - OrderNotFound(String), + #[error("got an order error")] + Order(#[from] OrderError), +} - #[error("order {0:?} is already paid")] - OrderAlreadyPaid(String), +#[derive(Debug, Error)] +#[expect(clippy::module_name_repetitions)] +pub enum OrderError { + #[error("order wasn't found")] + NotFound, + + #[error("order is already paid")] + Paid, + + #[error("order has recorded transactions")] + NotEmptyTxs, + + #[error("order amount is less than the existential deposit of the given currency")] + ExistentialDeposit(f64), + + #[error("got an unknown currency")] + UnknownCurrency, + + #[error("internal error is occurred")] + InternalError, } #[derive(Debug, Error)] -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] pub enum TimestampError { #[error("system clock's drifted backwards")] ClockDrift(#[from] SystemTimeError), @@ -530,25 +543,6 @@ pub enum TimestampError { Overflow, } -#[derive(Debug, Error)] -#[allow(clippy::module_name_repetitions)] -pub enum OrderError { - #[error("invoice amount is less than the existential deposit")] - LessThanExistentialDeposit(f64), - - #[error("unknown currency")] - UnknownCurrency, - - #[error("order parameter is missing: {0:?}")] - MissingParameter(String), - - #[error("order parameter invalid: {0:?}")] - InvalidParameter(String), - - #[error("internal error is occurred")] - InternalError, -} - #[derive(Debug, Error)] #[allow(clippy::module_name_repetitions)] pub enum ForceWithdrawalError { diff --git a/src/main.rs b/src/main.rs index 72c1001..e1e0d34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ mod signer; mod state; mod utils; -use arguments::{CliArgs, Config, Account}; +use arguments::{Account, CliArgs, Config}; use chain::ChainManager; use database::Database; use error::{Error, PrettyCause}; @@ -119,7 +119,7 @@ async fn async_try_main( let (task_tracker, error_rx) = TaskTracker::new(); let connected_chains = chain_wip::connect(config.chain.clone(), config.intervals.clone()).await?; - let (database, signer) = Database::new( + let (database, signer, prepared_chains) = Database::new( db_option_option.map_or_else( || { Some(match config.database { @@ -129,7 +129,7 @@ async fn async_try_main( }, |path| path.map(Into::into), ), - &connected_chains, + connected_chains, key_store, )?; diff --git a/src/server/definitions.rs b/src/server/definitions.rs index 4dac160..d16ef54 100644 --- a/src/server/definitions.rs +++ b/src/server/definitions.rs @@ -288,13 +288,18 @@ pub mod api_v2 { } pub mod new { - use crate::database::definitions::{ - Amount, AmountKind as DbAmountKind, AssetId, Bytes, CurrencyInfo as DbCurrencyInfo, - FinalizedTx, OrderInfo as DbOrderInfo, PaymentStatus, Timestamp, TokenKind, - TransactionInfo as DbTransactionInfo, TxStatus, WithdrawalStatus, + use crate::{ + arguments::ChainName, + chain_wip::definitions::Url, + database::definitions::{ + Amount, AmountKind as DbAmountKind, AssetId, Bytes, CurrencyInfo as DbCurrencyInfo, + FinalizedTx, OrderInfo as DbOrderInfo, PaymentStatus, Timestamp, TokenKind, + TransactionInfo as DbTransactionInfo, TxStatus, Url as DbUrl, WithdrawalStatus, + }, }; - use ahash::{HashMap, HashSet}; + use ahash::HashMap; use serde::{Deserialize, Serialize, Serializer}; + use std::sync::Arc; use substrate_crypto_light::common::{AccountId32, AsBase58}; #[derive(Serialize, Deserialize, Clone, Copy, Debug)] @@ -303,12 +308,6 @@ pub mod new { #[derive(Clone, Copy)] pub struct SS58Prefix(pub u16); - impl From for SS58Prefix { - fn from(value: u16) -> Self { - Self(value) - } - } - #[derive(Clone, Copy)] pub struct SubstrateAccount(pub SS58Prefix, pub AccountId32); @@ -327,23 +326,23 @@ pub mod new { #[derive(Serialize)] pub struct OrderStatus { pub order: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, pub recipient: SubstrateAccount, pub server_info: ServerInfo, #[serde(flatten)] pub order_info: OrderInfo, - pub payment_page: String, - pub redirect_url: String, + pub payment_page: &'static str, + pub redirect_url: &'static str, } #[derive(Serialize)] pub struct OrderInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, pub withdrawal_status: WithdrawalStatus, pub payment_status: PaymentStatus, - pub amount: AmountWithDecimals, + pub amount: f64, pub currency: CurrencyInfo, - pub callback: String, + pub callback: DbUrl, pub transactions: Vec, pub payment_account: SubstrateAccount, pub death: Timestamp, @@ -354,48 +353,60 @@ pub mod new { db: DbOrderInfo, prefix: SS58Prefix, decimals: Decimals, - currency: CurrencyInfo, + currency_name: Arc, + db_currency: DbCurrencyInfo, + rpc_url: Url, + chain_name: ChainName, ) -> Self { + let currency = CurrencyInfo { + currency: currency_name, + properties: CurrencyProperties::from_db(db_currency, chain_name, decimals, rpc_url), + }; + Self { + message: db.message, withdrawal_status: db.withdrawal_status, payment_status: db.payment_status, + amount: db.amount.format(decimals), callback: db.callback, - amount: todo!(), - currency, transactions: db .transactions .into_iter() - .map(|tx| TransactionInfo::from_db(tx, prefix, decimals, currency.clone())) + .map(|tx| TransactionInfo::from_db(tx, currency.clone(), decimals, prefix)) .collect(), + currency, payment_account: SubstrateAccount(prefix, db.payment_account.into()), death: db.death, } } } - // impl OrderInfo { - // pub fn new( - // query: OrderQuery, - // currency: CurrencyInfo, - // payment_account: String, - // death: Timestamp, - // ) -> Self { - // OrderInfo { - // withdrawal_status: WithdrawalStatus::Waiting, - // payment_status: PaymentStatus::Pending, - // amount: query.amount, - // currency, - // callback: query.callback, - // transactions: Vec::new(), - // payment_account, - // death, - // } - // } - // } + #[derive(Debug, Deserialize)] + pub struct OrderPayloadRaw { + pub amount: f64, + pub currency: String, + pub callback: DbUrl, + } + + pub struct OrderPayload { + pub amount: Amount, + pub currency: String, + pub callback: DbUrl, + } + + impl OrderPayload { + pub fn from_raw(raw: OrderPayloadRaw, decimals: Decimals) -> Self { + Self { + amount: Amount::parse(raw.amount, decimals), + currency: raw.currency, + callback: raw.callback, + } + } + } #[derive(Serialize)] pub struct ServerStatus { - pub description: String, + pub description: &'static str, pub server_info: ServerInfo, pub supported_currencies: HashMap, } @@ -403,14 +414,14 @@ pub mod new { #[derive(Serialize)] struct ServerHealth { server_info: ServerInfo, - connected_rpcs: HashSet, + connected_rpcs: Vec, status: Health, } #[derive(Serialize)] struct RpcInfo { - rpc_url: String, - chain_name: String, + rpc_url: Url, + chain_name: ChainName, status: Health, } @@ -424,78 +435,46 @@ pub mod new { #[derive(Serialize, Clone)] pub struct CurrencyInfo { - pub currency: String, - pub chain_name: String, + pub currency: Arc, + #[serde(flatten)] + pub properties: CurrencyProperties, + } + + #[derive(Serialize, Clone)] + pub struct CurrencyProperties { + pub chain_name: ChainName, pub kind: TokenKind, pub decimals: Decimals, - pub rpc_url: String, + pub rpc_url: Url, #[serde(skip_serializing_if = "Option::is_none")] pub asset_id: Option, } - impl CurrencyInfo { + impl CurrencyProperties { pub fn from_db( db: DbCurrencyInfo, - currency: String, - chain_name: String, + chain_name: ChainName, decimals: Decimals, - rpc_url: String, + rpc_url: Url, ) -> Self { Self { - kind: db.kind, - asset_id: db.asset_id, - currency, chain_name, + kind: db.kind, decimals, rpc_url, + asset_id: db.asset_id, } } } - // impl CurrencyInfo { - // pub fn properties(&self) -> CurrencyProperties { - // CurrencyProperties { - // chain_name: self.chain_name.clone(), - // kind: self.kind, - // decimals: self.decimals, - // rpc_url: self.rpc_url.clone(), - // asset_id: self.asset_id, - // ss58: 0, - // } - // } - // } - - #[derive(Serialize)] - pub struct CurrencyProperties { - pub chain_name: String, - pub kind: TokenKind, - pub decimals: Decimals, - pub rpc_url: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub asset_id: Option, - } - - // impl CurrencyProperties { - // pub fn info(&self, currency: String) -> CurrencyInfo { - // CurrencyInfo { - // currency, - // chain_name: self.chain_name.clone(), - // kind: self.kind, - // decimals: self.decimals, - // rpc_url: self.rpc_url.clone(), - // asset_id: self.asset_id, - // } - // } - // } - #[derive(Serialize)] pub struct ServerInfo { - pub version: String, - pub instance_id: String, + pub version: &'static str, + pub instance_id: Arc, #[serde(skip_serializing_if = "Option::is_none")] pub debug: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub kalatori_remark: Option, + pub kalatori_remark: Option>, } #[derive(Serialize)] @@ -515,11 +494,9 @@ pub mod new { impl TransactionInfo { pub fn from_db( db: DbTransactionInfo, - currency: DbCurrencyInfo, - prefix: SS58Prefix, + currency: CurrencyInfo, decimals: Decimals, - chain_name: String, - rpc_url: String, + prefix: SS58Prefix, ) -> Self { Self { finalized_tx: db.finalized_tx, @@ -527,8 +504,8 @@ pub mod new { sender: SubstrateAccount(prefix, db.sender.into()), recipient: SubstrateAccount(prefix, db.recipient.into()), amount: AmountKind::from_db(db.amount, decimals), + currency, status: db.status, - currency: CurrencyInfo::from_db(currency, chain_name, decimals, rpc_url), } } } @@ -536,13 +513,13 @@ pub mod new { #[derive(Serialize)] enum AmountKind { All, - Exact(AmountWithDecimals), + Exact(f64), } impl AmountKind { fn from_db(db: DbAmountKind, decimals: Decimals) -> Self { match db { - DbAmountKind::Exact(amount) => Self::Exact((amount, decimals).into()), + DbAmountKind::Exact(amount) => Self::Exact(amount.format(decimals)), DbAmountKind::All => Self::All, } } @@ -557,18 +534,4 @@ pub mod new { AmountKind::Exact(exact) => exact.serialize(serializer), } } - - pub struct AmountWithDecimals(pub Amount, pub Decimals); - - impl From<(Amount, Decimals)> for AmountWithDecimals { - fn from((a, d): (Amount, Decimals)) -> Self { - Self(a, d) - } - } - - impl Serialize for AmountWithDecimals { - fn serialize(&self, serializer: S) -> Result { - todo!() - } - } } diff --git a/src/server/handlers/order.rs b/src/server/handlers/order.rs index 4253658..5f2fdc1 100644 --- a/src/server/handlers/order.rs +++ b/src/server/handlers/order.rs @@ -26,7 +26,7 @@ pub async fn process_order( payload: OrderPayload, ) -> Result { if payload.amount < 0.07 { - return Err(OrderError::LessThanExistentialDeposit(0.07)); + return Err(OrderError::ExistentialDeposit(0.07)); } state @@ -54,7 +54,7 @@ pub async fn order( OrderResponse::NotFound => (StatusCode::NOT_FOUND, "").into_response(), }, Err(error) => match error { - OrderError::LessThanExistentialDeposit(existential_deposit) => ( + OrderError::ExistentialDeposit(existential_deposit) => ( StatusCode::BAD_REQUEST, Json([InvalidParameter { parameter: AMOUNT.into(), @@ -70,23 +70,7 @@ pub async fn order( }]), ) .into_response(), - OrderError::MissingParameter(parameter) => ( - StatusCode::BAD_REQUEST, - Json([InvalidParameter { - parameter, - message: "parameter wasn't found".into(), - }]), - ) - .into_response(), - OrderError::InvalidParameter(parameter) => ( - StatusCode::BAD_REQUEST, - Json([InvalidParameter { - parameter, - message: "parameter's format is invalid".into(), - }]), - ) - .into_response(), - OrderError::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + _ => StatusCode::INTERNAL_SERVER_ERROR.into_response(), }, } } diff --git a/src/state.rs b/src/state.rs index 5be5ee4..9e1f7bc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use crate::{ callback, chain::ChainManager, - database::{definitions::Timestamp, Database}, + database::{definitions::Timestamp, Database, OrdersReadable}, error::{Error, OrderError}, server::definitions::api_v2::{ CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, OrderResponse, OrderStatus,