diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 0d406fe3..adcaf0d5 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
+
[[package]]
name = "arrayvec"
version = "0.7.4"
@@ -169,6 +184,12 @@ version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
[[package]]
name = "minreq"
version = "2.11.2"
@@ -198,6 +219,35 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
[[package]]
name = "ryu"
version = "1.0.18"
@@ -283,6 +333,14 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+[[package]]
+name = "verify"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "regex",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 0d406fe3..adcaf0d5 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
+
[[package]]
name = "arrayvec"
version = "0.7.4"
@@ -169,6 +184,12 @@ version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
[[package]]
name = "minreq"
version = "2.11.2"
@@ -198,6 +219,35 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
[[package]]
name = "ryu"
version = "1.0.18"
@@ -283,6 +333,14 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+[[package]]
+name = "verify"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "regex",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 5ec7bc2b..9c329fd4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-members = ["client", "types", "jsonrpc"]
+members = ["client", "types", "jsonrpc", "verify"]
exclude = ["integration_test", "node"]
resolver = "2"
diff --git a/contrib/extract.pl b/contrib/extract.pl
new file mode 100755
index 00000000..c7683ee5
--- /dev/null
+++ b/contrib/extract.pl
@@ -0,0 +1,138 @@
+#!/usr/bin/perl
+# SPDX-License-Identifier: CC0-1.0
+#
+# Create the `types/src/vXYZ/mod.rs` rusdoc.
+
+use strict;
+use warnings;
+use Getopt::Long qw(:config no_auto_abbrev);
+
+# The script name.
+my $SCRIPT = $0;
+
+# The Bitcoin Core version we are working with.
+my $CORE_VERSION = "0.17.1";
+# The file holding output of `bitcoin-cli --help`.
+my $RPC_HELP_FILE = "types/src/v17/rpc-api.txt";
+
+# Command line options.
+my $help = 0;
+my $debug = 0;
+
+sub help
+{
+ my ($exitcode) = @_;
+
+ print << "EOM";
+
+Usage: $SCRIPT [OPTIONS]
+
+Options:
+
+ -d, --debug Display debugging output.
+ -h, --help Display this help and exit.
+
+Generates the rustdocs for the `types/vX.rs` module. Before running script set CORE_VERSION and RPC_HELP_FILE.
+
+EOM
+ exit($exitcode);
+}
+
+GetOptions(
+ 'd|debug' => \$debug,
+ 'h|help' => \$help,
+) or help(1);
+
+help(0) if ($help);
+
+main();
+
+exit 0;
+
+sub dprint
+{
+ printf(STDERR @_) if $debug;
+}
+
+sub main {
+
+ # Open the file for reading
+ open(my $fh, '<', $RPC_HELP_FILE) or die "Could not open file '$RPC_HELP_FILE': $!\n";
+
+ # Loop over each line in the file
+ while (my $line = <$fh>) {
+ chomp($line);
+ if ($line =~ /^== (.+) ==/) { # Section heading.
+ my $section = $1; # Captures the placeholder text
+ start_section($section);
+ } elsif ($line =~ /^\s*$/) { # Blank line.
+ end_section();
+ } else {
+ add_method($line);
+ }
+ }
+
+ end_section();
+ print_footer();
+
+ # Close the file handle.
+ close($fh);
+}
+
+sub print_header {
+ print <<'EOM';
+//! # JSON-RPC types for Bitcoin Core `v$CORE_VERSION`
+//!
+//! These structs are shaped for the JSON data returned by the JSON-RPC API. They use stdlib types
+//! (or custom types) and where necessary implement an `into_model` function to convert the type to
+//! a [`crate::model`] type of the same name. The types in this module are version specific. The
+//! types in the `model` module are version nonspecific and are strongly typed using `rust-bitcoin`.
+//!
+//! ### Method name and implementation status
+//!
+//! Every JSON-RPC method supported by this version of Bitcoin Core is listed below along with its
+//! current implementation status.
+//!
+EOM
+}
+
+sub print_footer {
+ print <<'EOM'
+//!
+//! **Items marked omitted were omitted because:**
+//!
+//! - Method does not return anything.
+//! - Method returns a simple type (e.g. bool or integer).
+//! - Method is deprecated.
+EOM
+}
+
+# Print the blurb for the start of a section.
+sub start_section {
+ my ($section) = @_; # Get the first argument
+
+ print <<'EOM';
+//!
+//! Methods from the $section section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+EOM
+}
+
+# Print the blurb for the end of a section.
+sub end_section {
+ print<<'EOM';
+//!
+//!
+//!
+EOM
+}
+
+sub add_method {
+ my ($line) = @_;
+ if ($line =~ /^(\S+)/) {
+ my ($method) = $1;
+ printf "//! \| %-34s \| |\n", $method
+ }
+}
diff --git a/integration_test/tests/blockchain.rs b/integration_test/tests/blockchain.rs
index e37b6d88..194bb71d 100644
--- a/integration_test/tests/blockchain.rs
+++ b/integration_test/tests/blockchain.rs
@@ -76,15 +76,17 @@ fn get_block_header_verbose() { // verbose = true
// optional as suggested in the docs but to no avail.
#[test]
#[cfg(feature = "0_17_1")]
+fn get_block_stats() {
+ get_block_stats_by_height();
+ get_block_stats_by_hash();
+}
+
fn get_block_stats_by_height() {
let node = Node::new_no_wallet();
let json = node.client.get_block_stats_by_height(0).expect("getblockstats");
assert!(json.into_model().is_ok());
}
-// FIXME: Same as get_block_stats_by_height above.
-#[test]
-#[cfg(feature = "0_17_1")]
fn get_block_stats_by_hash() { // verbose = true
let node = Node::new_no_wallet();
let block_hash = best_block_hash();
@@ -92,6 +94,7 @@ fn get_block_stats_by_hash() { // verbose = true
assert!(json.into_model().is_ok());
}
+
#[test]
fn get_block_stats_by_height_txindex() {
let node = Node::new_no_wallet_txindex();
@@ -130,37 +133,37 @@ fn get_difficulty() {
#[test]
#[cfg(feature = "TODO")]
-fn get_mempool_ancestors() {}
+fn get_mempool_ancestors() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_mempool_descendants() {}
+fn get_mempool_descendants() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_mempool_entry() {}
+fn get_mempool_entry() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_mempool_info() {}
+fn get_mempool_info() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_raw_mempool() {}
+fn get_raw_mempool() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_tx_out() {}
+fn get_tx_out() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_tx_out_proof() {}
+fn get_tx_out_proof() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn get_tx_out_set_info() {}
+fn get_tx_out_set_info() { todo!() }
#[test]
#[cfg(feature = "TODO")]
-fn verify_tx_out_proof() {}
+fn verify_tx_out_proof() { todo!() }
diff --git a/types/src/v17/mod.rs b/types/src/v17/mod.rs
index 53bf0ce7..76281a06 100644
--- a/types/src/v17/mod.rs
+++ b/types/src/v17/mod.rs
@@ -184,7 +184,7 @@
//! | lockunspent | omitted |
//! | move | omitted |
//! | removeprunedfunds | omitted |
-//! | rescanblockchain | done |
+//! | rescanblockchain | done (untested) |
//! | sendfrom | omitted |
//! | sendmany | done (untested) |
//! | sendtoaddress | done |
@@ -207,7 +207,7 @@
//!
//! | JSON-PRC Method Name | Status |
//! |:-----------------------------------|:---------------:|
-//! | getzmqnotifications` | done (untested) |
+//! | getzmqnotifications | done (untested) |
//!
//!
//!
@@ -260,4 +260,5 @@ pub use self::{
SignErrorData, SignMessage, SignRawTransactionWithWallet, TransactionCategory,
WalletCreateFundedPsbt, WalletProcessPsbt,
},
+ zmq::GetZmqNotifications,
};
diff --git a/types/src/v18/mod.rs b/types/src/v18/mod.rs
index 9efbc10a..890ee956 100644
--- a/types/src/v18/mod.rs
+++ b/types/src/v18/mod.rs
@@ -8,168 +8,225 @@
// differences.
// - Double check that nothing marked `x` has a feature gated test (i.e. ensure tested).
-//! Structs with standard types.
-//!
-//! These structs model the types returned by the JSON-RPC API and use stdlib types (or custom
-//! types) and are specific to a specific to Bitcoin Core `v0.18.1`.
-//!
-//! ## Key:
-//!
-//! - `[ ]` Not yet done.
-//! - `[i]` Implemented but not yet tested (includes `into_model`).
-//! - `[x]` Implemented _and_ tested.
-//! - `[-]` Intentionally not done, typically for one of the following reasons:
-//! - Method does not return anything.
-//! - Method returns a simple type (e.g. bool or integer).
-//! - Method is deprecated.
-// TODO: After all the `[i]` is gone (ie testing done) remove the backticks.
-//!
-//! ** == Blockchain ==**
-//! - `[x]` `getbestblockhash`
-//! - `[x]` `getblock "blockhash" ( verbosity )`
-//! - `[x]` `getblockchaininfo`
-//! - `[x]` `getblockcount`
-//! - `[x]` `getblockhash height`
-//! - `[x]` `getblockheader "blockhash" ( verbose )`
-//! - `[x]` `getblockstats hash_or_height ( stats )`
-//! - `[x]` `getchaintips`
-//! - `[x]` `getchaintxstats ( nblocks "blockhash" )`
-//! - `[x]` `getdifficulty`
-//! - `[i]` `getmempoolancestors "txid" ( verbose )`
-//! - `[i]` `getmempooldescendants "txid" ( verbose )`
-//! - `[i]` `getmempoolentry "txid"`
-//! - `[i]` `getmempoolinfo`
-//! - `[i]` `getrawmempool ( verbose )`
-//! - `[i]` `gettxout "txid" n ( include_mempool )`
-//! - `[i]` `gettxoutproof ["txid",...] ( "blockhash" )`
-//! - `[i]` `gettxoutsetinfo`
-//! - `[-]` `preciousblock "blockhash"`
-//! - `[-]` `pruneblockchain height`
-//! - `[-]` `savemempool`
-//! - `[-]` `scantxoutset "action" [scanobjects,...]`
-//! - `[-]` `verifychain ( checklevel nblocks )`
-//! - `[i]` `verifytxoutproof "proof"`
-//!
-//! ** == Control ==**
-//! - `[i]` `getmemoryinfo ( "mode" )`
-//! - `[ ]` `getrpcinfo`
-//! - `[-]` `help ( "command" )`
-//! - `[x]` `logging ( ["include_category",...] ["exclude_category",...] )`
-//! - `[x]` `stop`
-//! - `[x]` `uptime`
-//!
-//! ** == Generating ==**
-//! - `[x]` `generate nblocks ( maxtries )`
-//! - `[x]` `generatetoaddress nblocks "address" ( maxtries )`
-//!
-//! ** == Mining ==**
-//! - `[ ]` `getblocktemplate "template_request"`
-//! - `[ ]` `getmininginfo`
-//! - `[ ]` `getnetworkhashps ( nblocks height )`
-//! - `[ ]` `prioritisetransaction "txid" ( dummy ) fee_delta`
-//! - `[ ]` `submitblock "hexdata" ( "dummy" )`
-//! - `[ ]` `submitheader "hexdata"`
-//!
-//! ** == Network ==**
-//! - `[-]` `addnode "node" "command"`
-//! - `[-]` `clearbanned`
-//! - `[-]` `disconnectnode ( "address" nodeid )`
-//! - `[x]` `getaddednodeinfo ( "node" )`
-//! - `[-]` `getconnectioncount`
-//! - `[x]` `getnettotals`
-//! - `[x]` `getnetworkinfo`
-//! - `[ ]` `getnodeaddresses ( count )`
-//! - `[x]` `getpeerinfo`
-//! - `[-]` `listbanned`
-//! - `[-]` `ping`
-//! - `[-]` `setban "subnet" "command" ( bantime absolute )`
-//! - `[-]` `setnetworkactive state`
-//!
-//! ** == Rawtransactions ==**
-//! - `[ ]` `analyzepsbt "psbt"`
-//! - `[ ]` `combinepsbt ["psbt",...]`
-//! - `[ ]` `combinerawtransaction ["hexstring",...]`
-//! - `[ ]` `converttopsbt "hexstring" ( permitsigdata iswitness )`
-//! - `[ ]` `createpsbt [{"txid":"hex","vout":n,"sequence":n},...] [{"address":amount},{"data":"hex"},...] ( locktime replaceable )`
-//! - `[ ]` `createrawtransaction [{"txid":"hex","vout":n,"sequence":n},...] [{"address":amount},{"data":"hex"},...] ( locktime replaceable )`
-//! - `[ ]` `decodepsbt "psbt"`
-//! - `[ ]` `decoderawtransaction "hexstring" ( iswitness )`
-//! - `[ ]` `decodescript "hexstring"`
-//! - `[ ]` `finalizepsbt "psbt" ( extract )`
-//! - `[ ]` `fundrawtransaction "hexstring" ( options iswitness )`
-//! - `[ ]` `getrawtransaction "txid" ( verbose "blockhash" )`
-//! - `[ ]` `joinpsbts ["psbt",...]`
-//! - `[i]` `sendrawtransaction "hexstring" ( allowhighfees )`
-//! - `[ ]` `signrawtransactionwithkey "hexstring" ["privatekey",...] ( [{"txid":"hex","vout":n,"scriptPubKey":"hex","redeemScript":"hex","witnessScript":"hex","amount":amount},...] "sighashtype" )`
-//! - `[ ]` `testmempoolaccept ["rawtx",...] ( allowhighfees )`
-//! - `[ ]` `utxoupdatepsbt "psbt"`
-//!
-//! ** == Util ==**
-//! - `[ ]` `createmultisig nrequired ["key",...] ( "address_type" )`
-//! - `[ ]` `deriveaddresses "descriptor" ( range )`
-//! - `[ ]` `estimatesmartfee conf_target ( "estimate_mode" )`
-//! - `[ ]` `getdescriptorinfo "descriptor"`
-//! - `[ ]` `signmessagewithprivkey "privkey" "message"`
-//! - `[ ]` `validateaddress "address"`
-//! - `[ ]` `verifymessage "address" "signature" "message"`
-//!
-//! ** == Wallet ==**
-//! - `[-]` `abandontransaction "txid"`
-//! - `[-]` `abortrescan`
-//! - `[x]` `addmultisigaddress nrequired ["key",...] ( "label" "address_type" )`
-//! - `[-]` `backupwallet "destination"`
-//! - `[x]` `bumpfee "txid" ( options )`
-//! - `[x]` `createwallet "wallet_name" ( disable_private_keys blank )`
-//! - `[x]` `dumpprivkey "address"`
-//! - `[x]` `dumpwallet "filename"`
-//! - `[-]` `encryptwallet "passphrase"`
-//! - `[x]` `getaddressesbylabel "label"`
-//! - `[x]` `getaddressinfo "address"`
-//! - `[x]` `getbalance ( "dummy" minconf include_watchonly )`
-//! - `[x]` `getnewaddress ( "label" "address_type" )`
-//! - `[x]` `getrawchangeaddress ( "address_type" )`
-//! - `[i]` `getreceivedbyaddress "address" ( minconf )`
-//! - `[ ]` `getreceivedbylabel "label" ( minconf )`
-//! - `[x]` `gettransaction "txid" ( include_watchonly )`
-//! - `[i]` `getunconfirmedbalance`
-//! - `[i]` `getwalletinfo`
-//! - `[-]` `importaddress "address" ( "label" rescan p2sh )`
-//! - `[-]` `importmulti "requests" ( "options" )`
-//! - `[-]` `importprivkey "privkey" ( "label" rescan )`
-//! - `[-]` `importprunedfunds "rawtransaction" "txoutproof"`
-//! - `[-]` `importpubkey "pubkey" ( "label" rescan )`
-//! - `[-]` `importwallet "filename"`
-//! - `[-]` `keypoolrefill ( newsize )`
-//! - `[i]` `listaddressgroupings`
-//! - `[i]` `listlabels ( "purpose" )`
-//! - `[i]` `listlockunspent`
-//! - `[i]` `listreceivedbyaddress ( minconf include_empty include_watchonly "address_filter" )`
-//! - `[ ]` `listreceivedbylabel ( minconf include_empty include_watchonly )`
-//! - `[i]` `listsinceblock ( "blockhash" target_confirmations include_watchonly include_removed )`
-//! - `[i]` `listtransactions ( "label" count skip include_watchonly )`
-//! - `[i]` `listunspent ( minconf maxconf ["address",...] include_unsafe query_options )`
-//! - `[ ]` `listwalletdir`
-//! - `[i]` `listwallets`
-//! - `[x]` `loadwallet "filename"`
-//! - `[-]` `lockunspent unlock ( [{"txid":"hex","vout":n},...] )`
-//! - `[i]` `removeprunedfunds "txid"`
-//! - `[x]` `rescanblockchain ( start_height stop_height )`
-//! - `[i]` `sendmany "" {"address":amount} ( minconf "comment" ["address",...] replaceable conf_target "estimate_mode" )`
-//! - `[x]` `sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode" )`
-//! - `[i]` `sethdseed ( newkeypool "seed" )`
-//! - `[ ]` `setlabel "address" "label"`
-//! - `[ ]` `settxfee amount`
-//! - `[i]` `signmessage "address" "message"`
-//! - `[i]` `signrawtransactionwithwallet "hexstring" ( [{"txid":"hex","vout":n,"scriptPubKey":"hex","redeemScript":"hex","witnessScript":"hex","amount":amount},...] "sighashtype" )`
-//! - `[-]` `unloadwallet ( "wallet_name" )`
-//! - `[i]` `walletcreatefundedpsbt [{"txid":"hex","vout":n,"sequence":n},...] [{"address":amount},{"data":"hex"},...] ( locktime options bip32derivs )`
-//! - `[-]` `walletlock`
-//! - `[-]` `walletpassphrase "passphrase" timeout`
-//! - `[-]` `walletpassphrasechange "oldpassphrase" "newpassphrase"`
-//! - `[-]` `walletprocesspsbt "psbt" ( sign "sighashtype" bip32derivs )`
-//!
-//! ** == Zmq ==**`
-//! - `[i]` `getzmqnotifications`
+//! # JSON-RPC types for Bitcoin Core `v0.18.1`
+//!
+//! These structs are shaped for the JSON data returned by the JSON-RPC API. They use stdlib types
+//! (or custom types) and where necessary implement an `into_model` function to convert the type to
+//! a [`crate::model`] type of the same name. The types in this module are version specific. The
+//! types in the `model` module are version nonspecific and are strongly typed using `rust-bitcoin`.
+//!
+//! ### Method name and implementation status
+//!
+//! Every JSON-RPC method supported by this version of Bitcoin Core is listed below along with its
+//! current implementation status.
+//!
+//!
+//! Methods from the == Blockchain == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | getbestblockhash | done |
+//! | getblock | done |
+//! | getblockchaininfo | done |
+//! | getblockcount | done |
+//! | getblockhash | done |
+//! | getblockheader | done |
+//! | getblockstats | done |
+//! | getchaintips | done |
+//! | getchaintxstats | done |
+//! | getdifficulty | done |
+//! | getmempoolancestors | done (untested) |
+//! | getmempooldescendants | done (untested) |
+//! | getmempoolentry | done (untested) |
+//! | getmempoolinfo | done (untested) |
+//! | getrawmempool | done (untested) |
+//! | gettxout | done (untested) |
+//! | gettxoutproof | done (untested) |
+//! | gettxoutsetinfo | done (untested) |
+//! | preciousblock | omitted |
+//! | pruneblockchain | omitted |
+//! | savemempool | omitted |
+//! | scantxoutset | omitted |
+//! | verifychain | omitted |
+//! | verifytxoutproof | done (untested) |
+//!
+//!
+//!
+//!
+//! Methods from the == Control == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | getmemoryinfo | done |
+//! | getrpcinfo | todo |
+//! | help | omitted |
+//! | logging | done |
+//! | stop | omitted |
+//! | uptime | omitted |
+//!
+//!
+//!
+//!
+//! Methods from the == Generating == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | generate | done |
+//! | generatetoaddress | done |
+//!
+//!
+//!
+//!
+//! Methods from the == Mining == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | getblocktemplate | todo |
+//! | getmininginfo | todo |
+//! | getnetworkhashps | todo |
+//! | prioritisetransaction | todo |
+//! | submitblock | todo |
+//! | submitheader | todo |
+//!
+//!
+//!
+//!
+//! Methods from the == Network == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | addnode | omitted |
+//! | clearbanned | omitted |
+//! | disconnectnode | omitted |
+//! | getaddednodeinfo | done |
+//! | getconnectioncount | omitted |
+//! | getnettotals | done |
+//! | getnetworkinfo | done |
+//! | getnodeaddresses | todo |
+//! | getpeerinfo | done |
+//! | listbanned | omitted |
+//! | ping | omitted |
+//! | setban | omitted |
+//! | setnetworkactive | omitted |
+//!
+//!
+//!
+//!
+//! Methods from the == Rawtransactions == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | analyzepsbt | todo |
+//! | combinepsbt | todo |
+//! | combinerawtransaction | todo |
+//! | converttopsbt | todo |
+//! | createpsbt | todo |
+//! | createrawtransaction | todo |
+//! | decodepsbt | todo |
+//! | decoderawtransaction | todo |
+//! | decodescript | todo |
+//! | finalizepsbt | todo |
+//! | fundrawtransaction | todo |
+//! | getrawtransaction | todo |
+//! | joinpsbts | todo |
+//! | sendrawtransaction | done |
+//! | signrawtransactionwithkey | todo |
+//! | testmempoolaccept | todo |
+//! | utxoupdatepsbt | todo |
+//!
+//!
+//!
+//!
+//! Methods from the == Util == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | createmultisig | omitted |
+//! | deriveaddresses | todo |
+//! | estimatesmartfee | omitted |
+//! | getdescriptorinfo | todo |
+//! | signmessagewithprivkey | omitted |
+//! | validateaddress | omitted |
+//! | verifymessage | omitted |
+//!
+//!
+//!
+//!
+//! Methods from the == Wallet == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | abandontransaction | omitted |
+//! | abortrescan | omitted |
+//! | addmultisigaddress | done |
+//! | backupwallet | omitted |
+//! | bumpfee | done |
+//! | createwallet | done |
+//! | dumpprivkey | done |
+//! | dumpwallet | done |
+//! | encryptwallet | omitted |
+//! | getaddressesbylabel | done |
+//! | getaddressinfo | done |
+//! | getbalance | done |
+//! | getnewaddress | done |
+//! | getrawchangeaddress | done |
+//! | getreceivedbyaddress | done |
+//! | getreceivedbylabel | todo |
+//! | gettransaction | done |
+//! | getunconfirmedbalance | done (untested) |
+//! | getwalletinfo | done (untested) |
+//! | importaddress | omitted |
+//! | importmulti | omitted |
+//! | importprivkey | omitted |
+//! | importprunedfunds | omitted |
+//! | importpubkey | omitted |
+//! | importwallet | omitted |
+//! | keypoolrefill | omitted |
+//! | listaddressgroupings | done (untested) |
+//! | listlabels | done (untested) |
+//! | listlockunspent | done (untested) |
+//! | listreceivedbyaddress | done (untested) |
+//! | listreceivedbylabel | todo |
+//! | listsinceblock | done (untested) |
+//! | listtransactions | done (untested) |
+//! | listunspent | done (untested) |
+//! | listwalletdir | todo |
+//! | listwallets | done (untested) |
+//! | loadwallet | done |
+//! | lockunspent | omitted |
+//! | removeprunedfunds | omitted |
+//! | rescanblockchain | done (untested) |
+//! | sendmany | done (untested) |
+//! | sendtoaddress | done |
+//! | sethdseed | omitted |
+//! | setlabel | todo |
+//! | settxfee | omitted |
+//! | signmessage | done (untested) |
+//! | signrawtransactionwithwallet | done (untested) |
+//! | unloadwallet | omitted |
+//! | walletcreatefundedpsbt | done (untested) |
+//! | walletlock | omitted |
+//! | walletpassphrase | omitted |
+//! | walletpassphrasechange | omitted |
+//! | walletprocesspsbt | done (untested) |
+//!
+//!
+//!
+//!
+//! Methods from the == Zmq == section
+//!
+//! | JSON-PRC Method Name | Status |
+//! |:-----------------------------------|:---------------:|
+//! | getzmqnotifications | done (untested) |
+//!
+//!
+//!
+//!
+//! **Items marked omitted were omitted because:**
+//!
+//! - Method does not return anything.
+//! - Method returns a simple type (e.g. bool or integer).
+//! - Method is deprecated.
#[doc(inline)]
pub use crate::v17::{
@@ -184,12 +241,12 @@ pub use crate::v17::{
GetNetworkInfoAddress, GetNetworkInfoError, GetNetworkInfoNetwork, GetNewAddress, GetPeerInfo,
GetRawChangeAddress, GetRawMempool, GetRawMempoolVerbose, GetReceivedByAddress, GetTransaction,
GetTransactionDetail, GetTxOut, GetTxOutProof, GetTxOutSetInfo, GetUnconfirmedBalance,
- GetWalletInfo, ListAddressGroupings, ListAddressGroupingsItem, ListBanned, ListLabels,
- ListLockUnspent, ListLockUnspentItem, ListReceivedByAddress, ListReceivedByAddressItem,
- ListSinceBlock, ListSinceBlockTransaction, ListTransactions, ListTransactionsItem, ListUnspent,
- ListUnspentItem, ListWallets, LoadWallet, Locked, Logging, MempoolEntry, MempoolEntryFees,
- PeerInfo, RescanBlockchain, ScriptPubkey, SendMany, SendRawTransaction, SendToAddress,
- SignErrorData, SignMessage, SignRawTransactionWithWallet, Softfork, SoftforkReject,
- TransactionCategory, UploadTarget, Uptime, VerifyTxOutProof, WalletCreateFundedPsbt,
- WalletProcessPsbt,
+ GetWalletInfo, GetZmqNotifications, ListAddressGroupings, ListAddressGroupingsItem, ListBanned,
+ ListLabels, ListLockUnspent, ListLockUnspentItem, ListReceivedByAddress,
+ ListReceivedByAddressItem, ListSinceBlock, ListSinceBlockTransaction, ListTransactions,
+ ListTransactionsItem, ListUnspent, ListUnspentItem, ListWallets, LoadWallet, Locked, Logging,
+ MempoolEntry, MempoolEntryFees, PeerInfo, RescanBlockchain, ScriptPubkey, SendMany,
+ SendRawTransaction, SendToAddress, SignErrorData, SignMessage, SignRawTransactionWithWallet,
+ Softfork, SoftforkReject, TransactionCategory, UploadTarget, Uptime, VerifyTxOutProof,
+ WalletCreateFundedPsbt, WalletProcessPsbt,
};
diff --git a/verify/Cargo.toml b/verify/Cargo.toml
new file mode 100644
index 00000000..573376d4
--- /dev/null
+++ b/verify/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "verify"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.93"
+regex = "1"
diff --git a/types/src/v17/rpc-api.txt b/verify/rpc-api-v17.txt
similarity index 99%
rename from types/src/v17/rpc-api.txt
rename to verify/rpc-api-v17.txt
index 7b5d2f88..2001348f 100644
--- a/types/src/v17/rpc-api.txt
+++ b/verify/rpc-api-v17.txt
@@ -1,6 +1,6 @@
== Blockchain ==
getbestblockhash
-getblock "blockhash" ( verbosity )
+getblock "blockhash" ( verbosity )
getblockchaininfo
getblockcount
getblockhash height
diff --git a/types/src/v18/rpc-api.txt b/verify/rpc-api-v18.txt
similarity index 100%
rename from types/src/v18/rpc-api.txt
rename to verify/rpc-api-v18.txt
diff --git a/verify/src/lib.rs b/verify/src/lib.rs
new file mode 100644
index 00000000..af427bd4
--- /dev/null
+++ b/verify/src/lib.rs
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Helper methods.
+
+pub mod method;
+pub mod model;
+pub mod ssot;
+pub mod versioned;
+
+use std::fmt;
+use std::fs::File;
+use std::io::{self, BufRead};
+use std::path::Path;
+use std::str::FromStr;
+
+use anyhow::{Context, Result};
+use regex::Regex;
+
+/// Supported Bitcoin Core versions.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Version {
+ /// Bitcoin Core v17.
+ V17,
+ /// Bitcoin Core v18.
+ V18,
+}
+
+impl Version {
+ /// Creates a new `Version` from string.
+ pub fn new(v: &str) -> Result {
+ match v {
+ "v17" | "17" => Ok(Version::V17),
+ "v18" | "18" => Ok(Version::V18),
+ other => Err(anyhow::Error::msg(format!("unknown version: '{}'", other))),
+ }
+ }
+}
+
+impl fmt::Display for Version {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Version::*;
+ let s = match *self {
+ V17 => "v17",
+ V18 => "v18",
+ };
+ fmt::Display::fmt(&s, f)
+ }
+}
+
+impl FromStr for Version {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> Result { Version::new(s) }
+}
+
+/// Checks that `got` contains all methods from `want` and no additional methods.
+///
+/// # Returns
+///
+/// `true` if all methods are correct, `false` otherwise.
+pub fn correct_methods(got: &[&str], want: &[&str], msg: &str) -> bool {
+ let mut correct = true;
+ let ret = has_all_expected(got, want);
+ if !ret.is_empty() {
+ eprintln!("\nMissing methods ({}):", msg);
+ correct = false;
+ for method in ret {
+ eprintln!(" - {}", method);
+ }
+ eprintln!();
+ }
+
+ let ret = has_no_additional(got, want);
+ if !ret.is_empty() {
+ eprintln!("Unexpected additional methods ({}):", msg);
+ correct = false;
+ for method in ret {
+ eprintln!(" - {}", method);
+ }
+ eprintln!();
+ }
+ correct
+}
+
+/// Checks that all methods in `want` exist in `got`.
+///
+/// # Returns
+///
+/// A list of any methods found to be missing.
+fn has_all_expected<'b>(got: &[&str], want: &[&'b str]) -> Vec<&'b str> {
+ let mut missing = vec![];
+ for method in want {
+ if !got.contains(method) {
+ missing.push(*method);
+ }
+ }
+ missing
+}
+
+/// Checks that no methods in `got` exist in `want`.
+///
+/// # Returns
+///
+/// A list of any methods found to exist when they should not.
+fn has_no_additional<'a>(got: &[&'a str], want: &[&str]) -> Vec<&'a str> {
+ let mut additional = vec![];
+ // We did not get any additional methods we didn't expect.
+ for method in got {
+ if !want.contains(method) {
+ additional.push(*method);
+ }
+ }
+ additional
+}
+
+/// Opens file at `path` and greps for `s`.
+pub fn grep_for_string(path: &Path, s: &str) -> Result {
+ let file = File::open(path)
+ .with_context(|| format!("Failed to grep for string in {}", path.display()))?;
+ let reader = io::BufReader::new(file);
+
+ let re = Regex::new(s)?;
+
+ for line in reader.lines() {
+ let line = line?;
+
+ if re.is_match(&line) {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn equal() {
+ let got = vec!["one", "two", "three"];
+ let want = vec!["one", "two", "three"];
+
+ assert!(has_all_expected(&got, &want).is_empty());
+ assert!(has_no_additional(&got, &want).is_empty());
+ }
+
+ #[test]
+ fn missing_from_beginning() {
+ let got = vec!["two", "three"];
+ let want = vec!["one", "two", "three"];
+ assert_eq!(has_all_expected(&got, &want), &["one"]);
+ }
+
+ #[test]
+ fn missing_from_middle() {
+ let got = vec!["one", "three"];
+ let want = vec!["one", "two", "three"];
+ assert_eq!(has_all_expected(&got, &want), &["two"]);
+ }
+
+ #[test]
+ fn missing_from_end() {
+ let got = vec!["one", "two"];
+ let want = vec!["one", "two", "three"];
+ assert_eq!(has_all_expected(&got, &want), &["three"]);
+ }
+
+ #[test]
+ fn has_additional_at_beginning() {
+ let got = vec!["one", "two", "three"];
+ let want = vec!["two", "three"];
+
+ assert_eq!(has_no_additional(&got, &want), &["one"]);
+ }
+
+ #[test]
+ fn has_additional_in_middle() {
+ let got = vec!["one", "two", "three"];
+ let want = vec!["one", "three"];
+
+ assert_eq!(has_no_additional(&got, &want), &["two"]);
+ }
+
+ #[test]
+ fn has_additional_at_end() {
+ let got = vec!["one", "two", "three"];
+ let want = vec!["one", "two"];
+
+ assert_eq!(has_no_additional(&got, &want), &["three"]);
+ }
+}
diff --git a/verify/src/main.rs b/verify/src/main.rs
new file mode 100644
index 00000000..765f5c0e
--- /dev/null
+++ b/verify/src/main.rs
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Tool to help verify that what we claim in the rustdocs is true.
+//!
+//! Currently verifies:
+//!
+//! - That the correct set of methods is documented.
+//! - That an expected return type is provided if the method is supported.
+//! - That there is a `model` type if required.
+//! - That the method has an integration test.
+
+use std::env;
+
+use anyhow::Result;
+use verify::method::{Method, Return};
+use verify::versioned::{self, Status};
+use verify::{method, model, ssot, Version};
+
+/// The default version of Core if no command line argument is provided.
+const DEFAULT_VERSION: Version = Version::V17;
+
+// FIXME: Enable running from any directory, currently errors if run from `src/`.
+
+fn main() -> Result<()> {
+ let args: Vec = env::args().collect();
+
+ let version = if args.len() < 2 {
+ DEFAULT_VERSION
+ } else {
+ let v = &args[1];
+ v.parse::()?
+ };
+ println!("\nVerifying support for Bitcoin Core {}\n", version);
+
+ let s = format!("{}::METHOD data", version);
+ let msg = format!("Checking that the {} list is correct", s);
+ check(&msg);
+ let correct = verify_correct_methods(version, method::all_methods(version), &s)?;
+ close(correct);
+ if !correct {
+ std::process::exit(1);
+ }
+
+ let s = "rustdoc version specific rustdocs";
+ let msg = format!("Checking that the {} list is correct", s);
+ check(&msg);
+ let correct = verify_correct_methods(version, method::all_methods(version), s)?;
+ close(correct);
+ if !correct {
+ std::process::exit(1);
+ }
+
+ let msg = "Checking that the status claimed in the version specific rustdocs is correct";
+ check(msg);
+ verify_status(version)?;
+ close(correct);
+
+ Ok(())
+}
+
+fn check(msg: &str) { println!("{} ... ", msg) }
+
+fn close(correct: bool) {
+ if correct {
+ println!("Correct \u{2713} \n");
+ }
+}
+
+/// Verifies that the correct set of methods are documented.
+fn verify_correct_methods(version: Version, methods: Vec, msg: &str) -> Result {
+ let ssot = ssot::all_methods(version)?;
+ let want = ssot.iter().map(|s| s.as_str()).collect::>();
+
+ let got = methods.iter().map(|s| s.as_str()).collect::>();
+
+ Ok(verify::correct_methods(&got, &want, msg))
+}
+
+/// Verifies that the status we claim is correct.
+fn verify_status(version: Version) -> Result<()> {
+ let methods = versioned::methods_and_status(version)?;
+ for method in methods {
+ let out =
+ Method::from_name(version, &method.name).expect("guaranteed by methods_and_status()");
+ match method.status {
+ Status::Done => {
+ if !versioned::return_type_exists(version, &method.name)? {
+ eprintln!("missing return type: {}", output_method(out));
+ }
+ if !model::type_exists(version, &method.name)? {
+ eprintln!("missing model type: {}", output_method(out));
+ }
+ if !check_integration_test_crate::test_exists(version, &method.name)? {
+ eprintln!("missing integration test: {}", method.name);
+ }
+ }
+ Status::Untested => {
+ if !versioned::return_type_exists(version, &method.name)? {
+ eprintln!("missing return type: {}", output_method(out));
+ }
+ if !model::type_exists(version, &method.name)? {
+ eprintln!("missing model type: {}", output_method(out));
+ }
+ // Make sure we didn't forget to mark as tested after implementing integration test.
+ if check_integration_test_crate::test_exists(version, &method.name)? {
+ eprintln!("found integration test for untested method: {}", method.name);
+ }
+ }
+ Status::Omitted | Status::Todo => { /* Nothing to verify */ }
+ }
+ }
+
+ Ok(())
+}
+
+fn output_method(method: &Method) -> String {
+ if let Some(Return::Type(s)) = method.ret {
+ format!("{} {}", method.name, s)
+ } else {
+ method.name.to_string()
+ }
+}
+
+// Use a module because a file with this name is confusing.
+mod check_integration_test_crate {
+ //! Things related to parsing the `integration_test` crate.
+
+ use std::fs::File;
+ use std::io::{self, BufRead};
+ use std::path::PathBuf;
+
+ use anyhow::{Context, Result};
+ use regex::Regex;
+ use verify::method;
+
+ use crate::Version;
+
+ /// Path to the model module file.
+ fn paths() -> Vec {
+ // TODO: "mining", "util", "zmq"
+ let sections =
+ ["blockchain", "control", "generating", "network", "raw_transactions", "wallet"];
+ let mut paths = vec![];
+ for section in sections {
+ paths.push(PathBuf::from(format!("../integration_test/tests/{}.rs", section)));
+ }
+ paths
+ }
+
+ fn all_test_functions() -> Result> {
+ let mut functions = vec![];
+
+ for path in paths() {
+ let file = File::open(&path).with_context(|| {
+ format!("Failed to grep for test functions in {}", path.display())
+ })?;
+ let reader = io::BufReader::new(file);
+
+ // let re = Regex::new(®ex::escape(r"fn ([a-z_]+)\(\) \{"))?;
+ let fn_re = Regex::new(r"fn ([a-z_]+)")?;
+ let todo_re = Regex::new(r"todo")?;
+
+ for line in reader.lines() {
+ let line = line?;
+
+ if todo_re.is_match(&line) {
+ continue;
+ }
+
+ if let Some(caps) = fn_re.captures(&line) {
+ let function = caps.get(1).unwrap().as_str();
+ functions.push(function.to_string());
+ }
+ }
+ }
+ Ok(functions)
+ }
+
+ /// Checks that a type exists in `model` module.
+ pub fn test_exists(version: Version, method_name: &str) -> Result {
+ let method = match method::Method::from_name(version, method_name) {
+ Some(m) => m,
+ None =>
+ return Err(anyhow::Error::msg(format!(
+ "expected test method not found: {}",
+ method_name
+ ))),
+ };
+
+ let tests = all_test_functions()?;
+ if !tests.contains(&method.function.to_string()) {
+ Ok(false)
+ } else {
+ Ok(true)
+ }
+ }
+}
diff --git a/verify/src/method/mod.rs b/verify/src/method/mod.rs
new file mode 100644
index 00000000..4dc91e32
--- /dev/null
+++ b/verify/src/method/mod.rs
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Provides a data structure that describes each JSON RPC method.
+
+pub mod v17;
+pub mod v18;
+
+use crate::Version;
+
+/// Returns a list of all the method names.
+pub fn all_methods(version: Version) -> Vec {
+ use Version::*;
+
+ let list = match version {
+ V17 => v17::METHODS,
+ V18 => v18::METHODS,
+ };
+
+ list.iter().map(|m| m.name.to_string()).collect()
+}
+
+/// Describes a single JSON RPC method.
+// TODO: This name is overloaded (`versioned::Method`).
+#[derive(Debug)]
+pub struct Method {
+ /// The method name.
+ pub name: &'static str,
+ /// The type it is expected to return (method name in snake case).
+ ///
+ /// `None` if the method is either intentionally omitted or not yet done.
+ pub ret: Option,
+ /// `true` if this method requires a type to exist in `model`.
+ ///
+ /// This is true if the return type includes types that can be
+ /// more strongly typed using `rust-bitcoin`.
+ pub requires_model: bool,
+ /// The function name (snake case) for this method.
+ pub function: &'static str,
+}
+
+impl Method {
+ /// Returns a `Method` type if one exists in the `METHODS` list for `name`.
+ pub fn from_name(version: Version, name: &str) -> Option<&'static Method> {
+ use Version::*;
+ let list = match version {
+ V17 => v17::METHODS,
+ V18 => v18::METHODS,
+ };
+
+ list.iter().find(|&method| method.name == name)
+ }
+
+ /// Represents a `Method` that requires a custom type as well as a type in `model`.
+ const fn new_modeled(name: &'static str, ty: &'static str, function: &'static str) -> Method {
+ Method { name, ret: Some(Return::Type(ty)), requires_model: true, function }
+ }
+
+ /// Represents a method that requires a custom type as but no type in `model`.
+ ///
+ /// Implies this method does not return data that can be strongly type using `rust-bitcoin`.
+ const fn new_no_model(name: &'static str, ty: &'static str, function: &'static str) -> Method {
+ Method { name, ret: Some(Return::Type(ty)), requires_model: false, function }
+ }
+
+ const fn new_nothing(name: &'static str, function: &'static str) -> Method {
+ Method { name, ret: Some(Return::Nothing), function, requires_model: false }
+ }
+
+ const fn new_numeric(name: &'static str, function: &'static str) -> Method {
+ Method { name, ret: Some(Return::Numeric), function, requires_model: false }
+ }
+
+ const fn new_bool(name: &'static str, function: &'static str) -> Method {
+ Method { name, ret: Some(Return::Bool), function, requires_model: false }
+ }
+
+ const fn new_string(name: &'static str, function: &'static str) -> Method {
+ Method { name, ret: Some(Return::String), function, requires_model: false }
+ }
+
+ const fn new_none(name: &'static str, function: &'static str) -> Method {
+ Method { name, ret: None, function, requires_model: false }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Return {
+ /// Method returns a type (that should exist).
+ Type(&'static str),
+ /// Method does not return anything.
+ Nothing,
+ /// Method returns a numeric type.
+ Numeric,
+ /// Method returns a boolean.
+ Bool,
+ /// Method returns a string.
+ String,
+}
diff --git a/verify/src/method/v17.rs b/verify/src/method/v17.rs
new file mode 100644
index 00000000..1d4db878
--- /dev/null
+++ b/verify/src/method/v17.rs
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! JSON RPC methods provided by Bitcoin Core v17.
+
+use super::Method;
+
+/// Data for the JSON RPC methods provided by Bitcoin Core v17.
+pub const METHODS: &[Method] = &[
+ Method::new_modeled("getbestblockhash", "GetBestBlockHash", "get_best_block_hash"),
+ Method::new_modeled("getblock", "GetBlock", "get_block"),
+ Method::new_modeled("getblockchaininfo", "GetBlockchainInfo", "get_blockchain_info"),
+ Method::new_modeled("getblockcount", "GetBlockCount", "get_block_count"),
+ Method::new_modeled("getblockhash", "GetBlockHash", "get_block_hash"),
+ Method::new_modeled("getblockheader", "GetBlockHeader", "get_block_header"),
+ Method::new_modeled("getblockstats", "GetBlockStats", "get_block_stats"),
+ Method::new_modeled("getchaintips", "GetChainTips", "get_chain_tips"),
+ Method::new_modeled("getchaintxstats", "GetChainTxStats", "get_chain_tx_stats"),
+ Method::new_modeled("getdifficulty", "GetDifficulty", "get_difficulty"),
+ Method::new_modeled("getmempoolancestors", "GetMempoolAncestors", "get_mempool_ancestors"),
+ Method::new_modeled(
+ "getmempooldescendants",
+ "GetMempoolDescendants",
+ "get_mempool_descendants",
+ ),
+ Method::new_modeled("getmempoolentry", "GetMempoolEntry", "get_mempool_entry"),
+ Method::new_modeled("getmempoolinfo", "GetMempoolInfo", "get_mempool_info"),
+ Method::new_modeled("getrawmempool", "GetRawMempool", "get_raw_mempool"),
+ Method::new_modeled("gettxout", "GetTxOut", "get_tx_out"),
+ Method::new_modeled("gettxoutproof", "GetTxOutProof", "get_tx_out_proof"),
+ Method::new_modeled("gettxoutsetinfo", "GetTxOutSetInfo", "get_tx_out_set_info"),
+ Method::new_nothing("preciousblock", "precious_block"),
+ Method::new_numeric("pruneblockchain", "prune_blockchain"),
+ Method::new_nothing("savemempool", "save_mempool"),
+ Method::new_modeled("scantxoutset", "ScanTxOutSet", "scan_tx_out_set"),
+ Method::new_bool("verifychain", "verify_chain"),
+ Method::new_modeled("verifytxoutproof", "VerifyTxOutProof", "verify_tx_out_proof"),
+ Method::new_no_model("getmemoryinfo", "GetMemoryInfo", "get_memory_info"),
+ Method::new_string("help", "help"),
+ Method::new_no_model("logging", "Logging", "logging"),
+ Method::new_nothing("stop", "stop"),
+ Method::new_numeric("uptime", "uptime"),
+ Method::new_modeled("generate", "Generate", "generate"),
+ Method::new_modeled("generatetoaddress", "GenerateToAddress", "generate_to_address"),
+ Method::new_none("getblocktemplate", "get_block_template"),
+ Method::new_none("getmininginfo", "get_mining_info"),
+ Method::new_none("getnetworkhashps", "get_network_hashes_per_second"),
+ Method::new_bool("prioritisetransaction", "prioritise_transaction"),
+ Method::new_none("submitblock", "submit_block"),
+ Method::new_nothing("addnode", "add_node"),
+ Method::new_nothing("clearbanned", "clear_banned"),
+ Method::new_nothing("disconnectnode", "disconnect_node"),
+ Method::new_no_model("getaddednodeinfo", "GetAddedNodeInfo", "get_added_node_info"),
+ Method::new_numeric("getconnectioncount", "get_connection_count"),
+ Method::new_no_model("getnettotals", "GetNetTotals", "get_net_totals"),
+ Method::new_modeled("getnetworkinfo", "GetNetworkInfo", "get_network_info"),
+ Method::new_no_model("getpeerinfo", "GetPeerInfo", "get_peer_info"),
+ Method::new_string("listbanned", "list_banned"), // v17 docs seem wrong, says no return.
+ Method::new_nothing("ping", "ping"),
+ Method::new_nothing("setban", "set_ban"),
+ Method::new_nothing("setnetworkactive", "set_network_active"),
+ Method::new_none("combinepsbt", "combine_psbt"),
+ Method::new_none("combinerawtransaction", "combine_raw_transaction"),
+ Method::new_none("converttopsbt", "convert_to_psbt"),
+ Method::new_none("createpsbt", "create_psbt"),
+ Method::new_none("createrawtransaction", "create_raw_transaction"),
+ Method::new_none("decodepsbt", "decode_psbt"),
+ Method::new_none("decoderawtransaction", "decode_raw_transaction"),
+ Method::new_none("decodescript", "decode_script"),
+ Method::new_none("finalizepsbt", "finalize_psbt"),
+ Method::new_none("fundrawtransaction", "fund_raw_transaciton"),
+ Method::new_none("getrawtransaction", "get_raw_transaction"),
+ Method::new_modeled("sendrawtransaction", "SendRawTransaction", "send_raw_transaction"),
+ Method::new_none("signrawtransaction", "sign_raw_transaction"),
+ Method::new_none("signrawtransactionwithkey", "sign_raw_transaction_with_key"),
+ Method::new_none("testmempoolaccept", "test_mempool_accept"),
+ Method::new_modeled("createmultisig", "CreateMultisig", "create_multisig"),
+ Method::new_nothing("estimatesmartfee", "estimate_smart_fee"),
+ Method::new_string("signmessagewithprivkey", "sign_message_with_priv_key"),
+ Method::new_modeled("validateaddress", "ValidateAddress", "validate_address"),
+ Method::new_bool("verifymessage", "verify_message"),
+ Method::new_nothing("abandontransaction", "abandon_transaction"),
+ Method::new_nothing("abortrescan", "abort_rescan"),
+ Method::new_modeled("addmultisigaddress", "AddMultisigAddress", "add_multisig_address"),
+ Method::new_nothing("backupwallet", "backup_wallet"),
+ Method::new_modeled("bumpfee", "BumpFee", "bump_fee"),
+ Method::new_modeled("createwallet", "CreateWallet", "create_wallet"),
+ Method::new_modeled("dumpprivkey", "DumpPrivKey", "dump_priv_key"),
+ Method::new_modeled("dumpwallet", "DumpWallet", "dump_wallet"),
+ Method::new_nothing("encryptwallet", "encrypt_wallet"),
+ Method::new_none("getaccount", "get_account"), // Deprecated
+ Method::new_none("getaccountaddress", "get_account_address"), // Deprecated
+ Method::new_none("getaddressbyaccount", "get_address_by_account"), // Deprecated
+ Method::new_modeled("getaddressesbylabel", "GetAddressesByLabel", "get_addresses_by_label"),
+ Method::new_modeled("getaddressinfo", "GetAddressInfo", "get_address_info"),
+ Method::new_modeled("getbalance", "GetBalance", "get_balance"),
+ Method::new_modeled("getnewaddress", "GetNewAddress", "get_new_address"),
+ Method::new_modeled("getrawchangeaddress", "GetRawChangeAddress", "get_raw_change_address"),
+ Method::new_none("getreceivedbyaccount", "get_received_by_account"), // Deprecated
+ Method::new_modeled("getreceivedbyaddress", "GetReceivedByAddress", "get_received_by_address"),
+ Method::new_modeled("gettransaction", "GetTransaction", "get_transaction"),
+ Method::new_modeled(
+ "getunconfirmedbalance",
+ "GetUnconfirmedBalance",
+ "get_unconfirmed_balance",
+ ),
+ Method::new_modeled("getwalletinfo", "GetWalletInfo", "get_wallet_info"),
+ Method::new_nothing("importaddress", "import_addressss"),
+ Method::new_nothing("importmulti", "import_multi"),
+ Method::new_nothing("importprivkey", "import_priv_key"),
+ Method::new_nothing("importprunedfunds", "import_pruned_funds"),
+ Method::new_nothing("importpubkey", "import_pubkey"),
+ Method::new_nothing("importwallet", "import_walet"),
+ Method::new_nothing("keypoolrefill", "keypool_refill"),
+ Method::new_none("listaccounts", "list_accounts"), // Deprecated
+ Method::new_modeled("listaddressgroupings", "ListAddressGroupings", "list_address_groupings"),
+ Method::new_modeled("listlabels", "ListLabels", "list_labels"),
+ Method::new_modeled("listlockunspent", "ListLockUnspent", "list_lock_unspent"),
+ Method::new_none("listreceivedbyaccount", "list_received_by_account"), // Deprecated
+ Method::new_modeled(
+ "listreceivedbyaddress",
+ "ListReceivedByAddress",
+ "list_received_by_address",
+ ),
+ Method::new_modeled("listsinceblock", "ListSinceBlock", "list_since_block"),
+ Method::new_modeled("listtransactions", "ListTransactions", "list_transactions"),
+ Method::new_modeled("listunspent", "ListUnspent", "list_unspent"),
+ Method::new_modeled("listwallets", "ListWallets", "list_wallets"),
+ Method::new_modeled("loadwallet", "LoadWallet", "load_wallet"),
+ Method::new_bool("lockunspent", "lock_unspent"),
+ Method::new_bool("move", "move"),
+ Method::new_nothing("removeprunedfunds", "remove_pruned_funds"),
+ Method::new_modeled("rescanblockchain", "RescanBlockchain", "rescan_blockchain"),
+ Method::new_none("sendfrom", "send_from"), // Deprecated
+ Method::new_modeled("sendmany", "SendMany", "send_many"),
+ Method::new_modeled("sendtoaddress", "SendToAddress", "send_to_address"),
+ Method::new_none("setaccount", "set_account"), // Deprecated
+ Method::new_nothing("sethdseed", "set_hd_seed"),
+ Method::new_bool("settxfee", "set_tx_fee"),
+ Method::new_modeled("signmessage", "SignMessage", "sign_message"),
+ Method::new_modeled(
+ "signrawtransactionwithwallet",
+ "SignRawTransactionWithWallet",
+ "sign_raw_transaction_with_wallet",
+ ),
+ Method::new_nothing("unloadwallet", "unload_wallet"),
+ Method::new_modeled(
+ "walletcreatefundedpsbt",
+ "WalletCreateFundedPsbt",
+ "wallet_create_funded_psbt",
+ ),
+ Method::new_nothing("walletlock", "wallet_lock"),
+ Method::new_nothing("walletpassphrase", "wallet_passphrase"),
+ Method::new_nothing("walletpassphrasechange", "wallet_passphrase_change"),
+ Method::new_modeled("walletprocesspsbt", "WalletProcessPsbt", "wallet_process_psbt"),
+ Method::new_no_model("getzmqnotifications", "GetZmqNotifications", "get_zmq_notifications"),
+];
diff --git a/verify/src/method/v18.rs b/verify/src/method/v18.rs
new file mode 100644
index 00000000..ffde399d
--- /dev/null
+++ b/verify/src/method/v18.rs
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Provides the list of v17 methods.
+
+use super::Method;
+
+pub const METHODS: &[Method] = &[
+ Method::new_modeled("getbestblockhash", "GetBestBlockHash", "get_best_block_hash"),
+ Method::new_modeled("getblock", "GetBlock", "get_block"),
+ Method::new_modeled("getblockchaininfo", "GetBlockchainInfo", "get_blockchain_info"),
+ Method::new_modeled("getblockcount", "GetBlockCount", "get_block_count"),
+ Method::new_modeled("getblockhash", "GetBlockHash", "get_block_hash"),
+ Method::new_modeled("getblockheader", "GetBlockHeader", "get_block_header"),
+ Method::new_modeled("getblockstats", "GetBlockStats", "get_block_stats"),
+ Method::new_modeled("getchaintips", "GetChainTips", "get_chain_tips"),
+ Method::new_modeled("getchaintxstats", "GetChainTxStats", "get_chain_tx_stats"),
+ Method::new_modeled("getdifficulty", "GetDifficulty", "get_difficulty"),
+ Method::new_modeled("getmempoolancestors", "GetMempoolAncestors", "get_mempool_ancestors"),
+ Method::new_modeled(
+ "getmempooldescendants",
+ "GetMempoolDescendants",
+ "get_mempool_descendants",
+ ),
+ Method::new_modeled("getmempoolentry", "GetMempoolEntry", "get_mempool_entry"),
+ Method::new_modeled("getmempoolinfo", "GetMempoolInfo", "get_mempool_info"),
+ Method::new_modeled("getrawmempool", "GetRawMempool", "get_raw_mempool"),
+ Method::new_modeled("gettxout", "GetTxOut", "get_tx_out"),
+ Method::new_modeled("gettxoutproof", "GetTxOutProof", "get_tx_out_proof"),
+ Method::new_modeled("gettxoutsetinfo", "GetTxOutSetInfo", "get_tx_out_set_info"),
+ Method::new_nothing("preciousblock", "precious_block"),
+ Method::new_numeric("pruneblockchain", "prune_blockchain"),
+ Method::new_nothing("savemempool", "save_mempool"),
+ Method::new_modeled("scantxoutset", "ScanTxOutSet", "scan_tx_out_set"),
+ Method::new_bool("verifychain", "verify_chain"),
+ Method::new_modeled("verifytxoutproof", "VerifyTxOutProof", "verify_tx_out_proof"),
+ Method::new_no_model("getmemoryinfo", "GetMemoryInfo", "get_memory_info"),
+ Method::new_string("help", "help"),
+ Method::new_no_model("logging", "Logging", "logging"),
+ Method::new_nothing("stop", "stop"),
+ Method::new_numeric("uptime", "uptime"),
+ Method::new_modeled("generate", "Generate", "generate"),
+ Method::new_modeled("generatetoaddress", "GenerateToAddress", "generate_to_address"),
+ Method::new_none("getblocktemplate", "get_block_template"),
+ Method::new_none("getmininginfo", "get_mining_info"),
+ Method::new_none("getnetworkhashps", "get_network_hashes_per_second"),
+ Method::new_bool("prioritisetransaction", "prioritise_transaction"),
+ Method::new_none("submitblock", "submit_block"),
+ Method::new_nothing("addnode", "add_node"),
+ Method::new_nothing("clearbanned", "clear_banned"),
+ Method::new_nothing("disconnectnode", "disconnect_node"),
+ Method::new_no_model("getaddednodeinfo", "GetAddedNodeInfo", "get_added_node_info"),
+ Method::new_numeric("getconnectioncount", "get_connection_count"),
+ Method::new_no_model("getnettotals", "GetNetTotals", "get_net_totals"),
+ Method::new_modeled("getnetworkinfo", "GetNetworkInfo", "get_network_info"),
+ Method::new_no_model("getpeerinfo", "GetPeerInfo", "get_peer_info"),
+ Method::new_string("listbanned", "list_banned"), // v17 docs seem wrong, says no return.
+ Method::new_nothing("ping", "ping"),
+ Method::new_nothing("setban", "set_ban"),
+ Method::new_nothing("setnetworkactive", "set_network_active"),
+ Method::new_none("combinepsbt", "combine_psbt"),
+ Method::new_none("combinerawtransaction", "combine_raw_transaction"),
+ Method::new_none("converttopsbt", "convert_to_psbt"),
+ Method::new_none("createpsbt", "create_psbt"),
+ Method::new_none("createrawtransaction", "create_raw_transaction"),
+ Method::new_none("decodepsbt", "decode_psbt"),
+ Method::new_none("decoderawtransaction", "decode_raw_transaction"),
+ Method::new_none("decodescript", "decode_script"),
+ Method::new_none("finalizepsbt", "finalize_psbt"),
+ Method::new_none("fundrawtransaction", "fund_raw_transaciton"),
+ Method::new_none("getrawtransaction", "get_raw_transaction"),
+ Method::new_modeled("sendrawtransaction", "SendRawTransaction", "send_raw_transaction"),
+ Method::new_none("signrawtransactionwithkey", "sign_raw_transaction_with_key"),
+ Method::new_none("testmempoolaccept", "test_mempool_accept"),
+ Method::new_modeled("createmultisig", "CreateMultisig", "create_multisig"),
+ Method::new_nothing("estimatesmartfee", "estimate_smart_fee"),
+ Method::new_string("signmessagewithprivkey", "sign_message_with_priv_key"),
+ Method::new_modeled("validateaddress", "ValidateAddress", "validate_address"),
+ Method::new_bool("verifymessage", "verify_message"),
+ Method::new_nothing("abandontransaction", "abandon_transaction"),
+ Method::new_nothing("abortrescan", "abort_rescan"),
+ Method::new_modeled("addmultisigaddress", "AddMultisigAddress", "add_multisig_address"),
+ Method::new_nothing("backupwallet", "backup_wallet"),
+ Method::new_modeled("bumpfee", "BumpFee", "bump_fee"),
+ Method::new_modeled("createwallet", "CreateWallet", "create_wallet"),
+ Method::new_modeled("dumpprivkey", "DumpPrivKey", "dump_priv_key"),
+ Method::new_modeled("dumpwallet", "DumpWallet", "dump_wallet"),
+ Method::new_nothing("encryptwallet", "encrypt_wallet"),
+ Method::new_modeled("getaddressesbylabel", "GetAddressesByLabel", "get_addresses_by_label"),
+ Method::new_modeled("getaddressinfo", "GetAddressInfo", "get_address_info"),
+ Method::new_modeled("getbalance", "GetBalance", "get_balance"),
+ Method::new_modeled("getnewaddress", "GetNewAddress", "get_new_address"),
+ Method::new_modeled("getrawchangeaddress", "GetRawChangeAddress", "get_raw_change_address"),
+ Method::new_modeled("getreceivedbyaddress", "GetReceivedByAddress", "get_received_by_address"),
+ Method::new_modeled("gettransaction", "GetTransaction", "get_transaction"),
+ Method::new_modeled(
+ "getunconfirmedbalance",
+ "GetUnconfirmedBalance",
+ "get_unconfirmed_balance",
+ ),
+ Method::new_modeled("getwalletinfo", "GetWalletInfo", "get_wallet_info"),
+ Method::new_nothing("importaddress", "import_addressss"),
+ Method::new_nothing("importmulti", "import_multi"),
+ Method::new_nothing("importprivkey", "import_priv_key"),
+ Method::new_nothing("importprunedfunds", "import_pruned_funds"),
+ Method::new_nothing("importpubkey", "import_pubkey"),
+ Method::new_nothing("importwallet", "import_walet"),
+ Method::new_nothing("keypoolrefill", "keypool_refill"),
+ Method::new_modeled("listaddressgroupings", "ListAddressGroupings", "list_address_groupings"),
+ Method::new_modeled("listlabels", "ListLabels", "list_labels"),
+ Method::new_modeled("listlockunspent", "ListLockUnspent", "list_lock_unspent"),
+ Method::new_modeled(
+ "listreceivedbyaddress",
+ "ListReceivedByAddress",
+ "list_received_by_address",
+ ),
+ Method::new_modeled("listsinceblock", "ListSinceBlock", "list_since_block"),
+ Method::new_modeled("listtransactions", "ListTransactions", "list_transactions"),
+ Method::new_modeled("listunspent", "ListUnspent", "list_unspent"),
+ Method::new_modeled("listwallets", "ListWallets", "list_wallets"),
+ Method::new_modeled("loadwallet", "LoadWallet", "load_wallet"),
+ Method::new_bool("lockunspent", "lock_unspent"),
+ Method::new_nothing("removeprunedfunds", "remove_pruned_funds"),
+ Method::new_modeled("rescanblockchain", "RescanBlockchain", "rescan_blockchain"),
+ Method::new_modeled("sendmany", "SendMany", "send_many"),
+ Method::new_modeled("sendtoaddress", "SendToAddress", "send_to_address"),
+ Method::new_nothing("sethdseed", "set_hd_seed"),
+ Method::new_bool("settxfee", "set_tx_fee"),
+ Method::new_modeled("signmessage", "SignMessage", "sign_message"),
+ Method::new_modeled(
+ "signrawtransactionwithwallet",
+ "SignRawTransactionWithWallet",
+ "sign_raw_transaction_with_wallet",
+ ),
+ Method::new_nothing("unloadwallet", "unload_wallet"),
+ Method::new_modeled(
+ "walletcreatefundedpsbt",
+ "WalletCreateFundedPsbt",
+ "wallet_create_funded_psbt",
+ ),
+ Method::new_nothing("walletlock", "wallet_lock"),
+ Method::new_nothing("walletpassphrase", "wallet_passphrase"),
+ Method::new_nothing("walletpassphrasechange", "wallet_passphrase_change"),
+ Method::new_modeled("walletprocesspsbt", "WalletProcessPsbt", "wallet_process_psbt"),
+ Method::new_no_model("getzmqnotifications", "GetZmqNotifications", "get_zmq_notifications"),
+ Method::new_no_model("getrpcinfo", "GetRpcInfo", "get_rpc_info"),
+ Method::new_nothing("submitheader", "submit_header"),
+ Method::new_modeled("analyzepsbt", "AnalyzePsbt", "analyze_psbt"),
+ Method::new_no_model("getnodeaddresses", "GetNodeAddresses", "get_node_addresses"),
+ Method::new_modeled("joinpsbts", "JoinPsbts", "join_psbts"),
+ Method::new_modeled("utxoupdatepsbt", "UtxoUpdatePsbt", "utxo_update_psbt"),
+ Method::new_modeled("deriveaddresses", "DeriveAddresses", "derive_addresses"),
+ Method::new_no_model("getdescriptorinfo", "GetDescriptorInfo", "get_descriptor_info"),
+ Method::new_modeled("getreceivedbylabel", "GetReceivedByLabel", "get_received_by_label"),
+ Method::new_modeled("listreceivedbylabel", "ListReceivedByLabel", "list_received_by_label"),
+ Method::new_no_model("listwalletdir", "ListWalletDir", "list_wallet_dir"),
+ Method::new_nothing("setlabel", "set_label"),
+];
diff --git a/verify/src/model.rs b/verify/src/model.rs
new file mode 100644
index 00000000..00f35894
--- /dev/null
+++ b/verify/src/model.rs
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Things related to parsing the model files.
+//!
+//! The "model files" are the files in `types/src/model/`.
+
+use std::path::PathBuf;
+
+use anyhow::Result;
+
+use crate::method::{self, Return};
+use crate::Version;
+
+/// Path to the model module file.
+fn path() -> PathBuf { PathBuf::from("../types/src/model/mod.rs") }
+
+/// Checks that a type exists in `model` module.
+pub fn type_exists(version: Version, method_name: &str) -> Result {
+ let method = match method::Method::from_name(version, method_name) {
+ Some(m) => m,
+ None =>
+ return Err(anyhow::Error::msg(format!(
+ "model type for method not found: {}",
+ method_name
+ ))),
+ };
+
+ if let Some(Return::Type(s)) = method.ret {
+ if method.requires_model {
+ return crate::grep_for_string(&path(), s);
+ } else {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
diff --git a/verify/src/ssot.rs b/verify/src/ssot.rs
new file mode 100644
index 00000000..8dc29fbc
--- /dev/null
+++ b/verify/src/ssot.rs
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Things related to parsing the JSON RPC Single Source Of Truth (SSOT) file.
+//!
+//! The SSOT used by this tool is a file that contains the output of
+//!
+//! `bitcoin-cli --help`
+//!
+//! Run against the version of Core that we are verifying.
+
+use std::fs::File;
+use std::io::{self, BufRead};
+use std::path::PathBuf;
+
+use anyhow::{Context, Result};
+use regex::Regex;
+
+use crate::Version;
+
+/// Path to the RPC SSOT file.
+pub fn path(version: Version) -> PathBuf { PathBuf::from(format!("./rpc-api-{}.txt", version)) }
+
+/// Parses the Bitcoin Core docs (from SSOT file) and gets all the method names.
+pub fn all_methods(version: Version) -> Result> {
+ let path = path(version);
+ let file = File::open(&path)
+ .with_context(|| format!("Failed to grep for method names in {}", path.display()))?;
+ let reader = io::BufReader::new(file);
+
+ let header_re = Regex::new(r"==").unwrap();
+ let empty_re = Regex::new(r"^$").unwrap();
+
+ let mut methods = Vec::new();
+
+ for line in reader.lines() {
+ let line = line?;
+
+ if header_re.is_match(&line) || empty_re.is_match(&line) {
+ continue;
+ }
+
+ let parts: Vec<&str> = line.split_whitespace().collect();
+ // We know this is not empty because of `empty_re`.
+ methods.push(parts[0].to_string());
+ }
+
+ Ok(methods)
+}
diff --git a/verify/src/versioned.rs b/verify/src/versioned.rs
new file mode 100644
index 00000000..2512ec08
--- /dev/null
+++ b/verify/src/versioned.rs
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Things related to parsing the version specific module file.
+//!
+//! The "version specific module file" is for example `types/src/v17/mod.rs`.
+
+use std::fmt;
+use std::fs::File;
+use std::io::{self, BufRead};
+use std::path::PathBuf;
+use std::str::FromStr;
+
+use anyhow::{Context, Result};
+use regex::Regex;
+
+use crate::method::{self, Return};
+use crate::Version;
+
+/// Path to the version specific module file.
+pub fn path(version: Version) -> PathBuf {
+ PathBuf::from(format!("../types/src/{}/mod.rs", version))
+}
+
+/// Parses module rustdocs and gets all the method names.
+pub fn all_methods(version: Version) -> Result> {
+ let methods = methods_and_status(version)?;
+ Ok(methods.iter().map(|m| m.name.to_string()).collect())
+}
+
+/// Parses module rustdocs and grabs each method and its current status.
+pub fn methods_and_status(version: Version) -> Result> {
+ let path = path(version);
+ let file = File::open(&path)
+ .with_context(|| format!("Failed to grep rustdocs in {}", path.display()))?;
+ let reader = io::BufReader::new(file);
+
+ // let re = Regex::new(r"\/\/\! \| ([a-z]+) \| ([.*?]) \|").unwrap();
+ let re = Regex::new(r"\/\/\! \| ([a-z]+) .* \| ([a-z ()]+?) \|").unwrap();
+
+ let mut methods = Vec::new();
+
+ for line in reader.lines() {
+ let line = line?;
+
+ if let Some(caps) = re.captures(&line) {
+ let name = caps.get(1).unwrap().as_str();
+ let status = caps.get(2).unwrap().as_str();
+ let status = status.trim().parse::()?;
+ methods.push(Method { name: name.to_string(), status });
+ }
+ }
+ Ok(methods)
+}
+
+/// Checks that a type exists in version specific module.
+pub fn return_type_exists(version: Version, method_name: &str) -> Result {
+ let path = path(version);
+ let method = match method::Method::from_name(version, method_name) {
+ Some(m) => m,
+ None =>
+ return Err(anyhow::Error::msg(format!(
+ "return type for method not found: {}",
+ method_name
+ ))),
+ };
+ if let Some(Return::Type(s)) = method.ret {
+ return crate::grep_for_string(&path, s);
+ }
+ Ok(false)
+}
+
+/// A list item from rustdocs (e.g. in in `types/src/v17/mod.rs`).
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+// TODO: This name is overloaded (`method::Method`).
+pub struct Method {
+ /// The JSON RPC method name.
+ pub name: String,
+ /// The current implementation status for this method.
+ pub status: Status,
+}
+
+impl fmt::Display for Method {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} (status: {})", self.name, self.status)
+ }
+}
+
+/// Possible status for a method.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Status {
+ /// Done - implemented and tested.
+ Done,
+ /// Intentionally omitted.
+ Omitted,
+ /// Implemented but not yet tested.
+ Untested,
+ /// Still to do.
+ Todo,
+}
+
+impl FromStr for Status {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "done" => Ok(Status::Done),
+ "omitted" => Ok(Status::Omitted),
+ "done (untested)" => Ok(Status::Untested),
+ "todo" => Ok(Status::Todo),
+ other => Err(anyhow::Error::msg(format!("unknown status: '{}'", other))),
+ }
+ }
+}
+
+impl fmt::Display for Status {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Status::*;
+ let s = match self {
+ Done => "done",
+ Omitted => "omitted",
+ Untested => "done (untested)",
+ Todo => "todo",
+ };
+ fmt::Display::fmt(&s, f)
+ }
+}