From 2ce37bffecd9e7a93826ee176ec4323c3e86cfc7 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 17:57:13 -0400 Subject: [PATCH 1/7] Refactor interface sources. --- builds/gnu/Makefile.am | 40 ++++++--- .../libbitcoin-server-test.vcxproj | 12 +-- .../libbitcoin-server-test.vcxproj.filters | 30 ++++--- .../libbitcoin-server.vcxproj | 18 ++-- .../libbitcoin-server.vcxproj.filters | 90 +++++++++++-------- .../libbitcoin-server-test.vcxproj | 12 +-- .../libbitcoin-server-test.vcxproj.filters | 30 ++++--- .../libbitcoin-server.vcxproj | 18 ++-- .../libbitcoin-server.vcxproj.filters | 90 +++++++++++-------- include/bitcoin/server.hpp | 10 ++- .../server/channels/channel_electrum.hpp | 2 + include/bitcoin/server/parsers/parsers.hpp | 4 - .../server/protocols/protocol_electrum.hpp | 2 + .../protocols/protocol_electrum_version.hpp | 2 + .../bitcoind_block_stats.hpp | 4 +- .../server/serializers/serializers.hpp | 24 +++++ .../bitcoind_descriptor.hpp | 4 +- .../bitcoind_merkle.hpp | 4 +- .../electrum_version.hpp | 4 +- .../bitcoin/server/utilities/utilities.hpp | 26 ++++++ src/protocols/admin/protocol_admin.cpp | 2 + src/protocols/bitcoind/protocol_bitcoind.cpp | 2 + .../bitcoind/protocol_bitcoind_blockchain.cpp | 2 + .../bitcoind/protocol_bitcoind_control.cpp | 2 + .../bitcoind/protocol_bitcoind_json.cpp | 2 + .../bitcoind/protocol_bitcoind_mining.cpp | 2 + .../bitcoind/protocol_bitcoind_network.cpp | 4 +- .../protocol_bitcoind_notifications.cpp | 2 + .../bitcoind/protocol_bitcoind_rest.cpp | 2 + .../bitcoind/protocol_bitcoind_test.cpp | 2 + .../protocol_bitcoind_transaction.cpp | 4 +- .../bitcoind/protocol_bitcoind_utility.cpp | 2 + .../bitcoind/protocol_bitcoind_wallet.cpp | 2 + src/protocols/btcd/protocol_btcd_filter.cpp | 2 + .../electrum/protocol_electrum_version.cpp | 2 + .../bitcoind_block_stats.cpp | 2 +- .../bitcoind_descriptor.cpp | 2 +- .../bitcoind_merkle.cpp | 2 +- .../electrum_version.cpp | 2 +- .../bitcoind_block_stats.cpp | 0 .../bitcoind_descriptor.cpp | 0 .../bitcoind_merkle.cpp | 0 .../electrum_version.cpp | 0 43 files changed, 308 insertions(+), 160 deletions(-) rename include/bitcoin/server/{parsers => serializers}/bitcoind_block_stats.hpp (89%) create mode 100644 include/bitcoin/server/serializers/serializers.hpp rename include/bitcoin/server/{parsers => utilities}/bitcoind_descriptor.hpp (89%) rename include/bitcoin/server/{parsers => utilities}/bitcoind_merkle.hpp (94%) rename include/bitcoin/server/{parsers => utilities}/electrum_version.hpp (96%) create mode 100644 include/bitcoin/server/utilities/utilities.hpp rename src/{parsers => serializers}/bitcoind_block_stats.cpp (99%) rename src/{parsers => utilities}/bitcoind_descriptor.cpp (98%) rename src/{parsers => utilities}/bitcoind_merkle.cpp (99%) rename src/{parsers => utilities}/electrum_version.cpp (98%) rename test/{parsers => serializers}/bitcoind_block_stats.cpp (100%) rename test/{parsers => utilities}/bitcoind_descriptor.cpp (100%) rename test/{parsers => utilities}/bitcoind_merkle.cpp (100%) rename test/{parsers => utilities}/electrum_version.cpp (100%) diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index fc2da4c3..3b98db9e 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -57,14 +57,10 @@ 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_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 +101,11 @@ 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/utilities/bitcoind_descriptor.cpp \ + ${srcdir}/../../src/utilities/bitcoind_merkle.cpp \ + ${srcdir}/../../src/utilities/electrum_version.cpp include_bitcoindir = \ ${includedir}/bitcoin @@ -180,14 +180,10 @@ 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_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 +217,13 @@ 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/serializers.hpp + include_bitcoin_server_sessionsdir = \ ${includedir}/bitcoin/server/sessions @@ -230,6 +233,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,12 +277,8 @@ 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 \ @@ -300,7 +308,11 @@ 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/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..5d10743f 100644 --- a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj @@ -126,14 +126,8 @@ - - - - - $(IntDir)test_parsers_electrum_version.obj - @@ -166,8 +160,14 @@ + + + + + $(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..5438ef1d 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 @@ -171,12 +165,24 @@ src\protocols\native + + 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..916f9927 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj @@ -129,14 +129,10 @@ - - - - @@ -178,8 +174,12 @@ + + + + @@ -218,14 +218,10 @@ - - - - @@ -253,12 +249,18 @@ + + + + + + diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters index 5f052b7b..a3bd7a6c 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,15 +111,6 @@ src\parsers - - src\parsers - - - src\parsers - - - src\parsers - src\parsers @@ -120,9 +123,6 @@ src\parsers - - src\parsers - src\parsers @@ -246,12 +246,24 @@ src\protocols\stratum_v1 + + src\serializers + src src + + src\utilities + + + src\utilities + + + src\utilities + @@ -362,15 +374,6 @@ include\bitcoin\server\parsers - - include\bitcoin\server\parsers - - - include\bitcoin\server\parsers - - - include\bitcoin\server\parsers - include\bitcoin\server\parsers @@ -383,9 +386,6 @@ include\bitcoin\server\parsers - - include\bitcoin\server\parsers - include\bitcoin\server\parsers @@ -467,6 +467,12 @@ include\bitcoin\server\protocols + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + include\bitcoin\server @@ -485,6 +491,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..24e82f8f 100644 --- a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj @@ -126,14 +126,8 @@ - - - - - $(IntDir)test_parsers_electrum_version.obj - @@ -166,8 +160,14 @@ + + + + + $(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..5438ef1d 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 @@ -171,12 +165,24 @@ src\protocols\native + + 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..2945073d 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj @@ -129,14 +129,10 @@ - - - - @@ -178,8 +174,12 @@ + + + + @@ -218,14 +218,10 @@ - - - - @@ -253,12 +249,18 @@ + + + + + + diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters index 5f052b7b..a3bd7a6c 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,15 +111,6 @@ src\parsers - - src\parsers - - - src\parsers - - - src\parsers - src\parsers @@ -120,9 +123,6 @@ src\parsers - - src\parsers - src\parsers @@ -246,12 +246,24 @@ src\protocols\stratum_v1 + + src\serializers + src src + + src\utilities + + + src\utilities + + + src\utilities + @@ -362,15 +374,6 @@ include\bitcoin\server\parsers - - include\bitcoin\server\parsers - - - include\bitcoin\server\parsers - - - include\bitcoin\server\parsers - include\bitcoin\server\parsers @@ -383,9 +386,6 @@ include\bitcoin\server\parsers - - include\bitcoin\server\parsers - include\bitcoin\server\parsers @@ -467,6 +467,12 @@ include\bitcoin\server\protocols + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + include\bitcoin\server @@ -485,6 +491,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..ce0130a7 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -52,14 +52,10 @@ #include #include #include -#include -#include -#include #include #include #include #include -#include #include #include #include @@ -87,9 +83,15 @@ #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/parsers/parsers.hpp b/include/bitcoin/server/parsers/parsers.hpp index 8c45a8b7..07689ddd 100644 --- a/include/bitcoin/server/parsers/parsers.hpp +++ b/include/bitcoin/server/parsers/parsers.hpp @@ -21,14 +21,10 @@ #include #include -#include -#include -#include #include #include #include #include -#include #include #include 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/serializers.hpp b/include/bitcoin/server/serializers/serializers.hpp new file mode 100644 index 00000000..22a8382b --- /dev/null +++ b/include/bitcoin/server/serializers/serializers.hpp @@ -0,0 +1,24 @@ +/** + * 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 + +#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/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..53ffffbd 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 { 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..960db5b2 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_json.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_json.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include namespace libbitcoin { namespace server { 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..20b60f5e 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 { 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..a526a363 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 { 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 +#include namespace libbitcoin { namespace server { diff --git a/src/protocols/bitcoind/protocol_bitcoind_wallet.cpp b/src/protocols/bitcoind/protocol_bitcoind_wallet.cpp index c3c04eb0..94e78030 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_wallet.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_wallet.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include namespace libbitcoin { namespace server { diff --git a/src/protocols/btcd/protocol_btcd_filter.cpp b/src/protocols/btcd/protocol_btcd_filter.cpp index 8664df07..d9c7251f 100644 --- a/src/protocols/btcd/protocol_btcd_filter.cpp +++ b/src/protocols/btcd/protocol_btcd_filter.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include namespace libbitcoin { namespace server { diff --git a/src/protocols/electrum/protocol_electrum_version.cpp b/src/protocols/electrum/protocol_electrum_version.cpp index 1c6790ea..baeda972 100644 --- a/src/protocols/electrum/protocol_electrum_version.cpp +++ b/src/protocols/electrum/protocol_electrum_version.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include namespace libbitcoin { diff --git a/src/parsers/bitcoind_block_stats.cpp b/src/serializers/bitcoind_block_stats.cpp similarity index 99% rename from src/parsers/bitcoind_block_stats.cpp rename to src/serializers/bitcoind_block_stats.cpp index b4eb79d2..dea93cfd 100644 --- a/src/parsers/bitcoind_block_stats.cpp +++ b/src/serializers/bitcoind_block_stats.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include +#include #include #include diff --git a/src/parsers/bitcoind_descriptor.cpp b/src/utilities/bitcoind_descriptor.cpp similarity index 98% rename from src/parsers/bitcoind_descriptor.cpp rename to src/utilities/bitcoind_descriptor.cpp index 23b70ccb..b41fc038 100644 --- a/src/parsers/bitcoind_descriptor.cpp +++ b/src/utilities/bitcoind_descriptor.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include +#include #include diff --git a/src/parsers/bitcoind_merkle.cpp b/src/utilities/bitcoind_merkle.cpp similarity index 99% rename from src/parsers/bitcoind_merkle.cpp rename to src/utilities/bitcoind_merkle.cpp index cf98a45a..6f3dc63a 100644 --- a/src/parsers/bitcoind_merkle.cpp +++ b/src/utilities/bitcoind_merkle.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include +#include #include diff --git a/src/parsers/electrum_version.cpp b/src/utilities/electrum_version.cpp similarity index 98% rename from src/parsers/electrum_version.cpp rename to src/utilities/electrum_version.cpp index f000a079..1ad40751 100644 --- a/src/parsers/electrum_version.cpp +++ b/src/utilities/electrum_version.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include +#include #include #include diff --git a/test/parsers/bitcoind_block_stats.cpp b/test/serializers/bitcoind_block_stats.cpp similarity index 100% rename from test/parsers/bitcoind_block_stats.cpp rename to test/serializers/bitcoind_block_stats.cpp diff --git a/test/parsers/bitcoind_descriptor.cpp b/test/utilities/bitcoind_descriptor.cpp similarity index 100% rename from test/parsers/bitcoind_descriptor.cpp rename to test/utilities/bitcoind_descriptor.cpp diff --git a/test/parsers/bitcoind_merkle.cpp b/test/utilities/bitcoind_merkle.cpp similarity index 100% rename from test/parsers/bitcoind_merkle.cpp rename to test/utilities/bitcoind_merkle.cpp diff --git a/test/parsers/electrum_version.cpp b/test/utilities/electrum_version.cpp similarity index 100% rename from test/parsers/electrum_version.cpp rename to test/utilities/electrum_version.cpp From b726ea9aa9ac8bb46be0adafc23b7762436bf42a Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 19:47:39 -0400 Subject: [PATCH 2/7] Move serializers and utils out of interface impls. --- builds/gnu/Makefile.am | 11 +- .../libbitcoin-server-test.vcxproj | 2 +- .../libbitcoin-server-test.vcxproj.filters | 6 +- .../libbitcoin-server.vcxproj | 9 + .../libbitcoin-server.vcxproj.filters | 27 ++ .../libbitcoin-server-test.vcxproj | 2 +- .../libbitcoin-server-test.vcxproj.filters | 6 +- .../libbitcoin-server.vcxproj | 9 + .../libbitcoin-server.vcxproj.filters | 27 ++ include/bitcoin/server.hpp | 5 + .../bitcoin/server/parsers/bitcoind_scan.hpp | 35 +++ include/bitcoin/server/parsers/parsers.hpp | 1 + .../server/protocols/protocol_bitcoind.hpp | 41 --- .../server/serializers/bitcoind_coin.hpp | 35 +++ .../server/serializers/bitcoind_data.hpp | 58 ++++ .../server/serializers/bitcoind_json.hpp | 71 +++++ .../server/serializers/bitcoind_psbt.hpp | 45 ++++ .../server/serializers/serializers.hpp | 4 + src/parsers/bitcoind_scan.cpp | 114 ++++++++ .../bitcoind/protocol_bitcoind_blockchain.cpp | 105 -------- .../bitcoind/protocol_bitcoind_json.cpp | 217 +-------------- .../bitcoind/protocol_bitcoind_rest.cpp | 22 +- .../protocol_bitcoind_transaction.cpp | 164 ------------ src/protocols/btcd/protocol_btcd_utility.cpp | 1 + src/serializers/bitcoind_coin.cpp | 53 ++++ src/serializers/bitcoind_json.cpp | 248 ++++++++++++++++++ src/serializers/bitcoind_psbt.cpp | 199 ++++++++++++++ .../bitcoind_json.cpp | 34 +-- 28 files changed, 972 insertions(+), 579 deletions(-) create mode 100644 include/bitcoin/server/parsers/bitcoind_scan.hpp create mode 100644 include/bitcoin/server/serializers/bitcoind_coin.hpp create mode 100644 include/bitcoin/server/serializers/bitcoind_data.hpp create mode 100644 include/bitcoin/server/serializers/bitcoind_json.hpp create mode 100644 include/bitcoin/server/serializers/bitcoind_psbt.hpp create mode 100644 src/parsers/bitcoind_scan.cpp create mode 100644 src/serializers/bitcoind_coin.cpp create mode 100644 src/serializers/bitcoind_json.cpp create mode 100644 src/serializers/bitcoind_psbt.cpp rename test/{protocols/bitcoind => serializers}/bitcoind_json.cpp (86%) diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 3b98db9e..7fcdee55 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -58,6 +58,7 @@ src_libbitcoin_server_la_SOURCES = \ ${srcdir}/../../src/parsers/admin_query.cpp \ ${srcdir}/../../src/parsers/admin_target.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 \ @@ -103,6 +104,9 @@ src_libbitcoin_server_la_SOURCES = \ ${srcdir}/../../src/protocols/native/protocol_native_tx.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_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 @@ -181,6 +185,7 @@ 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_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 \ @@ -222,6 +227,10 @@ include_bitcoin_server_serializersdir = \ 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_json.hpp \ + ${srcdir}/../../include/bitcoin/server/serializers/bitcoind_psbt.hpp \ ${srcdir}/../../include/bitcoin/server/serializers/serializers.hpp include_bitcoin_server_sessionsdir = \ @@ -283,7 +292,6 @@ test_libbitcoin_server_test_SOURCES = \ ${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 \ @@ -310,6 +318,7 @@ test_libbitcoin_server_test_SOURCES = \ ${srcdir}/../../test/protocols/native/native_setup_fixture.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 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 5d10743f..9a80d562 100644 --- a/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server-test/libbitcoin-server-test.vcxproj @@ -132,7 +132,6 @@ - @@ -161,6 +160,7 @@ + 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 5438ef1d..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 @@ -87,9 +87,6 @@ src\protocols\admin - - src\protocols\bitcoind - src\protocols\bitcoind @@ -168,6 +165,9 @@ src\serializers + + src\serializers + src diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj index 916f9927..5ebca65e 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj @@ -130,6 +130,7 @@ + @@ -175,6 +176,9 @@ + + + @@ -219,6 +223,7 @@ + @@ -250,6 +255,10 @@ + + + + diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters index a3bd7a6c..523d4291 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -114,6 +114,9 @@ src\parsers + + src\parsers + src\parsers @@ -249,6 +252,15 @@ src\serializers + + src\serializers + + + src\serializers + + + src\serializers + src @@ -377,6 +389,9 @@ include\bitcoin\server\parsers + + include\bitcoin\server\parsers + include\bitcoin\server\parsers @@ -470,6 +485,18 @@ include\bitcoin\server\serializers + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + include\bitcoin\server\serializers 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 24e82f8f..e2d927de 100644 --- a/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server-test/libbitcoin-server-test.vcxproj @@ -132,7 +132,6 @@ - @@ -161,6 +160,7 @@ + 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 5438ef1d..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 @@ -87,9 +87,6 @@ src\protocols\admin - - src\protocols\bitcoind - src\protocols\bitcoind @@ -168,6 +165,9 @@ src\serializers + + src\serializers + src diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj index 2945073d..c34d9ae7 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj @@ -130,6 +130,7 @@ + @@ -175,6 +176,9 @@ + + + @@ -219,6 +223,7 @@ + @@ -250,6 +255,10 @@ + + + + diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters index a3bd7a6c..523d4291 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -114,6 +114,9 @@ src\parsers + + src\parsers + src\parsers @@ -249,6 +252,15 @@ src\serializers + + src\serializers + + + src\serializers + + + src\serializers + src @@ -377,6 +389,9 @@ include\bitcoin\server\parsers + + include\bitcoin\server\parsers + include\bitcoin\server\parsers @@ -470,6 +485,18 @@ include\bitcoin\server\serializers + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + + + include\bitcoin\server\serializers + include\bitcoin\server\serializers diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index ce0130a7..c52a354f 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include 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 07689ddd..efebcd14 100644 --- a/include/bitcoin/server/parsers/parsers.hpp +++ b/include/bitcoin/server/parsers/parsers.hpp @@ -22,6 +22,7 @@ #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/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_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 index 22a8382b..79683474 100644 --- a/include/bitcoin/server/serializers/serializers.hpp +++ b/include/bitcoin/server/serializers/serializers.hpp @@ -20,5 +20,9 @@ #define LIBBITCOIN_SERVER_SERIALIZERS_SERIALIZERS_HPP #include +#include +#include +#include +#include #endif 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/protocols/bitcoind/protocol_bitcoind_blockchain.cpp b/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp index 53ffffbd..6e4afd31 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp @@ -63,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) @@ -612,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, @@ -1554,86 +1529,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_json.cpp b/src/protocols/bitcoind/protocol_bitcoind_json.cpp index 960db5b2..269ce390 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_json.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_json.cpp @@ -18,11 +18,8 @@ */ #include -#include -#include +#include #include -#include -#include #include namespace libbitcoin { @@ -30,218 +27,6 @@ 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_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp index 20b60f5e..f738d0ee 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp @@ -136,29 +136,9 @@ void protocol_bitcoind_rest::handle_receive_get(const code& ec, 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); diff --git a/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp b/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp index a526a363..0cc551b2 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_transaction.cpp @@ -424,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/btcd/protocol_btcd_utility.cpp b/src/protocols/btcd/protocol_btcd_utility.cpp index cd8bd64b..f53814ea 100644 --- a/src/protocols/btcd/protocol_btcd_utility.cpp +++ b/src/protocols/btcd/protocol_btcd_utility.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace libbitcoin { namespace server { diff --git a/src/serializers/bitcoind_coin.cpp b/src/serializers/bitcoind_coin.cpp new file mode 100644 index 00000000..a819ba61 --- /dev/null +++ b/src/serializers/bitcoind_coin.cpp @@ -0,0 +1,53 @@ +/** + * 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 + +namespace libbitcoin { +namespace server { + +using namespace system; + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +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); +} + +BC_POP_WARNING() + +} // namespace server +} // namespace libbitcoin diff --git a/src/serializers/bitcoind_json.cpp b/src/serializers/bitcoind_json.cpp new file mode 100644 index 00000000..a67d515d --- /dev/null +++ b/src/serializers/bitcoind_json.cpp @@ -0,0 +1,248 @@ +/** + * 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 +#include + +namespace libbitcoin { +namespace server { + +using namespace system; + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +// Clamped ratio of validated blocks to chain height. +double 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 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 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 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 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 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 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 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 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; +} + +BC_POP_WARNING() + +} // namespace server +} // namespace libbitcoin diff --git a/src/serializers/bitcoind_psbt.cpp b/src/serializers/bitcoind_psbt.cpp new file mode 100644 index 00000000..14fdb915 --- /dev/null +++ b/src/serializers/bitcoind_psbt.cpp @@ -0,0 +1,199 @@ +/** + * 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 + +namespace libbitcoin { +namespace server { + +using namespace system; +using namespace network::rpc; +using namespace boost::json; + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +BC_POP_WARNING() + +} // namespace server +} // namespace libbitcoin diff --git a/test/protocols/bitcoind/bitcoind_json.cpp b/test/serializers/bitcoind_json.cpp similarity index 86% rename from test/protocols/bitcoind/bitcoind_json.cpp rename to test/serializers/bitcoind_json.cpp index 5528d282..42838d8a 100644 --- a/test/protocols/bitcoind/bitcoind_json.cpp +++ b/test/serializers/bitcoind_json.cpp @@ -16,9 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include "../../test.hpp" -#include "../../mocks/blocks.hpp" -#include +#include "../test.hpp" +#include "../mocks/blocks.hpp" using namespace system; @@ -27,17 +26,6 @@ static std::string as_text(const boost::json::value& value) NOEXCEPT return { value.as_string().c_str() }; } -// Exposes the protected static json helpers for direct testing. -struct json - : server::protocol_bitcoind -{ - using protocol_bitcoind::median_time; - using protocol_bitcoind::inject_block_context; - using protocol_bitcoind::inject_tx_context; - using protocol_bitcoind::header_to_bitcoind; - using protocol_bitcoind::chain_name; -}; - // header_to_bitcoind // ---------------------------------------------------------------------------- @@ -46,7 +34,7 @@ BOOST_AUTO_TEST_SUITE(bitcoind_header_to_bitcoind_tests) BOOST_AUTO_TEST_CASE(bitcoind_json__header_to_bitcoind__block1_header__maps_fields) { const auto& header = test::block1.header(); - const auto out = json::header_to_bitcoind(header); + const auto out = header_to_bitcoind(header); BOOST_REQUIRE_EQUAL(as_text(out.at("hash")), encode_hash(header.hash())); BOOST_REQUIRE_EQUAL(out.at("version").to_number(), header.version()); @@ -112,7 +100,7 @@ BOOST_FIXTURE_TEST_SUITE(bitcoind_json_tests, bitcoind_json_setup_fixture) BOOST_AUTO_TEST_CASE(bitcoind_json__chain_name__mainnet_genesis__main) { - BOOST_REQUIRE_EQUAL(json::chain_name(query_), "main"); + BOOST_REQUIRE_EQUAL(chain_name(query_), "main"); } // median_time @@ -122,7 +110,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__median_time__self_inclusive_window) { const system::settings settings{ chain::selection::mainnet }; const auto link = query_.to_header(test::block5_hash); - BOOST_REQUIRE_EQUAL(json::median_time(query_, settings, link), 1231470173u); + BOOST_REQUIRE_EQUAL(median_time(query_, settings, link), 1231470173u); } // inject_block_context @@ -135,7 +123,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__middle__height_confirm BOOST_REQUIRE(header); boost::json::object out{}; - json::inject_block_context(out, query_, settings, link, *header); + inject_block_context(out, query_, settings, link, *header); BOOST_REQUIRE_EQUAL(out.at("height").to_number(), 5u); BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number(), 5); @@ -152,7 +140,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__genesis__no_previous) BOOST_REQUIRE(header); boost::json::object out{}; - json::inject_block_context(out, query_, settings, link, *header); + inject_block_context(out, query_, settings, link, *header); BOOST_REQUIRE_EQUAL(out.at("height").to_number(), 0u); BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number(), 10); @@ -160,7 +148,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__genesis__no_previous) BOOST_REQUIRE_EQUAL(as_text(out.at("nextblockhash")), encode_hash(test::block1_hash)); } -BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__tip__no_next) +BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__top__no_next) { const system::settings settings{ chain::selection::mainnet }; const auto link = query_.to_header(test::block9_hash); @@ -168,7 +156,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__tip__no_next) BOOST_REQUIRE(header); boost::json::object out{}; - json::inject_block_context(out, query_, settings, link, *header); + inject_block_context(out, query_, settings, link, *header); BOOST_REQUIRE_EQUAL(out.at("height").to_number(), 9u); BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number(), 1); @@ -184,7 +172,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_tx_context__confirmed_coinbase__block const auto link = query_.to_tx(txid); boost::json::object out{}; - json::inject_tx_context(out, query_, link); + inject_tx_context(out, query_, link); BOOST_REQUIRE(out.at("in_active_chain").as_bool()); BOOST_REQUIRE_EQUAL(as_text(out.at("blockhash")), encode_hash(test::block1_hash)); @@ -197,7 +185,7 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_tx_context__unknown__zero_confirmatio const auto link = query_.to_tx(null_hash); boost::json::object out{}; - json::inject_tx_context(out, query_, link); + inject_tx_context(out, query_, link); BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number(), 0); BOOST_REQUIRE(!out.contains("blockhash")); From 8d3b59ab00475159b587fa073bc1027198fae59c Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 20:12:03 -0400 Subject: [PATCH 3/7] Add the rest deploymentinfo endpoint. --- builds/gnu/Makefile.am | 2 + .../libbitcoin-server.vcxproj | 2 + .../libbitcoin-server.vcxproj.filters | 6 ++ .../libbitcoin-server.vcxproj | 2 + .../libbitcoin-server.vcxproj.filters | 6 ++ include/bitcoin/server.hpp | 1 + .../server/interfaces/bitcoind_rest.hpp | 6 +- .../protocols/protocol_bitcoind_rest.hpp | 3 + .../serializers/bitcoind_deployments.hpp | 35 ++++++++ .../server/serializers/serializers.hpp | 1 + src/parsers/bitcoind_target.cpp | 22 ++++++ .../bitcoind/protocol_bitcoind_blockchain.cpp | 41 +--------- .../bitcoind/protocol_bitcoind_rest.cpp | 34 ++++++++ src/serializers/bitcoind_deployments.cpp | 79 +++++++++++++++++++ test/parsers/bitcoind_target.cpp | 21 +++++ test/protocols/bitcoind/bitcoind_rest.cpp | 23 ++++++ 16 files changed, 243 insertions(+), 41 deletions(-) create mode 100644 include/bitcoin/server/serializers/bitcoind_deployments.hpp create mode 100644 src/serializers/bitcoind_deployments.cpp diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 7fcdee55..eb36e721 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -105,6 +105,7 @@ src_libbitcoin_server_la_SOURCES = \ ${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 \ @@ -229,6 +230,7 @@ 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 diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj index 5ebca65e..b5b3505f 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj @@ -177,6 +177,7 @@ + @@ -257,6 +258,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters index 523d4291..469fe578 100644 --- a/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -255,6 +255,9 @@ src\serializers + + src\serializers + src\serializers @@ -491,6 +494,9 @@ include\bitcoin\server\serializers + + include\bitcoin\server\serializers + include\bitcoin\server\serializers diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj index c34d9ae7..3c079136 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj @@ -177,6 +177,7 @@ + @@ -257,6 +258,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters index 523d4291..469fe578 100644 --- a/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -255,6 +255,9 @@ src\serializers + + src\serializers + src\serializers @@ -491,6 +494,9 @@ include\bitcoin\server\serializers + + include\bitcoin\server\serializers + include\bitcoin\server\serializers diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index c52a354f..25d3f770 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -87,6 +87,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/server/interfaces/bitcoind_rest.hpp b/include/bitcoin/server/interfaces/bitcoind_rest.hpp index b0e13aae..6a44e442 100644 --- a/include/bitcoin/server/interfaces/bitcoind_rest.hpp +++ b/include/bitcoin/server/interfaces/bitcoind_rest.hpp @@ -56,7 +56,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 @@ -81,6 +84,7 @@ struct bitcoind_rest_methods using mempool_information = at<12>; using fork_information = at<13>; using tx = at<14>; + using deployment_info = at<15>; }; } // namespace interface diff --git a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp index 692aace0..88b50d6a 100644 --- a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp +++ b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp @@ -79,6 +79,9 @@ class BCS_API protocol_bitcoind_rest const system::hash_cptr& hash, uint8_t type) NOEXCEPT; bool handle_get_chain_information(const code& ec, rest_interface::chain_information) 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/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/serializers.hpp b/include/bitcoin/server/serializers/serializers.hpp index 79683474..36be7141 100644 --- a/include/bitcoin/server/serializers/serializers.hpp +++ b/include/bitcoin/server/serializers/serializers.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/src/parsers/bitcoind_target.cpp b/src/parsers/bitcoind_target.cpp index db2c2003..1a8b6b9e 100644 --- a/src/parsers/bitcoind_target.cpp +++ b/src/parsers/bitcoind_target.cpp @@ -161,6 +161,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") { diff --git a/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp b/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp index 6e4afd31..13f52326 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp @@ -1303,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 { @@ -1358,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; } diff --git a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp index f738d0ee..92345e64 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp @@ -63,6 +63,7 @@ void protocol_bitcoind_rest::start() NOEXCEPT SUBSCRIBE_BITCOIND(handle_get_block_filter_headers, _1, _2, _3, _4, _5); SUBSCRIBE_BITCOIND(handle_get_chain_information, _1, _2); SUBSCRIBE_BITCOIND(handle_get_tx, _1, _2, _3, _4); + SUBSCRIBE_BITCOIND(handle_get_deployment_info, _1, _2, _3); SUBSCRIBE_CHANNEL(get, handle_receive_get, _1, _2); network::protocol::start(); } @@ -627,6 +628,39 @@ bool protocol_bitcoind_rest::handle_get_chain_information(const code& ec, 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/serializers/bitcoind_deployments.cpp b/src/serializers/bitcoind_deployments.cpp new file mode 100644 index 00000000..f7d937ae --- /dev/null +++ b/src/serializers/bitcoind_deployments.cpp @@ -0,0 +1,79 @@ +/** + * 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 + +namespace libbitcoin { +namespace server { + +using namespace system; +using namespace network::rpc; + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +// 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). +object_t deployment_info(const node::query& query, + const system::settings& settings, const database::header_link& link, + size_t height) NOEXCEPT +{ + 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); + + return object_t + { + { "hash", encode_hash(query.get_header_key(link)) }, + { "height", height }, + { "deployments", std::move(deployments) } + }; +} + +BC_POP_WARNING() + +} // namespace server +} // namespace libbitcoin diff --git a/test/parsers/bitcoind_target.cpp b/test/parsers/bitcoind_target.cpp index 4a311882..1a806d62 100644 --- a/test/parsers/bitcoind_target.cpp +++ b/test/parsers/bitcoind_target.cpp @@ -82,6 +82,8 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__error_paths__expected) { "/rest/headers/abc/" + test_hash + ".json", server::error::invalid_number }, { "/rest/headers/3", server::error::invalid_target }, { "/rest/headers/3/nothex.json", server::error::invalid_hash }, + { "/rest/deploymentinfo/nothex.json", server::error::invalid_hash }, + { "/rest/deploymentinfo/" + test_hash + ".bin", server::error::invalid_target }, { "/rest/blockfilter", server::error::missing_target }, { "/rest/blockfilter/extended/" + test_hash + ".json", server::error::invalid_target }, { "/rest/blockfilter/basic", server::error::missing_hash }, @@ -234,6 +236,25 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__headers_no_query__default_count) BOOST_REQUIRE_EQUAL(*hash_of(object), expected_hash); } +// deploymentinfo + +BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__deploymentinfo__no_hash) +{ + request_t out{}; + BOOST_REQUIRE(!bitcoind_target(out, "/rest/deploymentinfo.json")); + BOOST_REQUIRE_EQUAL(out.method, "deployment_info"); + BOOST_REQUIRE(params_of(out).empty()); +} + +BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__deploymentinfo_hash__deployment_info) +{ + request_t out{}; + const auto path = "/rest/deploymentinfo/" + test_hash + ".json"; + BOOST_REQUIRE(!bitcoind_target(out, path)); + BOOST_REQUIRE_EQUAL(out.method, "deployment_info"); + BOOST_REQUIRE_EQUAL(*hash_of(params_of(out)), expected_hash); +} + // blockfilter BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilter_basic__block_filter) diff --git a/test/protocols/bitcoind/bitcoind_rest.cpp b/test/protocols/bitcoind/bitcoind_rest.cpp index 2ff058c2..471c382e 100644 --- a/test/protocols/bitcoind/bitcoind_rest.cpp +++ b/test/protocols/bitcoind/bitcoind_rest.cpp @@ -51,6 +51,29 @@ BOOST_AUTO_TEST_CASE(bitcoind_rest__chaininfo_json__main_nine) BOOST_REQUIRE_EQUAL(as_text(result.at("bestblockhash")), block9); } +BOOST_AUTO_TEST_CASE(bitcoind_rest__deploymentinfo_json__top__height_nine) +{ + const auto result = rest_json("/rest/deploymentinfo.json"); + BOOST_REQUIRE_EQUAL(result.at("height").as_int64(), 9); + BOOST_REQUIRE_EQUAL(as_text(result.at("hash")), block9); + BOOST_REQUIRE(result.at("deployments").is_object()); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rest__deploymentinfo_json__block5__height_five) +{ + const auto result = rest_json("/rest/deploymentinfo/" + block5 + ".json"); + BOOST_REQUIRE_EQUAL(result.at("height").as_int64(), 5); + BOOST_REQUIRE_EQUAL(as_text(result.at("hash")), block5); +} + +// bitcoind reports an unknown deploymentinfo block as a bad request. +BOOST_AUTO_TEST_CASE(bitcoind_rest__deploymentinfo_unknown__bad_request) +{ + const std::string unknown(64, '1'); + const auto result = rest_status("/rest/deploymentinfo/" + unknown + ".json"); + BOOST_REQUIRE(result == bitcoind_setup_fixture::status::bad_request); +} + BOOST_AUTO_TEST_CASE(bitcoind_rest__block_json__block9_with_txs) { const auto result = rest_json("/rest/block/" + block9 + ".json"); From 4f46688645b058f52be1b31851f206e699fb964f Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 20:19:24 -0400 Subject: [PATCH 4/7] Support both blockfilterheaders count forms. --- .../server/interfaces/bitcoind_rest.hpp | 2 +- .../protocols/protocol_bitcoind_rest.hpp | 3 +- src/parsers/bitcoind_target.cpp | 43 ++++++++++-- .../bitcoind/protocol_bitcoind_rest.cpp | 69 ++++++++++++++++--- test/parsers/bitcoind_target.cpp | 11 +++ 5 files changed, 112 insertions(+), 16 deletions(-) diff --git a/include/bitcoin/server/interfaces/bitcoind_rest.hpp b/include/bitcoin/server/interfaces/bitcoind_rest.hpp index 6a44e442..c2847cd5 100644 --- a/include/bitcoin/server/interfaces/bitcoind_rest.hpp +++ b/include/bitcoin/server/interfaces/bitcoind_rest.hpp @@ -41,7 +41,7 @@ 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" }, diff --git a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp index 88b50d6a..0f3b4840 100644 --- a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp +++ b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp @@ -76,7 +76,8 @@ 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_deployment_info(const code& ec, diff --git a/src/parsers/bitcoind_target.cpp b/src/parsers/bitcoind_target.cpp index 1a8b6b9e..2e70bec7 100644 --- a/src/parsers/bitcoind_target.cpp +++ b/src/parsers/bitcoind_target.cpp @@ -253,8 +253,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; @@ -275,14 +275,49 @@ 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/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; + + 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/blockpart///. (libbitcoin extension) if (target == "blockpart") { diff --git a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp index 92345e64..e8e66be2 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp @@ -60,7 +60,7 @@ 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_deployment_info, _1, _2, _3); @@ -558,7 +558,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; @@ -570,27 +570,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(); diff --git a/test/parsers/bitcoind_target.cpp b/test/parsers/bitcoind_target.cpp index 1a806d62..2f85ec0a 100644 --- a/test/parsers/bitcoind_target.cpp +++ b/test/parsers/bitcoind_target.cpp @@ -277,6 +277,17 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_basic__block_f BOOST_REQUIRE(!bitcoind_target(out, path)); BOOST_REQUIRE_EQUAL(out.method, "block_filter_headers"); BOOST_REQUIRE_EQUAL(std::get(params_of(out).at("type").value()), 0u); + BOOST_REQUIRE_EQUAL(std::get(params_of(out).at("count").value()), 5u); +} + +BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_count__legacy_form) +{ + request_t out{}; + const auto path = "/rest/blockfilterheaders/basic/7/" + test_hash + ".json"; + BOOST_REQUIRE(!bitcoind_target(out, path)); + BOOST_REQUIRE_EQUAL(out.method, "block_filter_headers"); + BOOST_REQUIRE_EQUAL(std::get(params_of(out).at("count").value()), 7u); + BOOST_REQUIRE_EQUAL(*hash_of(params_of(out)), expected_hash); } // blockpart From e1b76093e7efc40219a49c12736952af27021aa1 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 20:22:51 -0400 Subject: [PATCH 5/7] Reshape blockpart to the bitcoind query form. --- src/parsers/bitcoind_query.cpp | 15 +++++++----- src/parsers/bitcoind_target.cpp | 24 ++++--------------- .../bitcoind/protocol_bitcoind_rest.cpp | 24 ++++++++++++------- test/parsers/bitcoind_target.cpp | 13 ++++------ test/protocols/bitcoind/bitcoind_rest.cpp | 17 ++++++++++++- 5 files changed, 48 insertions(+), 45 deletions(-) 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_target.cpp b/src/parsers/bitcoind_target.cpp index 2e70bec7..4bb82935 100644 --- a/src/parsers/bitcoind_target.cpp +++ b/src/parsers/bitcoind_target.cpp @@ -318,40 +318,24 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT return error::success; } - // /rest/blockpart///. (libbitcoin extension) + // /rest/blockpart/.?offset=&size= if (target == "blockpart") { if (segment == segments.size()) return error::missing_hash; - const auto hash = to_hash(segments[segment++]); - if (!hash) - return error::invalid_hash; - - if (segment == segments.size()) - return error::missing_target; - - uint32_t offset{}; - if (!to_number(offset, segments[segment++])) - return error::invalid_number; - - if (segment == segments.size()) - return error::missing_target; - 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_part"; params["media"] = media; params["hash"] = hash; - params["offset"] = offset; - params["size"] = size; return error::success; } diff --git a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp index e8e66be2..7ac4075f 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp @@ -133,8 +133,14 @@ 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(); + } } // Media types. @@ -423,16 +429,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: @@ -443,8 +449,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; } diff --git a/test/parsers/bitcoind_target.cpp b/test/parsers/bitcoind_target.cpp index 2f85ec0a..8451423b 100644 --- a/test/parsers/bitcoind_target.cpp +++ b/test/parsers/bitcoind_target.cpp @@ -89,11 +89,8 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__error_paths__expected) { "/rest/blockfilter/basic", server::error::missing_hash }, { "/rest/blockfilterheaders/basic", server::error::missing_hash }, { "/rest/blockpart", server::error::missing_hash }, - { "/rest/blockpart/nothex/0/80.bin", server::error::invalid_hash }, - { "/rest/blockpart/" + test_hash, server::error::missing_target }, - { "/rest/blockpart/" + test_hash + "/abc/80.bin", server::error::invalid_number }, - { "/rest/blockpart/" + test_hash + "/0", server::error::missing_target }, - { "/rest/blockpart/" + test_hash + "/0/abc.bin", server::error::invalid_number } + { "/rest/blockpart/nothex.bin", server::error::invalid_hash }, + { "/rest/blockpart/" + test_hash, server::error::invalid_target } }; for (const auto& [path, expected]: cases) @@ -295,14 +292,12 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_count__legacy_ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockpart__block_part) { request_t out{}; - const auto path = "/rest/blockpart/" + test_hash + "/0/80.bin"; + const auto path = "/rest/blockpart/" + test_hash + ".bin"; BOOST_REQUIRE(!bitcoind_target(out, path)); BOOST_REQUIRE_EQUAL(out.method, "block_part"); const auto& object = params_of(out); - BOOST_REQUIRE_EQUAL(object.size(), 4u); - BOOST_REQUIRE_EQUAL(std::get(object.at("offset").value()), 0u); - BOOST_REQUIRE_EQUAL(std::get(object.at("size").value()), 80u); + BOOST_REQUIRE_EQUAL(object.size(), 2u); BOOST_REQUIRE_EQUAL(*hash_of(object), expected_hash); BOOST_REQUIRE_EQUAL(media_of(object), to_value(media_type::application_octet_stream)); } diff --git a/test/protocols/bitcoind/bitcoind_rest.cpp b/test/protocols/bitcoind/bitcoind_rest.cpp index 471c382e..60d60eb0 100644 --- a/test/protocols/bitcoind/bitcoind_rest.cpp +++ b/test/protocols/bitcoind/bitcoind_rest.cpp @@ -186,11 +186,26 @@ BOOST_AUTO_TEST_CASE(bitcoind_rest__headers_hex__one_header__eighty_bytes) BOOST_AUTO_TEST_CASE(bitcoind_rest__blockpart_bin__block9_header) { - const auto wire = rest_data("/rest/blockpart/" + block9 + "/0/80.bin"); + const auto target = "/rest/blockpart/" + block9 + ".bin?offset=0&size=80"; + const auto wire = rest_data(target); BOOST_REQUIRE_EQUAL(wire.size(), 80u); BOOST_REQUIRE_EQUAL(encode_base16(wire), header9); } +// bitcoind reports missing part parameters as bad requests. +BOOST_AUTO_TEST_CASE(bitcoind_rest__blockpart_no_query__bad_request) +{ + const auto result = rest_status("/rest/blockpart/" + block9 + ".bin"); + BOOST_REQUIRE(result == bitcoind_setup_fixture::status::bad_request); +} + +// bitcoind reports an out of range part as a bad request. +BOOST_AUTO_TEST_CASE(bitcoind_rest__blockpart_excess__bad_request) +{ + const auto target = "/rest/blockpart/" + block9 + ".bin?offset=0&size=1000000"; + BOOST_REQUIRE(rest_status(target) == bitcoind_setup_fixture::status::bad_request); +} + BOOST_AUTO_TEST_CASE(bitcoind_rest__blockfilter_basic__filters_disabled__not_ok) { const auto target = "/rest/blockfilter/basic/" + block9 + ".json"; From b42dd727bf9bc3a5e43933cc234faa00d6e2a880 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 20:27:05 -0400 Subject: [PATCH 6/7] Serve spent tx outputs at the bitcoind path and framing. --- src/parsers/bitcoind_target.cpp | 26 ++++++- .../bitcoind/protocol_bitcoind_rest.cpp | 77 +++++++++++++++---- test/parsers/bitcoind_target.cpp | 7 +- test/protocols/bitcoind/bitcoind_rest.cpp | 15 +++- 4 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/parsers/bitcoind_target.cpp b/src/parsers/bitcoind_target.cpp index 4bb82935..8d4b774f 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; @@ -318,6 +315,27 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT 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; + + 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") { diff --git a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp index 7ac4075f..9042400e 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp @@ -467,23 +467,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; + } + + outs.push_back(output); + } - size_t size{}; - for (const auto& output: spent) - size += output->serialized_size(); + 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) { @@ -492,9 +524,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; } @@ -503,15 +533,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; } } diff --git a/test/parsers/bitcoind_target.cpp b/test/parsers/bitcoind_target.cpp index 8451423b..4c28a928 100644 --- a/test/parsers/bitcoind_target.cpp +++ b/test/parsers/bitcoind_target.cpp @@ -74,7 +74,8 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__error_paths__expected) { "/rest/block/" + test_hash + ".txt", server::error::invalid_target }, { "/rest/block/nothex.json", server::error::invalid_hash }, { "/rest/block/notxdetails", server::error::missing_hash }, - { "/rest/block/spent", server::error::missing_hash }, + { "/rest/spenttxouts", server::error::missing_hash }, + { "/rest/spenttxouts/nothex.json", server::error::invalid_hash }, { "/rest/blockhashbyheight", server::error::missing_target }, { "/rest/blockhashbyheight/abc.json", server::error::invalid_number }, { "/rest/blockhashbyheight/01.json", server::error::invalid_number }, @@ -174,10 +175,10 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__block_notxdetails__block_txs) BOOST_REQUIRE_EQUAL(*hash_of(object), expected_hash); } -BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__block_spent__block_spent_tx_outputs) +BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__spenttxouts__block_spent_tx_outputs) { request_t out{}; - const auto path = "/rest/block/spent/" + test_hash + ".json"; + const auto path = "/rest/spenttxouts/" + test_hash + ".json"; BOOST_REQUIRE(!bitcoind_target(out, path)); BOOST_REQUIRE_EQUAL(out.method, "block_spent_tx_outputs"); BOOST_REQUIRE_EQUAL(*hash_of(params_of(out)), expected_hash); diff --git a/test/protocols/bitcoind/bitcoind_rest.cpp b/test/protocols/bitcoind/bitcoind_rest.cpp index 60d60eb0..fd4f5c3f 100644 --- a/test/protocols/bitcoind/bitcoind_rest.cpp +++ b/test/protocols/bitcoind/bitcoind_rest.cpp @@ -133,10 +133,19 @@ BOOST_AUTO_TEST_CASE(bitcoind_rest__block_notxdetails_json__txid_list) BOOST_REQUIRE(result.at("tx").at(0).is_string()); } -BOOST_AUTO_TEST_CASE(bitcoind_rest__block_spent_json__structured) +// A coinbase-only block undo is one empty per-tx prevout list. +BOOST_AUTO_TEST_CASE(bitcoind_rest__spenttxouts_json__block9__coinbase_only) { - const auto result = rest_json("/rest/block/spent/" + block9 + ".json"); - BOOST_REQUIRE(result.is_array() || result.is_object()); + const auto result = rest_json("/rest/spenttxouts/" + block9 + ".json"); + BOOST_REQUIRE(result.is_array()); + BOOST_REQUIRE_EQUAL(result.as_array().size(), 1u); + BOOST_REQUIRE(result.at(0).as_array().empty()); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rest__spenttxouts_bin__block9__undo_framing) +{ + const auto wire = rest_data("/rest/spenttxouts/" + block9 + ".bin"); + BOOST_REQUIRE_EQUAL(encode_base16(wire), "0100"); } BOOST_AUTO_TEST_CASE(bitcoind_rest__blockhashbyheight_json__height_five__block5) From ed74a28b462350360c1fdf39c6c7436a7761c802 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 26 Aug 2026 20:35:39 -0400 Subject: [PATCH 7/7] Add the bip64 getutxos endpoint. --- .../server/interfaces/bitcoind_rest.hpp | 18 ++- .../protocols/protocol_bitcoind_rest.hpp | 2 + src/parsers/bitcoind_target.cpp | 47 +++++++ .../bitcoind/protocol_bitcoind_rest.cpp | 132 ++++++++++++++++++ test/parsers/bitcoind_target.cpp | 17 +++ test/protocols/bitcoind/bitcoind_rest.cpp | 27 ++++ 6 files changed, 233 insertions(+), 10 deletions(-) diff --git a/include/bitcoin/server/interfaces/bitcoind_rest.hpp b/include/bitcoin/server/interfaces/bitcoind_rest.hpp index c2847cd5..8cf8570e 100644 --- a/include/bitcoin/server/interfaces/bitcoind_rest.hpp +++ b/include/bitcoin/server/interfaces/bitcoind_rest.hpp @@ -43,9 +43,8 @@ struct bitcoind_rest_methods method<"block_filter", 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" }, @@ -78,13 +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 deployment_info = at<15>; + 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/protocols/protocol_bitcoind_rest.hpp b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp index 0f3b4840..bf4dc3cb 100644 --- a/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp +++ b/include/bitcoin/server/protocols/protocol_bitcoind_rest.hpp @@ -80,6 +80,8 @@ class BCS_API protocol_bitcoind_rest 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; diff --git a/src/parsers/bitcoind_target.cpp b/src/parsers/bitcoind_target.cpp index 8d4b774f..bc13e6f1 100644 --- a/src/parsers/bitcoind_target.cpp +++ b/src/parsers/bitcoind_target.cpp @@ -315,6 +315,53 @@ code bitcoind_target(request_t& out, const std::string_view& path) NOEXCEPT return error::success; } + // /rest/getutxos[/checkmempool]/-/.../ + if (target == "getutxos") + { + if (segment == segments.size()) + return error::missing_target; + + // 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") { diff --git a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp index 9042400e..09e86cd5 100644 --- a/src/protocols/bitcoind/protocol_bitcoind_rest.cpp +++ b/src/protocols/bitcoind/protocol_bitcoind_rest.cpp @@ -63,6 +63,7 @@ void protocol_bitcoind_rest::start() NOEXCEPT 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(); @@ -728,6 +729,137 @@ 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 diff --git a/test/parsers/bitcoind_target.cpp b/test/parsers/bitcoind_target.cpp index 4c28a928..618cb25e 100644 --- a/test/parsers/bitcoind_target.cpp +++ b/test/parsers/bitcoind_target.cpp @@ -74,6 +74,11 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__error_paths__expected) { "/rest/block/" + test_hash + ".txt", server::error::invalid_target }, { "/rest/block/nothex.json", server::error::invalid_hash }, { "/rest/block/notxdetails", server::error::missing_hash }, + { "/rest/getutxos", server::error::missing_target }, + { "/rest/getutxos/checkmempool", server::error::missing_target }, + { "/rest/getutxos/nothex-0.json", server::error::invalid_hash }, + { "/rest/getutxos/" + test_hash + "-abc.json", server::error::invalid_number }, + { "/rest/getutxos/" + test_hash + ".json", server::error::invalid_hash }, { "/rest/spenttxouts", server::error::missing_hash }, { "/rest/spenttxouts/nothex.json", server::error::invalid_hash }, { "/rest/blockhashbyheight", server::error::missing_target }, @@ -175,6 +180,18 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__block_notxdetails__block_txs) BOOST_REQUIRE_EQUAL(*hash_of(object), expected_hash); } +BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__getutxos__outpoints) +{ + request_t out{}; + const auto path = "/rest/getutxos/checkmempool/" + test_hash + "-0/" + test_hash + "-7.json"; + BOOST_REQUIRE(!bitcoind_target(out, path)); + BOOST_REQUIRE_EQUAL(out.method, "get_utxos"); + + const auto& object = params_of(out); + BOOST_REQUIRE_EQUAL(media_of(object), to_value(media_type::application_json)); + BOOST_REQUIRE_EQUAL(std::get(object.at("outpoints").value()).size(), 2u); +} + BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__spenttxouts__block_spent_tx_outputs) { request_t out{}; diff --git a/test/protocols/bitcoind/bitcoind_rest.cpp b/test/protocols/bitcoind/bitcoind_rest.cpp index fd4f5c3f..23d36eec 100644 --- a/test/protocols/bitcoind/bitcoind_rest.cpp +++ b/test/protocols/bitcoind/bitcoind_rest.cpp @@ -133,6 +133,33 @@ BOOST_AUTO_TEST_CASE(bitcoind_rest__block_notxdetails_json__txid_list) BOOST_REQUIRE(result.at("tx").at(0).is_string()); } +BOOST_AUTO_TEST_CASE(bitcoind_rest__getutxos_json__block1_coinbase__hit) +{ + const auto txid = encode_hash(test::block1.transactions_ptr()->front()->hash(false)); + const auto result = rest_json("/rest/getutxos/" + txid + "-0.json"); + BOOST_REQUIRE_EQUAL(result.at("chainHeight").as_int64(), 9); + BOOST_REQUIRE_EQUAL(as_text(result.at("chaintipHash")), block9); + BOOST_REQUIRE_EQUAL(as_text(result.at("bitmap")), "1"); + BOOST_REQUIRE_EQUAL(result.at("utxos").as_array().size(), 1u); + BOOST_REQUIRE_EQUAL(result.at("utxos").at(0).at("height").as_int64(), 1); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rest__getutxos_json__checkmempool_miss__empty) +{ + const auto txid = encode_hash(test::block1.transactions_ptr()->front()->hash(false)); + const auto result = rest_json("/rest/getutxos/checkmempool/" + txid + "-1.json"); + BOOST_REQUIRE_EQUAL(as_text(result.at("bitmap")), "0"); + BOOST_REQUIRE(result.at("utxos").as_array().empty()); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rest__getutxos_bin__miss__bip64_framing) +{ + const std::string unknown(64, '1'); + const auto wire = rest_data("/rest/getutxos/" + unknown + "-0.bin"); + BOOST_REQUIRE_EQUAL(wire.size(), 39u); + BOOST_REQUIRE_EQUAL(wire.at(0), 9u); +} + // A coinbase-only block undo is one empty per-tx prevout list. BOOST_AUTO_TEST_CASE(bitcoind_rest__spenttxouts_json__block9__coinbase_only) {