diff --git a/Cargo.lock b/Cargo.lock index f992ac4..f886af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2565,6 +2565,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -2873,6 +2874,27 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "l2l-openapi" +version = "0.0.0" +source = "git+https://github.com/Ash-L2L/l2l-openapi?rev=6e440bb2715ec0d495050e9dbbda3f1590a07385#6e440bb2715ec0d495050e9dbbda3f1590a07385" +dependencies = [ + "jsonrpsee", + "l2l-openapi-macros", + "utoipa", +] + +[[package]] +name = "l2l-openapi-macros" +version = "0.0.0" +source = "git+https://github.com/Ash-L2L/l2l-openapi?rev=6e440bb2715ec0d495050e9dbbda3f1590a07385#6e440bb2715ec0d495050e9dbbda3f1590a07385" +dependencies = [ + "proc-macro2", + "proc_macro_roids", + "quote", + "syn 2.0.61", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -3665,7 +3687,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain_bitnames" -version = "0.7.7" +version = "0.7.8" dependencies = [ "addr", "anyhow", @@ -3705,12 +3727,13 @@ dependencies = [ "tokio-stream", "tokio-util", "tracing", + "utoipa", "x25519-dalek", ] [[package]] name = "plain_bitnames_app" -version = "0.7.7" +version = "0.7.8" dependencies = [ "anyhow", "async_zmq", @@ -3748,11 +3771,12 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "utoipa", ] [[package]] name = "plain_bitnames_app_cli" -version = "0.7.7" +version = "0.7.8" dependencies = [ "anyhow", "bip300301", @@ -3762,16 +3786,19 @@ dependencies = [ "plain_bitnames_app_rpc_api", "serde_json", "tokio", + "utoipa", ] [[package]] name = "plain_bitnames_app_rpc_api" -version = "0.7.7" +version = "0.7.8" dependencies = [ "bip300301", "jsonrpsee", + "l2l-openapi", "plain_bitnames", "serde", + "utoipa", ] [[package]] @@ -3893,6 +3920,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", + "syn 1.0.109", "version_check", ] @@ -3916,6 +3944,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc_macro_roids" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c2a098cd8aaa29f66da27a684ad19f4b7bc886f576abf12f7df4a7391071c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "profiling" version = "1.0.15" @@ -5332,6 +5371,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf0e16c02bc4bf5322ab65f10ab1149bdbcaa782cba66dc7057370a3f8190be" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0d39cf3..2d1fc9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ [workspace.package] authors = [ "Ash Manning " ] edition = "2021" -version = "0.7.7" +version = "0.7.8" [workspace.dependencies.bip300301] git = "https://github.com/Ash-L2L/bip300301.git" diff --git a/app/Cargo.toml b/app/Cargo.toml index 96aab8e..be17d5c 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -44,6 +44,7 @@ tokio-util = { version = "0.7.10", features = ["rt"] } tracing = "0.1.40" tracing-appender = "0.2.3" tracing-subscriber = "0.3.18" +utoipa = "4.2.3" [dependencies.libes] version = "0.9.1" diff --git a/app/rpc_server.rs b/app/rpc_server.rs index a313a04..14b3d86 100644 --- a/app/rpc_server.rs +++ b/app/rpc_server.rs @@ -193,6 +193,12 @@ impl RpcServer for RpcServerImpl { Ok(utxos) } + async fn openapi_schema(&self) -> RpcResult { + let res = + ::openapi(); + Ok(res) + } + async fn reserve_bitname(&self, plain_name: String) -> RpcResult { let mut tx = Transaction::default(); let () = match self.app.wallet.reserve_bitname(&mut tx, &plain_name) { diff --git a/cli/Cargo.toml b/cli/Cargo.toml index be76b69..4a0863f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -13,6 +13,7 @@ plain_bitnames = { path = "../lib" } plain_bitnames_app_rpc_api = { path = "../rpc-api" } serde_json = "1.0.113" tokio = "1.29.1" +utoipa = "4.2.3" [lib] name = "plain_bitnames_app_cli_lib" diff --git a/cli/lib.rs b/cli/lib.rs index 12646ce..fcf5847 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -43,6 +43,9 @@ pub enum Command { }, /// List owned UTXOs MyUtxos, + /// Show OpenAPI schema + #[command(name = "openapi-schema")] + OpenApiSchema, /// Reserve a BitName ReserveBitname { plaintext_name: String }, /// Set the wallet seed from a mnemonic seed phrase @@ -144,6 +147,11 @@ impl Cli { let utxos = rpc_client.my_utxos().await?; serde_json::to_string_pretty(&utxos)? } + Command::OpenApiSchema => { + let openapi = + ::openapi(); + openapi.to_pretty_json()? + } Command::ReserveBitname { plaintext_name } => { let txid = rpc_client.reserve_bitname(plaintext_name).await?; format!("{txid}") diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 4ea5a1d..3a33662 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -42,6 +42,7 @@ tokio = { version = "1.29.1", features = ["rt-multi-thread", "sync"] } tokio-stream = { version = "0.1.15", features = ["sync"] } tokio-util = { version = "0.7.10", features = ["rt"] } tracing = "0.1.40" +utoipa = "4.2.3" x25519-dalek = { version = "2.0.0", features = ["serde"] } [target.'cfg(not(target_os = "windows"))'.dependencies.async_zmq] diff --git a/lib/authorization.rs b/lib/authorization.rs index 9b05219..760b084 100644 --- a/lib/authorization.rs +++ b/lib/authorization.rs @@ -1,6 +1,7 @@ use borsh::BorshSerialize; use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _}; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; use crate::types::{ Address, AuthorizedTransaction, Body, GetAddress, Transaction, Verify, @@ -49,12 +50,21 @@ where } #[derive( - BorshSerialize, Debug, Clone, Deserialize, Eq, PartialEq, Serialize, + BorshSerialize, + Debug, + Clone, + Deserialize, + Eq, + PartialEq, + Serialize, + ToSchema, )] pub struct Authorization { #[borsh(serialize_with = "borsh_serialize_verifying_key")] + #[schema(schema_with = ::schema)] pub verifying_key: VerifyingKey, #[borsh(serialize_with = "borsh_serialize_signature")] + #[schema(schema_with = ::schema)] pub signature: Signature, } diff --git a/lib/types/address.rs b/lib/types/address.rs index 5330740..f67cf81 100644 --- a/lib/types/address.rs +++ b/lib/types/address.rs @@ -79,3 +79,21 @@ 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 92ad938..678b1b0 100644 --- a/lib/types/hashes.rs +++ b/lib/types/hashes.rs @@ -79,6 +79,24 @@ impl FromStr for BlockHash { } } +impl utoipa::PartialSchema for BlockHash { + 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 BlockHash { + fn schema() -> ( + &'static str, + utoipa::openapi::RefOr, + ) { + ("BlockHash", ::schema()) + } +} + #[derive( BorshSerialize, Clone, @@ -120,6 +138,24 @@ 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, + ); + utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(obj)) + } +} + +impl utoipa::ToSchema<'static> for MerkleRoot { + fn schema() -> ( + &'static str, + utoipa::openapi::RefOr, + ) { + ("MerkleRoot", ::schema()) + } +} + #[derive( BorshSerialize, Clone, @@ -180,6 +216,24 @@ 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, + ); + utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(obj)) + } +} + +impl utoipa::ToSchema<'static> for Txid { + fn schema() -> ( + &'static str, + utoipa::openapi::RefOr, + ) { + ("Txid", ::schema()) + } +} + /// Identifier for a BitName #[derive( BorshDeserialize, @@ -213,6 +267,24 @@ impl FromHex for BitName { } } +impl utoipa::PartialSchema for BitName { + 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 BitName { + fn schema() -> ( + &'static str, + utoipa::openapi::RefOr, + ) { + ("BitName", ::schema()) + } +} + pub fn hash(data: &T) -> Hash where T: BorshSerialize, diff --git a/lib/types/mod.rs b/lib/types/mod.rs index fe30b23..4c17361 100644 --- a/lib/types/mod.rs +++ b/lib/types/mod.rs @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use bip300301::bitcoin; use thiserror::Error; +use utoipa::ToSchema; -use crate::authorization::Authorization; +pub use crate::authorization::Authorization; mod address; pub mod constants; @@ -20,11 +21,12 @@ mod transaction; pub use address::*; pub use hashes::{BitName, BlockHash, Hash, MerkleRoot, Txid}; pub use transaction::{ - Authorized, AuthorizedTransaction, BatchIcannRegistrationData, BitNameData, - BitNameDataUpdates, Content as OutputContent, - FilledContent as FilledOutputContent, FilledOutput, FilledTransaction, - InPoint, OutPoint, Output, Pointed as PointedOutput, SpentOutput, - Transaction, TxData, Update, + open_api_schemas, Authorized, AuthorizedTransaction, + BatchIcannRegistrationData, BitNameData, BitNameDataUpdates, + Content as OutputContent, FilledContent as FilledOutputContent, + FilledOutput, FilledTransaction, InPoint, OutPoint, Output, + Pointed as PointedOutput, SpentOutput, Transaction, TransactionData, + TxData, Update, }; /// (de)serialize as Display/FromStr for human-readable forms like json, @@ -155,7 +157,15 @@ where } #[derive( - BorshSerialize, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, + BorshSerialize, + Clone, + Debug, + Deserialize, + Eq, + Hash, + PartialEq, + Serialize, + ToSchema, )] pub struct Header { pub merkle_root: MerkleRoot, @@ -164,6 +174,12 @@ pub struct Header { pub prev_main_hash: bitcoin::BlockHash, } +impl Header { + pub fn hash(&self) -> BlockHash { + hashes::hash(self).into() + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum WithdrawalBundleStatus { Failed, @@ -241,7 +257,7 @@ impl merkle_cbt::merkle_tree::Merge for MergeFeeSizeTotal { // Complete binary merkle tree with annotated fee and canonical size totals type CbmtWithFeeTotal = merkle_cbt::CBMT; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct Body { pub coinbase: Vec, pub transactions: Vec, @@ -355,7 +371,7 @@ impl Body { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct Block { #[serde(flatten)] pub header: Header, @@ -442,12 +458,6 @@ where } } -impl Header { - pub fn hash(&self) -> BlockHash { - hashes::hash(self).into() - } -} - impl Ord for AggregatedWithdrawal { fn cmp(&self, other: &Self) -> Ordering { if self == other { @@ -470,7 +480,7 @@ impl PartialOrd for AggregatedWithdrawal { } /// Transaction index -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, ToSchema)] pub struct TxIn { pub block_hash: BlockHash, pub idx: u32, diff --git a/lib/types/transaction.rs b/lib/types/transaction.rs index b39cf2a..45a4556 100644 --- a/lib/types/transaction.rs +++ b/lib/types/transaction.rs @@ -8,6 +8,10 @@ use educe::Educe; use serde::{Deserialize, Serialize}; use bip300301::bitcoin; +use utoipa::{ + openapi::{RefOr, Schema}, + PartialSchema, ToSchema, +}; use super::{ address::Address, @@ -41,6 +45,7 @@ where PartialEq, PartialOrd, Serialize, + ToSchema, )] pub enum OutPoint { // Created by transactions. @@ -60,6 +65,12 @@ pub enum OutPoint { ), } +impl PartialSchema for OutPoint { + fn schema() -> RefOr { + ::schema().1 + } +} + /// Reference to a tx input. #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub enum InPoint { @@ -91,8 +102,16 @@ where } #[derive( - BorshSerialize, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, + BorshSerialize, + Clone, + Debug, + Deserialize, + Eq, + PartialEq, + Serialize, + ToSchema, )] +#[schema(as = OutputContent)] pub enum Content { BitName, BitNameReservation, @@ -106,11 +125,19 @@ pub enum Content { } #[derive( - BorshSerialize, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, + BorshSerialize, + Clone, + Debug, + Deserialize, + Eq, + PartialEq, + Serialize, + ToSchema, )] pub struct Output { #[serde(with = "serde_display_fromstr_human_readable")] pub address: Address, + #[schema(value_type = OutputContent)] pub content: Content, #[serde(with = "serde_hexstr_human_readable")] pub memo: Vec, @@ -148,6 +175,7 @@ where Eq, PartialEq, Serialize, + ToSchema, )] #[educe(Hash)] pub struct BitNameData { @@ -175,6 +203,76 @@ pub enum Update { Set(T), } +impl Update { + /// Create a schema from a schema for `T`. + fn schema(schema_t: RefOr) -> RefOr { + let schema_delete = utoipa::openapi::ObjectBuilder::new() + .schema_type(utoipa::openapi::SchemaType::String) + .enum_values(Some(["Delete"])); + let schema_retain = utoipa::openapi::ObjectBuilder::new() + .schema_type(utoipa::openapi::SchemaType::String) + .enum_values(Some(["Retain"])); + let schema_set = utoipa::openapi::ObjectBuilder::new() + .property("Set", schema_t) + .required("Set"); + let schema = utoipa::openapi::OneOfBuilder::new() + .item(schema_delete) + .item(schema_retain) + .item(schema_set) + .build() + .into(); + RefOr::T(schema) + } +} + +pub type UpdateHash = Update; +impl<'a> ToSchema<'a> for Update { + fn schema() -> (&'a str, RefOr) { + let schema_ref = utoipa::openapi::Ref::from_schema_name("Hash"); + ("UpdateHash", Self::schema(schema_ref.into())) + } +} + +pub type UpdateIpv4Addr = Update; +impl<'a> ToSchema<'a> for Update { + fn schema() -> (&'a str, RefOr) { + let schema_ref = utoipa::openapi::Ref::from_schema_name("Ipv4Addr"); + ("UpdateIpv4Addr", Self::schema(schema_ref.into())) + } +} + +pub type UpdateIpv6Addr = Update; +impl<'a> ToSchema<'a> for Update { + fn schema() -> (&'a str, RefOr) { + let schema_ref = utoipa::openapi::Ref::from_schema_name("Ipv6Addr"); + ("UpdateIpv6Addr", Self::schema(schema_ref.into())) + } +} + +pub type UpdateEncryptionPubKey = Update; +impl<'a> ToSchema<'a> for Update { + fn schema() -> (&'a str, RefOr) { + let schema_ref = + utoipa::openapi::Ref::from_schema_name("EncryptionPubKey"); + ("UpdateEncryptionPubKey", Self::schema(schema_ref.into())) + } +} + +pub type UpdateVerifyingKey = Update; +impl<'a> ToSchema<'a> for Update { + fn schema() -> (&'a str, RefOr) { + let schema_ref = utoipa::openapi::Ref::from_schema_name("VerifyingKey"); + ("UpdateVerifyingKey", Self::schema(schema_ref.into())) + } +} + +pub type UpdateU64 = Update; +impl<'a> ToSchema<'a> for Update { + fn schema() -> (&'a str, RefOr) { + ("UpdateU64", Self::schema(::schema())) + } +} + fn borsh_serialize_update_pubkey( update: &Update, writer: &mut W, @@ -191,20 +289,26 @@ where } /// updates to the data associated with a BitName -#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize)] +#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct BitNameDataUpdates { /// commitment to arbitrary data + #[schema(value_type = UpdateHash)] pub commitment: Update, /// optional ipv4 addr + #[schema(value_type = UpdateIpv4Addr)] pub ipv4_addr: Update, /// optional ipv6 addr + #[schema(value_type = UpdateIpv6Addr)] pub ipv6_addr: Update, /// optional pubkey used for encryption + #[schema(value_type = UpdateEncryptionPubKey)] pub encryption_pubkey: Update, /// optional pubkey used for signing messages #[borsh(serialize_with = "borsh_serialize_update_pubkey")] + #[schema(value_type = UpdateVerifyingKey)] pub signing_pubkey: Update, /// optional minimum paymail fee, in sats + #[schema(value_type = UpdateU64)] pub paymail_fee: Update, } @@ -219,17 +323,18 @@ where } /// batch icann registration tx payload -#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize)] +#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct BatchIcannRegistrationData { /// Plaintext names of the bitnames to be registered as ICANN domains pub plain_names: Vec, /// Signature over the batch icann registration tx #[borsh(serialize_with = "borsh_serialize_signature")] + #[schema(schema_with = ::schema)] pub signature: Signature, } #[allow(clippy::enum_variant_names)] -#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize)] +#[derive(BorshSerialize, Clone, Debug, Deserialize, Serialize, ToSchema)] pub enum TransactionData { BitNameReservation { /// commitment to the BitName that will be registered @@ -251,9 +356,13 @@ pub enum TransactionData { pub type TxData = TransactionData; -#[derive(BorshSerialize, Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + BorshSerialize, Clone, Debug, Default, Deserialize, Serialize, ToSchema, +)] pub struct Transaction { + #[schema(schema_with = TxInputs::schema)] pub inputs: TxInputs, + #[schema(schema_with = TxOutputs::schema)] pub outputs: TxOutputs, #[serde(with = "serde_hexstr_human_readable")] pub memo: Vec, @@ -262,7 +371,8 @@ pub struct Transaction { /// Representation of Output Content that includes asset type and/or /// reservation commitment -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)] +#[schema(as = FilledOutputContent)] pub enum FilledContent { Bitcoin(u64), BitcoinWithdrawal { @@ -275,11 +385,16 @@ pub enum FilledContent { BitNameReservation(Txid, Hash), } +fn filled_output_content_schema_ref() -> utoipa::openapi::Ref { + utoipa::openapi::Ref::new("FilledOutputContent") +} + /// Representation of output that includes asset type -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)] pub struct FilledOutput { #[serde(with = "serde_display_fromstr_human_readable")] pub address: Address, + #[schema(schema_with = filled_output_content_schema_ref)] pub content: FilledContent, #[serde(with = "serde_hexstr_human_readable")] pub memo: Vec, @@ -913,9 +1028,29 @@ impl FilledTransaction { } #[derive( - BorshSerialize, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, + BorshSerialize, + Clone, + Debug, + Deserialize, + Eq, + PartialEq, + Serialize, + ToSchema, +)] +#[aliases( + PointedOutput = Pointed, + PointedFilledOutput = Pointed )] pub struct Pointed { pub outpoint: OutPoint, + // Utoipa can't handle generics properly + #[schema(value_type = Value)] pub output: OutputKind, } + +pub mod open_api_schemas { + pub use super::{ + PointedFilledOutput, PointedOutput, UpdateEncryptionPubKey, UpdateHash, + UpdateIpv4Addr, UpdateIpv6Addr, UpdateU64, UpdateVerifyingKey, + }; +} diff --git a/rpc-api/Cargo.toml b/rpc-api/Cargo.toml index f1621f9..4113e38 100644 --- a/rpc-api/Cargo.toml +++ b/rpc-api/Cargo.toml @@ -9,6 +9,11 @@ bip300301.workspace = true jsonrpsee = { version = "0.20.0", features = ["macros"] } plain_bitnames = { path = "../lib" } serde = { version = "1.0.179", features = ["derive"] } +utoipa = "4.2.3" + +[dependencies.l2l-openapi] +git = "https://github.com/Ash-L2L/l2l-openapi" +rev = "6e440bb2715ec0d495050e9dbbda3f1590a07385" [lib] name = "plain_bitnames_app_rpc_api" diff --git a/rpc-api/lib.rs b/rpc-api/lib.rs index 4545767..ccf8811 100644 --- a/rpc-api/lib.rs +++ b/rpc-api/lib.rs @@ -1,22 +1,222 @@ //! RPC API -use std::{collections::HashMap, net::SocketAddr}; +use std::{collections::HashMap, marker::PhantomData, net::SocketAddr}; use bip300301::bitcoin; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use l2l_openapi::open_api; use plain_bitnames::types::{ - hashes::BitName, Address, BitNameData, Block, BlockHash, FilledOutput, - OutPoint, PointedOutput, Transaction, TxIn, Txid, + hashes::BitName, open_api_schemas, Address, Authorization, + BatchIcannRegistrationData, BitNameData, BitNameDataUpdates, Block, + BlockHash, Body, FilledOutput, FilledOutputContent, Header, MerkleRoot, + OutPoint, Output, OutputContent, PointedOutput, Transaction, + TransactionData, TxIn, Txid, }; use serde::{Deserialize, Serialize}; +use utoipa::{ + openapi::{RefOr, Schema, SchemaType}, + PartialSchema, ToSchema, +}; + +/// Utoipa does not support tuples at all, so these are represented as an +/// arbitrary json value +#[derive(Default)] +struct ArrayTupleSchema(PhantomData, PhantomData); + +impl PartialSchema for ArrayTupleSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::Value); + RefOr::T(Schema::Object(obj)) + } +} + +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)) + } +} -#[derive(Clone, Debug, Deserialize, Serialize)] +impl ToSchema<'static> for BitcoinAmountSchema { + fn schema() -> (&'static str, RefOr) { + ("bitcoin.Amount", ::schema()) + } +} + +struct BitcoinBlockHashSchema; + +impl PartialSchema for BitcoinBlockHashSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for BitcoinBlockHashSchema { + fn schema() -> (&'static str, RefOr) { + ("bitcoin.BlockHash", ::schema()) + } +} + +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 EncryptionPubKeySchema; + +impl PartialSchema for EncryptionPubKeySchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for EncryptionPubKeySchema { + fn schema() -> (&'static str, RefOr) { + ("EncryptionPubKey", ::schema()) + } +} + +struct HashSchema; + +impl PartialSchema for HashSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::new(); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for HashSchema { + fn schema() -> (&'static str, RefOr) { + ("Hash", ::schema()) + } +} + +struct Ipv4AddrSchema; + +impl PartialSchema for Ipv4AddrSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for Ipv4AddrSchema { + fn schema() -> (&'static str, RefOr) { + ("Ipv4Addr", ::schema()) + } +} + +struct Ipv6AddrSchema; + +impl PartialSchema for Ipv6AddrSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for Ipv6AddrSchema { + fn schema() -> (&'static str, RefOr) { + ("Ipv6Addr", ::schema()) + } +} + +struct OpenApiSchema; + +impl PartialSchema for OpenApiSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::new(); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for OpenApiSchema { + fn schema() -> (&'static str, RefOr) { + ("OpenApiSchema", ::schema()) + } +} + +struct SocketAddrSchema; + +impl PartialSchema for SocketAddrSchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for SocketAddrSchema { + fn schema() -> (&'static str, RefOr) { + ("SocketAddr", ::schema()) + } +} + +struct VerifyingKeySchema; + +impl PartialSchema for VerifyingKeySchema { + fn schema() -> RefOr { + let obj = utoipa::openapi::Object::with_type(SchemaType::String); + RefOr::T(Schema::Object(obj)) + } +} + +impl ToSchema<'static> for VerifyingKeySchema { + fn schema() -> (&'static str, RefOr) { + ("VerifyingKey", ::schema()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct TxInfo { pub confirmations: Option, pub fee_sats: u64, pub txin: Option, } +#[open_api(ref_schemas[ + open_api_schemas::PointedFilledOutput, open_api_schemas::PointedOutput, + open_api_schemas::UpdateHash, + open_api_schemas::UpdateIpv4Addr, open_api_schemas::UpdateIpv6Addr, + open_api_schemas::UpdateEncryptionPubKey, + open_api_schemas::UpdateVerifyingKey, open_api_schemas::UpdateU64, + Address, Authorization, BatchIcannRegistrationData, BitcoinAddrSchema, + BitcoinBlockHashSchema, BitcoinOutPointSchema, BitName, BitNameData, + BitNameDataUpdates, + BlockHash, Body, EncryptionPubKeySchema, + FilledOutputContent, HashSchema, + Header, Ipv4AddrSchema, Ipv6AddrSchema, MerkleRoot, OutPoint, + Output, OutputContent, Transaction, TransactionData, Txid, TxIn, + VerifyingKeySchema +])] #[rpc(client, server)] pub trait Rpc { /// Get balance in sats @@ -24,12 +224,20 @@ pub trait Rpc { async fn balance(&self) -> RpcResult; /// List all BitNames + #[open_api_method(output_schema( + PartialSchema = "ArrayTupleSchema" + ))] #[method(name = "bitnames")] async fn bitnames(&self) -> RpcResult>; /// Connect to a peer + #[open_api_method(output_schema(ToSchema))] #[method(name = "connect_peer")] - async fn connect_peer(&self, addr: SocketAddr) -> RpcResult<()>; + async fn connect_peer( + &self, + #[open_api_method_arg(schema(ToSchema = "SocketAddrSchema"))] + addr: SocketAddr, + ) -> RpcResult<()>; /// Format a deposit address #[method(name = "format_deposit_address")] @@ -43,6 +251,7 @@ pub trait Rpc { async fn generate_mnemonic(&self) -> RpcResult; /// Get block data + #[open_api_method(output_schema(ToSchema))] #[method(name = "get_block")] async fn get_block(&self, block_hash: BlockHash) -> RpcResult; @@ -83,10 +292,14 @@ pub trait Rpc { async fn getblockcount(&self) -> RpcResult; /// List all UTXOs + #[open_api_method(output_schema( + PartialSchema = "Vec" + ))] #[method(name = "list_utxos")] async fn list_utxos(&self) -> RpcResult>>; /// Attempt to mine a sidechain block + #[open_api_method(output_schema(ToSchema))] #[method(name = "mine")] async fn mine(&self, fee: Option) -> RpcResult<()>; @@ -94,15 +307,22 @@ pub trait Rpc { #[method(name = "my_utxos")] async fn my_utxos(&self) -> RpcResult>>; + /// Get OpenRPC schema + #[open_api_method(output_schema(PartialSchema = "OpenApiSchema"))] + #[method(name = "openapi_schema")] + async fn openapi_schema(&self) -> RpcResult; + /// Reserve a BitName #[method(name = "reserve_bitname")] async fn reserve_bitname(&self, plain_name: String) -> RpcResult; /// Set the wallet seed from a mnemonic seed phrase + #[open_api_method(output_schema(ToSchema))] #[method(name = "set_seed_from_mnemonic")] async fn set_seed_from_mnemonic(&self, mnemonic: String) -> RpcResult<()>; /// Get total sidechain wealth + #[open_api_method(output_schema(ToSchema = "BitcoinAmountSchema"))] #[method(name = "sidechain_wealth")] async fn sidechain_wealth(&self) -> RpcResult; @@ -124,7 +344,10 @@ pub trait Rpc { #[method(name = "withdraw")] async fn withdraw( &self, - mainchain_address: bitcoin::Address, + #[open_api_method_arg(schema(PartialSchema = "BitcoinAddrSchema"))] + mainchain_address: bitcoin::Address< + bitcoin::address::NetworkUnchecked, + >, amount_sats: u64, fee_sats: u64, mainchain_fee_sats: u64,