From 907c0b6ae0429ee09092ca1a8973bbcb68bdee31 Mon Sep 17 00:00:00 2001 From: Ash Manning Date: Tue, 19 Nov 2024 19:26:05 +0800 Subject: [PATCH] Fix merkle root display, display available balances, bump utoipa to 5.0 --- Cargo.lock | 108 +++++++++-------- app/Cargo.toml | 2 +- app/gui/mod.rs | 2 +- app/rpc_server.rs | 17 ++- app/tests/regtest.rs | 11 +- cli/Cargo.toml | 2 +- cli/lib.rs | 5 +- lib/Cargo.toml | 2 +- lib/types/address.rs | 22 +--- lib/types/hashes.rs | 33 ++---- lib/types/mod.rs | 1 + lib/types/schema.rs | 36 ++++++ lib/types/transaction.rs | 247 ++++++++++++++++++++++++++++++--------- lib/wallet.rs | 45 +++++-- rpc-api/Cargo.toml | 4 +- rpc-api/lib.rs | 105 +++-------------- rpc-api/schema.rs | 39 +++++++ 17 files changed, 417 insertions(+), 264 deletions(-) create mode 100644 lib/types/schema.rs create mode 100644 rpc-api/schema.rs diff --git a/Cargo.lock b/Cargo.lock index 8c1a79f..9690119 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,7 +412,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -452,7 +452,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -469,7 +469,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -875,7 +875,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", "syn_derive", ] @@ -902,7 +902,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1025,7 +1025,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1309,7 +1309,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1333,7 +1333,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1344,7 +1344,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1634,7 +1634,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1831,7 +1831,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1933,7 +1933,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -2800,7 +2800,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -2896,8 +2896,8 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "l2l-openapi" -version = "0.0.0" -source = "git+https://github.com/Ash-L2L/l2l-openapi?rev=c1fe05fd3cc80220b5db3413adb5ffb85f2f302e#c1fe05fd3cc80220b5db3413adb5ffb85f2f302e" +version = "0.1.0" +source = "git+https://github.com/Ash-L2L/l2l-openapi?rev=a3e84daa19fd7c1121346984c3ef85304a144792#a3e84daa19fd7c1121346984c3ef85304a144792" dependencies = [ "jsonrpsee", "l2l-openapi-macros", @@ -2906,13 +2906,13 @@ dependencies = [ [[package]] name = "l2l-openapi-macros" -version = "0.0.0" -source = "git+https://github.com/Ash-L2L/l2l-openapi?rev=c1fe05fd3cc80220b5db3413adb5ffb85f2f302e#c1fe05fd3cc80220b5db3413adb5ffb85f2f302e" +version = "0.1.0" +source = "git+https://github.com/Ash-L2L/l2l-openapi?rev=a3e84daa19fd7c1121346984c3ef85304a144792#a3e84daa19fd7c1121346984c3ef85304a144792" dependencies = [ "proc-macro2", "proc_macro_roids", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3043,7 +3043,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.3", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3159,7 +3159,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3207,7 +3207,7 @@ checksum = "a7ce64b975ed4f123575d11afd9491f2e37bbd5813fbfbc0f09ae1fbddea74e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3378,7 +3378,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3668,7 +3668,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3697,7 +3697,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3825,7 +3825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3856,7 +3856,6 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", "version_check", ] @@ -3888,7 +3887,7 @@ checksum = "d0c2a098cd8aaa29f66da27a684ad19f4b7bc886f576abf12f7df4a7391071c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3924,7 +3923,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.77", + "syn 2.0.87", "tempfile", ] @@ -3938,7 +3937,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -4559,22 +4558,22 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.201" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -4596,7 +4595,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -4626,7 +4625,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -4882,7 +4881,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -4904,9 +4903,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -4922,7 +4921,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -4986,7 +4985,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -5006,7 +5005,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -5247,7 +5246,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -5358,7 +5357,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -5438,7 +5437,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -5601,9 +5600,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "utoipa" -version = "4.2.3" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +checksum = "514a48569e4e21c86d0b84b5612b5e73c0b2cf09db63260134ba426d4e8ea714" dependencies = [ "indexmap 2.2.6", "serde", @@ -5613,14 +5612,13 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "4.3.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bf0e16c02bc4bf5322ab65f10ab1149bdbcaa782cba66dc7057370a3f8190be" +checksum = "5629efe65599d0ccd5d493688cbf6e03aa7c1da07fe59ff97cf5977ed0637f66" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -5687,7 +5685,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -5721,7 +5719,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6506,7 +6504,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -6526,7 +6524,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] diff --git a/app/Cargo.toml b/app/Cargo.toml index 228fb12..64a8d9e 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -42,7 +42,7 @@ tonic-reflection = "0.12.2" tracing = "0.1.40" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.18", features = ["json"] } -utoipa = "4.2.3" +utoipa = "5.2.0" [dev-dependencies.bip300301] git = "https://github.com/Ash-L2L/bip300301.git" diff --git a/app/gui/mod.rs b/app/gui/mod.rs index da9c774..6df5dbc 100644 --- a/app/gui/mod.rs +++ b/app/gui/mod.rs @@ -81,7 +81,7 @@ impl BottomPanel { /// Updates values fn update(&mut self, app: &App) { self.balance = match app.wallet.get_balance() { - Ok(balance) => Some(Some(balance)), + Ok(balance) => Some(Some(balance.total)), Err(err) => { let err = anyhow::Error::from(err); tracing::error!("Failed to update balance: {err:#}"); diff --git a/app/rpc_server.rs b/app/rpc_server.rs index dc1f457..5b76628 100644 --- a/app/rpc_server.rs +++ b/app/rpc_server.rs @@ -9,7 +9,7 @@ use jsonrpsee::{ use thunder::{ node, types::{Address, PointedOutput, Txid}, - wallet, + wallet::{self, Balance}, }; use thunder_app_rpc_api::RpcServer; @@ -47,11 +47,8 @@ fn convert_wallet_err(err: wallet::Error) -> ErrorObject<'static> { #[async_trait] impl RpcServer for RpcServerImpl { - async fn balance(&self) -> RpcResult { - match self.app.wallet.get_balance() { - Ok(balance) => Ok(balance.to_sat()), - Err(err) => Err(convert_wallet_err(err)), - } + async fn balance(&self) -> RpcResult { + self.app.wallet.get_balance().map_err(convert_wallet_err) } async fn create_deposit( @@ -167,11 +164,13 @@ impl RpcServer for RpcServerImpl { .map_err(convert_wallet_err) } - async fn sidechain_wealth(&self) -> RpcResult { - self.app + async fn sidechain_wealth_sats(&self) -> RpcResult { + let sidechain_wealth = self + .app .node .get_sidechain_wealth() - .map_err(convert_node_err) + .map_err(convert_node_err)?; + Ok(sidechain_wealth.to_sat()) } async fn stop(&self) { diff --git a/app/tests/regtest.rs b/app/tests/regtest.rs index ba279aa..8a19a61 100644 --- a/app/tests/regtest.rs +++ b/app/tests/regtest.rs @@ -350,7 +350,11 @@ async fn regtest_test() -> anyhow::Result<()> { // Verify that there are no deposits on Thunder { let balance = thunderd_client.balance().await?; - anyhow::ensure!(balance == 0, "Expected 0 balance, but got {balance}"); + anyhow::ensure!( + balance.total == bitcoin::Amount::ZERO, + "Expected 0 balance, but got {}", + balance.total + ); } // Mine a BMM block to process the deposit let () = mine_thunder_block( @@ -363,7 +367,10 @@ async fn regtest_test() -> anyhow::Result<()> { // Verify that the deposit was successful { let balance = thunderd_client.balance().await?; - anyhow::ensure!(balance > 0, "Expected positive balance"); + anyhow::ensure!( + balance.total > bitcoin::Amount::ZERO, + "Expected positive balance" + ); } /* Clean up */ diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3584845..79c7600 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -13,7 +13,7 @@ serde_json = "1.0.113" thunder = { path = "../lib" } thunder_app_rpc_api = { path = "../rpc-api" } tokio = "1.29.1" -utoipa = "4.2.3" +utoipa = "5.2.0" [lib] name = "thunder_app_cli_lib" diff --git a/cli/lib.rs b/cli/lib.rs index 93ae1b6..ebba1b4 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -93,7 +93,7 @@ impl Cli { let res = match self.command { Command::Balance => { let balance = rpc_client.balance().await?; - format!("{balance}") + serde_json::to_string_pretty(&balance)? } Command::ConnectPeer { addr } => { let () = rpc_client.connect_peer(addr).await?; @@ -151,7 +151,8 @@ impl Cli { String::default() } Command::SidechainWealth => { - let sidechain_wealth = rpc_client.sidechain_wealth().await?; + let sidechain_wealth = + rpc_client.sidechain_wealth_sats().await?; format!("{sidechain_wealth}") } Command::Stop => { diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 35960e1..9bef90b 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -46,7 +46,7 @@ tokio-stream = { version = "0.1.15", features = ["sync"] } tokio-util = { version = "0.7.10", features = ["rt"] } tonic = "0.12.3" tracing = "0.1.40" -utoipa = "4.2.3" +utoipa = { version = "5.2.0", features = ["non_strict_integers"] } [lib] name = "thunder" diff --git a/lib/types/address.rs b/lib/types/address.rs index bad6d8c..77c921c 100644 --- a/lib/types/address.rs +++ b/lib/types/address.rs @@ -1,6 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeAs, DisplayFromStr}; +use utoipa::ToSchema; #[derive(Debug, thiserror::Error)] pub enum AddressParseError { @@ -11,8 +12,9 @@ pub enum AddressParseError { } #[derive( - BorshDeserialize, BorshSerialize, Clone, Copy, Eq, PartialEq, Hash, + BorshDeserialize, BorshSerialize, Clone, Copy, Eq, Hash, PartialEq, ToSchema, )] +#[schema(value_type = String)] pub struct Address(pub [u8; 20]); impl Address { @@ -78,21 +80,3 @@ impl Serialize for Address { } } } - -impl utoipa::PartialSchema for Address { - fn schema() -> utoipa::openapi::RefOr { - let obj = utoipa::openapi::Object::with_type( - utoipa::openapi::SchemaType::String, - ); - utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(obj)) - } -} - -impl utoipa::ToSchema<'static> for Address { - fn schema() -> ( - &'static str, - utoipa::openapi::RefOr, - ) { - ("Address",
::schema()) - } -} diff --git a/lib/types/hashes.rs b/lib/types/hashes.rs index e2aea52..d2d2937 100644 --- a/lib/types/hashes.rs +++ b/lib/types/hashes.rs @@ -77,7 +77,7 @@ impl std::fmt::Debug for BlockHash { PartialOrd, Serialize, )] -pub struct MerkleRoot(Hash); +pub struct MerkleRoot(#[serde(with = "serde_hexstr_human_readable")] Hash); impl From for MerkleRoot { fn from(other: Hash) -> Self { @@ -105,22 +105,15 @@ impl std::fmt::Debug for MerkleRoot { impl utoipa::PartialSchema for MerkleRoot { fn schema() -> utoipa::openapi::RefOr { - let obj = utoipa::openapi::Object::with_type( - utoipa::openapi::SchemaType::String, - ); + let obj = + utoipa::openapi::Object::with_type(utoipa::openapi::Type::String); utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(obj)) } } -impl utoipa::ToSchema<'static> for MerkleRoot { - fn schema() -> ( - &'static str, - utoipa::openapi::RefOr, - ) { - ( - "MerkleRoot", - ::schema(), - ) +impl utoipa::ToSchema for MerkleRoot { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MerkleRoot") } } @@ -186,19 +179,15 @@ impl FromStr for Txid { impl utoipa::PartialSchema for Txid { fn schema() -> utoipa::openapi::RefOr { - let obj = utoipa::openapi::Object::with_type( - utoipa::openapi::SchemaType::String, - ); + let obj = + utoipa::openapi::Object::with_type(utoipa::openapi::Type::String); utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(obj)) } } -impl utoipa::ToSchema<'static> for Txid { - fn schema() -> ( - &'static str, - utoipa::openapi::RefOr, - ) { - ("Txid", ::schema()) +impl utoipa::ToSchema for Txid { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Txid") } } diff --git a/lib/types/mod.rs b/lib/types/mod.rs index 7c51ad9..b457f79 100644 --- a/lib/types/mod.rs +++ b/lib/types/mod.rs @@ -12,6 +12,7 @@ use thiserror::Error; mod address; pub mod hashes; pub mod proto; +pub mod schema; mod transaction; pub use address::Address; diff --git a/lib/types/schema.rs b/lib/types/schema.rs new file mode 100644 index 0000000..fc8eca3 --- /dev/null +++ b/lib/types/schema.rs @@ -0,0 +1,36 @@ +//! Schemas for OpenAPI + +use utoipa::{ + openapi::{self, RefOr, Schema}, + PartialSchema, ToSchema, +}; + +pub struct BitcoinAddr; + +impl PartialSchema for BitcoinAddr { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(openapi::Type::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema for BitcoinAddr { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("bitcoin.Address") + } +} + +pub struct BitcoinOutPoint; + +impl PartialSchema for BitcoinOutPoint { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::new(); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema for BitcoinOutPoint { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("bitcoin.OutPoint") + } +} diff --git a/lib/types/transaction.rs b/lib/types/transaction.rs index f772693..86e4e48 100644 --- a/lib/types/transaction.rs +++ b/lib/types/transaction.rs @@ -11,6 +11,20 @@ use utoipa::ToSchema; use super::{hash, Address, AmountOverflowError, Hash, M6id, MerkleRoot, Txid}; use crate::authorization::Authorization; +pub trait GetAddress { + fn get_address(&self) -> Address; +} + +pub trait GetValue { + fn get_value(&self) -> bitcoin::Amount; +} + +impl GetValue for () { + fn get_value(&self) -> bitcoin::Amount { + bitcoin::Amount::ZERO + } +} + fn borsh_serialize_bitcoin_outpoint( block_hash: &bitcoin::OutPoint, writer: &mut W, @@ -49,6 +63,7 @@ pub enum OutPoint { vout: u32, }, // Created by mainchain deposits. + #[schema(value_type = crate::types::schema::BitcoinOutPoint)] Deposit( #[borsh(serialize_with = "borsh_serialize_bitcoin_outpoint")] bitcoin::OutPoint, @@ -109,54 +124,190 @@ where borsh::BorshSerialize::serialize(spk.as_bytes(), writer) } -#[derive( - BorshSerialize, - Clone, - Debug, - Deserialize, - Eq, - PartialEq, - Serialize, - ToSchema, -)] -#[schema(as = OutputContent)] -pub enum Content { - Value( - #[borsh(serialize_with = "borsh_serialize_bitcoin_amount")] - bitcoin::Amount, - ), - Withdrawal { - #[borsh(serialize_with = "borsh_serialize_bitcoin_amount")] - value: bitcoin::Amount, - #[borsh(serialize_with = "borsh_serialize_bitcoin_amount")] - main_fee: bitcoin::Amount, - #[borsh(serialize_with = "borsh_serialize_bitcoin_address")] - main_address: bitcoin::Address, - }, -} +mod content { + use serde::{Deserialize, Serialize}; + use utoipa::{PartialSchema, ToSchema}; + + /// Default representation for Serde + #[derive(Deserialize, Serialize)] + enum DefaultRepr { + Value(bitcoin::Amount), + Withdrawal { + value: bitcoin::Amount, + main_fee: bitcoin::Amount, + main_address: bitcoin::Address, + }, + } -impl Content { - pub fn is_value(&self) -> bool { - matches!(self, Self::Value(_)) + /// Human-readable representation for Serde + #[derive(Deserialize, Serialize, ToSchema)] + #[schema(as = OutputContent, description = "")] + enum HumanReadableRepr { + #[schema(value_type = u64)] + Value( + #[serde(with = "bitcoin::amount::serde::as_sat")] bitcoin::Amount, + ), + Withdrawal { + #[serde(with = "bitcoin::amount::serde::as_sat")] + #[serde(rename = "value_sats")] + #[schema(value_type = u64)] + value: bitcoin::Amount, + #[serde(with = "bitcoin::amount::serde::as_sat")] + #[serde(rename = "main_fee_sats")] + #[schema(value_type = u64)] + main_fee: bitcoin::Amount, + #[schema(value_type = crate::types::schema::BitcoinAddr)] + main_address: bitcoin::Address, + }, } - pub fn is_withdrawal(&self) -> bool { - matches!(self, Self::Withdrawal { .. }) + + type SerdeRepr = serde_with::IfIsHumanReadable< + serde_with::FromInto, + serde_with::FromInto, + >; + + #[derive(borsh::BorshSerialize, Clone, Debug, Eq, PartialEq)] + pub enum Content { + Value( + #[borsh(serialize_with = "super::borsh_serialize_bitcoin_amount")] + bitcoin::Amount, + ), + Withdrawal { + #[borsh(serialize_with = "super::borsh_serialize_bitcoin_amount")] + value: bitcoin::Amount, + #[borsh(serialize_with = "super::borsh_serialize_bitcoin_amount")] + main_fee: bitcoin::Amount, + #[borsh(serialize_with = "super::borsh_serialize_bitcoin_address")] + main_address: bitcoin::Address, + }, } -} -impl GetValue for Content { - #[inline(always)] - fn get_value(&self) -> bitcoin::Amount { - match self { - Self::Value(value) => *value, - Self::Withdrawal { value, .. } => *value, + impl Content { + pub fn is_value(&self) -> bool { + matches!(self, Self::Value(_)) + } + pub fn is_withdrawal(&self) -> bool { + matches!(self, Self::Withdrawal { .. }) + } + + pub(in crate::types) fn schema_ref() -> utoipa::openapi::Ref { + utoipa::openapi::Ref::new("OutputContent") + } + } + + impl crate::wallet::GetValue for Content { + #[inline(always)] + fn get_value(&self) -> bitcoin::Amount { + match self { + Self::Value(value) => *value, + Self::Withdrawal { value, .. } => *value, + } } } -} -fn output_content_schema_ref() -> utoipa::openapi::Ref { - utoipa::openapi::Ref::new("OutputContent") + impl From for DefaultRepr { + fn from(content: Content) -> Self { + match content { + Content::Value(value) => Self::Value(value), + Content::Withdrawal { + value, + main_fee, + main_address, + } => Self::Withdrawal { + value, + main_fee, + main_address, + }, + } + } + } + + impl From for HumanReadableRepr { + fn from(content: Content) -> Self { + match content { + Content::Value(value) => Self::Value(value), + Content::Withdrawal { + value, + main_fee, + main_address, + } => Self::Withdrawal { + value, + main_fee, + main_address, + }, + } + } + } + + impl From for Content { + fn from(repr: DefaultRepr) -> Self { + match repr { + DefaultRepr::Value(value) => Self::Value(value), + DefaultRepr::Withdrawal { + value, + main_fee, + main_address, + } => Self::Withdrawal { + value, + main_fee, + main_address, + }, + } + } + } + + impl From for Content { + fn from(repr: HumanReadableRepr) -> Self { + match repr { + HumanReadableRepr::Value(value) => Self::Value(value), + HumanReadableRepr::Withdrawal { + value, + main_fee, + main_address, + } => Self::Withdrawal { + value, + main_fee, + main_address, + }, + } + } + } + + impl<'de> Deserialize<'de> for Content { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + >::deserialize_as( + deserializer, + ) + } + } + + impl Serialize for Content { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + >::serialize_as( + self, serializer, + ) + } + } + + impl PartialSchema for Content { + fn schema() -> utoipa::openapi::RefOr { + ::schema() + } + } + + impl ToSchema for Content { + fn name() -> std::borrow::Cow<'static, str> { + ::name() + } + } } +pub use content::Content; #[derive( BorshSerialize, @@ -170,7 +321,7 @@ fn output_content_schema_ref() -> utoipa::openapi::Ref { )] pub struct Output { pub address: Address, - #[schema(schema_with = output_content_schema_ref)] + #[schema(schema_with = Content::schema_ref)] pub content: Content, } @@ -400,20 +551,6 @@ impl Body { } } -pub trait GetAddress { - fn get_address(&self) -> Address; -} - -pub trait GetValue { - fn get_value(&self) -> bitcoin::Amount; -} - -impl GetValue for () { - fn get_value(&self) -> bitcoin::Amount { - bitcoin::Amount::ZERO - } -} - pub trait Verify { type Error; fn verify_transaction( diff --git a/lib/wallet.rs b/lib/wallet.rs index b045fd6..575bdaf 100644 --- a/lib/wallet.rs +++ b/lib/wallet.rs @@ -3,14 +3,17 @@ use std::{ path::Path, }; +use bitcoin::Amount; use byteorder::{BigEndian, ByteOrder}; use ed25519_dalek_bip32::{ChildIndex, DerivationPath, ExtendedSigningKey}; +use fallible_iterator::{FallibleIterator as _, IteratorExt as _}; use futures::{Stream, StreamExt}; use heed::{ types::{Bytes, SerdeBincode, U8}, RoTxn, }; use rustreexo::accumulator::node_hash::NodeHash; +use serde::{Deserialize, Serialize}; use tokio_stream::{wrappers::WatchStream, StreamMap}; pub use crate::{ @@ -28,6 +31,19 @@ use crate::{ util::{EnvExt, Watchable, WatchableDb}, }; +#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] +pub struct Balance { + #[serde(rename = "total_sats", with = "bitcoin::amount::serde::as_sat")] + #[schema(value_type = u64)] + pub total: Amount, + #[serde( + rename = "available_sats", + with = "bitcoin::amount::serde::as_sat" + )] + #[schema(value_type = u64)] + pub available: Amount, +} + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("address {address} does not exist")] @@ -305,15 +321,28 @@ impl Wallet { Ok(()) } - pub fn get_balance(&self) -> Result { - let mut balance = bitcoin::Amount::ZERO; + pub fn get_balance(&self) -> Result { + let mut balance = Balance::default(); let txn = self.env.read_txn()?; - for item in self.utxos.iter(&txn)? { - let (_, utxo) = item?; - balance = balance - .checked_add(utxo.get_value()) - .ok_or(AmountOverflowError)?; - } + let () = self + .utxos + .iter(&txn)? + .transpose_into_fallible() + .map_err(Error::from) + .for_each(|(_, utxo)| { + let value = utxo.get_value(); + balance.total = balance + .total + .checked_add(value) + .ok_or(AmountOverflowError)?; + if !utxo.content.is_withdrawal() { + balance.available = balance + .available + .checked_add(value) + .ok_or(AmountOverflowError)?; + } + Ok(()) + })?; Ok(balance) } diff --git a/rpc-api/Cargo.toml b/rpc-api/Cargo.toml index 9a67f5e..28a8cab 100644 --- a/rpc-api/Cargo.toml +++ b/rpc-api/Cargo.toml @@ -9,11 +9,11 @@ bitcoin = { version = "0.32.2", features = ["serde"] } jsonrpsee = { version = "0.23.2", features = ["macros"] } thunder = { path = "../lib" } serde_json = "1.0.113" -utoipa = "4.2.3" +utoipa = "5.2.0" [dependencies.l2l-openapi] git = "https://github.com/Ash-L2L/l2l-openapi" -rev = "c1fe05fd3cc80220b5db3413adb5ffb85f2f302e" +rev = "a3e84daa19fd7c1121346984c3ef85304a144792" [lib] name = "thunder_app_rpc_api" diff --git a/rpc-api/lib.rs b/rpc-api/lib.rs index 4b2d89d..649af66 100644 --- a/rpc-api/lib.rs +++ b/rpc-api/lib.rs @@ -4,107 +4,39 @@ use std::net::SocketAddr; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use l2l_openapi::open_api; -use thunder::types::{ - Address, MerkleRoot, OutPoint, Output, OutputContent, PointedOutput, Txid, +use thunder::{ + types::{ + schema as thunder_schema, Address, MerkleRoot, OutPoint, Output, + OutputContent, PointedOutput, Txid, + }, + wallet::Balance, }; -use utoipa::{ - openapi::{RefOr, Schema, SchemaType}, - PartialSchema, ToSchema, -}; - -struct BitcoinAddrSchema; - -impl PartialSchema for BitcoinAddrSchema { - fn schema() -> RefOr { - let obj = utoipa::openapi::Object::with_type(SchemaType::String); - RefOr::T(Schema::Object(obj)) - } -} - -impl ToSchema<'static> for BitcoinAddrSchema { - fn schema() -> (&'static str, RefOr) { - ("bitcoin.Address", ::schema()) - } -} - -struct BitcoinAmountSchema; -impl PartialSchema for BitcoinAmountSchema { - fn schema() -> RefOr { - let obj = utoipa::openapi::Object::with_type(SchemaType::String); - RefOr::T(Schema::Object(obj)) - } -} - -struct BitcoinOutPointSchema; - -impl PartialSchema for BitcoinOutPointSchema { - fn schema() -> RefOr { - let obj = utoipa::openapi::Object::new(); - RefOr::T(Schema::Object(obj)) - } -} - -impl ToSchema<'static> for BitcoinOutPointSchema { - fn schema() -> (&'static str, RefOr) { - ("bitcoin.OutPoint", ::schema()) - } -} - -struct BitcoinTxidSchema; - -impl PartialSchema for BitcoinTxidSchema { - fn schema() -> RefOr { - let obj = utoipa::openapi::Object::with_type(SchemaType::String); - RefOr::T(Schema::Object(obj)) - } -} - -impl ToSchema<'static> for BitcoinTxidSchema { - fn schema() -> (&'static str, RefOr) { - ("bitcoin.Txid", ::schema()) - } -} - -struct OpenApiSchema; - -impl PartialSchema for OpenApiSchema { - fn schema() -> RefOr { - let obj = utoipa::openapi::Object::new(); - RefOr::T(Schema::Object(obj)) - } -} - -struct SocketAddrSchema; - -impl PartialSchema for SocketAddrSchema { - fn schema() -> RefOr { - let obj = utoipa::openapi::Object::with_type(SchemaType::String); - RefOr::T(Schema::Object(obj)) - } -} +mod schema; #[open_api(ref_schemas[ - Address, BitcoinAddrSchema, BitcoinOutPointSchema, BitcoinTxidSchema, - MerkleRoot, OutPoint, Output, OutputContent, Txid + Address, MerkleRoot, OutPoint, Output, OutputContent, Txid, + schema::BitcoinTxid, thunder_schema::BitcoinAddr, + thunder_schema::BitcoinOutPoint, ])] #[rpc(client, server)] pub trait Rpc { /// Get balance in sats + #[open_api_method(output_schema(ToSchema))] #[method(name = "balance")] - async fn balance(&self) -> RpcResult; + async fn balance(&self) -> RpcResult; /// Connect to a peer #[open_api_method(output_schema(ToSchema))] #[method(name = "connect_peer")] async fn connect_peer( &self, - #[open_api_method_arg(schema(PartialSchema = "SocketAddrSchema"))] + #[open_api_method_arg(schema(PartialSchema = "schema::SocketAddr"))] addr: SocketAddr, ) -> RpcResult<()>; /// Deposit to address - #[open_api_method(output_schema(PartialSchema = "BitcoinTxidSchema"))] + #[open_api_method(output_schema(PartialSchema = "schema::BitcoinTxid"))] #[method(name = "create_deposit")] async fn create_deposit( &self, @@ -150,7 +82,7 @@ pub trait Rpc { async fn mine(&self, fee: Option) -> RpcResult<()>; /// Get OpenAPI schema - #[open_api_method(output_schema(PartialSchema = "OpenApiSchema"))] + #[open_api_method(output_schema(PartialSchema = "schema::OpenApi"))] #[method(name = "openapi_schema")] async fn openapi_schema(&self) -> RpcResult; @@ -165,9 +97,8 @@ pub trait Rpc { async fn set_seed_from_mnemonic(&self, mnemonic: String) -> RpcResult<()>; /// Get total sidechain wealth - #[open_api_method(output_schema(PartialSchema = "BitcoinAmountSchema"))] #[method(name = "sidechain_wealth")] - async fn sidechain_wealth(&self) -> RpcResult; + async fn sidechain_wealth_sats(&self) -> RpcResult; /// Stop the node #[method(name = "stop")] @@ -186,7 +117,9 @@ pub trait Rpc { #[method(name = "withdraw")] async fn withdraw( &self, - #[open_api_method_arg(schema(PartialSchema = "BitcoinAddrSchema"))] + #[open_api_method_arg(schema( + PartialSchema = "thunder::types::schema::BitcoinAddr" + ))] mainchain_address: bitcoin::Address< bitcoin::address::NetworkUnchecked, >, diff --git a/rpc-api/schema.rs b/rpc-api/schema.rs new file mode 100644 index 0000000..510c169 --- /dev/null +++ b/rpc-api/schema.rs @@ -0,0 +1,39 @@ +//! Schemas for OpenAPI + +use utoipa::{ + openapi::{self, RefOr, Schema}, + PartialSchema, ToSchema, +}; + +pub struct BitcoinTxid; + +impl PartialSchema for BitcoinTxid { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(openapi::Type::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema for BitcoinTxid { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("bitcoin.Txid") + } +} + +pub struct OpenApi; + +impl PartialSchema for OpenApi { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::new(); + RefOr::T(Schema::Object(obj)) + } +} + +pub struct SocketAddr; + +impl PartialSchema for SocketAddr { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(openapi::Type::String); + RefOr::T(Schema::Object(obj)) + } +}