diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am
index fc2da4c3..eb36e721 100644
--- a/builds/gnu/Makefile.am
+++ b/builds/gnu/Makefile.am
@@ -57,14 +57,11 @@ src_libbitcoin_server_la_SOURCES = \
${srcdir}/../../src/error/error_t.cpp \
${srcdir}/../../src/parsers/admin_query.cpp \
${srcdir}/../../src/parsers/admin_target.cpp \
- ${srcdir}/../../src/parsers/bitcoind_block_stats.cpp \
- ${srcdir}/../../src/parsers/bitcoind_descriptor.cpp \
- ${srcdir}/../../src/parsers/bitcoind_merkle.cpp \
${srcdir}/../../src/parsers/bitcoind_query.cpp \
+ ${srcdir}/../../src/parsers/bitcoind_scan.cpp \
${srcdir}/../../src/parsers/bitcoind_script.cpp \
${srcdir}/../../src/parsers/bitcoind_target.cpp \
${srcdir}/../../src/parsers/btcd_filter.cpp \
- ${srcdir}/../../src/parsers/electrum_version.cpp \
${srcdir}/../../src/parsers/native_query.cpp \
${srcdir}/../../src/parsers/native_target.cpp \
${srcdir}/../../src/protocols/protocol_html.cpp \
@@ -105,7 +102,15 @@ src_libbitcoin_server_la_SOURCES = \
${srcdir}/../../src/protocols/native/protocol_native_input.cpp \
${srcdir}/../../src/protocols/native/protocol_native_output.cpp \
${srcdir}/../../src/protocols/native/protocol_native_tx.cpp \
- ${srcdir}/../../src/protocols/stratum_v1/protocol_stratum_v1.cpp
+ ${srcdir}/../../src/protocols/stratum_v1/protocol_stratum_v1.cpp \
+ ${srcdir}/../../src/serializers/bitcoind_block_stats.cpp \
+ ${srcdir}/../../src/serializers/bitcoind_coin.cpp \
+ ${srcdir}/../../src/serializers/bitcoind_deployments.cpp \
+ ${srcdir}/../../src/serializers/bitcoind_json.cpp \
+ ${srcdir}/../../src/serializers/bitcoind_psbt.cpp \
+ ${srcdir}/../../src/utilities/bitcoind_descriptor.cpp \
+ ${srcdir}/../../src/utilities/bitcoind_merkle.cpp \
+ ${srcdir}/../../src/utilities/electrum_version.cpp
include_bitcoindir = \
${includedir}/bitcoin
@@ -180,14 +185,11 @@ include_bitcoin_server_parsersdir = \
include_bitcoin_server_parsers_HEADERS = \
${srcdir}/../../include/bitcoin/server/parsers/admin_query.hpp \
${srcdir}/../../include/bitcoin/server/parsers/admin_target.hpp \
- ${srcdir}/../../include/bitcoin/server/parsers/bitcoind_block_stats.hpp \
- ${srcdir}/../../include/bitcoin/server/parsers/bitcoind_descriptor.hpp \
- ${srcdir}/../../include/bitcoin/server/parsers/bitcoind_merkle.hpp \
${srcdir}/../../include/bitcoin/server/parsers/bitcoind_query.hpp \
+ ${srcdir}/../../include/bitcoin/server/parsers/bitcoind_scan.hpp \
${srcdir}/../../include/bitcoin/server/parsers/bitcoind_script.hpp \
${srcdir}/../../include/bitcoin/server/parsers/bitcoind_target.hpp \
${srcdir}/../../include/bitcoin/server/parsers/btcd_filter.hpp \
- ${srcdir}/../../include/bitcoin/server/parsers/electrum_version.hpp \
${srcdir}/../../include/bitcoin/server/parsers/native_query.hpp \
${srcdir}/../../include/bitcoin/server/parsers/native_target.hpp \
${srcdir}/../../include/bitcoin/server/parsers/parsers.hpp
@@ -221,6 +223,18 @@ include_bitcoin_server_protocols_HEADERS = \
${srcdir}/../../include/bitcoin/server/protocols/protocol_stratum_v2.hpp \
${srcdir}/../../include/bitcoin/server/protocols/protocols.hpp
+include_bitcoin_server_serializersdir = \
+ ${includedir}/bitcoin/server/serializers
+
+include_bitcoin_server_serializers_HEADERS = \
+ ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_block_stats.hpp \
+ ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_coin.hpp \
+ ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_data.hpp \
+ ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_deployments.hpp \
+ ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_json.hpp \
+ ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_psbt.hpp \
+ ${srcdir}/../../include/bitcoin/server/serializers/serializers.hpp
+
include_bitcoin_server_sessionsdir = \
${includedir}/bitcoin/server/sessions
@@ -230,6 +244,15 @@ include_bitcoin_server_sessions_HEADERS = \
${srcdir}/../../include/bitcoin/server/sessions/session_server.hpp \
${srcdir}/../../include/bitcoin/server/sessions/sessions.hpp
+include_bitcoin_server_utilitiesdir = \
+ ${includedir}/bitcoin/server/utilities
+
+include_bitcoin_server_utilities_HEADERS = \
+ ${srcdir}/../../include/bitcoin/server/utilities/bitcoind_descriptor.hpp \
+ ${srcdir}/../../include/bitcoin/server/utilities/bitcoind_merkle.hpp \
+ ${srcdir}/../../include/bitcoin/server/utilities/electrum_version.hpp \
+ ${srcdir}/../../include/bitcoin/server/utilities/utilities.hpp
+
# Tests.
#==============================================================================
# Target test 'test/libbitcoin-server-test'
@@ -265,17 +288,12 @@ test_libbitcoin_server_test_SOURCES = \
${srcdir}/../../test/mocks/blocks.cpp \
${srcdir}/../../test/parsers/admin_query.cpp \
${srcdir}/../../test/parsers/admin_target.cpp \
- ${srcdir}/../../test/parsers/bitcoind_block_stats.cpp \
- ${srcdir}/../../test/parsers/bitcoind_descriptor.cpp \
- ${srcdir}/../../test/parsers/bitcoind_merkle.cpp \
${srcdir}/../../test/parsers/bitcoind_query.cpp \
${srcdir}/../../test/parsers/bitcoind_target.cpp \
- ${srcdir}/../../test/parsers/electrum_version.cpp \
${srcdir}/../../test/parsers/native_query.cpp \
${srcdir}/../../test/parsers/native_target.cpp \
${srcdir}/../../test/protocols/admin/admin_diagnostics.cpp \
${srcdir}/../../test/protocols/admin/admin_setup_fixture.cpp \
- ${srcdir}/../../test/protocols/bitcoind/bitcoind_json.cpp \
${srcdir}/../../test/protocols/bitcoind/bitcoind_rest.cpp \
${srcdir}/../../test/protocols/bitcoind/bitcoind_rpc.cpp \
${srcdir}/../../test/protocols/bitcoind/bitcoind_setup_fixture.cpp \
@@ -300,7 +318,12 @@ test_libbitcoin_server_test_SOURCES = \
${srcdir}/../../test/protocols/native/native_input.cpp \
${srcdir}/../../test/protocols/native/native_output.cpp \
${srcdir}/../../test/protocols/native/native_setup_fixture.cpp \
- ${srcdir}/../../test/protocols/native/native_tx.cpp
+ ${srcdir}/../../test/protocols/native/native_tx.cpp \
+ ${srcdir}/../../test/serializers/bitcoind_block_stats.cpp \
+ ${srcdir}/../../test/serializers/bitcoind_json.cpp \
+ ${srcdir}/../../test/utilities/bitcoind_descriptor.cpp \
+ ${srcdir}/../../test/utilities/bitcoind_merkle.cpp \
+ ${srcdir}/../../test/utilities/electrum_version.cpp
TESTS = test_runner.sh
diff --git a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj
index 882f1466..9a80d562 100644
--- a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj
@@ -126,19 +126,12 @@
-
-
-
-
- $(IntDir)test_parsers_electrum_version.obj
-
-
@@ -166,8 +159,15 @@
+
+
+
+
+
+ $(IntDir)test_utilities_electrum_version.obj
+
diff --git a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters
index 2a4c0059..1abc63b1 100644
--- a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters
@@ -37,6 +37,12 @@
{66A0E586-2E3A-448F-0000-000000000009}
+
+ {66A0E586-2E3A-448F-0000-00000000000A}
+
+
+ {66A0E586-2E3A-448F-0000-00000000000B}
+
@@ -63,24 +69,12 @@
src\parsers
-
- src\parsers
-
-
- src\parsers
-
-
- src\parsers
-
src\parsers
src\parsers
-
- src\parsers
-
src\parsers
@@ -93,9 +87,6 @@
src\protocols\admin
-
- src\protocols\bitcoind
-
src\protocols\bitcoind
@@ -171,12 +162,27 @@
src\protocols\native
+
+ src\serializers
+
+
+ src\serializers
+
src
src
+
+ src\utilities
+
+
+ src\utilities
+
+
+ src\utilities
+
diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj
index b650cda3..b5b3505f 100644
--- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj
@@ -129,14 +129,11 @@
-
-
-
+
-
@@ -178,8 +175,16 @@
+
+
+
+
+
+
+
+
@@ -218,14 +223,11 @@
-
-
-
+
-
@@ -253,12 +255,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters
index 5f052b7b..469fe578 100644
--- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters
@@ -37,42 +37,54 @@
{73CE0AC2-ECB2-4E8D-0000-000000000009}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000A}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000B}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000C}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000D}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000E}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000F}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000001}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000002}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000003}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000004}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000005}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000006}
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000007}
+
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000008}
+
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000009}
+
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000010}
+
@@ -99,16 +111,10 @@
src\parsers
-
- src\parsers
-
-
+
src\parsers
-
- src\parsers
-
-
+
src\parsers
@@ -120,9 +126,6 @@
src\parsers
-
- src\parsers
-
src\parsers
@@ -246,12 +249,36 @@
src\protocols\stratum_v1
+
+ src\serializers
+
+
+ src\serializers
+
+
+ src\serializers
+
+
+ src\serializers
+
+
+ src\serializers
+
src
src
+
+ src\utilities
+
+
+ src\utilities
+
+
+ src\utilities
+
@@ -362,16 +389,10 @@
include\bitcoin\server\parsers
-
- include\bitcoin\server\parsers
-
-
- include\bitcoin\server\parsers
-
-
+
include\bitcoin\server\parsers
-
+
include\bitcoin\server\parsers
@@ -383,9 +404,6 @@
include\bitcoin\server\parsers
-
- include\bitcoin\server\parsers
-
include\bitcoin\server\parsers
@@ -467,6 +485,27 @@
include\bitcoin\server\protocols
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
include\bitcoin\server
@@ -485,6 +524,18 @@
include\bitcoin\server
+
+ include\bitcoin\server\utilities
+
+
+ include\bitcoin\server\utilities
+
+
+ include\bitcoin\server\utilities
+
+
+ include\bitcoin\server\utilities
+
include\bitcoin\server
diff --git a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj
index 27b1eea8..e2d927de 100644
--- a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj
@@ -126,19 +126,12 @@
-
-
-
-
- $(IntDir)test_parsers_electrum_version.obj
-
-
@@ -166,8 +159,15 @@
+
+
+
+
+
+ $(IntDir)test_utilities_electrum_version.obj
+
diff --git a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters
index 2a4c0059..1abc63b1 100644
--- a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters
+++ b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj.filters
@@ -37,6 +37,12 @@
{66A0E586-2E3A-448F-0000-000000000009}
+
+ {66A0E586-2E3A-448F-0000-00000000000A}
+
+
+ {66A0E586-2E3A-448F-0000-00000000000B}
+
@@ -63,24 +69,12 @@
src\parsers
-
- src\parsers
-
-
- src\parsers
-
-
- src\parsers
-
src\parsers
src\parsers
-
- src\parsers
-
src\parsers
@@ -93,9 +87,6 @@
src\protocols\admin
-
- src\protocols\bitcoind
-
src\protocols\bitcoind
@@ -171,12 +162,27 @@
src\protocols\native
+
+ src\serializers
+
+
+ src\serializers
+
src
src
+
+ src\utilities
+
+
+ src\utilities
+
+
+ src\utilities
+
diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj
index 34d36d14..3c079136 100644
--- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj
@@ -129,14 +129,11 @@
-
-
-
+
-
@@ -178,8 +175,16 @@
+
+
+
+
+
+
+
+
@@ -218,14 +223,11 @@
-
-
-
+
-
@@ -253,12 +255,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters
index 5f052b7b..469fe578 100644
--- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters
+++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters
@@ -37,42 +37,54 @@
{73CE0AC2-ECB2-4E8D-0000-000000000009}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000A}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000B}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000C}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000D}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000E}
-
+
{73CE0AC2-ECB2-4E8D-0000-00000000000F}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000001}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000002}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000003}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000004}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000005}
-
+
{73CE0AC2-ECB2-4E8D-0000-000000000006}
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000007}
+
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000008}
+
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000009}
+
+
+ {73CE0AC2-ECB2-4E8D-0000-000000000010}
+
@@ -99,16 +111,10 @@
src\parsers
-
- src\parsers
-
-
+
src\parsers
-
- src\parsers
-
-
+
src\parsers
@@ -120,9 +126,6 @@
src\parsers
-
- src\parsers
-
src\parsers
@@ -246,12 +249,36 @@
src\protocols\stratum_v1
+
+ src\serializers
+
+
+ src\serializers
+
+
+ src\serializers
+
+
+ src\serializers
+
+
+ src\serializers
+
src
src
+
+ src\utilities
+
+
+ src\utilities
+
+
+ src\utilities
+
@@ -362,16 +389,10 @@
include\bitcoin\server\parsers
-
- include\bitcoin\server\parsers
-
-
- include\bitcoin\server\parsers
-
-
+
include\bitcoin\server\parsers
-
+
include\bitcoin\server\parsers
@@ -383,9 +404,6 @@
include\bitcoin\server\parsers
-
- include\bitcoin\server\parsers
-
include\bitcoin\server\parsers
@@ -467,6 +485,27 @@
include\bitcoin\server\protocols
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
+
+ include\bitcoin\server\serializers
+
include\bitcoin\server
@@ -485,6 +524,18 @@
include\bitcoin\server
+
+ include\bitcoin\server\utilities
+
+
+ include\bitcoin\server\utilities
+
+
+ include\bitcoin\server\utilities
+
+
+ include\bitcoin\server\utilities
+
include\bitcoin\server
diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp
index 80f12783..25d3f770 100644
--- a/include/bitcoin/server.hpp
+++ b/include/bitcoin/server.hpp
@@ -52,14 +52,11 @@
#include
#include
#include
-#include
-#include
-#include
#include
+#include
#include
#include
#include
-#include
#include
#include
#include
@@ -87,9 +84,20 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include
#include
#include
#include
+#include
+#include
+#include
+#include
#endif
diff --git a/include/bitcoin/server/channels/channel_electrum.hpp b/include/bitcoin/server/channels/channel_electrum.hpp
index 43e60ebe..98f5c542 100644
--- a/include/bitcoin/server/channels/channel_electrum.hpp
+++ b/include/bitcoin/server/channels/channel_electrum.hpp
@@ -25,6 +25,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/include/bitcoin/server/interfaces/bitcoind_rest.hpp b/include/bitcoin/server/interfaces/bitcoind_rest.hpp
index b0e13aae..8cf8570e 100644
--- a/include/bitcoin/server/interfaces/bitcoind_rest.hpp
+++ b/include/bitcoin/server/interfaces/bitcoind_rest.hpp
@@ -41,11 +41,10 @@ struct bitcoind_rest_methods
// client filters
method<"block_filter", uint8_t, system::hash_cptr, uint8_t>{ "media", "hash", "type" },
- method<"block_filter_headers", uint8_t, system::hash_cptr, uint8_t>{ "media", "hash", "type" },
+ method<"block_filter_headers", uint8_t, system::hash_cptr, uint8_t, uint32_t>{ "media", "hash", "type", "count" },
- // unspent outputs
- method<"get_utxos", uint8_t, system::hash_cptr, uint8_t>{ "media", "hash", "type" },
- method<"get_utxos_confirmed", uint8_t, system::hash_cptr, uint8_t>{ "media", "hash", "type" },
+ // unspent outputs (bip64)
+ method<"get_utxos", uint8_t, network::rpc::array_t>{ "media", "outpoints" },
// mempool (json only)
method<"mempool", optional, optional>{ "verbose", "sequence" },
@@ -56,7 +55,10 @@ struct bitcoind_rest_methods
method<"fork_information", nullable>{ "hash" },
// transactions
- method<"tx", uint8_t, system::hash_cptr>{ "media", "hash" }
+ method<"tx", uint8_t, system::hash_cptr>{ "media", "hash" },
+
+ // deployments (json only)
+ method<"deployment_info", nullable>{ "hash" }
};
template
@@ -75,12 +77,12 @@ struct bitcoind_rest_methods
using block_filter = at<6>;
using block_filter_headers = at<7>;
using get_utxos = at<8>;
- using get_utxos_confirmed = at<9>;
- using mempool = at<10>;
- using chain_information = at<11>;
- using mempool_information = at<12>;
- using fork_information = at<13>;
- using tx = at<14>;
+ using mempool = at<9>;
+ using chain_information = at<10>;
+ using mempool_information = at<11>;
+ using fork_information = at<12>;
+ using tx = at<13>;
+ using deployment_info = at<14>;
};
} // namespace interface
diff --git a/include/bitcoin/server/parsers/bitcoind_scan.hpp b/include/bitcoin/server/parsers/bitcoind_scan.hpp
new file mode 100644
index 00000000..0595ec12
--- /dev/null
+++ b/include/bitcoin/server/parsers/bitcoind_scan.hpp
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_PARSERS_BITCOIND_SCAN_HPP
+#define LIBBITCOIN_SERVER_PARSERS_BITCOIND_SCAN_HPP
+
+#include
+
+namespace libbitcoin {
+namespace server {
+
+/// Expand a scan object, a descriptor string or { "desc", "range" } object,
+/// to its derived output scripts (false if malformed or underivable).
+BCS_API bool expand_scan_object(system::chain::scripts& out,
+ const network::rpc::value_t& item) NOEXCEPT;
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/server/parsers/parsers.hpp b/include/bitcoin/server/parsers/parsers.hpp
index 8c45a8b7..efebcd14 100644
--- a/include/bitcoin/server/parsers/parsers.hpp
+++ b/include/bitcoin/server/parsers/parsers.hpp
@@ -21,14 +21,11 @@
#include
#include
-#include
-#include
-#include
#include
+#include
#include
#include
#include
-#include
#include
#include
diff --git a/include/bitcoin/server/protocols/protocol_bitcoind.hpp b/include/bitcoin/server/protocols/protocol_bitcoind.hpp
index 1f5b88cf..f5dc2ee7 100644
--- a/include/bitcoin/server/protocols/protocol_bitcoind.hpp
+++ b/include/bitcoin/server/protocols/protocol_bitcoind.hpp
@@ -71,47 +71,6 @@ class BCS_API protocol_bitcoind
/// The method names reported by help (channel-registered on start).
std::string help_names() const NOEXCEPT;
- /// Serialize an object (chain::header, chain::transaction, ...) to a
- /// base16 string.
- template
- static std::string to_text(const Object& object, size_t size,
- Args&&... args) NOEXCEPT
- {
- std::string out(two * size, '\0');
- system::stream::out::fast sink{ out };
- system::write::base16::fast writer{ sink };
- object.to_data(writer, std::forward(args)...);
- BC_ASSERT(writer);
- return out;
- }
-
- /// Model composition.
- static double progress(size_t blocks, size_t headers) NOEXCEPT;
- static uint32_t median_time(const node::query& query,
- const system::settings& settings,
- const database::header_link& link) NOEXCEPT;
- static network::rpc::object_t chain_states_entry(const node::query& query,
- const database::header_link& link, double progress,
- bool validated) NOEXCEPT;
- static void inject_block_context(boost::json::object& out,
- const node::query& query, const system::settings& settings,
- const database::header_link& link,
- const system::chain::header& header) NOEXCEPT;
- static void inject_tx_context(boost::json::object& out,
- const node::query& query, const database::tx_link& link) NOEXCEPT;
- static void inject_tx_prevouts(boost::json::object& out,
- const node::query& query,
- const system::chain::transaction& tx) NOEXCEPT;
- static boost::json::object header_to_bitcoind(
- const system::chain::header& header) NOEXCEPT;
- static std::string chain_name(const node::query& query) NOEXCEPT;
-
- /// The getblockchaininfo result, bitcoind field set (btcd augments it).
- /// False if the store is inconsistent, the caller sends the error.
- static bool chain_info(network::rpc::object_t& out,
- const node::query& query, const system::settings& settings,
- bool pruned, bool current) NOEXCEPT;
-
/// The createmultisig result, empty if a key is invalid or the p2sh
/// embedded script exceeds one push element.
network::rpc::object_t create_multisig(uint8_t required,
diff --git a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp
index 692aace0..bf4dc3cb 100644
--- a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp
+++ b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp
@@ -76,9 +76,15 @@ class BCS_API protocol_bitcoind_rest
uint8_t media, const system::hash_cptr& hash, uint8_t type) NOEXCEPT;
bool handle_get_block_filter_headers(const code& ec,
rest_interface::block_filter_headers, uint8_t media,
- const system::hash_cptr& hash, uint8_t type) NOEXCEPT;
+ const system::hash_cptr& hash, uint8_t type,
+ uint32_t count) NOEXCEPT;
bool handle_get_chain_information(const code& ec,
rest_interface::chain_information) NOEXCEPT;
+ bool handle_get_utxos(const code& ec, rest_interface::get_utxos,
+ uint8_t media, const network::rpc::array_t& outpoints) NOEXCEPT;
+ bool handle_get_deployment_info(const code& ec,
+ rest_interface::deployment_info,
+ const std::optional& hash) NOEXCEPT;
/// REST raw-http response senders (not json-rpc enveloped).
void send_data(system::data_chunk&& bytes) NOEXCEPT;
diff --git a/include/bitcoin/server/protocols/protocol_electrum.hpp b/include/bitcoin/server/protocols/protocol_electrum.hpp
index ad6e6f28..b4c590ff 100644
--- a/include/bitcoin/server/protocols/protocol_electrum.hpp
+++ b/include/bitcoin/server/protocols/protocol_electrum.hpp
@@ -25,6 +25,8 @@
#include
#include
#include
+#include
+#include
#include
namespace libbitcoin {
diff --git a/include/bitcoin/server/protocols/protocol_electrum_version.hpp b/include/bitcoin/server/protocols/protocol_electrum_version.hpp
index a3e8ef2b..ead89f36 100644
--- a/include/bitcoin/server/protocols/protocol_electrum_version.hpp
+++ b/include/bitcoin/server/protocols/protocol_electrum_version.hpp
@@ -24,6 +24,8 @@
#include
#include
#include
+#include
+#include
#include
namespace libbitcoin {
diff --git a/include/bitcoin/server/parsers/bitcoind_block_stats.hpp b/include/bitcoin/server/serializers/bitcoind_block_stats.hpp
similarity index 89%
rename from include/bitcoin/server/parsers/bitcoind_block_stats.hpp
rename to include/bitcoin/server/serializers/bitcoind_block_stats.hpp
index 14172c91..63e5eaf1 100644
--- a/include/bitcoin/server/parsers/bitcoind_block_stats.hpp
+++ b/include/bitcoin/server/serializers/bitcoind_block_stats.hpp
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-#ifndef LIBBITCOIN_SERVER_PARSERS_BITCOIND_BLOCK_STATS_HPP
-#define LIBBITCOIN_SERVER_PARSERS_BITCOIND_BLOCK_STATS_HPP
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_BLOCK_STATS_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_BLOCK_STATS_HPP
#include
diff --git a/include/bitcoin/server/serializers/bitcoind_coin.hpp b/include/bitcoin/server/serializers/bitcoind_coin.hpp
new file mode 100644
index 00000000..ba5d3db2
--- /dev/null
+++ b/include/bitcoin/server/serializers/bitcoind_coin.hpp
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_COIN_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_COIN_HPP
+
+#include
+
+namespace libbitcoin {
+namespace server {
+
+/// bitcoind's utxo set coin serialization (outpoint, height code, value,
+/// script), the element of both set commitment forms.
+BCS_API void to_coin_data(system::data_chunk& out,
+ const database::unspent_coin& coin) NOEXCEPT;
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/server/serializers/bitcoind_data.hpp b/include/bitcoin/server/serializers/bitcoind_data.hpp
new file mode 100644
index 00000000..fb065a85
--- /dev/null
+++ b/include/bitcoin/server/serializers/bitcoind_data.hpp
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_DATA_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_DATA_HPP
+
+#include
+#include
+
+namespace libbitcoin {
+namespace server {
+
+/// Serialize an object (chain::header, chain::transaction, ...) to bytes.
+template
+system::data_chunk to_data(const Object& object, size_t size,
+ Args&&... args) NOEXCEPT
+{
+ system::data_chunk out(size);
+ system::stream::out::fast sink{ out };
+ system::write::bytes::fast writer{ sink };
+ object.to_data(writer, std::forward(args)...);
+ BC_ASSERT(writer);
+ return out;
+}
+
+/// Serialize an object (chain::header, chain::transaction, ...) to a
+/// base16 string.
+template
+std::string to_text(const Object& object, size_t size,
+ Args&&... args) NOEXCEPT
+{
+ std::string out(two * size, '\0');
+ system::stream::out::fast sink{ out };
+ system::write::base16::fast writer{ sink };
+ object.to_data(writer, std::forward(args)...);
+ BC_ASSERT(writer);
+ return out;
+}
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/server/serializers/bitcoind_deployments.hpp b/include/bitcoin/server/serializers/bitcoind_deployments.hpp
new file mode 100644
index 00000000..27206a7d
--- /dev/null
+++ b/include/bitcoin/server/serializers/bitcoind_deployments.hpp
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_DEPLOYMENTS_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_DEPLOYMENTS_HPP
+
+#include
+
+namespace libbitcoin {
+namespace server {
+
+/// The getdeploymentinfo result for the block at link/height.
+BCS_API network::rpc::object_t deployment_info(const node::query& query,
+ const system::settings& settings, const database::header_link& link,
+ size_t height) NOEXCEPT;
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/server/serializers/bitcoind_json.hpp b/include/bitcoin/server/serializers/bitcoind_json.hpp
new file mode 100644
index 00000000..3e2e8c4d
--- /dev/null
+++ b/include/bitcoin/server/serializers/bitcoind_json.hpp
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_JSON_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_JSON_HPP
+
+#include
+
+namespace libbitcoin {
+namespace server {
+
+/// Clamped ratio of validated blocks to chain height.
+BCS_API double progress(size_t blocks, size_t headers) NOEXCEPT;
+
+/// bitcoind's mediantime of the block (its window includes the block).
+BCS_API uint32_t median_time(const node::query& query,
+ const system::settings& settings,
+ const database::header_link& link) NOEXCEPT;
+
+/// A getchainstates entry for candidate or confirmed at the link (top).
+BCS_API network::rpc::object_t chain_states_entry(const node::query& query,
+ const database::header_link& link, double progress,
+ bool validated) NOEXCEPT;
+
+/// Block confirmation context (height, confirmations, siblings).
+BCS_API void inject_block_context(boost::json::object& out,
+ const node::query& query, const system::settings& settings,
+ const database::header_link& link,
+ const system::chain::header& header) NOEXCEPT;
+
+/// Transaction confirmation context (blockhash, confirmations, time).
+BCS_API void inject_tx_context(boost::json::object& out,
+ const node::query& query, const database::tx_link& link) NOEXCEPT;
+
+/// Per-input prevout context (requires populate_with_metadata).
+BCS_API void inject_tx_prevouts(boost::json::object& out,
+ const node::query& query,
+ const system::chain::transaction& tx) NOEXCEPT;
+
+/// The bitcoind block header object.
+BCS_API boost::json::object header_to_bitcoind(
+ const system::chain::header& header) NOEXCEPT;
+
+/// The bitcoind chain name, resolved from the genesis block.
+BCS_API std::string chain_name(const node::query& query) NOEXCEPT;
+
+/// The getblockchaininfo result, bitcoind field set (btcd augments it).
+/// False if the store is inconsistent, the caller sends the error.
+BCS_API bool chain_info(network::rpc::object_t& out,
+ const node::query& query, const system::settings& settings,
+ bool pruned, bool current) NOEXCEPT;
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/server/serializers/bitcoind_psbt.hpp b/include/bitcoin/server/serializers/bitcoind_psbt.hpp
new file mode 100644
index 00000000..94f76c19
--- /dev/null
+++ b/include/bitcoin/server/serializers/bitcoind_psbt.hpp
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_PSBT_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_BITCOIND_PSBT_HPP
+
+#include
+
+namespace libbitcoin {
+namespace server {
+
+/// The bip32 key path string ("m/0'/1") of a derivation path.
+BCS_API std::string to_key_path(const std::vector& path) NOEXCEPT;
+
+/// The proprietary (unknown) entries of a psbt map.
+BCS_API network::rpc::object_t to_unknown(
+ const system::wallet::psbt::entry::list& entries) NOEXCEPT;
+
+/// The bitcoind decodepsbt input object.
+BCS_API network::rpc::object_t decode_psbt_input(
+ const system::wallet::psbt::input& in) NOEXCEPT;
+
+/// The bitcoind decodepsbt output object.
+BCS_API network::rpc::object_t decode_psbt_output(
+ const system::wallet::psbt::output& out) NOEXCEPT;
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/server/serializers/serializers.hpp b/include/bitcoin/server/serializers/serializers.hpp
new file mode 100644
index 00000000..36be7141
--- /dev/null
+++ b/include/bitcoin/server/serializers/serializers.hpp
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_SERIALIZERS_SERIALIZERS_HPP
+#define LIBBITCOIN_SERVER_SERIALIZERS_SERIALIZERS_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#endif
diff --git a/include/bitcoin/server/parsers/bitcoind_descriptor.hpp b/include/bitcoin/server/utilities/bitcoind_descriptor.hpp
similarity index 89%
rename from include/bitcoin/server/parsers/bitcoind_descriptor.hpp
rename to include/bitcoin/server/utilities/bitcoind_descriptor.hpp
index 0b819c07..4feb70a5 100644
--- a/include/bitcoin/server/parsers/bitcoind_descriptor.hpp
+++ b/include/bitcoin/server/utilities/bitcoind_descriptor.hpp
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-#ifndef LIBBITCOIN_SERVER_PARSERS_BITCOIND_DESCRIPTOR_HPP
-#define LIBBITCOIN_SERVER_PARSERS_BITCOIND_DESCRIPTOR_HPP
+#ifndef LIBBITCOIN_SERVER_UTILITIES_BITCOIND_DESCRIPTOR_HPP
+#define LIBBITCOIN_SERVER_UTILITIES_BITCOIND_DESCRIPTOR_HPP
#include
diff --git a/include/bitcoin/server/parsers/bitcoind_merkle.hpp b/include/bitcoin/server/utilities/bitcoind_merkle.hpp
similarity index 94%
rename from include/bitcoin/server/parsers/bitcoind_merkle.hpp
rename to include/bitcoin/server/utilities/bitcoind_merkle.hpp
index a5281c2b..ebf201e3 100644
--- a/include/bitcoin/server/parsers/bitcoind_merkle.hpp
+++ b/include/bitcoin/server/utilities/bitcoind_merkle.hpp
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-#ifndef LIBBITCOIN_SERVER_PARSERS_BITCOIND_MERKLE_HPP
-#define LIBBITCOIN_SERVER_PARSERS_BITCOIND_MERKLE_HPP
+#ifndef LIBBITCOIN_SERVER_UTILITIES_BITCOIND_MERKLE_HPP
+#define LIBBITCOIN_SERVER_UTILITIES_BITCOIND_MERKLE_HPP
#include
diff --git a/include/bitcoin/server/parsers/electrum_version.hpp b/include/bitcoin/server/utilities/electrum_version.hpp
similarity index 96%
rename from include/bitcoin/server/parsers/electrum_version.hpp
rename to include/bitcoin/server/utilities/electrum_version.hpp
index b54dd44c..186a6172 100644
--- a/include/bitcoin/server/parsers/electrum_version.hpp
+++ b/include/bitcoin/server/utilities/electrum_version.hpp
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-#ifndef LIBBITCOIN_SERVER_PARSERS_ELECTRUM_VERSION_HPP
-#define LIBBITCOIN_SERVER_PARSERS_ELECTRUM_VERSION_HPP
+#ifndef LIBBITCOIN_SERVER_UTILITIES_ELECTRUM_VERSION_HPP
+#define LIBBITCOIN_SERVER_UTILITIES_ELECTRUM_VERSION_HPP
#include
diff --git a/include/bitcoin/server/utilities/utilities.hpp b/include/bitcoin/server/utilities/utilities.hpp
new file mode 100644
index 00000000..38b774f4
--- /dev/null
+++ b/include/bitcoin/server/utilities/utilities.hpp
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SERVER_UTILITIES_UTILITIES_HPP
+#define LIBBITCOIN_SERVER_UTILITIES_UTILITIES_HPP
+
+#include
+#include
+#include
+
+#endif
diff --git a/src/parsers/bitcoind_query.cpp b/src/parsers/bitcoind_query.cpp
index d4573dc7..b3cd3e02 100644
--- a/src/parsers/bitcoind_query.cpp
+++ b/src/parsers/bitcoind_query.cpp
@@ -56,14 +56,17 @@ bool bitcoind_query(rpc::request_t& out, const std::string& target) NOEXCEPT
auto query = uri.decode_query();
auto& params = std::get(out.params.value());
- // Count is optional (defaulted by the target parser where applicable).
- if (const auto count = query.find("count"); count != query.end())
+ // Decimal parameters, defaulted or required by the target interface.
+ for (const auto& name: { "count", "offset", "size" })
{
- uint32_t value{};
- if (!to_number(value, count->second))
- return false;
+ if (const auto it = query.find(name); it != query.end())
+ {
+ uint32_t value{};
+ if (!to_number(value, it->second))
+ return false;
- params["count"] = value;
+ params[name] = value;
+ }
}
return true;
diff --git a/src/parsers/bitcoind_scan.cpp b/src/parsers/bitcoind_scan.cpp
new file mode 100644
index 00000000..76d549ac
--- /dev/null
+++ b/src/parsers/bitcoind_scan.cpp
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include
+
+#include
+#include
+
+namespace libbitcoin {
+namespace server {
+
+using namespace system;
+using namespace network::rpc;
+
+BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+
+// A scan object is a descriptor string or { "desc", "range" } object.
+bool expand_scan_object(chain::scripts& out, const value_t& item) NOEXCEPT
+{
+ std::string expression{};
+ uint32_t begin{};
+ uint32_t end{};
+
+ // bitcoind's default and maximum ranges for ranged descriptors.
+ constexpr uint32_t default_range = 1'000;
+ constexpr uint32_t maximum_range = 1'000'000;
+
+ if (std::holds_alternative(item.value()))
+ {
+ expression = std::get(item.value());
+ end = default_range;
+ }
+ else if (std::holds_alternative(item.value()))
+ {
+ const auto& fields = std::get(item.value());
+ const auto desc = fields.find("desc");
+ if (desc == fields.end() ||
+ !std::holds_alternative(desc->second.value()))
+ return false;
+
+ expression = std::get(desc->second.value());
+ end = default_range;
+ const auto range = fields.find("range");
+ if (range != fields.end())
+ {
+ const auto& value = range->second.value();
+ if (std::holds_alternative(value))
+ {
+ if (!to_integer(end, std::get(value)))
+ return false;
+ }
+ else if (std::holds_alternative(value))
+ {
+ const auto& pair = std::get(value);
+ if (pair.size() != 2u ||
+ !std::holds_alternative(pair.front().value()) ||
+ !std::holds_alternative(pair.back().value()) ||
+ !to_integer(begin,
+ std::get(pair.front().value())) ||
+ !to_integer(end,
+ std::get(pair.back().value())) ||
+ end < begin)
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ const wallet::descriptor parsed{ expression };
+ if (!parsed || to_bool(shift_right(end, 31u)) ||
+ floored_subtract(end, begin) >= maximum_range)
+ return false;
+
+ if (!parsed.ranged())
+ end = begin;
+
+ for (auto index = begin; index <= end; ++index)
+ {
+ const auto derived = parsed.scripts(index);
+ if (derived.empty())
+ return false;
+
+ out.insert(out.end(), derived.begin(), derived.end());
+ }
+
+ return true;
+}
+
+BC_POP_WARNING()
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/src/parsers/bitcoind_target.cpp b/src/parsers/bitcoind_target.cpp
index db2c2003..bc13e6f1 100644
--- a/src/parsers/bitcoind_target.cpp
+++ b/src/parsers/bitcoind_target.cpp
@@ -130,8 +130,7 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT
return error::success;
}
- // /rest/block/., /rest/block/notxdetails/. and
- // /rest/block/spent/. (the latter is a libbitcoin extension).
+ // /rest/block/. and /rest/block/notxdetails/.
if (target == "block")
{
if (segment == segments.size())
@@ -140,8 +139,6 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT
std::string rest_method = "block";
if (segments[segment] == "notxdetails")
rest_method = "block_txs";
- else if (segments[segment] == "spent")
- rest_method = "block_spent_tx_outputs";
if (rest_method != "block" && ++segment == segments.size())
return error::missing_hash;
@@ -161,6 +158,28 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT
return error::success;
}
+ // /rest/deploymentinfo.json and /rest/deploymentinfo/.json
+ if (target == "deploymentinfo" || target == "deploymentinfo.json")
+ {
+ method = "deployment_info";
+ if (target == "deploymentinfo" && segment != segments.size())
+ {
+ std::string name{};
+ uint8_t media{};
+ if (!split_leaf(name, media, segments[segment++]) ||
+ media != to_value(media_type::application_json))
+ return error::invalid_target;
+
+ const auto hash = to_hash(name);
+ if (!hash)
+ return error::invalid_hash;
+
+ params["hash"] = hash;
+ }
+
+ return error::success;
+ }
+
// /rest/tx/.
if (target == "tx")
{
@@ -231,8 +250,8 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT
return error::success;
}
- // /rest/blockfilter//. and blockfilterheaders likewise.
- if (target == "blockfilter" || target == "blockfilterheaders")
+ // /rest/blockfilter//.
+ if (target == "blockfilter")
{
if (segment == segments.size())
return error::missing_target;
@@ -253,48 +272,135 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT
if (!hash)
return error::invalid_hash;
- method = target == "blockfilter" ? "block_filter" :
- "block_filter_headers";
+ method = "block_filter";
params["media"] = media;
params["hash"] = hash;
params["type"] = 0_u8;
return error::success;
}
- // /rest/blockpart///. (libbitcoin extension)
- if (target == "blockpart")
+ // /rest/blockfilterheaders//.?count= (count
+ // defaults to 5) and the legacy ...///. form.
+ if (target == "blockfilterheaders")
{
+ if (segment == segments.size())
+ return error::missing_target;
+
+ // libbitcoin supports only the "basic" (neutrino) filter type.
+ if (segments[segment++] != "basic")
+ return error::invalid_target;
+
if (segment == segments.size())
return error::missing_hash;
- const auto hash = to_hash(segments[segment++]);
+ uint32_t count{ 5 };
+ if ((segments.size() - segment > one) &&
+ !to_number(count, segments[segment++]))
+ return error::invalid_number;
+
+ std::string name{};
+ uint8_t media{};
+ if (!split_leaf(name, media, segments[segment++]))
+ return error::invalid_target;
+
+ const auto hash = to_hash(name);
if (!hash)
return error::invalid_hash;
+ method = "block_filter_headers";
+ params["media"] = media;
+ params["hash"] = hash;
+ params["type"] = 0_u8;
+ params["count"] = count;
+ return error::success;
+ }
+
+ // /rest/getutxos[/checkmempool]/-/.../
+ if (target == "getutxos")
+ {
if (segment == segments.size())
return error::missing_target;
- uint32_t offset{};
- if (!to_number(offset, segments[segment++]))
- return error::invalid_number;
+ // The mempool is empty, so the checkmempool option is ignored.
+ if (segments[segment] == "checkmempool")
+ ++segment;
if (segment == segments.size())
return error::missing_target;
+ uint8_t media{};
+ array_t outpoints{};
+ const auto last = sub1(segments.size());
+ for (; segment < segments.size(); ++segment)
+ {
+ std::string token{ segments[segment] };
+ if (segment == last &&
+ !split_leaf(token, media, segments[segment]))
+ return error::invalid_target;
+
+ const auto pair = split(token, "-", false, false);
+ if (pair.size() != two)
+ return error::invalid_hash;
+
+ const auto hash = to_hash(pair.front());
+ if (!hash)
+ return error::invalid_hash;
+
+ uint32_t index{};
+ if (!to_number(index, pair.back()))
+ return error::invalid_number;
+
+ object_t outpoint{};
+ outpoint["hash"] = hash;
+ outpoint["index"] = index;
+ outpoints.emplace_back(std::move(outpoint));
+ }
+
+ method = "get_utxos";
+ params["media"] = media;
+ params["outpoints"] = std::move(outpoints);
+ return error::success;
+ }
+
+ // /rest/spenttxouts/.
+ if (target == "spenttxouts")
+ {
+ if (segment == segments.size())
+ return error::missing_hash;
+
std::string name{};
uint8_t media{};
if (!split_leaf(name, media, segments[segment++]))
return error::invalid_target;
- uint32_t size{};
- if (!to_number(size, name))
- return error::invalid_number;
+ const auto hash = to_hash(name);
+ if (!hash)
+ return error::invalid_hash;
+
+ method = "block_spent_tx_outputs";
+ params["media"] = media;
+ params["hash"] = hash;
+ return error::success;
+ }
+
+ // /rest/blockpart/.?offset=&size=
+ if (target == "blockpart")
+ {
+ if (segment == segments.size())
+ return error::missing_hash;
+
+ std::string name{};
+ uint8_t media{};
+ if (!split_leaf(name, media, segments[segment++]))
+ return error::invalid_target;
+
+ const auto hash = to_hash(name);
+ if (!hash)
+ return error::invalid_hash;
method = "block_part";
params["media"] = media;
params["hash"] = hash;
- params["offset"] = offset;
- params["size"] = size;
return error::success;
}
diff --git a/src/protocols/admin/protocol_admin.cpp b/src/protocols/admin/protocol_admin.cpp
index 256ee805..43538305 100644
--- a/src/protocols/admin/protocol_admin.cpp
+++ b/src/protocols/admin/protocol_admin.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind.cpp b/src/protocols/bitcoind/protocol_bitcoind.cpp
index 194f25c3..ae9d9160 100644
--- a/src/protocols/bitcoind/protocol_bitcoind.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp b/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp
index 40d7313c..13f52326 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp
@@ -26,6 +26,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
@@ -61,9 +63,6 @@ enum block_verbosity : size_t
// bitcoind defines only the "basic" (neutrino) block filter type.
constexpr auto basic_filter = "basic";
-static bool expand_scan_object(chain::scripts& out,
- const value_t& item) NOEXCEPT;
-
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
@@ -610,28 +609,6 @@ bool protocol_bitcoind_blockchain::handle_get_tx_out(const code& ec,
return true;
}
-// bitcoind's utxo set coin serialization (outpoint, height code, value,
-// script), the element of both set commitment forms.
-static void to_coin_data(data_chunk& out,
- const database::unspent_coin& coin) NOEXCEPT
-{
- constexpr auto overhead = hash_size + sizeof(uint32_t) + sizeof(uint32_t) +
- sizeof(uint64_t);
-
- out.resize(overhead + variable_size(coin.script.size()) +
- coin.script.size());
- stream::out::fast ostream(out);
- write::bytes::fast sink(ostream);
- sink.write_bytes(coin.txid);
- sink.write_4_bytes_little_endian(coin.index);
- sink.write_4_bytes_little_endian(bit_or(shift_left(
- possible_narrow_cast(coin.height), 1),
- to_int(coin.coinbase)));
- sink.write_8_bytes_little_endian(coin.value);
- sink.write_variable(coin.script.size());
- sink.write_bytes(coin.script);
-}
-
// The response defers to completion of the store scan (see dispatch). This is
// an administrative query, expected to run long, performed off the strand.
bool protocol_bitcoind_blockchain::handle_get_tx_out_set_info(const code& ec,
@@ -1326,26 +1303,6 @@ bool protocol_bitcoind_blockchain::handle_get_chain_tips(const code& ec,
return true;
}
-// A frozen activation (bitcoind's "buried" type, which assumes depth).
-// bitcoind reports it as active from one block below the activation height
-// (the rules are enforced for the block that follows).
-static void push_frozen(object_t& out, const std::string& name, bool enabled,
- size_t activation, size_t height) NOEXCEPT
-{
- if (!enabled)
- return;
-
- out.emplace(name, object_t
- {
- { "type", std::string{ "buried" } },
- { "active", add1(height) >= activation },
- { "height", activation }
- });
-}
-
-// Deployments are configured, so this reads settings and needs no chain state.
-// Taproot is excluded, as bitcoind froze it and no longer reports it here
-// (btcd reports it under getblockchaininfo's bip9_softforks, which lnd reads).
bool protocol_bitcoind_blockchain::handle_get_deployment_info(const code& ec,
rpc_interface::get_deployment_info, const std::string& blockhash) NOEXCEPT
{
@@ -1381,26 +1338,7 @@ bool protocol_bitcoind_blockchain::handle_get_deployment_info(const code& ec,
return true;
}
- const auto& settings = system_settings();
- const auto& forks = settings.forks;
- object_t deployments{};
- push_frozen(deployments, "bip34", forks.bip34,
- settings.bip90_bip34_height, height);
- push_frozen(deployments, "bip66", forks.bip66,
- settings.bip90_bip66_height, height);
- push_frozen(deployments, "bip65", forks.bip65,
- settings.bip90_bip65_height, height);
- push_frozen(deployments, "csv", forks.bip68 && forks.bip112 &&
- forks.bip113, settings.bip9_bit0_active_checkpoint.height(), height);
- push_frozen(deployments, "segwit", forks.bip141 && forks.bip143 &&
- forks.bip147, settings.bip9_bit1_active_checkpoint.height(), height);
-
- send_result(object_t
- {
- { "hash", encode_hash(query.get_header_key(link)) },
- { "height", height },
- { "deployments", std::move(deployments) }
- }, 512);
+ send_result(deployment_info(query, system_settings(), link, height), 512);
return true;
}
@@ -1552,86 +1490,6 @@ bool protocol_bitcoind_blockchain::handle_precious_block(const code& ec,
return true;
}
-// A scan object is a descriptor string or { "desc", "range" } object.
-static bool expand_scan_object(chain::scripts& out,
- const value_t& item) NOEXCEPT
-{
- std::string expression{};
- uint32_t begin{};
- uint32_t end{};
-
- // bitcoind's default and maximum ranges for ranged descriptors.
- constexpr uint32_t default_range = 1'000;
- constexpr uint32_t maximum_range = 1'000'000;
-
- if (std::holds_alternative(item.value()))
- {
- expression = std::get(item.value());
- end = default_range;
- }
- else if (std::holds_alternative(item.value()))
- {
- const auto& fields = std::get(item.value());
- const auto desc = fields.find("desc");
- if (desc == fields.end() ||
- !std::holds_alternative(desc->second.value()))
- return false;
-
- expression = std::get(desc->second.value());
- end = default_range;
- const auto range = fields.find("range");
- if (range != fields.end())
- {
- const auto& value = range->second.value();
- if (std::holds_alternative(value))
- {
- if (!to_integer(end, std::get(value)))
- return false;
- }
- else if (std::holds_alternative(value))
- {
- const auto& pair = std::get(value);
- if (pair.size() != 2u ||
- !std::holds_alternative(pair.front().value()) ||
- !std::holds_alternative(pair.back().value()) ||
- !to_integer(begin,
- std::get(pair.front().value())) ||
- !to_integer(end,
- std::get(pair.back().value())) ||
- end < begin)
- return false;
- }
- else
- {
- return false;
- }
- }
- }
- else
- {
- return false;
- }
-
- const wallet::descriptor parsed{ expression };
- if (!parsed || to_bool(shift_right(end, 31u)) ||
- floored_subtract(end, begin) >= maximum_range)
- return false;
-
- if (!parsed.ranged())
- end = begin;
-
- for (auto index = begin; index <= end; ++index)
- {
- const auto derived = parsed.scripts(index);
- if (derived.empty())
- return false;
-
- out.insert(out.end(), derived.begin(), derived.end());
- }
-
- return true;
-}
-
// Exact index matching produces no false positives to optionally filter.
bool protocol_bitcoind_blockchain::handle_scan_blocks(const code& ec,
rpc_interface::scan_blocks, const std::string& action,
diff --git a/src/protocols/bitcoind/protocol_bitcoind_control.cpp b/src/protocols/bitcoind/protocol_bitcoind_control.cpp
index 53c8c774..73bce58e 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_control.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_control.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind_json.cpp b/src/protocols/bitcoind/protocol_bitcoind_json.cpp
index 0ff7b4ee..269ce390 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_json.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_json.cpp
@@ -18,228 +18,15 @@
*/
#include
-#include
-#include
+#include
#include
-#include
+#include
namespace libbitcoin {
namespace server {
using namespace system;
-// Clamped ratio of validated blocks to chain height.
-double protocol_bitcoind::progress(size_t blocks, size_t headers) NOEXCEPT
-{
- return is_zero(headers) ? 1.0 :
- std::min(1.0, to_floating(blocks) / headers);
-}
-
-// bitcoind's mtp window includes the block: the child's stored context mtp.
-uint32_t protocol_bitcoind::median_time(const node::query& query,
- const system::settings& settings,
- const database::header_link& link) NOEXCEPT
-{
- database::context ctx{};
- if (query.get_context(ctx, query.to_confirmed_child(link)))
- return ctx.mtp;
-
- // The top block has no child, its promoted chain state carries the value.
- const auto key = query.get_header_key(link);
- const auto state = query.get_confirmed_chain_state(settings, key);
- if (!state)
- return 0_u32;
-
- return chain::chain_state{ *state, settings }.context().median_time_past;
-}
-
-// A getchainstates entry for candidate or confirmed at the link (top).
-network::rpc::object_t protocol_bitcoind::chain_states_entry(
- const node::query& query, const database::header_link& link,
- double progress, bool validated) NOEXCEPT
-{
- using namespace chain;
- using namespace network::rpc;
-
- size_t height{};
- const auto header = query.get_header(link);
- if (!header || !query.get_height(height, link))
- return {};
-
- const auto bits = header->bits();
- return object_t
- {
- // bitcoind OB1 error ("blocks" wants height).
- { "blocks", height },
- { "bestblockhash", encode_hash(query.get_header_key(link)) },
- { "bits", encode_base16(to_big_endian(bits)) },
- { "target", encode_hash(from_uintx(compact::expand(bits))) },
- { "difficulty", header->difficulty() },
- { "verificationprogress", progress },
- { "coins_db_cache_bytes", zero },
- { "coins_tip_cache_bytes", zero },
- { "validated", validated }
- };
-}
-
-void protocol_bitcoind::inject_block_context(boost::json::object& out,
- const node::query& query, const system::settings& settings,
- const database::header_link& link, const chain::header& header) NOEXCEPT
-{
- size_t height{};
- if (!query.get_height(height, link))
- return;
-
- const auto top = query.get_top_confirmed();
- const auto confirmed = query.is_confirmed_block(link);
- out["height"] = height;
-
- // bitcoind reports -1 confirmations for a block not on the active chain.
- out["confirmations"] = confirmed ?
- to_signed(add1(floored_subtract(top, height))) : -1;
- out["mediantime"] = median_time(query, settings, link);
-
- // Cumulative work to this block, big-endian per bitcoind chainwork.
- uint256_t work{};
- if (query.get_branch_work(work, link))
- out["chainwork"] = encode_hash(from_uintx(work));
-
- if (header.previous_block_hash() != null_hash)
- out["previousblockhash"] = encode_hash(header.previous_block_hash());
-
- if (confirmed && height < top)
- out["nextblockhash"] = encode_hash(
- query.get_header_key(query.to_confirmed(add1(height))));
-}
-
-void protocol_bitcoind::inject_tx_context(boost::json::object& out,
- const node::query& query, const database::tx_link& link) NOEXCEPT
-{
- size_t height{};
- if (!query.get_tx_height(height, link))
- {
- out["confirmations"] = zero;
- return;
- }
-
- const auto block = query.to_confirmed(height);
- const auto top = query.get_top_confirmed();
- const auto header = query.get_header(block);
- out["blockhash"] = encode_hash(query.get_header_key(block));
- out["confirmations"] = add1(floored_subtract(top, height));
- out["in_active_chain"] = true;
- if (header)
- {
- out["blocktime"] = header->timestamp();
- out["time"] = header->timestamp();
- }
-}
-
-// The tx must be populated (populate_with_metadata).
-void protocol_bitcoind::inject_tx_prevouts(boost::json::object& out,
- const node::query& query, const chain::transaction& tx) NOEXCEPT
-{
- size_t height{};
- auto entry = out.at("vin").as_array().begin();
- std::ranges::for_each(*tx.inputs_ptr(), [&](const auto& in) NOEXCEPT
- {
- if (query.get_tx_height(height, in->metadata.parent_tx))
- {
- auto put = value_from(bitcoind(*in->prevout)).as_object();
- boost::json::object prevout
- {
- { "generated", in->metadata.coinbase },
- { "height", height },
- { "value", put.at("value") },
- { "scriptPubKey", std::move(put.at("scriptPubKey")) }
- };
-
- entry->as_object()["prevout"] = std::move(prevout);
- }
-
- ++entry;
- });
-}
-
-boost::json::object protocol_bitcoind::header_to_bitcoind(
- const chain::header& header) NOEXCEPT
-{
- return boost::json::object
- {
- { "hash", encode_hash(header.hash()) },
- { "version", header.version() },
- { "versionHex", encode_base16(to_big_endian(header.version())) },
- { "merkleroot", encode_hash(header.merkle_root()) },
- { "time", header.timestamp() },
- { "nonce", header.nonce() },
- { "bits", encode_base16(to_big_endian(header.bits())) },
- { "target", encode_hash(from_uintx(chain::compact::expand(header.bits()))) },
- { "difficulty", header.difficulty() }
- };
-}
-
-std::string protocol_bitcoind::chain_name(const node::query& query) NOEXCEPT
-{
- const auto genesis = query.get_header_key(query.to_confirmed(zero));
-
- // TODO: create signet chain selector.
- // See libbitcoin-system#1908.
- using selection = chain::selection;
- constexpr auto signet = base16_hash(
- "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6");
- static const std::vector> networks
- {
- { system::settings{ selection::mainnet }.genesis_block.hash(), "main" },
- { system::settings{ selection::testnet3 }.genesis_block.hash(), "test" },
- { system::settings{ selection::regtest }.genesis_block.hash(), "regtest" },
- { signet, "signet" }
- };
-
- for (const auto& [hash, name]: networks)
- if (hash == genesis)
- return name;
-
- return "unknown";
-}
-
-// Shared by the bitcoind blockchain subgroup and the btcd endpoint, which
-// augments the result with bip9_softforks (required by lnd).
-bool protocol_bitcoind::chain_info(network::rpc::object_t& out,
- const node::query& query, const system::settings& settings,
- bool pruned, bool current) NOEXCEPT
-{
- const auto blocks = query.get_top_confirmed();
- const auto headers = query.get_top_candidate();
- const auto link = query.to_confirmed(blocks);
- const auto header = query.get_header(link);
-
- uint256_t work{};
- if (!header || !query.get_branch_work(work, link))
- return false;
-
- const auto bits = header->bits();
- out = network::rpc::object_t
- {
- // bitcoind OB1 error ("blocks" wants height).
- { "chain", chain_name(query) },
- { "blocks", blocks },
- { "headers", headers },
- { "bestblockhash", encode_hash(query.get_header_key(link)) },
- { "bits", encode_base16(to_big_endian(bits)) },
- { "target", encode_hash(from_uintx(chain::compact::expand(bits))) },
- { "difficulty", header->difficulty() },
- { "time", header->timestamp() },
- { "mediantime", median_time(query, settings, link) },
- { "verificationprogress", progress(blocks, headers) },
- { "initialblockdownload", !current },
- { "chainwork", encode_hash(from_uintx(work)) },
- { "size_on_disk", query.store_size() },
- { "pruned", pruned },
- { "warnings", network::rpc::array_t{} }
- };
-
- return true;
-}
// The createmultisig result, empty if a key is invalid or the p2sh embedded
// script exceeds one push element. An uncompressed key downgrades a segwit
// address type to legacy with a warning (as bitcoind).
diff --git a/src/protocols/bitcoind/protocol_bitcoind_mining.cpp b/src/protocols/bitcoind/protocol_bitcoind_mining.cpp
index bd79bd03..692f72cd 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_mining.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_mining.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind_network.cpp b/src/protocols/bitcoind/protocol_bitcoind_network.cpp
index 5e3a67d4..997ce8a1 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_network.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_network.cpp
@@ -1,4 +1,4 @@
-/**
+/**
* Copyright (c) 2011-2026 libbitcoin developers
*
* This file is part of libbitcoin.
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind_notifications.cpp b/src/protocols/bitcoind/protocol_bitcoind_notifications.cpp
index ecc0e90a..00de23a4 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_notifications.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_notifications.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp
index 94e0f31c..09e86cd5 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
@@ -58,9 +60,11 @@ void protocol_bitcoind_rest::start() NOEXCEPT
SUBSCRIBE_BITCOIND(handle_get_block_part, _1, _2, _3, _4, _5, _6);
SUBSCRIBE_BITCOIND(handle_get_block_spent_tx_outputs, _1, _2, _3, _4);
SUBSCRIBE_BITCOIND(handle_get_block_filter, _1, _2, _3, _4, _5);
- SUBSCRIBE_BITCOIND(handle_get_block_filter_headers, _1, _2, _3, _4, _5);
+ SUBSCRIBE_BITCOIND(handle_get_block_filter_headers, _1, _2, _3, _4, _5, _6);
SUBSCRIBE_BITCOIND(handle_get_chain_information, _1, _2);
SUBSCRIBE_BITCOIND(handle_get_tx, _1, _2, _3, _4);
+ SUBSCRIBE_BITCOIND(handle_get_utxos, _1, _2, _3, _4);
+ SUBSCRIBE_BITCOIND(handle_get_deployment_info, _1, _2, _3);
SUBSCRIBE_CHANNEL(get, handle_receive_get, _1, _2);
network::protocol::start();
}
@@ -130,33 +134,19 @@ void protocol_bitcoind_rest::handle_receive_get(const code& ec,
return;
}
- if (rest_dispatcher_.notify(model))
- send_not_found();
+ // Required parameters may be query string sourced (e.g. blockpart).
+ if (const auto fault = rest_dispatcher_.notify(model))
+ {
+ if (fault == network::error::missing_parameter)
+ send_bad_request(*get);
+ else
+ send_not_found();
+ }
}
-// Serializers.
+// Media types.
// ----------------------------------------------------------------------------
-template
-data_chunk to_data(const Object& object, size_t size, Args&&... args) NOEXCEPT
-{
- data_chunk out(size);
- stream::out::fast sink{ out };
- write::bytes::fast writer{ sink };
- object.to_data(writer, std::forward(args)...);
- return out;
-}
-
-template
-std::string to_text(const Object& object, size_t size, Args&&... args) NOEXCEPT
-{
- std::string out(two * size, '\0');
- stream::out::fast sink{ out };
- write::base16::fast writer{ sink };
- object.to_data(writer, std::forward(args)...);
- return out;
-}
-
constexpr auto data = to_value(http::media_type::application_octet_stream);
constexpr auto json = to_value(http::media_type::application_json);
constexpr auto text = to_value(http::media_type::text_plain);
@@ -440,16 +430,16 @@ bool protocol_bitcoind_rest::handle_get_block_part(const code& ec,
return true;
}
+ // bitcoind reports an out of range part as a bad request.
const auto full = to_data(*block, block->serialized_size(witness), witness);
- if (!is_lesser(offset, full.size()))
+ if (ceilinged_add(offset, size) > full.size())
{
- send_not_found();
+ send_bad_request();
return true;
}
- const auto begin = full.begin();
- const auto stop = lesser(ceilinged_add(offset, size), full.size());
- data_chunk part{ std::next(begin, offset), std::next(begin, stop) };
+ const auto begin = std::next(full.begin(), offset);
+ data_chunk part{ begin, std::next(begin, size) };
switch (media)
{
case data:
@@ -460,8 +450,8 @@ bool protocol_bitcoind_rest::handle_get_block_part(const code& ec,
return true;
}
- // block_part is bin|hex only (json not supported).
- send_not_found();
+ // block_part is bin|hex only (json is a bad request, as bitcoind).
+ send_bad_request();
return true;
}
@@ -478,23 +468,55 @@ bool protocol_bitcoind_rest::handle_get_block_spent_tx_outputs(const code& ec,
return true;
}
+ // bitcoind serves spent outputs from undo data (confirmed blocks only).
const auto& query = archive();
const auto link = query.to_header(*hash);
- if (!query.is_associated(link))
+ if (!query.is_confirmed_block(link))
{
send_not_found();
return true;
}
- // Resolve every prevout spent by the block's non-coinbase transactions.
- chain::output_cptrs spent{};
- for (const auto& out: query.to_block_prevouts(link))
- if (const auto output = query.get_output(out))
- spent.push_back(output);
+ // Prevouts grouped per spending tx (the undo form).
+ std::vector spent{};
+ for (const auto& tx: query.to_spending_txs(link))
+ {
+ chain::output_cptrs outs{};
+ for (const auto& out: query.to_prevouts(tx))
+ {
+ const auto output = query.get_output(out);
+ if (!output)
+ {
+ send_internal_server_error(database::error::integrity);
+ return true;
+ }
- size_t size{};
- for (const auto& output: spent)
- size += output->serialized_size();
+ outs.push_back(output);
+ }
+
+ spent.push_back(std::move(outs));
+ }
+
+ // The tx count includes the coinbase, which has no prevouts.
+ auto size = variable_size(add1(spent.size())) + variable_size(zero);
+ for (const auto& outs: spent)
+ {
+ size += variable_size(outs.size());
+ for (const auto& output: outs)
+ size += output->serialized_size();
+ }
+
+ const auto serialize = [&spent](auto& writer) NOEXCEPT
+ {
+ writer.write_variable(add1(spent.size()));
+ writer.write_variable(zero);
+ for (const auto& outs: spent)
+ {
+ writer.write_variable(outs.size());
+ for (const auto& output: outs)
+ output->to_data(writer);
+ }
+ };
switch (media)
{
@@ -503,9 +525,7 @@ bool protocol_bitcoind_rest::handle_get_block_spent_tx_outputs(const code& ec,
data_chunk out(size);
stream::out::fast sink{ out };
write::bytes::fast writer{ sink };
- for (const auto& output: spent)
- output->to_data(writer);
-
+ serialize(writer);
send_data(std::move(out));
return true;
}
@@ -514,15 +534,30 @@ bool protocol_bitcoind_rest::handle_get_block_spent_tx_outputs(const code& ec,
std::string out(two * size, '\0');
stream::out::fast sink{ out };
write::base16::fast writer{ sink };
- for (const auto& output: spent)
- output->to_data(writer);
-
+ serialize(writer);
send_text(std::move(out));
return true;
}
case json:
{
- send_json(value_from(bitcoind(spent)), two * size);
+ array models{};
+ models.emplace_back(array{});
+ for (const auto& outs: spent)
+ {
+ array prevouts{};
+ for (const auto& output: outs)
+ prevouts.emplace_back(object
+ {
+ { "value", output->value() /
+ to_floating(chain::satoshi_per_bitcoin) },
+ { "scriptPubKey", value_from(bitcoind(
+ output->script())) }
+ });
+
+ models.emplace_back(std::move(prevouts));
+ }
+
+ send_json(std::move(models), two * size);
return true;
}
}
@@ -575,7 +610,7 @@ bool protocol_bitcoind_rest::handle_get_block_filter(const code& ec,
bool protocol_bitcoind_rest::handle_get_block_filter_headers(const code& ec,
rest_interface::block_filter_headers, uint8_t media, const hash_cptr& hash,
- uint8_t) NOEXCEPT
+ uint8_t, uint32_t count) NOEXCEPT
{
if (stopped(ec))
return false;
@@ -587,27 +622,76 @@ bool protocol_bitcoind_rest::handle_get_block_filter_headers(const code& ec,
return true;
}
- hash_digest filter_head{};
- if (!query.get_filter_head(filter_head, query.to_header(*hash)))
+ // bitcoind serves filter headers only for a hash on the active chain.
+ const auto header_link = query.to_header(*hash);
+ if (!query.is_confirmed_block(header_link))
{
send_not_found();
return true;
}
+ size_t height{};
+ if (!query.get_height(height, header_link))
+ {
+ send_internal_server_error(database::error::integrity);
+ return true;
+ }
+
+ constexpr size_t maximum_headers = 2000;
+ const auto limit = lesser(count, maximum_headers);
+ const auto links = query.get_confirmed_headers(height, limit);
+ if (links.empty())
+ {
+ send_not_found();
+ return true;
+ }
+
+ hashes filter_heads{};
+ filter_heads.reserve(links.size());
+ for (const auto& link: links)
+ {
+ hash_digest filter_head{};
+ if (!query.get_filter_head(filter_head, link))
+ {
+ send_internal_server_error(database::error::integrity);
+ return true;
+ }
+
+ filter_heads.push_back(filter_head);
+ }
+
switch (media)
{
case data:
- send_data(to_chunk(filter_head));
+ {
+ data_chunk out{};
+ out.reserve(filter_heads.size() * hash_size);
+ for (const auto& head: filter_heads)
+ out.insert(out.end(), head.begin(), head.end());
+
+ send_data(std::move(out));
return true;
+ }
case text:
- send_text(encode_base16(filter_head));
+ {
+ std::string out{};
+ out.reserve(filter_heads.size() * two * hash_size);
+ for (const auto& head: filter_heads)
+ out += encode_base16(head);
+
+ send_text(std::move(out));
return true;
+ }
case json:
- send_json(object
- {
- { "filter_header", encode_hash(filter_head) }
- }, two * hash_size);
+ {
+ array models{};
+ for (const auto& head: filter_heads)
+ models.emplace_back(encode_hash(head));
+
+ const auto size = filter_heads.size() * two * hash_size;
+ send_json(std::move(models), size);
return true;
+ }
}
send_not_found();
@@ -645,6 +729,170 @@ bool protocol_bitcoind_rest::handle_get_chain_information(const code& ec,
return true;
}
+// bitcoind's bip64 form: dummy version, height, output (all confirmed only).
+bool protocol_bitcoind_rest::handle_get_utxos(const code& ec,
+ rest_interface::get_utxos, uint8_t media,
+ const array_t& outpoints) NOEXCEPT
+{
+ if (stopped(ec))
+ return false;
+
+ // bitcoind's bip64 outpoint limit.
+ constexpr size_t maximum_outpoints = 15;
+ if (outpoints.empty() || outpoints.size() > maximum_outpoints)
+ {
+ send_bad_request();
+ return true;
+ }
+
+ struct utxo { uint32_t height; chain::output::cptr out; };
+ const auto& query = archive();
+ std::vector hits{};
+ std::vector utxos{};
+ for (const auto& item: outpoints)
+ {
+ const auto& fields = std::get(item.value());
+ const auto& any = std::get(fields.at("hash").value());
+ const auto hash = any.get();
+ const auto index = std::get(fields.at("index").value());
+
+ const auto output_link = query.to_output(*hash, index);
+ const auto hit = !output_link.is_terminal() &&
+ query.is_confirmed_output(output_link) &&
+ !query.is_confirmed_spent(output_link);
+ hits.push_back(hit);
+ if (!hit)
+ continue;
+
+ size_t height{};
+ const auto output = query.get_output(output_link);
+ if (!output ||
+ !query.get_tx_height(height, query.to_output_tx(output_link)))
+ {
+ send_internal_server_error(database::error::integrity);
+ return true;
+ }
+
+ utxos.push_back({ possible_narrow_cast(height), output });
+ }
+
+ const auto top = query.get_top_confirmed();
+ const auto top_hash = query.get_header_key(query.to_confirmed(top));
+
+ // The bip64 bitmap flags each outpoint hit, lsb first within each byte.
+ data_chunk bitmap(ceilinged_divide(hits.size(), 8u), 0x00);
+ for (size_t index = 0; index < hits.size(); ++index)
+ if (hits.at(index))
+ bitmap.at(index / 8u) |= possible_narrow_cast(
+ shift_left(one, index % 8u));
+
+ auto size = sizeof(uint32_t) + hash_size +
+ variable_size(bitmap.size()) + bitmap.size() +
+ variable_size(utxos.size());
+ for (const auto& unspent: utxos)
+ size += two * sizeof(uint32_t) + unspent.out->serialized_size();
+
+ const auto serialize = [&](auto& writer) NOEXCEPT
+ {
+ writer.write_4_bytes_little_endian(
+ possible_narrow_cast(top));
+ writer.write_bytes(top_hash);
+ writer.write_variable(bitmap.size());
+ writer.write_bytes(bitmap);
+ writer.write_variable(utxos.size());
+ for (const auto& unspent: utxos)
+ {
+ writer.write_4_bytes_little_endian(0);
+ writer.write_4_bytes_little_endian(unspent.height);
+ unspent.out->to_data(writer);
+ }
+ };
+
+ switch (media)
+ {
+ case data:
+ {
+ data_chunk out(size);
+ stream::out::fast sink{ out };
+ write::bytes::fast writer{ sink };
+ serialize(writer);
+ send_data(std::move(out));
+ return true;
+ }
+ case text:
+ {
+ std::string out(two * size, '\0');
+ stream::out::fast sink{ out };
+ write::base16::fast writer{ sink };
+ serialize(writer);
+ send_text(std::move(out));
+ return true;
+ }
+ case json:
+ {
+ std::string bits{};
+ for (const auto hit: hits)
+ bits += hit ? "1" : "0";
+
+ array models{};
+ for (const auto& unspent: utxos)
+ models.emplace_back(object
+ {
+ { "height", unspent.height },
+ { "value", unspent.out->value() /
+ to_floating(chain::satoshi_per_bitcoin) },
+ { "scriptPubKey", value_from(bitcoind(
+ unspent.out->script())) }
+ });
+
+ send_json(object
+ {
+ { "chainHeight", top },
+ { "chaintipHash", encode_hash(top_hash) },
+ { "bitmap", bits },
+ { "utxos", std::move(models) }
+ }, two * size);
+ return true;
+ }
+ }
+
+ send_not_found();
+ return true;
+}
+
+bool protocol_bitcoind_rest::handle_get_deployment_info(const code& ec,
+ rest_interface::deployment_info,
+ const std::optional& hash) NOEXCEPT
+{
+ if (stopped(ec))
+ return false;
+
+ const auto& query = archive();
+ auto link = query.to_confirmed(query.get_top_confirmed());
+
+ // bitcoind reports an unknown block as a bad request here.
+ if (hash.has_value())
+ {
+ link = query.to_header(*hash.value());
+ if (link.is_terminal())
+ {
+ send_bad_request();
+ return true;
+ }
+ }
+
+ size_t height{};
+ if (!query.get_height(height, link))
+ {
+ send_internal_server_error(database::error::integrity);
+ return true;
+ }
+
+ const auto doc = deployment_info(query, system_settings(), link, height);
+ send_json(value_from(doc), 512);
+ return true;
+}
+
// Raw-http response senders (mirror protocol_html, not json-rpc enveloped).
// ----------------------------------------------------------------------------
diff --git a/src/protocols/bitcoind/protocol_bitcoind_test.cpp b/src/protocols/bitcoind/protocol_bitcoind_test.cpp
index 2126557c..6779f811 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_test.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_test.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
diff --git a/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp b/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp
index a06b92eb..0cc551b2 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp
@@ -1,4 +1,4 @@
-/**
+/**
* Copyright (c) 2011-2026 libbitcoin developers
*
* This file is part of libbitcoin.
@@ -24,6 +24,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace server {
@@ -422,170 +424,6 @@ bool protocol_bitcoind_transaction::handle_test_mempool_accept(const code& ec,
using psbt_tx = wallet::psbt::transaction;
-static std::string sighash_name(uint32_t type) NOEXCEPT
-{
- std::string name{};
- switch (type & 0x03_u32)
- {
- case 0: name = "DEFAULT"; break;
- case 1: name = "ALL"; break;
- case 2: name = "NONE"; break;
- default: name = "SINGLE";
- }
-
- if (to_bool(type & 0x80_u32))
- name += "|ANYONECANPAY";
-
- return name;
-}
-
-static std::string to_key_path(const std_vector& path) NOEXCEPT
-{
- constexpr auto hardened = 0x80000000_u32;
- std::string out{ "m" };
- for (const auto& index: path)
- {
- out += "/" + std::to_string(index & ~hardened);
- if (to_bool(index & hardened))
- out += "'";
- }
-
- return out;
-}
-
-static object_t to_unknown(const wallet::psbt::entry::list& entries) NOEXCEPT
-{
- object_t out{};
- for (const auto& entry: entries)
- out.emplace(encode_base16(entry.key), encode_base16(entry.value));
-
- return out;
-}
-
-static array_t to_derivations(
- const wallet::psbt::derivation::list& derivations) NOEXCEPT
-{
- array_t out{};
- for (const auto& derived: derivations)
- {
- out.emplace_back(object_t
- {
- { "pubkey", encode_base16(derived.point) },
- { "master_fingerprint", encode_base16(to_little_endian(
- derived.origin.fingerprint)) },
- { "path", to_key_path(derived.origin.path) }
- });
- }
-
- return out;
-}
-
-static object_t decode_psbt_input(const wallet::psbt::input& in) NOEXCEPT
-{
- using namespace chain;
- object_t entry{};
-
- if (in.non_witness_utxo)
- entry.emplace("non_witness_utxo",
- value_from(bitcoind(*in.non_witness_utxo)));
-
- if (in.witness_utxo)
- {
- entry.emplace("witness_utxo", object_t
- {
- { "amount", in.witness_utxo->value() /
- to_floating(satoshi_per_bitcoin) },
- { "scriptPubKey", value_from(bitcoind(in.witness_utxo->script())) }
- });
- }
-
- if (!in.partial_signatures.empty())
- {
- object_t signatures{};
- for (const auto& signature: in.partial_signatures)
- signatures.emplace(encode_base16(signature.keydata()),
- encode_base16(signature.value));
-
- entry.emplace("partial_signatures", std::move(signatures));
- }
-
- if (in.sighash_type.has_value())
- entry.emplace("sighash", sighash_name(in.sighash_type.value()));
-
- if (in.embedded_script)
- entry.emplace("redeem_script",
- value_from(bitcoind(*in.embedded_script)));
-
- if (in.witness_script)
- entry.emplace("witness_script",
- value_from(bitcoind(*in.witness_script)));
-
- if (!in.derivations.empty())
- entry.emplace("bip32_derivs", to_derivations(in.derivations));
-
- if (in.final_script_sig)
- entry.emplace("final_scriptSig",
- value_from(bitcoind(*in.final_script_sig)));
-
- if (in.final_script_witness)
- {
- array_t stack{};
- for (const auto& item: in.final_script_witness->stack())
- stack.emplace_back(encode_base16(*item));
-
- entry.emplace("final_scriptwitness", std::move(stack));
- }
-
- if (in.previous_txid.has_value())
- entry.emplace("previous_txid", encode_hash(in.previous_txid.value()));
-
- if (in.output_index.has_value())
- entry.emplace("output_index", in.output_index.value());
-
- if (in.sequence.has_value())
- entry.emplace("sequence", in.sequence.value());
-
- if (in.required_time_locktime.has_value())
- entry.emplace("time_locktime", in.required_time_locktime.value());
-
- if (in.required_height_locktime.has_value())
- entry.emplace("height_locktime", in.required_height_locktime.value());
-
- if (!in.others.empty())
- entry.emplace("unknown", to_unknown(in.others));
-
- return entry;
-}
-
-static object_t decode_psbt_output(const wallet::psbt::output& out) NOEXCEPT
-{
- using namespace chain;
- object_t entry{};
-
- if (out.embedded_script)
- entry.emplace("redeem_script",
- value_from(bitcoind(*out.embedded_script)));
-
- if (out.witness_script)
- entry.emplace("witness_script",
- value_from(bitcoind(*out.witness_script)));
-
- if (!out.derivations.empty())
- entry.emplace("bip32_derivs", to_derivations(out.derivations));
-
- if (out.amount.has_value())
- entry.emplace("amount", out.amount.value() /
- to_floating(satoshi_per_bitcoin));
-
- if (out.script)
- entry.emplace("script", value_from(bitcoind(*out.script)));
-
- if (!out.others.empty())
- entry.emplace("unknown", to_unknown(out.others));
-
- return entry;
-}
-
bool protocol_bitcoind_transaction::handle_analyze_psbt(const code& ec,
rpc_interface::analyze_psbt, const std::string& psbt) NOEXCEPT
{
diff --git a/src/protocols/bitcoind/protocol_bitcoind_utility.cpp b/src/protocols/bitcoind/protocol_bitcoind_utility.cpp
index 641368da..7cb3bff7 100644
--- a/src/protocols/bitcoind/protocol_bitcoind_utility.cpp
+++ b/src/protocols/bitcoind/protocol_bitcoind_utility.cpp
@@ -23,6 +23,8 @@
#include
#include
#include
+#include