diff --git a/Cargo.lock b/Cargo.lock index e94cfb8c4..2108a51e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,7 +239,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -250,7 +250,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -409,7 +409,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.50", + "syn 2.0.52", "which", ] @@ -805,7 +805,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1066,7 +1066,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1090,7 +1090,7 @@ dependencies = [ "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1107,7 +1107,7 @@ checksum = "2fa16a70dd58129e4dfffdff535fb1bce66673f7bbeec4a5a1765a504e1ccd84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1155,7 +1155,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1177,7 +1177,7 @@ checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" dependencies = [ "darling_core 0.20.6", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1250,7 +1250,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1546,7 +1546,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2500,7 +2500,7 @@ checksum = "38b4faf00617defe497754acde3024865bc143d44a86799b24e191ecff91354f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2935,7 +2935,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2976,7 +2976,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -3077,7 +3077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -3200,7 +3200,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.50", + "syn 2.0.52", "tempfile", "which", ] @@ -3215,7 +3215,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -3923,7 +3923,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -4008,7 +4008,20 @@ dependencies = [ "darling 0.20.6", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", +] + +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap 2.2.3", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] @@ -4224,9 +4237,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -4325,7 +4338,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -4448,7 +4461,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -4632,7 +4645,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -4645,7 +4658,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -4775,7 +4788,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -4996,6 +5009,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "untrusted" version = "0.7.1" @@ -5207,7 +5226,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -5241,7 +5260,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6029,10 +6048,14 @@ dependencies = [ "hex", "itertools 0.12.1", "jsonrpc", + "quote", "regex", "reqwest", + "serde", "serde_json", + "serde_yaml", "structopt", + "syn 2.0.52", "thiserror", "tinyvec", "tokio", @@ -6133,7 +6156,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -6153,5 +6176,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] diff --git a/book/src/user/openapi.md b/book/src/user/openapi.md new file mode 100644 index 000000000..e9f52dec4 --- /dev/null +++ b/book/src/user/openapi.md @@ -0,0 +1,47 @@ +# Zebra OpenAPI specification + +The Zebra RPC methods are a collection of endpoints used for interacting with the Zcash blockchain. These methods are utilized by wallets, block explorers, web and mobile applications, and more, for retrieving and sending information to the blockchain. + +While the Zebra source code and RPC methods are well-documented, accessing this information typically involves searching for each function within the [Zebra crate documentation](https://docs.rs/zebrad/latest/zebrad/#zebra-crates), which may be inconvenient for users who are not familiar with Rust development. + +To address this issue, the Zebra team has created an [OpenAPI](https://www.openapis.org/) specification in the [YAML](https://en.wikipedia.org/wiki/YAML) format. + +The Zebra OpenAPI specification is stored in a file named openapi.yaml, located at the root of the project. The latest version of this specification will always be available [here](https://github.com/ZcashFoundation/zebra/blob/main/openapi.yaml). + +## Usage + +There are several ways to utilize the specification. For users unfamiliar with OpenAPI and Swagger, simply navigate to the [Swagger Editor](https://editor.swagger.io/) and paste the specification there. + +![image info](openapi1.png) + +To send and receive data directly from/to the blockchain within the Swagger web app, you'll need a Zebra node with the RPC endpoint enabled. + +To enable this functionality, start zebrad with a custom configuration. Generate a default configuration by running the following command: + +```console +mkdir -p ~/.config +zebrad generate -o ~/.config/zebrad.toml +``` + +Then, add the IP address and port to the `rpc` section of the configuration: + +``` +[rpc] +listen_addr = "127.0.0.1:8232" +``` + +If you modify the address and port in the Zebra configuration, ensure to update it in the `openapi.yaml` specification as well. + +Start Zebra with the following command: + +```console +zebrad +``` + +You should now be able to send requests and receive responses within Swagger. + +![image info](openapi2.png) + +![image info](openapi3.png) + + diff --git a/book/src/user/openapi1.png b/book/src/user/openapi1.png new file mode 100644 index 000000000..e01f43c2d Binary files /dev/null and b/book/src/user/openapi1.png differ diff --git a/book/src/user/openapi2.png b/book/src/user/openapi2.png new file mode 100644 index 000000000..efbc1f872 Binary files /dev/null and b/book/src/user/openapi2.png differ diff --git a/book/src/user/openapi3.png b/book/src/user/openapi3.png new file mode 100644 index 000000000..c7504123b Binary files /dev/null and b/book/src/user/openapi3.png differ diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 000000000..679ac1348 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,1007 @@ +openapi: 3.0.3 +info: + title: Swagger Zebra API - OpenAPI 3.0 + version: 0.0.1 + description: |- + This is the Zebra API. It is a JSON-RPC 2.0 API that allows you to interact with the Zebra node. + + Useful links: + - [The Zebra repository](https://github.com/ZcashFoundation/zebra) + - [The latests API spec](https://github.com/ZcashFoundation/zebra/blob/main/openapi.yaml) +servers: + - url: http://localhost:8232 +paths: + /sendrawtransaction: + post: + tags: + - transaction + description: |- + Sends the raw bytes of a signed transaction to the local node''s mempool, if the transaction is valid."] + + **Request body `params` arguments:** + + - `raw_transaction_hex` - The hex-encoded raw transaction bytes. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '["signedhex"]' + method: + type: string + default: sendrawtransaction + id: + type: number + default: '123' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + /getinfo: + post: + tags: + - control + description: Returns software information from the RPC server, as a [`GetInfo`] JSON struct."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: getinfo + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{"build":"some build version","subversion":"some subversion"}' + /getblockhash: + post: + tags: + - blockchain + description: |- + Returns the hash of the block of a given height iff the index argument correspond"] + + **Request body `params` arguments:** + + - `index` - The block index. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '[1]' + method: + type: string + default: getblockhash + id: + type: number + default: '123' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '"0000000000000000000000000000000000000000000000000000000000000000"' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + /getmininginfo: + post: + tags: + - mining + description: Returns mining-related information."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: getmininginfo + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /validateaddress: + post: + tags: + - util + description: |- + Checks if a zcash address is valid."] + + **Request body `params` arguments:** + + - `address` - The zcash address to validate. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: validateaddress + params: + type: array + items: {} + default: '[]' + id: + type: number + default: '123' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getblocksubsidy: + post: + tags: + - mining + description: |- + Returns the block subsidy reward of the block at `height`, taking into account the mining slow start."] + + **Request body `params` arguments:** + + - `height` - Can be any valid current or future height. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[1]' + method: + type: string + default: getblocksubsidy + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + /submitblock: + post: + tags: + - mining + description: |- + Submits block to the node to be validated and committed."] + + **Request body `params` arguments:** + + - `jsonparametersobject` - - currently ignored + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + method: + type: string + default: submitblock + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /z_listunifiedreceivers: + post: + tags: + - wallet + description: |- + Returns the list of individual payment addresses given a unified address."] + + **Request body `params` arguments:** + + - `address` - The zcash unified address to get the list from. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: z_listunifiedreceivers + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getblockcount: + post: + tags: + - blockchain + description: Returns the height of the most recent block in the best valid block chain (equivalently,"] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + method: + type: string + default: getblockcount + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getaddressutxos: + post: + tags: + - address + description: |- + Returns all unspent outputs for a list of addresses."] + + **Request body `params` arguments:** + + - `addresses` - The addresses to get outputs from. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + method: + type: string + default: getaddressutxos + params: + type: array + items: {} + default: '[{"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}]' + responses: + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getaddresstxids: + post: + tags: + - address + description: |- + Returns the transaction ids made by the provided transparent addresses."] + + **Request body `params` arguments:** + + - `request` - A struct with the following named fields: + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: getaddresstxids + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[{"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"], "start": 1000, "end": 2000}]' + responses: + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /z_getsubtreesbyindex: + post: + tags: + - blockchain + description: |- + Returns information about a range of Sapling or Orchard subtrees."] + + **Request body `params` arguments:** + + - `pool` - The pool from which subtrees should be returned. Either \"sapling\" or \"orchard\". + - `start_index` - The index of the first 2^16-leaf subtree to return. + - `limit` - The maximum number of subtree values to return. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '[]' + method: + type: string + default: z_getsubtreesbyindex + id: + type: number + default: '123' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getpeerinfo: + post: + tags: + - network + description: Returns data about each connected network node."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + method: + type: string + default: getpeerinfo + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getbestblockhash: + post: + tags: + - blockchain + description: Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + method: + type: string + default: getbestblockhash + params: + type: array + items: {} + default: '[]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '"0000000000000000000000000000000000000000000000000000000000000000"' + /getblocktemplate: + post: + tags: + - mining + description: |- + Returns a block template for mining new Zcash blocks."] + + **Request body `params` arguments:** + + - `jsonrequestobject` - A JSON object containing arguments. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + method: + type: string + default: getblocktemplate + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getdifficulty: + post: + tags: + - blockchain + description: Returns the proof-of-work difficulty as a multiple of the minimum difficulty."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + method: + type: string + default: getdifficulty + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getrawmempool: + post: + tags: + - blockchain + description: Returns all transaction ids in the memory pool, as a JSON array."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '[]' + id: + type: number + default: '123' + method: + type: string + default: getrawmempool + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getaddressbalance: + post: + tags: + - address + description: |- + Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance."] + + **Request body `params` arguments:** + + - `address_strings` - A JSON map with a single entry + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + method: + type: string + default: getaddressbalance + params: + type: array + items: {} + default: '[{"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + /getnetworksolps: + post: + tags: + - mining + description: Returns the estimated network solutions per second based on the last `num_blocks` before"] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '[]' + id: + type: number + default: '123' + method: + type: string + default: getnetworksolps + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /z_gettreestate: + post: + tags: + - blockchain + description: |- + Returns information about the given block''s Sapling & Orchard tree state."] + + **Request body `params` arguments:** + + - `hash | height` - The block hash or height. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: number + default: '123' + params: + type: array + items: {} + default: '["00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5"]' + method: + type: string + default: z_gettreestate + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{"hash":"0000000000000000000000000000000000000000000000000000000000000000","height":0,"time":0}' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + /getrawtransaction: + post: + tags: + - transaction + description: |- + Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure."] + + **Request body `params` arguments:** + + - `txid` - The transaction ID of the transaction to be returned. + - `verbose` - If 0, return a string of hex-encoded data, otherwise return a JSON object. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: getrawtransaction + id: + type: number + default: '123' + params: + type: array + items: {} + default: '["mytxid", 1]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + /z_validateaddress: + post: + tags: + - util + description: |- + Checks if a zcash address is valid."] + + **Request body `params` arguments:** + + - `address` - The zcash address to validate. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '[]' + id: + type: number + default: '123' + method: + type: string + default: z_validateaddress + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getblock: + post: + tags: + - blockchain + description: |- + Returns the requested block by hash or height, as a [`GetBlock`] JSON string."] + + **Request body `params` arguments:** + + - `hash_or_height` - The hash or height for the block to be returned. + - `verbosity` - 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: getblock + params: + type: array + items: {} + default: '["1", 1]' + id: + type: number + default: '123' + responses: + '400': + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: string + default: Invalid parameters + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{"hash":"0000000000000000000000000000000000000000000000000000000000000000","confirmations":0,"tx":[],"trees":{}}' + /getnetworkhashps: + post: + tags: + - mining + description: Returns the estimated network solutions per second based on the last `num_blocks` before"] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + params: + type: array + items: {} + default: '[]' + method: + type: string + default: getnetworkhashps + id: + type: number + default: '123' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{}' + /getblockchaininfo: + post: + tags: + - blockchain + description: Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct."] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + method: + type: string + default: getblockchaininfo + id: + type: number + default: '123' + params: + type: array + items: {} + default: '[]' + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + result: + type: object + default: '{"chain":"main","blocks":1,"bestblockhash":"0000000000000000000000000000000000000000000000000000000000000000","estimatedheight":1,"upgrades":{},"consensus":{"chaintip":"00000000","nextblock":"00000000"}}' diff --git a/zebra-chain/src/orchard/tree.rs b/zebra-chain/src/orchard/tree.rs index 9da1b00ab..1447b03a5 100644 --- a/zebra-chain/src/orchard/tree.rs +++ b/zebra-chain/src/orchard/tree.rs @@ -11,6 +11,7 @@ //! A root of a note commitment tree is associated with each treestate. use std::{ + default::Default, fmt, hash::{Hash, Hasher}, io, @@ -710,7 +711,7 @@ impl From> for NoteCommitmentTree { /// It is likely that the dense format will be used in future RPCs, in which /// case the current implementation will have to change and use the format /// compatible with [`Frontier`](bridgetree::Frontier) instead. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize)] pub struct SerializedTree(Vec); impl From<&NoteCommitmentTree> for SerializedTree { diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index c26253081..38529babd 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -128,7 +128,7 @@ const FAKE_TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[ /// The Consensus Branch Id, used to bind transactions and blocks to a /// particular network upgrade. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct ConsensusBranchId(u32); impl ConsensusBranchId { diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs index c1c15bb5d..1d11620ba 100644 --- a/zebra-chain/src/sapling/tree.rs +++ b/zebra-chain/src/sapling/tree.rs @@ -11,6 +11,7 @@ //! A root of a note commitment tree is associated with each treestate. use std::{ + default::Default, fmt, hash::{Hash, Hasher}, io, @@ -692,7 +693,7 @@ impl From> for NoteCommitmentTree { /// It is likely that the dense format will be used in future RPCs, in which /// case the current implementation will have to change and use the format /// compatible with [`Frontier`] instead. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize)] pub struct SerializedTree(Vec); impl From<&NoteCommitmentTree> for SerializedTree { diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 6c2668a34..40b15bb6d 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -6,7 +6,7 @@ //! Some parts of the `zcashd` RPC documentation are outdated. //! So this implementation follows the `zcashd` server and `lightwalletd` client implementations. -use std::{collections::HashSet, fmt::Debug, sync::Arc}; +use std::{collections::HashSet, default::Default, fmt::Debug, sync::Arc}; use chrono::Utc; use futures::{FutureExt, TryFutureExt}; @@ -53,9 +53,12 @@ mod tests; #[rpc(server)] /// RPC method signatures. pub trait Rpc { + #[rpc(name = "getinfo")] /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. /// /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) + /// method: post + /// tags: control /// /// # Notes /// @@ -65,12 +68,13 @@ pub trait Rpc { /// /// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields /// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95) - #[rpc(name = "getinfo")] fn get_info(&self) -> Result; /// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct. /// /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html) + /// method: post + /// tags: blockchain /// /// # Notes /// @@ -82,11 +86,13 @@ pub trait Rpc { /// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance. /// /// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html) + /// method: post + /// tags: address /// /// # Parameters /// - /// - `address_strings`: (map) A JSON map with a single entry - /// - `addresses`: (array of strings) A list of base-58 encoded addresses. + /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry + /// - `addresses`: (array of strings) A list of base-58 encoded addresses. /// /// # Notes /// @@ -109,10 +115,12 @@ pub trait Rpc { /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string. /// /// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html) + /// method: post + /// tags: transaction /// /// # Parameters /// - /// - `raw_transaction_hex`: (string, required) The hex-encoded raw transaction bytes. + /// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes. /// /// # Notes /// @@ -129,12 +137,13 @@ pub trait Rpc { /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) /// /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html) + /// method: post + /// tags: blockchain /// /// # Parameters /// - /// - `hash | height`: (string, required) The hash or height for the block to be returned. - /// - `verbosity`: (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object, - /// and 2 for json object with transaction data. + /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. + /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. /// /// # Notes /// @@ -154,22 +163,28 @@ pub trait Rpc { /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string. /// /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html) + /// method: post + /// tags: blockchain #[rpc(name = "getbestblockhash")] fn get_best_block_hash(&self) -> Result; /// Returns all transaction ids in the memory pool, as a JSON array. /// /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) + /// method: post + /// tags: blockchain #[rpc(name = "getrawmempool")] fn get_raw_mempool(&self) -> BoxFuture>>; /// Returns information about the given block's Sapling & Orchard tree state. /// /// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html) + /// method: post + /// tags: blockchain /// /// # Parameters /// - /// - `hash | height`: (string, required) The block hash or height. + /// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height. /// /// # Notes /// @@ -182,14 +197,15 @@ pub trait Rpc { /// Returns information about a range of Sapling or Orchard subtrees. /// - /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) + /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link + /// method: post + /// tags: blockchain /// /// # Parameters /// - /// - `pool`: (string, required) The pool from which subtrees should be returned. - /// Either "sapling" or "orchard". - /// - `start_index`: (numeric, required) The index of the first 2^16-leaf subtree to return. - /// - `limit`: (numeric, optional) The maximum number of subtree values to return. + /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard". + /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return. + /// - `limit`: (number, optional) The maximum number of subtree values to return. /// /// # Notes /// @@ -208,11 +224,13 @@ pub trait Rpc { /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure. /// /// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html) + /// method: post + /// tags: transaction /// /// # Parameters /// - /// - `txid`: (string, required) The transaction ID of the transaction to be returned. - /// - `verbose`: (numeric, optional, default=0) If 0, return a string of hex-encoded data, otherwise return a JSON object. + /// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned. + /// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object. /// /// # Notes /// @@ -232,13 +250,15 @@ pub trait Rpc { /// Returns the transaction ids made by the provided transparent addresses. /// /// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html) + /// method: post + /// tags: address /// /// # Parameters /// - /// A [`GetAddressTxIdsRequest`] struct with the following named fields: - /// - `addresses`: (json array of string, required) The addresses to get transactions from. - /// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive). - /// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive). + /// - `request`: (object, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}) A struct with the following named fields: + /// - `addresses`: (json array of string, required) The addresses to get transactions from. + /// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive). + /// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive). /// /// # Notes /// @@ -251,10 +271,12 @@ pub trait Rpc { /// Returns all unspent outputs for a list of addresses. /// /// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html) + /// method: post + /// tags: address /// /// # Parameters /// - /// - `addresses`: (json array of string, required) The addresses to get outputs from. + /// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from. /// /// # Notes /// @@ -1428,6 +1450,15 @@ pub struct GetInfo { subversion: String, } +impl Default for GetInfo { + fn default() -> Self { + GetInfo { + build: "some build version".to_string(), + subversion: "some subversion".to_string(), + } + } +} + /// Response to a `getblockchaininfo` RPC request. /// /// See the notes for the [`Rpc::get_blockchain_info` method]. @@ -1456,6 +1487,22 @@ pub struct GetBlockChainInfo { consensus: TipConsensusBranch, } +impl Default for GetBlockChainInfo { + fn default() -> Self { + GetBlockChainInfo { + chain: "main".to_string(), + blocks: Height(1), + best_block_hash: block::Hash([0; 32]), + estimated_height: Height(1), + upgrades: IndexMap::new(), + consensus: TipConsensusBranch { + chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()), + next_block: ConsensusBranchIdHex(ConsensusBranchId::default()), + }, + } + } +} + /// A wrapper type with a list of transparent address strings. /// /// This is used for the input parameter of [`Rpc::get_address_balance`], @@ -1590,6 +1637,18 @@ pub enum GetBlock { }, } +impl Default for GetBlock { + fn default() -> Self { + GetBlock::Object { + hash: GetBlockHash::default(), + confirmations: 0, + height: None, + tx: Vec::new(), + trees: GetBlockTrees::default(), + } + } +} + /// Response to a `getbestblockhash` and `getblockhash` RPC request. /// /// Contains the hex-encoded hash of the requested block. @@ -1599,6 +1658,12 @@ pub enum GetBlock { #[serde(transparent)] pub struct GetBlockHash(#[serde(with = "hex")] pub block::Hash); +impl Default for GetBlockHash { + fn default() -> Self { + GetBlockHash(block::Hash([0; 32])) + } +} + /// Response to a `z_gettreestate` RPC request. /// /// Contains the hex-encoded Sapling & Orchard note commitment trees, and their @@ -1627,6 +1692,26 @@ pub struct GetTreestate { orchard: Treestate, } +impl Default for GetTreestate { + fn default() -> Self { + GetTreestate { + hash: block::Hash([0; 32]), + height: Height(0), + time: 0, + sapling: Treestate { + commitments: Commitments { + final_state: sapling::tree::SerializedTree::default(), + }, + }, + orchard: Treestate { + commitments: Commitments { + final_state: orchard::tree::SerializedTree::default(), + }, + }, + } + } +} + /// A treestate that is included in the [`z_gettreestate`][1] RPC response. /// /// [1]: https://zcash.github.io/rpc/z_gettreestate.html @@ -1758,6 +1843,15 @@ pub struct GetBlockTrees { orchard: OrchardTrees, } +impl Default for GetBlockTrees { + fn default() -> Self { + GetBlockTrees { + sapling: SaplingTrees { size: 0 }, + orchard: OrchardTrees { size: 0 }, + } + } +} + /// Sapling note commitment tree information. #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SaplingTrees { diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 57f27e228..c1f679fa8 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -69,6 +69,8 @@ pub trait GetBlockTemplateRpc { /// the number of blocks in this chain excluding the genesis block). /// /// zcashd reference: [`getblockcount`](https://zcash.github.io/rpc/getblockcount.html) + /// method: post + /// tags: blockchain /// /// # Notes /// @@ -80,10 +82,12 @@ pub trait GetBlockTemplateRpc { /// to a block in the best chain. /// /// zcashd reference: [`getblockhash`](https://zcash-rpc.github.io/getblockhash.html) + /// method: post + /// tags: blockchain /// /// # Parameters /// - /// - `index`: (numeric, required) The block index. + /// - `index`: (numeric, required, example=1) The block index. /// /// # Notes /// @@ -100,6 +104,8 @@ pub trait GetBlockTemplateRpc { /// - `jsonrequestobject`: (string, optional) A JSON object containing arguments. /// /// zcashd reference: [`getblocktemplate`](https://zcash-rpc.github.io/getblocktemplate.html) + /// method: post + /// tags: mining /// /// # Notes /// @@ -124,11 +130,17 @@ pub trait GetBlockTemplateRpc { /// Returns the [`submit_block::Response`] for the operation, as a JSON string. /// /// zcashd reference: [`submitblock`](https://zcash.github.io/rpc/submitblock.html) + /// method: post + /// tags: mining /// /// # Parameters - /// - `hexdata` (string, required) - /// - `jsonparametersobject` (string, optional) - currently ignored - /// - holds a single field, workid, that must be included in submissions if provided by the server. + /// + /// - `hexdata`: (string, required) + /// - `jsonparametersobject`: (string, optional) - currently ignored + /// + /// # Notes + /// + /// - `jsonparametersobject` holds a single field, workid, that must be included in submissions if provided by the server. #[rpc(name = "submitblock")] fn submit_block( &self, @@ -139,6 +151,8 @@ pub trait GetBlockTemplateRpc { /// Returns mining-related information. /// /// zcashd reference: [`getmininginfo`](https://zcash.github.io/rpc/getmininginfo.html) + /// method: post + /// tags: mining #[rpc(name = "getmininginfo")] fn get_mining_info(&self) -> BoxFuture>; @@ -150,6 +164,8 @@ pub trait GetBlockTemplateRpc { /// If `height` is not supplied or is -1, uses the tip height. /// /// zcashd reference: [`getnetworksolps`](https://zcash.github.io/rpc/getnetworksolps.html) + /// method: post + /// tags: mining #[rpc(name = "getnetworksolps")] fn get_network_sol_ps( &self, @@ -164,6 +180,8 @@ pub trait GetBlockTemplateRpc { /// See that method for details. /// /// zcashd reference: [`getnetworkhashps`](https://zcash.github.io/rpc/getnetworkhashps.html) + /// method: post + /// tags: mining #[rpc(name = "getnetworkhashps")] fn get_network_hash_ps( &self, @@ -176,6 +194,8 @@ pub trait GetBlockTemplateRpc { /// Returns data about each connected network node. /// /// zcashd reference: [`getpeerinfo`](https://zcash.github.io/rpc/getpeerinfo.html) + /// method: post + /// tags: network #[rpc(name = "getpeerinfo")] fn get_peer_info(&self) -> BoxFuture>>; @@ -183,6 +203,16 @@ pub trait GetBlockTemplateRpc { /// Returns information about the given address if valid. /// /// zcashd reference: [`validateaddress`](https://zcash.github.io/rpc/validateaddress.html) + /// method: post + /// tags: util + /// + /// # Parameters + /// + /// - `address`: (string, required) The zcash address to validate. + /// + /// # Notes + /// + /// - No notes #[rpc(name = "validateaddress")] fn validate_address(&self, address: String) -> BoxFuture>; @@ -190,6 +220,16 @@ pub trait GetBlockTemplateRpc { /// Returns information about the given address if valid. /// /// zcashd reference: [`z_validateaddress`](https://zcash.github.io/rpc/z_validateaddress.html) + /// method: post + /// tags: util + /// + /// # Parameters + /// + /// - `address`: (string, required) The zcash address to validate. + /// + /// # Notes + /// + /// - No notes #[rpc(name = "z_validateaddress")] fn z_validate_address( &self, @@ -199,22 +239,41 @@ pub trait GetBlockTemplateRpc { /// Returns the block subsidy reward of the block at `height`, taking into account the mining slow start. /// Returns an error if `height` is less than the height of the first halving for the current network. /// - /// `height` can be any valid current or future height. - /// If `height` is not supplied, uses the tip height. - /// /// zcashd reference: [`getblocksubsidy`](https://zcash.github.io/rpc/getblocksubsidy.html) + /// method: post + /// tags: mining + /// + /// # Parameters + /// + /// - `height`: (numeric, optional, example=1) Can be any valid current or future height. + /// + /// # Notes + /// + /// If `height` is not supplied, uses the tip height. #[rpc(name = "getblocksubsidy")] fn get_block_subsidy(&self, height: Option) -> BoxFuture>; /// Returns the proof-of-work difficulty as a multiple of the minimum difficulty. /// /// zcashd reference: [`getdifficulty`](https://zcash.github.io/rpc/getdifficulty.html) + /// method: post + /// tags: blockchain #[rpc(name = "getdifficulty")] fn get_difficulty(&self) -> BoxFuture>; /// Returns the list of individual payment addresses given a unified address. /// /// zcashd reference: [`z_listunifiedreceivers`](https://zcash.github.io/rpc/z_listunifiedreceivers.html) + /// method: post + /// tags: wallet + /// + /// # Parameters + /// + /// - `address`: (string, required) The zcash unified address to get the list from. + /// + /// # Notes + /// + /// - No notes #[rpc(name = "z_listunifiedreceivers")] fn z_list_unified_receivers( &self, diff --git a/zebra-utils/Cargo.toml b/zebra-utils/Cargo.toml index 179b7357e..915946736 100644 --- a/zebra-utils/Cargo.toml +++ b/zebra-utils/Cargo.toml @@ -41,6 +41,11 @@ name = "scanning-results-reader" path = "src/bin/scanning-results-reader/main.rs" required-features = ["shielded-scan"] +[[bin]] +name = "openapi-generator" +path = "src/bin/openapi-generator/main.rs" +required-features = ["openapi-generator"] + [features] default = [] @@ -74,6 +79,14 @@ shielded-scan = [ "zebra-scan" ] +openapi-generator = [ + "zebra-rpc", + "syn", + "quote", + "serde_yaml", + "serde" +] + [dependencies] color-eyre = "0.6.2" # This is a transitive dependency via color-eyre. @@ -109,3 +122,9 @@ jsonrpc = { version = "0.17.0", optional = true } zcash_primitives = { version = "0.13.0-rc.1", optional = true } zcash_client_backend = {version = "0.10.0-rc.1", optional = true} + +# For the openapi generator +syn = { version = "2.0.52", features = ["full"], optional = true } +quote = { version = "1.0.35", optional = true } +serde_yaml = { version = "0.9.32", optional = true } +serde = { version = "1.0.196", features = ["serde_derive"], optional = true } diff --git a/zebra-utils/README.md b/zebra-utils/README.md index 0e215cc03..48a887e09 100644 --- a/zebra-utils/README.md +++ b/zebra-utils/README.md @@ -7,6 +7,7 @@ Tools for maintaining and testing Zebra: - [zebrad-log-filter](#zebrad-log-filter) - [zcash-rpc-diff](#zcash-rpc-diff) - [scanning-results-reader](#scanning-results-reader) +- [openapi-generator](#openapi-generator) Binaries are easier to use if they are located in your system execution path. @@ -232,3 +233,80 @@ A utility for displaying Zebra's scanning results. ``` bash cargo run --release --features shielded-scan --bin scanning-results-reader ``` + +## OpenAPI generator + +This utility generates an `openapi.yaml` specification by extracting information from RPC method documentation in the `zebra-rpc` crate code. + +### Usage + +To use the generator tool, build and run it with the following command: + +```console +cargo run --bin openapi-generator --features="openapi-generator" +``` + +This command will create or update an `openapi.yaml` file at the root of the Zebra project repository. + +The latest specification generated using this utility can be found [here](https://github.com/ZcashFoundation/zebra/blob/main/openapi.yaml). + +### Documentation standard + +In order for the script to work, each RPC method documentation needs to follow a specific well-defined format. For example, here is the in-code documentation for the `getblock` method, which takes arguments: + +```rust +/// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. +/// If the block is not in Zebra's state, returns +/// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) +/// +/// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html) +/// method: post +/// tags: blockchain +/// +/// # Parameters +/// +/// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. +/// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. +/// +/// # Notes +/// +/// With verbosity=1, [`lightwalletd` only reads the `tx` field of the +/// result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152), +/// and other clients only read the `hash` and `confirmations` fields, +/// so we only return a few fields for now. +/// +/// `lightwalletd` and mining clients also do not use verbosity=2, so we don't support it. +#[rpc(name = "getblock")] +fn get_block( + &self, + hash_or_height: String, + verbosity: Option, +) -> BoxFuture>; +``` + +An example of a method with no arguments can be the `getinfo` call: + + +```rust +#[rpc(name = "getinfo")] +/// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. +/// +/// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) +/// method: post +/// tags: control +/// +/// # Notes +/// +/// [The zcashd reference](https://zcash.github.io/rpc/getinfo.html) might not show some fields +/// in Zebra's [`GetInfo`]. Zebra uses the field names and formats from the +/// [zcashd code](https://github.com/zcash/zcash/blob/v4.6.0-1/src/rpc/misc.cpp#L86-L87). +/// +/// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields +/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95) +fn get_info(&self) -> Result; +``` + +Find more examples inside the `zebra-rpc/src/methods.rs` and the `zebra-rpc/src/methods/get_block_template_rpcs.rs` files. + +The generator will detect new methods added if they are members of the `Rpc` trait for the `zebra-rpc/src/methods.rs` file and inside the `GetBlockTemplateRpc` in the file `zebra-rpc/src/methods/get_block_template_rpcs.rs`. + diff --git a/zebra-utils/src/bin/openapi-generator/main.rs b/zebra-utils/src/bin/openapi-generator/main.rs new file mode 100644 index 000000000..6dc24db95 --- /dev/null +++ b/zebra-utils/src/bin/openapi-generator/main.rs @@ -0,0 +1,544 @@ +//! Generate an openapi.yaml file from the Zebra RPC methods + +use std::{collections::HashMap, error::Error, fs::File, io::Write}; + +use quote::ToTokens; +use serde::Serialize; +use syn::LitStr; + +use zebra_rpc::methods::*; + +// The API server +const SERVER: &str = "http://localhost:8232"; + +// The API methods +#[derive(Serialize, Debug)] +struct Methods { + paths: HashMap>, +} + +// The configuration for each method +#[derive(Serialize, Clone, Debug)] +struct MethodConfig { + tags: Vec, + description: String, + #[serde(rename = "requestBody")] + request_body: RequestBody, + responses: HashMap, +} + +// The request body +#[derive(Serialize, Clone, Debug)] +struct RequestBody { + required: bool, + content: Content, +} + +// The content of the request body +#[derive(Serialize, Clone, Debug)] +struct Content { + #[serde(rename = "application/json")] + application_json: Application, +} + +// The application of the request body +#[derive(Serialize, Clone, Debug)] +struct Application { + schema: Schema, +} + +// The schema of the request body +#[derive(Serialize, Clone, Debug)] +struct Schema { + #[serde(rename = "type")] + type_: String, + properties: HashMap, +} + +// The properties of the request body +#[derive(Serialize, Clone, Debug)] +struct Property { + #[serde(rename = "type")] + type_: String, + #[serde(skip_serializing_if = "Option::is_none")] + items: Option, + default: String, +} + +// The response +#[derive(Serialize, Clone, Debug)] +struct Response { + description: String, + content: Content, +} + +// The array items +#[derive(Serialize, Clone, Debug)] +struct ArrayItems {} + +fn main() -> Result<(), Box> { + let current_path = env!("CARGO_MANIFEST_DIR"); + + // Define the paths to the Zebra RPC methods + let paths = vec![ + ( + format!("{}/../zebra-rpc/src/methods.rs", current_path), + "Rpc", + ), + ( + format!( + "{}/../zebra-rpc/src/methods/get_block_template_rpcs.rs", + current_path + ), + "GetBlockTemplateRpc", + ), + ]; + + // Create a hashmap to store the method names and configuration + let mut methods = HashMap::new(); + + for zebra_rpc_methods_path in paths { + // Read the source code from the file + let source_code = std::fs::read_to_string(zebra_rpc_methods_path.0)?; + + // Parse the source code into a syn AST + let syn_file = syn::parse_file(&source_code)?; + + // Create a hashmap to store the methods configuration + let mut methods_config = HashMap::new(); + + // Iterate over items in the file looking for traits + for item in &syn_file.items { + if let syn::Item::Trait(trait_item) = item { + // Check if this trait is the one we're interested in + if trait_item.ident == zebra_rpc_methods_path.1 { + // Iterate over the trait items looking for methods + for trait_item in &trait_item.items { + // Extract method name + let method_name = method_name(trait_item)?; + + // Extract method documentation and description + let (method_doc, mut description) = method_doc(trait_item)?; + + // Request type. TODO: All methods are POST so we just hardcode it + let request_type = "post".to_string(); + + // Tags. TODO: We are assuming 1 tag per call for now + let tags = tags(&method_doc)?; + + // Parameters + let mut parameters_example = "[]".to_string(); + if let Ok((params_description, params_example)) = get_params(&method_doc) { + // Add parameters to method description: + description = + add_params_to_description(&description, ¶ms_description); + // The Zebra API uses a `params` array to pass arguments to the RPC methods, + // so we need to add this to the OpenAPI spec instead of `parameters` + parameters_example = params_example; + } + + // Create the request body + let request_body = create_request_body(&method_name, ¶meters_example); + + // Check if we have parameters + let mut have_parameters = true; + if parameters_example == "[]" { + have_parameters = false; + } + + // Create the responses + let responses = create_responses(&method_name, have_parameters)?; + + // Add the method configuration to the hashmap + methods_config.insert( + request_type, + MethodConfig { + tags, + description, + request_body, + responses, + }, + ); + + // Add the method name and configuration to the hashmap + methods.insert(format!("/{}", method_name), methods_config.clone()); + } + } + } + } + } + + // Create a struct to hold all the methods + let all_methods = Methods { paths: methods }; + + // Add openapi header and write to file + let yaml_string = serde_yaml::to_string(&all_methods)?; + let mut w = File::create("openapi.yaml")?; + w.write_all(format!("{}{}", create_yaml(), yaml_string).as_bytes())?; + + Ok(()) +} + +// Create the openapi.yaml header +fn create_yaml() -> String { + format!("openapi: 3.0.3 +info: + title: Swagger Zebra API - OpenAPI 3.0 + version: 0.0.1 + description: |- + This is the Zebra API. It is a JSON-RPC 2.0 API that allows you to interact with the Zebra node. + + Useful links: + - [The Zebra repository](https://github.com/ZcashFoundation/zebra) + - [The latests API spec](https://github.com/ZcashFoundation/zebra/blob/main/openapi.yaml) +servers: + - url: {} +", SERVER) +} + +// Extract the method name from the trait item +fn method_name(trait_item: &syn::TraitItem) -> Result> { + let mut method_name = "".to_string(); + if let syn::TraitItem::Fn(method) = trait_item { + method_name = method.sig.ident.to_string(); + + // Refine name if needed + method.attrs.iter().for_each(|attr| { + if attr.path().is_ident("rpc") { + let _ = attr.parse_nested_meta(|meta| { + method_name = meta.value()?.parse::()?.value(); + Ok(()) + }); + } + }); + } + Ok(method_name) +} + +// Return the method docs array and the description of the method +fn method_doc(method: &syn::TraitItem) -> Result<(Vec, String), Box> { + let mut method_doc = vec![]; + if let syn::TraitItem::Fn(method) = method { + // Filter only doc attributes + let doc_attrs: Vec<_> = method + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + .collect(); + + // If no doc attributes found, return an error + if doc_attrs.is_empty() { + return Err("No documentation attribute found for the method".into()); + } + + method.attrs.iter().for_each(|attr| { + if attr.path().is_ident("doc") { + method_doc.push(attr.to_token_stream().to_string()); + } + }); + } + + // Extract the description from the first line of documentation + let description = match method_doc[0].split_once('"') { + Some((_, desc)) => desc.trim().to_string().replace('\'', "''"), + None => return Err("Description not found in method documentation".into()), + }; + + Ok((method_doc, description)) +} + +// Extract the tags from the method documentation. TODO: Assuming 1 tag per method for now +fn tags(method_doc: &[String]) -> Result, Box> { + // Find the line containing tags information + let tags_line = method_doc + .iter() + .find(|line| line.contains("tags:")) + .ok_or("Tags not found in method documentation")?; + + // Extract tags from the tags line + let mut tags = Vec::new(); + let tags_str = tags_line + .split(':') + .nth(1) + .ok_or("Invalid tags line")? + .trim(); + + // Split the tags string into individual tags + for tag in tags_str.split(',') { + let trimmed_tag = tag.trim_matches(|c: char| !c.is_alphanumeric()); + if !trimmed_tag.is_empty() { + tags.push(trimmed_tag.to_string()); + } + } + + Ok(tags) +} + +// Extract the parameters from the method documentation +fn get_params(method_doc: &[String]) -> Result<(String, String), Box> { + // Find the start and end index of the parameters + let params_start_index = method_doc + .iter() + .enumerate() + .find(|(_, line)| line.contains("# Parameters")); + let notes_start_index = method_doc + .iter() + .enumerate() + .find(|(_, line)| line.contains("# Notes")); + + // If start and end indices of parameters are found, extract them + if let (Some((params_index, _)), Some((notes_index, _))) = + (params_start_index, notes_start_index) + { + let params = &method_doc[params_index + 2..notes_index - 1]; + + // Initialize variables to store parameter descriptions and examples + let mut param_descriptions = Vec::new(); + let mut param_examples = Vec::new(); + + // Iterate over the parameters and extract information + for param_line in params { + // Check if the line starts with the expected format + if param_line.trim().starts_with("# [doc = \" -") { + // Extract parameter name and description + if let Some((name, description)) = extract_param_info(param_line) { + param_descriptions.push(format!("- `{}` - {}", name, description)); + + // Extract parameter example if available + if let Some(example) = extract_param_example(param_line) { + param_examples.push(example); + } + } + } + } + + // Format parameters and examples + let params_formatted = format!("[{}]", param_examples.join(", ")); + let params_description = param_descriptions.join("\n"); + + return Ok((params_description, params_formatted)); + } + + Err("No parameters found".into()) +} + +// Extract parameter name and description +fn extract_param_info(param_line: &str) -> Option<(String, String)> { + let start_idx = param_line.find('`')?; + let end_idx = param_line.rfind('`')?; + let name = param_line[start_idx + 1..end_idx].trim().to_string(); + + let description_starts = param_line.find(") ")?; + let description_ends = param_line.rfind("\"]")?; + let description = param_line[description_starts + 2..description_ends] + .trim() + .to_string(); + + Some((name, description)) +} + +// Extract parameter example if available +fn extract_param_example(param_line: &str) -> Option { + if let Some(example_start) = param_line.find("example=") { + let example_ends = param_line.rfind(')')?; + let example = param_line[example_start + 8..example_ends].trim(); + Some(example.to_string()) + } else { + None + } +} + +// Create the request body +fn create_request_body(method_name: &str, parameters_example: &str) -> RequestBody { + // Add the method name to the request body + let method_name_prop = Property { + type_: "string".to_string(), + items: None, + default: method_name.to_string(), + }; + + // Add a hardcoded request_id to the request body + let request_id_prop = Property { + type_: "number".to_string(), + items: None, + default: "123".to_string(), + }; + + // Create the schema and add the first 2 properties + let mut schema = HashMap::new(); + schema.insert("method".to_string(), method_name_prop); + schema.insert("id".to_string(), request_id_prop); + + // Add the parameters with the extracted examples + let default = parameters_example.replace('\\', ""); + schema.insert( + "params".to_string(), + Property { + type_: "array".to_string(), + items: Some(ArrayItems {}), + default, + }, + ); + + // Create the request body + let content = Content { + application_json: Application { + schema: Schema { + type_: "object".to_string(), + properties: schema, + }, + }, + }; + + RequestBody { + required: true, + content, + } +} + +// Create the responses +fn create_responses( + method_name: &str, + have_parameters: bool, +) -> Result, Box> { + let mut responses = HashMap::new(); + + let properties = get_default_properties(method_name)?; + + let res_ok = Response { + description: "OK".to_string(), + content: Content { + application_json: Application { + schema: Schema { + type_: "object".to_string(), + properties, + }, + }, + }, + }; + responses.insert("200".to_string(), res_ok); + + let mut properties = HashMap::new(); + if have_parameters { + properties.insert( + "error".to_string(), + Property { + type_: "string".to_string(), + items: None, + default: "Invalid parameters".to_string(), + }, + ); + let res_bad_request = Response { + description: "Bad request".to_string(), + content: Content { + application_json: Application { + schema: Schema { + type_: "object".to_string(), + properties, + }, + }, + }, + }; + responses.insert("400".to_string(), res_bad_request); + } + + Ok(responses) +} + +// Add the parameters to the method description +fn add_params_to_description(description: &str, params_description: &str) -> String { + let mut new_description = description.to_string(); + new_description.push_str("\n\n**Request body `params` arguments:**\n\n"); + new_description.push_str(params_description); + new_description +} + +// Get requests examples by using defaults from the Zebra RPC methods +fn get_default_properties(method_name: &str) -> Result, Box> { + // TODO: Complete the list of methods + + let type_ = "object".to_string(); + let items = None; + let mut props = HashMap::new(); + + let properties = match method_name { + "getinfo" => { + props.insert( + "result".to_string(), + Property { + type_, + items, + default: serde_json::to_string(&GetInfo::default())?, + }, + ); + props + } + "getbestblockhash" => { + props.insert( + "result".to_string(), + Property { + type_, + items, + default: serde_json::to_string(&GetBlockHash::default())?, + }, + ); + props + } + "getblockchaininfo" => { + props.insert( + "result".to_string(), + Property { + type_, + items, + default: serde_json::to_string(&GetBlockChainInfo::default())?, + }, + ); + props + } + "getblock" => { + props.insert( + "result".to_string(), + Property { + type_, + items, + default: serde_json::to_string(&GetBlock::default())?, + }, + ); + props + } + "getblockhash" => { + props.insert( + "result".to_string(), + Property { + type_, + items, + default: serde_json::to_string(&GetBlockHash::default())?, + }, + ); + props + } + "z_gettreestate" => { + props.insert( + "result".to_string(), + Property { + type_, + items, + default: serde_json::to_string(&GetTreestate::default())?, + }, + ); + props + } + _ => { + props.insert( + "result".to_string(), + Property { + type_, + items: None, + default: "{}".to_string(), + }, + ); + props + } + }; + Ok(properties) +}