mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 06:49:38 -04:00
Merge 44fbc9d83d
into c5e44a0435
This commit is contained in:
commit
a2314dfb20
10 changed files with 516 additions and 0 deletions
66
contrib/codegen/generate_client.py
Normal file
66
contrib/codegen/generate_client.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
def get_schema():
|
||||
"""Get the schema from the Bitcoin Core RPC help."""
|
||||
try:
|
||||
result = subprocess.run(['bitcoin-cli', 'schema'], capture_output=True, text=True, check=True)
|
||||
schema = json.loads(result.stdout)
|
||||
return schema
|
||||
except Exception as e:
|
||||
raise RuntimeError("Failed to get schema from bitcoin-cli: " + str(e))
|
||||
|
||||
def sanitize_argument_names(rpcs):
|
||||
for rpc_name, details in rpcs.items():
|
||||
if "argument_names" in details:
|
||||
sanitized = []
|
||||
for arg in details["argument_names"]:
|
||||
sanitized.append(arg.split("|")[0])
|
||||
details["argument_names"] = sanitized
|
||||
|
||||
return rpcs
|
||||
|
||||
def generate_client(schema, language, output_dir):
|
||||
"""Generate client code using the specified language template."""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
templates_path = os.path.join(script_dir, "templates")
|
||||
env = Environment(loader=FileSystemLoader(templates_path))
|
||||
|
||||
# choose template based on target language
|
||||
if language.lower() == "python":
|
||||
template_file = env.get_template("python_client.jinja2")
|
||||
output_file_name = "bitcoin_rpc_client.py"
|
||||
elif language.lower() == "cpp":
|
||||
template_file = env.get_template("cpp_client.jinja2")
|
||||
output_file_name = "bitcoin_rpc_client.cpp"
|
||||
else:
|
||||
raise ValueError("Unsupported language: " + language)
|
||||
|
||||
template = env.get_template(template_file)
|
||||
rendered_code = template.render(rpcs=schema.get("rpcs", {}))
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_path = os.path.join(output_dir, output_file_name)
|
||||
with open(output_path, "w") as f:
|
||||
f.write(rendered_code)
|
||||
print(f"Generated {language.upper()} client code at {output_path}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Bitcoin Core RPC client code.")
|
||||
parser.add_argument('--language', required=True, choices=["python", "cpp"], help="The target language for the client code.")
|
||||
parser.add_argument('--output-dir', default="generated", help="The directory to write the generated code to.")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("Getting schema from Bitcoin Core RPC...")
|
||||
schema = get_schema()
|
||||
rpcs = schema.get("rpcs", {})
|
||||
rpcs = sanitize_argument_names(rpcs)
|
||||
generate_client(schema, args.language, args.output_dir)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
84
contrib/codegen/templates/cpp_client.jinja2
Normal file
84
contrib/codegen/templates/cpp_client.jinja2
Normal file
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Auto-generated Bitcoin RPC client (C++)
|
||||
*/
|
||||
#ifndef BITCOIN_RPC_CLIENT_H
|
||||
#define BITCOIN_RPC_CLIENT_H
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <curl/curl.h>
|
||||
#include <json/json.h>
|
||||
#include <iostream>
|
||||
|
||||
class BitcoinRPCClient {
|
||||
public:
|
||||
BitcoinRPCClient(const std::string &rpc_user, const std::string &rpc_password,
|
||||
const std::string &host = "127.0.0.1", int port = 8332)
|
||||
: m_rpcUser(rpc_user), m_rpcPassword(rpc_password), m_host(host), m_port(port) {}
|
||||
|
||||
/**
|
||||
* Send a JSON-RPC call to the Bitcoin node.
|
||||
*/
|
||||
std::string call(const std::string &method, const std::string ¶ms_json) {
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
throw std::runtime_error("Failed to initialize CURL");
|
||||
}
|
||||
std::stringstream url;
|
||||
url << "http://" << m_host << ":" << m_port;
|
||||
|
||||
// Build JSON-RPC payload
|
||||
Json::Value payload;
|
||||
payload["method"] = method;
|
||||
Json::Reader reader;
|
||||
Json::Value params;
|
||||
if (!reader.parse(params_json, params)) {
|
||||
params = Json::Value(Json::arrayValue);
|
||||
}
|
||||
payload["params"] = params;
|
||||
payload["id"] = 1;
|
||||
Json::StreamWriterBuilder writer;
|
||||
std::string payload_str = Json::writeString(writer, payload);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.str().c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload_str.c_str());
|
||||
std::string auth = m_rpcUser + ":" + m_rpcPassword;
|
||||
curl_easy_setopt(curl, CURLOPT_USERPWD, auth.c_str());
|
||||
// NOTE: For demonstration purposes, we do not capture the response body.
|
||||
curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
return "{}"; // Dummy response for demonstration.
|
||||
}
|
||||
|
||||
{%- for rpc_name, details in rpcs.items() %}
|
||||
/**
|
||||
* {{ details.description | replace("\n", "\n * ") }}
|
||||
{%- if details.argument_names|length > 0 %}
|
||||
* Arguments:
|
||||
{%- for arg in details.argument_names %}
|
||||
* - {{ arg }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
*/
|
||||
std::string {{ rpc_name }}({% if details.argument_names|length > 0 %}{% for arg in details.argument_names %}const std::string &{{ arg }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}) {
|
||||
// Build JSON array of parameters
|
||||
Json::Value params(Json::arrayValue);
|
||||
{%- for arg in details.argument_names %}
|
||||
params.append({{ arg }});
|
||||
{%- endfor %}
|
||||
Json::StreamWriterBuilder writer;
|
||||
std::string params_json = Json::writeString(writer, params);
|
||||
return call("{{ rpc_name }}", params_json);
|
||||
}
|
||||
{%- endfor %}
|
||||
|
||||
private:
|
||||
std::string m_rpcUser;
|
||||
std::string m_rpcPassword;
|
||||
std::string m_host;
|
||||
int m_port;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_RPC_CLIENT_H
|
40
contrib/codegen/templates/python_client.jinja2
Normal file
40
contrib/codegen/templates/python_client.jinja2
Normal file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Auto-generated Bitcoin RPC client (Python)
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
class BitcoinRPCClient:
|
||||
def __init__(self, rpc_user, rpc_password, host="127.0.0.1", port=8332):
|
||||
self.url = f"http://{host}:{port}"
|
||||
self.auth = (rpc_user, rpc_password)
|
||||
|
||||
def call(self, method, params):
|
||||
payload = {"method": method, "params": params, "id": 1}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(self.url, auth=self.auth, headers=headers, data=json.dumps(payload))
|
||||
return response.json()
|
||||
|
||||
{% for rpc_name, details in rpcs.items() %}
|
||||
def {{ rpc_name }}(self{% if details.argument_names|length > 0 %}, {% for arg in details.argument_names %}{{ arg }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}):
|
||||
"""
|
||||
{{ details.description }}
|
||||
{% if details.argument_names|length > 0 %}
|
||||
Arguments:
|
||||
{% for arg in details.argument_names %}
|
||||
- {{ arg }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
"""
|
||||
params = [{% for arg in details.argument_names %}{{ arg }}{% if not loop.last %}, {% endif %}{% endfor %}]
|
||||
return self.call("{{ rpc_name }}", params)
|
||||
|
||||
{% endfor %}
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Example usage:
|
||||
client = BitcoinRPCClient("rpcuser", "rpcpassword")
|
||||
result = client.getblockchaininfo()
|
||||
print(result)
|
62
contrib/codegen/test.py
Normal file
62
contrib/codegen/test.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import json
|
||||
import unittest
|
||||
from subprocess import run, PIPE
|
||||
from generate_client import load_schema, generate_client, sanitize_argument_names
|
||||
|
||||
class TestGenerateClient(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Create a temporary directory for output files
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
# Create a sample schema dictionary
|
||||
self.sample_schema = {
|
||||
"rpcs": {
|
||||
"getblockchaininfo": {
|
||||
"description": "Returns blockchain info.",
|
||||
"argument_names": []
|
||||
},
|
||||
"addconnection": {
|
||||
"description": "Opens an outbound connection.",
|
||||
"argument_names": ["address", "connection_type", "v2transport"]
|
||||
}
|
||||
}
|
||||
}
|
||||
# Sanitize the sample schema if necessary
|
||||
self.sample_schema["rpcs"] = sanitize_argument_names(self.sample_schema["rpcs"])
|
||||
|
||||
def tearDown(self):
|
||||
# Clean up temporary directory
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_generate_cpp_client(self):
|
||||
# Test generating C++ code
|
||||
output_dir = self.temp_dir.name
|
||||
generate_client(self.sample_schema, "cpp", output_dir)
|
||||
generated_file = os.path.join(output_dir, "bitcoin_rpc_client.cpp")
|
||||
self.assertTrue(os.path.exists(generated_file))
|
||||
with open(generated_file, "r") as f:
|
||||
content = f.read()
|
||||
# Check that key parts appear in the generated file
|
||||
self.assertIn("class BitcoinRPCClient", content)
|
||||
self.assertIn("std::string getblockchaininfo()", content)
|
||||
self.assertIn("std::string addconnection(", content)
|
||||
|
||||
def test_generate_python_client(self):
|
||||
# Test generating Python code
|
||||
output_dir = self.temp_dir.name
|
||||
generate_client(self.sample_schema, "python", output_dir)
|
||||
generated_file = os.path.join(output_dir, "bitcoin_rpc_client.py")
|
||||
self.assertTrue(os.path.exists(generated_file))
|
||||
with open(generated_file, "r") as f:
|
||||
content = f.read()
|
||||
# Check that key parts appear in the generated file
|
||||
self.assertIn("class BitcoinRPCClient", content)
|
||||
self.assertIn("def getblockchaininfo(self):", content)
|
||||
self.assertIn("def addconnection(self, address, connection_type, v2transport):", content)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -297,6 +297,7 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
|
|||
rpc/node.cpp
|
||||
rpc/output_script.cpp
|
||||
rpc/rawtransaction.cpp
|
||||
rpc/schema.cpp
|
||||
rpc/server.cpp
|
||||
rpc/server_util.cpp
|
||||
rpc/signmessage.cpp
|
||||
|
|
220
src/rpc/schema.cpp
Normal file
220
src/rpc/schema.cpp
Normal file
|
@ -0,0 +1,220 @@
|
|||
#include <rpc/schema.h>
|
||||
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/util.h>
|
||||
#include <univalue.h>
|
||||
#include <util/string.h>
|
||||
|
||||
using util::SplitString;
|
||||
|
||||
// Notes
|
||||
// =====
|
||||
//
|
||||
// This file implements the `schema` RPC. See `Schema::Commands` for the entry
|
||||
// point. This RPC is intended to facilite writing code generators which can
|
||||
// generate Bitcoin Core RPC clients in other languages. It is as
|
||||
// self-contained as possible in this file, to facilitate back-porting to older
|
||||
// versions and rebasing onto newer versions.
|
||||
//
|
||||
// We should probably use something like Open RPC, but the Bitcoin Core RPC API
|
||||
// is weird enough that this may be difficult.
|
||||
//
|
||||
// The returned JSON includes all avaialable information about the RPC, whether
|
||||
// useful to external callers or not. There is certainly more detail than
|
||||
// necessary, and some of it should probably be elided.
|
||||
//
|
||||
// The top level type is a map of strings to `vector<CRPCCommand>`. This is
|
||||
// because commands can have aliases, at least according to the types. However,
|
||||
// I haven't actually seen one, so we just assert that there are no aliases so
|
||||
// we don't have to worry about it.
|
||||
|
||||
class Schema {
|
||||
public:
|
||||
static UniValue Commands(const std::map<std::string, std::vector<const CRPCCommand*>>& commands) {
|
||||
UniValue value{UniValue::VOBJ};
|
||||
|
||||
UniValue rpcs{UniValue::VOBJ};
|
||||
|
||||
for (const auto& entry: commands) {
|
||||
assert(entry.second.size() == 1);
|
||||
|
||||
const auto& command = entry.second[0];
|
||||
|
||||
RPCHelpMan rpc = ((RpcMethodFnType)command->unique_id)();
|
||||
|
||||
rpcs.pushKV(entry.first, Schema::Command(command->category, rpc, command->argNames));
|
||||
}
|
||||
|
||||
value.pushKV("rpcs", rpcs);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
static UniValue Argument(const RPCArg& argument) {
|
||||
UniValue value{UniValue::VOBJ};
|
||||
|
||||
UniValue names{UniValue::VARR};
|
||||
for (auto const& name: SplitString(argument.m_names, '|')) {
|
||||
names.push_back(name);
|
||||
}
|
||||
value.pushKV("names", names);
|
||||
|
||||
value.pushKV("description", argument.m_description);
|
||||
|
||||
value.pushKV("oneline_description", argument.m_opts.oneline_description);
|
||||
|
||||
value.pushKV("also_positional", argument.m_opts.also_positional);
|
||||
|
||||
UniValue type_str{UniValue::VARR};
|
||||
for (auto const& str: argument.m_opts.type_str) {
|
||||
type_str.push_back(str);
|
||||
}
|
||||
value.pushKV("type_str", type_str);
|
||||
|
||||
bool required = std::holds_alternative<RPCArg::Optional>(argument.m_fallback)
|
||||
&& std::get<RPCArg::Optional>(argument.m_fallback) == RPCArg::Optional::NO;
|
||||
|
||||
value.pushKV("required", required);
|
||||
|
||||
if (std::holds_alternative<UniValue>(argument.m_fallback)) {
|
||||
value.pushKV("default", std::get<UniValue>(argument.m_fallback));
|
||||
}
|
||||
|
||||
if (std::holds_alternative<std::string>(argument.m_fallback)) {
|
||||
value.pushKV("default_hint", std::get<std::string>(argument.m_fallback));
|
||||
}
|
||||
|
||||
value.pushKV("hidden", argument.m_opts.hidden);
|
||||
|
||||
value.pushKV("type", Schema::ArgumentType(argument.m_type));
|
||||
|
||||
UniValue inner{UniValue::VARR};
|
||||
for (auto const& argument: argument.m_inner) {
|
||||
inner.push_back(Schema::Argument(argument));
|
||||
}
|
||||
if (!inner.empty()) {
|
||||
value.pushKV("inner", inner);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string ArgumentType(const RPCArg::Type& type) {
|
||||
switch (type) {
|
||||
case RPCArg::Type::AMOUNT:
|
||||
return "amount";
|
||||
case RPCArg::Type::ARR:
|
||||
return "array";
|
||||
case RPCArg::Type::BOOL:
|
||||
return "boolean";
|
||||
case RPCArg::Type::NUM:
|
||||
return "number";
|
||||
case RPCArg::Type::OBJ:
|
||||
return "object";
|
||||
case RPCArg::Type::OBJ_NAMED_PARAMS:
|
||||
return "object";
|
||||
case RPCArg::Type::OBJ_USER_KEYS:
|
||||
return "object";
|
||||
case RPCArg::Type::RANGE:
|
||||
return "range";
|
||||
case RPCArg::Type::STR:
|
||||
return "string";
|
||||
case RPCArg::Type::STR_HEX:
|
||||
return "hex";
|
||||
default:
|
||||
NONFATAL_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static UniValue Command(
|
||||
const std::string& category,
|
||||
const RPCHelpMan& command,
|
||||
const std::vector<std::pair<std::string, bool>>& argNames
|
||||
) {
|
||||
UniValue value{UniValue::VOBJ};
|
||||
|
||||
value.pushKV("category", category);
|
||||
value.pushKV("description", command.m_description);
|
||||
value.pushKV("examples", command.m_examples.m_examples);
|
||||
value.pushKV("name", command.m_name);
|
||||
|
||||
UniValue argument_names{UniValue::VARR};
|
||||
for (const auto& pair : argNames) {
|
||||
UniValue argument_name{UniValue::VARR};
|
||||
argument_names.push_back(pair.first);
|
||||
}
|
||||
value.pushKV("argument_names", argument_names);
|
||||
|
||||
UniValue arguments{UniValue::VARR};
|
||||
for (const auto& argument : command.m_args) {
|
||||
arguments.push_back(Schema::Argument(argument));
|
||||
}
|
||||
value.pushKV("arguments", arguments);
|
||||
|
||||
UniValue results{UniValue::VARR};
|
||||
for (const auto& result : command.m_results.m_results) {
|
||||
results.push_back(Schema::Result(result));
|
||||
}
|
||||
value.pushKV("results", results);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static UniValue Result(const RPCResult& result) {
|
||||
UniValue value{UniValue::VOBJ};
|
||||
value.pushKV("type", Schema::ResultType(result.m_type));
|
||||
value.pushKV("optional", result.m_optional);
|
||||
value.pushKV("description", result.m_description);
|
||||
value.pushKV("skip_type_check", result.m_skip_type_check);
|
||||
value.pushKV("key_name", result.m_key_name);
|
||||
value.pushKV("condition", result.m_cond);
|
||||
|
||||
UniValue inner{UniValue::VARR};
|
||||
for (auto const& result: result.m_inner) {
|
||||
inner.push_back(Schema::Result(result));
|
||||
}
|
||||
if (!inner.empty()) {
|
||||
value.pushKV("inner", inner);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string ResultType(const RPCResult::Type& type) {
|
||||
switch (type) {
|
||||
case RPCResult::Type::OBJ:
|
||||
return "object";
|
||||
case RPCResult::Type::ARR:
|
||||
return "array";
|
||||
case RPCResult::Type::STR:
|
||||
return "string";
|
||||
case RPCResult::Type::NUM:
|
||||
return "number";
|
||||
case RPCResult::Type::BOOL:
|
||||
return "boolean";
|
||||
case RPCResult::Type::NONE:
|
||||
return "none";
|
||||
case RPCResult::Type::ANY:
|
||||
return "any";
|
||||
case RPCResult::Type::STR_AMOUNT:
|
||||
return "amount";
|
||||
case RPCResult::Type::STR_HEX:
|
||||
return "hex";
|
||||
case RPCResult::Type::OBJ_DYN:
|
||||
return "object";
|
||||
case RPCResult::Type::ARR_FIXED:
|
||||
return "object";
|
||||
case RPCResult::Type::NUM_TIME:
|
||||
return "timestamp";
|
||||
case RPCResult::Type::ELISION:
|
||||
return "elision";
|
||||
default:
|
||||
NONFATAL_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UniValue CommandSchemas(const std::map<std::string, std::vector<const CRPCCommand*>>& commands) {
|
||||
return Schema::Commands(commands);
|
||||
}
|
15
src/rpc/schema.h
Normal file
15
src/rpc/schema.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef BITCOIN_RPC_SCHEMA_H
|
||||
#define BITCOIN_RPC_SCHEMA_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class CRPCCommand;
|
||||
class UniValue;
|
||||
|
||||
class Schema;
|
||||
|
||||
UniValue CommandSchemas(const std::map<std::string, std::vector<const CRPCCommand*>>& commands);
|
||||
|
||||
#endif // BITCOIN_RPC_SCHEMA_H
|
|
@ -12,6 +12,7 @@
|
|||
#include <logging.h>
|
||||
#include <node/context.h>
|
||||
#include <node/kernel_notifications.h>
|
||||
#include <rpc/schema.h>
|
||||
#include <rpc/server_util.h>
|
||||
#include <rpc/util.h>
|
||||
#include <sync.h>
|
||||
|
@ -124,6 +125,12 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest&
|
|||
return strRet;
|
||||
}
|
||||
|
||||
UniValue CRPCTable::schema() const
|
||||
{
|
||||
return CommandSchemas(this->mapCommands);
|
||||
}
|
||||
|
||||
|
||||
static RPCHelpMan help()
|
||||
{
|
||||
return RPCHelpMan{"help",
|
||||
|
@ -152,6 +159,22 @@ static RPCHelpMan help()
|
|||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan schema()
|
||||
{
|
||||
return RPCHelpMan{"schema",
|
||||
"\nReturn RPC command JSON Schema descriptions.\n",
|
||||
{},
|
||||
{
|
||||
RPCResult{RPCResult::Type::OBJ, "", "FOO"},
|
||||
},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
|
||||
{
|
||||
return tableRPC.schema();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan stop()
|
||||
{
|
||||
static const std::string RESULT{CLIENT_NAME " stopping"};
|
||||
|
@ -246,6 +269,7 @@ static const CRPCCommand vRPCCommands[]{
|
|||
/* Overall control/query calls */
|
||||
{"control", &getrpcinfo},
|
||||
{"control", &help},
|
||||
{"control", &schema},
|
||||
{"control", &stop},
|
||||
{"control", &uptime},
|
||||
};
|
||||
|
|
|
@ -164,6 +164,8 @@ public:
|
|||
*/
|
||||
void appendCommand(const std::string& name, const CRPCCommand* pcmd);
|
||||
bool removeCommand(const std::string& name, const CRPCCommand* pcmd);
|
||||
|
||||
UniValue schema() const;
|
||||
};
|
||||
|
||||
bool IsDeprecatedRPCEnabled(const std::string& method);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <pubkey.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <rpc/request.h>
|
||||
#include <rpc/schema.h>
|
||||
#include <script/script.h>
|
||||
#include <script/sign.h>
|
||||
#include <uint256.h>
|
||||
|
@ -503,6 +504,7 @@ private:
|
|||
R ArgValue(size_t i) const;
|
||||
//! Return positional index of a parameter using its name as key.
|
||||
size_t GetParamIndex(std::string_view key) const;
|
||||
friend Schema;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue