diff --git a/CHANGELOG.md b/CHANGELOG.md index d7f1b13d..ec70f8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Transaction::check` for context-free consensus validation of a transaction. Returns `TxCheckResult::Valid` on success or `TxCheckResult::Invalid(TxValidationResult)` on failure. - Added `TxValidationResult` enum with all transaction validation result variants. - Added `ChainParams::new_signet` and `ContextBuilder::signet` to configure a custom signet from a user-provided challenge. +- Added `TransactionExt::is_coinbase` to check whether a transaction is a coinbase transaction. +- Added `Coin::new` to construct a coin from a transaction output, confirmation height, and coinbase flag, for use with `ChainstateManager::validate_block` when validating a block without the full UTXO set present. +- Added `TxOutPoint::new` to construct a new outpoint from a transaction ID and output index. +- Added `ChainstateManager::validate_block` to validate a block against caller-supplied spent coins, without requiring the full UTXO set to be present. Returns a `ValidateBlockResult` enum carrying the validation state on failure. ### Changed - The `verify` function's `flags` parameter now uses `ScriptVerificationFlags` instead of `u32`, making the type explicit in the public API. diff --git a/libbitcoinkernel-sys/CHANGELOG.md b/libbitcoinkernel-sys/CHANGELOG.md index 5ad65bb6..52e03aca 100644 --- a/libbitcoinkernel-sys/CHANGELOG.md +++ b/libbitcoinkernel-sys/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New `btck_tx_validation_state_create`, `btck_tx_validation_state_get_validation_mode`, `btck_tx_validation_state_get_tx_validation_result` and `btck_tx_validation_state_destroy` for managing and inspecting transaction validation state - New `btck_transaction_check` for context-free consensus validation of transactions (reachable results: `UNSET` on success, `CONSENSUS` on failure) - New `btck_chain_parameters_create_signet` for creating chain parameters for a custom signet from a user-provided challenge. +- New `btck_transaction_is_coinbase` for checking whether a transaction is a coinbase transaction +- New `btck_coin_create` for constructing a `btck_Coin` from a transaction output, confirmation height, and coinbase flag +- New `btck_transaction_out_point_create` for constructing a `btck_TransactionOutPoint` from a txid and output index +- New `btck_chainstate_manager_validate_block` for validating a block against caller-supplied spent coins (as parallel `btck_TransactionOutPoint`/`btck_Coin` arrays) without requiring the full UTXO set to be present ### Changed - `btck_block_header_create` now asserts that the input buffer is non-null and exactly 80 bytes; previously non-null buffer of any length was accepted diff --git a/libbitcoinkernel-sys/bitcoin/.github/workflows/ci.yml b/libbitcoinkernel-sys/bitcoin/.github/workflows/ci.yml index 34dd688e..bce9c603 100644 --- a/libbitcoinkernel-sys/bitcoin/.github/workflows/ci.yml +++ b/libbitcoinkernel-sys/bitcoin/.github/workflows/ci.yml @@ -463,6 +463,12 @@ jobs: timeout-minutes: 120 file-env: './ci/test/00_setup_env_mac_cross_intel.sh' + - name: 'NetBSD Cross' + warp-runner: 'warp-ubuntu-latest-x64-8x' + fallback-runner: 'ubuntu-latest' + timeout-minutes: 120 + file-env: './ci/test/00_setup_env_netbsd_cross.sh' + - name: 'FreeBSD Cross' warp-runner: 'warp-ubuntu-latest-x64-8x' fallback-runner: 'ubuntu-latest' diff --git a/libbitcoinkernel-sys/bitcoin/SECURITY.md b/libbitcoinkernel-sys/bitcoin/SECURITY.md index 5a63a762..17dedcd7 100644 --- a/libbitcoinkernel-sys/bitcoin/SECURITY.md +++ b/libbitcoinkernel-sys/bitcoin/SECURITY.md @@ -13,7 +13,6 @@ The following keys may be used to communicate sensitive information to developer | Name | Fingerprint | |------|-------------| -| Pieter Wuille | 133E AC17 9436 F14A 5CF1 B794 860F EB80 4E66 9320 | | Michael Ford | E777 299F C265 DD04 7930 70EB 944D 35F9 AC3D B76A | | Ava Chow | 1528 1230 0785 C964 44D3 334D 1756 5732 E08E 5E41 | | Niklas Gögge | 2CBB F208 E594 BF43 9B5F 276C 7465 CFFF 6793 242E | diff --git a/libbitcoinkernel-sys/bitcoin/ci/test/00_setup_env_netbsd_cross.sh b/libbitcoinkernel-sys/bitcoin/ci/test/00_setup_env_netbsd_cross.sh new file mode 100755 index 00000000..9fb4c18a --- /dev/null +++ b/libbitcoinkernel-sys/bitcoin/ci/test/00_setup_env_netbsd_cross.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# Copyright (c) The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit. + +export LC_ALL=C.UTF-8 + +export CONTAINER_NAME=ci_netbsd_cross +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" +export APT_LLVM_V="22" +export HOST=x86_64-unknown-netbsd +export NETBSD_VERSION=11.0_RC6 +export NETBSD_SDK_BASENAME="netbsd-${HOST}-${NETBSD_VERSION}" +export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} lld-${APT_LLVM_V}" +export SYSROOT="--sysroot=${DEPENDS_DIR}/SDKs/${NETBSD_SDK_BASENAME}" +export DEP_OPTS="build_CC=clang build_CXX=clang++ \ + CC='clang --target=${HOST} ${SYSROOT}' \ + CXX='clang++ --target=${HOST} ${SYSROOT} -stdlib=libstdc++' \ + LDFLAGS='-fuse-ld=lld -lgcc_s' \ + AR=llvm-ar-${APT_LLVM_V} \ + NM=llvm-nm-${APT_LLVM_V} \ + OBJCOPY=llvm-objcopy-${APT_LLVM_V} \ + OBJDUMP=llvm-objdump-${APT_LLVM_V} \ + RANLIB=llvm-ranlib-${APT_LLVM_V} \ + STRIP=llvm-strip-${APT_LLVM_V}" +export GOAL="install" +export BITCOIN_CONFIG="\ + --preset=dev-mode \ + -DBUILD_GUI=OFF \ + -DREDUCE_EXPORTS=ON \ + -DWITH_USDT=OFF \ +" +export RUN_UNIT_TESTS=false +export RUN_FUNCTIONAL_TESTS=false diff --git a/libbitcoinkernel-sys/bitcoin/ci/test/01_base_install.sh b/libbitcoinkernel-sys/bitcoin/ci/test/01_base_install.sh index 652416b9..f619a088 100755 --- a/libbitcoinkernel-sys/bitcoin/ci/test/01_base_install.sh +++ b/libbitcoinkernel-sys/bitcoin/ci/test/01_base_install.sh @@ -118,6 +118,17 @@ if [ -n "$XCODE_VERSION" ] && [ ! -d "${DEPENDS_DIR}/SDKs/${OSX_SDK_BASENAME}" ] tar -C "${DEPENDS_DIR}/SDKs" -xf "$OSX_SDK_PATH" fi +if [ -n "$NETBSD_VERSION" ] && [ ! -d "${DEPENDS_DIR}/SDKs/${NETBSD_SDK_BASENAME}" ]; then + mkdir -p "${DEPENDS_DIR}/SDKs/${NETBSD_SDK_BASENAME}" + for NETBSD_SDK_FILENAME in base.tar.xz comp.tar.xz; do + NETBSD_SDK_PATH="${DEPENDS_DIR}/sdk-sources/${NETBSD_SDK_FILENAME}" + if [ ! -f "$NETBSD_SDK_PATH" ]; then + ${CI_RETRY_EXE} curl --location --fail "https://cdn.netbsd.org/pub/NetBSD/NetBSD-${NETBSD_VERSION}/amd64/binary/sets/${NETBSD_SDK_FILENAME}" -o "$NETBSD_SDK_PATH" + fi + tar -C "${DEPENDS_DIR}/SDKs/${NETBSD_SDK_BASENAME}" -xf "$NETBSD_SDK_PATH" + done +fi + if [ -n "$FREEBSD_VERSION" ] && [ ! -d "${DEPENDS_DIR}/SDKs/${FREEBSD_SDK_BASENAME}" ]; then FREEBSD_SDK_FILENAME="base-${FREEBSD_VERSION}.txz" FREEBSD_SDK_PATH="${DEPENDS_DIR}/sdk-sources/${FREEBSD_SDK_FILENAME}" diff --git a/libbitcoinkernel-sys/bitcoin/contrib/seeds/README.md b/libbitcoinkernel-sys/bitcoin/contrib/seeds/README.md index 2eb68467..388ddd54 100644 --- a/libbitcoinkernel-sys/bitcoin/contrib/seeds/README.md +++ b/libbitcoinkernel-sys/bitcoin/contrib/seeds/README.md @@ -15,8 +15,7 @@ DNS seed, virtu's crawler, and asmap community AS map data. Run the following co from the `/contrib/seeds` directory: ``` -curl https://bitcoin.sipa.be/seeds.txt.gz | gzip -dc > seeds_main.txt -curl https://21.ninja/seeds.txt.gz | gzip -dc >> seeds_main.txt +curl https://21.ninja/seeds.txt.gz | gzip -dc > seeds_main.txt curl https://mainnet.achownodes.xyz/seeds.txt.gz | gzip -dc >> seeds_main.txt curl https://signet.achownodes.xyz/seeds.txt.gz | gzip -dc > seeds_signet.txt curl https://testnet.achownodes.xyz/seeds.txt.gz | gzip -dc > seeds_test.txt diff --git a/libbitcoinkernel-sys/bitcoin/depends/hosts/netbsd.mk b/libbitcoinkernel-sys/bitcoin/depends/hosts/netbsd.mk index 0e1256c3..df7a5ea9 100644 --- a/libbitcoinkernel-sys/bitcoin/depends/hosts/netbsd.mk +++ b/libbitcoinkernel-sys/bitcoin/depends/hosts/netbsd.mk @@ -1,5 +1,6 @@ netbsd_CFLAGS= netbsd_CXXFLAGS= +netbsd_LDFLAGS= ifneq ($(LTO),) netbsd_AR = $(host_toolchain)gcc-ar diff --git a/libbitcoinkernel-sys/bitcoin/doc/release-notes-35634.md b/libbitcoinkernel-sys/bitcoin/doc/release-notes-35634.md index ccbd7fd7..2398305b 100644 --- a/libbitcoinkernel-sys/bitcoin/doc/release-notes-35634.md +++ b/libbitcoinkernel-sys/bitcoin/doc/release-notes-35634.md @@ -1,9 +1,8 @@ Indexes ------- -- The transaction output spender index (`-txospenderindex`) now stores one byte - less per spender entry for newly indexed blocks. Existing indexes remain - compatible and no action is required. To apply the same saving to entries - indexed before upgrading, stop the node, delete the +- The transaction output spender index (`-txospenderindex`) now uses less disk + space. Existing indexes remain compatible and no action is required. To reclaim + the space for data indexed before upgrading, stop the node, delete the `/indexes/txospenderindex/` directory, and restart with - `-txospenderindex` enabled to rebuild it. + `-txospenderindex` enabled to rebuild it. (#35634, #35568) diff --git a/libbitcoinkernel-sys/bitcoin/doc/release-notes/release-notes-29.4.md b/libbitcoinkernel-sys/bitcoin/doc/release-notes/release-notes-29.4.md new file mode 100644 index 00000000..3ff905db --- /dev/null +++ b/libbitcoinkernel-sys/bitcoin/doc/release-notes/release-notes-29.4.md @@ -0,0 +1,107 @@ +Bitcoin Core version 29.4 is now available from: + + + +This release includes various bug fixes and performance +improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + + +To receive security and update notifications, please subscribe to: + + + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes in some cases), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on macOS) +or `bitcoind`/`bitcoin-qt` (on Linux). + +Upgrading directly from a version of Bitcoin Core that has reached its EOL is +possible, but it might take some time if the data directory needs to be migrated. Old +wallet versions of Bitcoin Core are generally supported. + +Compatibility +============== + +Bitcoin Core is supported and tested on operating systems using the +Linux Kernel 3.17+, macOS 13+, and Windows 10+. Bitcoin +Core should also work on most other Unix-like systems but is not as +frequently tested on them. It is not recommended to use Bitcoin Core on +unsupported systems. + +Notable changes +=============== + +This release fixes an issue where the chainstate database would repeatedly +rewrite large portions of itself, causing excessive disk reads and writes +during normal operation. + +### Validation + +- #35209 validation: correct lifetime of precomputed tx data +- #35465 coins: compact chainstate regularly + +### Leveldb + +- #61(bitcoin-core/leveldb): Disable seek compaction + +### Net + +- #34093 netif: fix compilation warning in QueryDefaultGatewayImpl() + +### Wallet + +- #35228 wallet: use outpoint when estimating input size + +### Build + +- #34228 depends: Unset SOURCE_DATE_EPOCH in gen_id script +- #34848 cmake: Migrate away from deprecated SQLite3 target + +### Test + +- #34918 fuzz: [refactor] Remove unused g_setup pointers + +### Doc + +- #34510 doc: fix broken bpftrace installation link +- #34561 wallet: rpc: manpage: fix example missing `fee_rate` argument +- #34671 doc: Update Guix install for Debian/Ubuntu +- #35283 doc: mention -DWITH_ZMQ=ON in BSD build guides + +### CI + +- #35202 ci: restore sockets in i686, no IPC job +- #35378 ci: switch runners from cirrus to warpbuild +- #35408 ci: 35378 followups + +### Misc + +- #35175 multi_index: fix compilation failure with boost >= 1.91 + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- andrewtoth +- Cory Fields +- Daniel Pfeifer +- darosior +- fanquake +- Hennadii Stepanov +- jayvaliya +- junbyjun1238 +- Lőrinc +- MarcoFalke +- SomberNight +- ToRyVand +- willcl-ark + +As well as to everyone that helped with translations on +[Transifex](https://explore.transifex.com/bitcoin/bitcoin/). diff --git a/libbitcoinkernel-sys/bitcoin/src/dbwrapper.cpp b/libbitcoinkernel-sys/bitcoin/src/dbwrapper.cpp index ffe6f267..ed914273 100644 --- a/libbitcoinkernel-sys/bitcoin/src/dbwrapper.cpp +++ b/libbitcoinkernel-sys/bitcoin/src/dbwrapper.cpp @@ -136,12 +136,12 @@ static void SetMaxOpenFiles(leveldb::Options *options) { options->max_open_files, default_open_files); } -static leveldb::Options GetOptions(size_t nCacheSize) +static leveldb::Options GetOptions(size_t nCacheSize, bool bloom_filter) { leveldb::Options options; options.block_cache = leveldb::NewLRUCache(nCacheSize / 2); options.write_buffer_size = nCacheSize / 4; // up to two write buffers may be held in memory simultaneously - options.filter_policy = leveldb::NewBloomFilterPolicy(10); + options.filter_policy = bloom_filter ? leveldb::NewBloomFilterPolicy(10) : nullptr; options.compression = leveldb::kNoCompression; options.info_log = new CBitcoinLevelDBLogger(); if (leveldb::kMajorVersion > 1 || (leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16)) { @@ -225,7 +225,7 @@ CDBWrapper::CDBWrapper(const DBParams& params) DBContext().iteroptions.verify_checksums = true; DBContext().iteroptions.fill_cache = false; DBContext().syncoptions.sync = true; - DBContext().options = GetOptions(params.cache_bytes); + DBContext().options = GetOptions(params.cache_bytes, params.bloom_filter); DBContext().options.create_if_missing = true; DBContext().options.max_file_size = params.max_file_size; assert(!(params.testing_env && params.memory_only)); diff --git a/libbitcoinkernel-sys/bitcoin/src/dbwrapper.h b/libbitcoinkernel-sys/bitcoin/src/dbwrapper.h index 2c1f8039..1eb68a3b 100644 --- a/libbitcoinkernel-sys/bitcoin/src/dbwrapper.h +++ b/libbitcoinkernel-sys/bitcoin/src/dbwrapper.h @@ -50,6 +50,8 @@ struct DBParams { //! If true, store data obfuscated via simple XOR. If false, XOR with a //! zero'd byte array. bool obfuscate = false; + //! If true, build a LevelDB bloom filter to accelerate point lookups. + bool bloom_filter = true; //! Passed-through options. DBOptions options{}; //! If non-null, use this as the leveldb::Env instead of the default. diff --git a/libbitcoinkernel-sys/bitcoin/src/index/base.cpp b/libbitcoinkernel-sys/bitcoin/src/index/base.cpp index 906ed265..5820448b 100644 --- a/libbitcoinkernel-sys/bitcoin/src/index/base.cpp +++ b/libbitcoinkernel-sys/bitcoin/src/index/base.cpp @@ -65,13 +65,14 @@ CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash) return locator; } -BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) : +BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate, bool f_bloom) : CDBWrapper{DBParams{ .path = path, .cache_bytes = n_cache_size, .memory_only = f_memory, .wipe_data = f_wipe, .obfuscate = f_obfuscate, + .bloom_filter = f_bloom, .options = [] { DBOptions options; node::ReadDatabaseArgs(gArgs, options); return options; }()}} {} @@ -273,12 +274,23 @@ void BaseIndex::Sync() } } -bool BaseIndex::Commit() +void BaseIndex::Commit() { // Don't commit anything if we haven't indexed any block yet // (this could happen if init is interrupted). bool ok = m_best_block_index != nullptr; if (ok) { + // Don't commit if the index best block is not an ancestor of the chainstate's last flushed + // block. Otherwise, after an unclean shutdown, the index could be + // persisted ahead of a chainstate it can no longer roll back to, which + // would corrupt indexes with state (e.g. coinstatsindex). + const CBlockIndex* index_tip = m_best_block_index.load(); + const CBlockIndex* last_flushed = WITH_LOCK(::cs_main, return m_chainstate->GetLastFlushedBlock()); + if (!last_flushed || last_flushed->GetAncestor(index_tip->nHeight) != index_tip) { + LogDebug(BCLog::COINDB, "Skipping commit, index is ahead of flushed chainstate (index height %d, last flush at height %d)", + index_tip->nHeight, last_flushed ? last_flushed->nHeight : -1); + return; + } CDBBatch batch(GetDB()); ok = CustomCommit(batch); if (ok) { @@ -288,9 +300,7 @@ bool BaseIndex::Commit() } if (!ok) { LogError("Failed to commit latest %s state", GetName()); - return false; } - return true; } bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip) diff --git a/libbitcoinkernel-sys/bitcoin/src/index/base.h b/libbitcoinkernel-sys/bitcoin/src/index/base.h index 6d7e86ec..73282a96 100644 --- a/libbitcoinkernel-sys/bitcoin/src/index/base.h +++ b/libbitcoinkernel-sys/bitcoin/src/index/base.h @@ -65,7 +65,7 @@ class BaseIndex : public CValidationInterface { public: DB(const fs::path& path, size_t n_cache_size, - bool f_memory = false, bool f_wipe = false, bool f_obfuscate = false); + bool f_memory = false, bool f_wipe = false, bool f_obfuscate = false, bool f_bloom = true); /// Read block locator of the chain that the index is in sync with. /// Note, the returned locator will be empty if no record exists. @@ -94,14 +94,10 @@ class BaseIndex : public CValidationInterface CThreadInterrupt m_interrupt; /// Write the current index state (eg. chain block locator and subclass-specific items) to disk. - /// - /// Recommendations for error handling: - /// If called on a successor of the previous committed best block in the index, the index can - /// continue processing without risk of corruption, though the index state will need to catch up - /// from further behind on reboot. If the new state is not a successor of the previous state (due - /// to a chain reorganization), the index must halt until Commit succeeds or else it could end up - /// getting corrupted. - bool Commit(); + /// Will skip the commit if no block has been indexed yet or if the index's best block is + /// ahead of the chainstate's last flushed block. This avoids persisting state an unclean shutdown + /// could not roll back from. A later call commits when the chainstate has flushed far enough. + void Commit(); /// Loop over disconnected blocks and call CustomRemove. bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip); diff --git a/libbitcoinkernel-sys/bitcoin/src/index/txospenderindex.cpp b/libbitcoinkernel-sys/bitcoin/src/index/txospenderindex.cpp index 50b9bfeb..304104df 100644 --- a/libbitcoinkernel-sys/bitcoin/src/index/txospenderindex.cpp +++ b/libbitcoinkernel-sys/bitcoin/src/index/txospenderindex.cpp @@ -62,7 +62,7 @@ struct DBKey { }; TxoSpenderIndex::TxoSpenderIndex(std::unique_ptr chain, size_t n_cache_size, bool f_memory, bool f_wipe) - : BaseIndex(std::move(chain), "txospenderindex", "txospenderidx"), m_db{std::make_unique(gArgs.GetDataDirNet() / "indexes" / "txospenderindex" / "db", n_cache_size, f_memory, f_wipe)} + : BaseIndex(std::move(chain), "txospenderindex", "txospenderidx"), m_db{std::make_unique(gArgs.GetDataDirNet() / "indexes" / "txospenderindex" / "db", n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/false)} { if (!m_db->Read("siphash_key", m_siphash_key)) { FastRandomContext rng(false); diff --git a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/usage.md b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/usage.md index f387db4d..df103e9f 100644 --- a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/usage.md +++ b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/usage.md @@ -10,7 +10,7 @@ _libmultiprocess_ is a library and code generator that allows calling C++ class The `*.capnp` data definition files are consumed by the _libmultiprocess_ code generator and each `X.capnp` file generates `X.capnp.c++`, `X.capnp.h`, `X.capnp.proxy-client.c++`, `X.capnp.proxy-server.c++`, `X.capnp.proxy-types.c++`, `X.capnp.proxy-types.h`, and `X.capnp.proxy.h` output files. The generated files include `mp::ProxyClient` and `mp::ProxyServer` class specializations for all the interfaces in the `.capnp` files. These allow methods on C++ objects in one process to be called from other processes over IPC sockets. -The `ProxyServer` objects help translate IPC requests from a socket to method calls on a local object. The `ProxyServer` objects are just used internally by the `mp::ServeStream(loop, socket, wrapped_object)` and `mp::ListenConnections(loop, socket, wrapped_object)` functions, and aren't exposed externally. The `ProxyClient` classes are exposed, and returned from the `mp::ConnectStream(loop, socket)` function and meant to be used directly. The classes implement methods described in `.capnp` definitions, and whenever any method is called, a request with the method arguments is sent over the associated IPC connection, and the corresponding `wrapped_object` method on the other end of the connection is called, with the `ProxyClient` method blocking until it returns and forwarding back any return value to the `ProxyClient` method caller. +The `ProxyServer` objects help translate IPC requests from a socket to method calls on a local object. The `ProxyServer` objects are just used internally by the `mp::ServeStream(loop, socket, wrapped_object)` and `mp::ListenConnections(loop, socket, wrapped_object[, max_connections])` functions, and aren't exposed externally. The `ProxyClient` classes are exposed, and returned from the `mp::ConnectStream(loop, socket)` function and meant to be used directly. The classes implement methods described in `.capnp` definitions, and whenever any method is called, a request with the method arguments is sent over the associated IPC connection, and the corresponding `wrapped_object` method on the other end of the connection is called, with the `ProxyClient` method blocking until it returns and forwarding back any return value to the `ProxyClient` method caller. ## Example diff --git a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/versions.md b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/versions.md index 3cfa28e3..83b5cf53 100644 --- a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/versions.md +++ b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/doc/versions.md @@ -7,8 +7,24 @@ Library versions are tracked with simple Versioning policy is described in the [version.h](../include/mp/version.h) include. -## v11 +## v12 - Current unstable version. +- Adds an optional per-listener `max_connections` parameter to `ListenConnections()` + so servers can stop accepting new connections when a local connection cap is reached, + and resume accepting after existing connections disconnect. + +## [v11.0](https://github.com/bitcoin-core/libmultiprocess/commits/v11.0) +- Adds `makePool` method on `ThreadMap` to support thread pool routing, allowing requests without a specific client thread to be dispatched to a pool using a shortest-queue strategy ([#283](https://github.com/bitcoin-core/libmultiprocess/pull/283)). +- Adds `std::unordered_set` support, a `BuildList` helper, and a `ReadList` helper to reduce duplication in list build and read handlers ([#277](https://github.com/bitcoin-core/libmultiprocess/pull/277), [#285](https://github.com/bitcoin-core/libmultiprocess/pull/285)). +- Adds support for translating C++ `std::optional` struct fields to pairs of `T` + `hasT :Bool` Cap'n Proto struct fields, allowing unset optional primitive fields to be represented ([#243](https://github.com/bitcoin-core/libmultiprocess/pull/243)). +- Produces more readable log output for Proxy object lifecycle events and IPC server-side failures ([#218](https://github.com/bitcoin-core/libmultiprocess/pull/218)). +- Handles exceptions thrown by `destroy` methods by logging instead of aborting ([#273](https://github.com/bitcoin-core/libmultiprocess/pull/273)). This can prevent server crashes when non-libmultiprocess clients disconnect without destroying objects, in the case where a server object owns client objects and the server destructor tries to call the disconnected client to free them ([#219](https://github.com/bitcoin-core/libmultiprocess/issues/219)). +- Handles unexpected exceptions thrown by callbacks (that should never happen) by logging errors instead of deadlocking ([#260](https://github.com/bitcoin-core/libmultiprocess/pull/260)). +- Fixes a rare mptest hang on musl builds caused by a lost wakeup bug in `Waiter` ([#295](https://github.com/bitcoin-core/libmultiprocess/pull/295)). +- Fixes a race condition in a log print detected by TSan ([#286](https://github.com/bitcoin-core/libmultiprocess/pull/286)). +- Build improvements: makes `target_capnp_sources` work correctly when libmultiprocess is used as a CMake subproject ([#289](https://github.com/bitcoin-core/libmultiprocess/pull/289)), adds `mp_headers` target for better lint tool support ([#291](https://github.com/bitcoin-core/libmultiprocess/pull/291)), and fixes compatibility with recent Nix and CMake 4.0 ([#238](https://github.com/bitcoin-core/libmultiprocess/pull/238)). +- Test, CI, documentation, and minor code improvements: design document corrections ([#278](https://github.com/bitcoin-core/libmultiprocess/pull/278)), field constant comments ([#279](https://github.com/bitcoin-core/libmultiprocess/pull/279)), clang-tidy fix ([#292](https://github.com/bitcoin-core/libmultiprocess/pull/292)), new smoke test for double-precision float values ([#294](https://github.com/bitcoin-core/libmultiprocess/pull/294)), new test for recursive async IPC calls ([#301](https://github.com/bitcoin-core/libmultiprocess/pull/301)), removal of libevent from Core CI builds ([#299](https://github.com/bitcoin-core/libmultiprocess/pull/299)), and rename of `EventLoop::m_num_clients` to `m_num_refs` ([#302](https://github.com/bitcoin-core/libmultiprocess/pull/302)). +- Used in Bitcoin Core master branch, pulled in by [#35661](https://github.com/bitcoin/bitcoin/pull/35661). ## [v10.0](https://github.com/bitcoin-core/libmultiprocess/commits/v10.0) - Increases spawn test timeout to avoid spurious failures. diff --git a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/proxy-io.h b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/proxy-io.h index 259696a0..ec9a1539 100644 --- a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/proxy-io.h +++ b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/proxy-io.h @@ -13,7 +13,9 @@ #include #include +#include #include +#include #include #include #include @@ -25,6 +27,7 @@ namespace mp { struct ThreadContext; +struct Listener; struct InvokeContext { @@ -360,6 +363,12 @@ class EventLoop //! Hook called on the worker thread just before returning results. std::function testing_hook_async_request_done; + + //! Hook called on the event loop thread when a client has connected. + std::function testing_hook_connected; + + //! Hook called on the event loop thread when a client has disconnected. + std::function testing_hook_disconnected; }; //! Single element task queue used to handle recursive capnp calls. (If the @@ -842,8 +851,8 @@ std::unique_ptr> ConnectStream(EventLoop& loop, int f //! handles requests from the stream by calling the init object. Embed the //! ProxyServer in a Connection object that is stored and erased if //! disconnected. This should be called from the event loop thread. -template -void _Serve(EventLoop& loop, kj::Own&& stream, InitImpl& init) +template +void _Serve(EventLoop& loop, kj::Own&& stream, InitImpl& init, OnDisconnect&& on_disconnect) { loop.m_incoming_connections.emplace_front(loop, kj::mv(stream), [&](Connection& connection) { // Disable deleter so proxy server object doesn't attempt to delete the @@ -853,23 +862,46 @@ void _Serve(EventLoop& loop, kj::Own&& stream, InitImpl& init }); auto it = loop.m_incoming_connections.begin(); MP_LOG(loop, Log::Info) << "IPC server: socket connected."; - it->onDisconnect([&loop, it] { + if (loop.testing_hook_connected) loop.testing_hook_connected(); + it->onDisconnect([&loop, it, on_disconnect = std::forward(on_disconnect)]() mutable { MP_LOG(loop, Log::Info) << "IPC server: socket disconnected."; loop.m_incoming_connections.erase(it); + on_disconnect(); + if (loop.testing_hook_disconnected) loop.testing_hook_disconnected(); }); } -//! Given connection receiver and an init object, handle incoming connections by -//! calling _Serve, to create ProxyServer objects and forward requests to the -//! init object. +struct Listener +{ + explicit Listener(kj::Own&& receiver, std::optional max_connections) + : m_receiver(kj::mv(receiver)), m_max_connections(max_connections) {} + + bool atCapacity() const + { + return m_max_connections && m_active_connections >= *m_max_connections; + } + + kj::Own m_receiver; + std::optional m_max_connections; + size_t m_active_connections{0}; +}; + template -void _Listen(EventLoop& loop, kj::Own&& listener, InitImpl& init) +void _Listen(const std::shared_ptr& listener, EventLoop& loop, InitImpl& init) { - auto* ptr = listener.get(); - loop.m_task_set->add(ptr->accept().then( - [&loop, &init, listener = kj::mv(listener)](kj::Own&& stream) mutable { - _Serve(loop, kj::mv(stream), init); - _Listen(loop, kj::mv(listener), init); + if (listener->atCapacity()) return; + + auto* receiver = listener->m_receiver.get(); + loop.m_task_set->add(receiver->accept().then( + [&loop, &init, listener](kj::Own&& stream) { + ++listener->m_active_connections; + _Serve(loop, kj::mv(stream), init, [&loop, &init, listener] { + const bool resume_accept{listener->atCapacity()}; + assert(listener->m_active_connections > 0); + --listener->m_active_connections; + if (resume_accept) _Listen(listener, loop, init); + }); + _Listen(listener, loop, init); })); } @@ -879,18 +911,22 @@ template void ServeStream(EventLoop& loop, int fd, InitImpl& init) { _Serve( - loop, loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP), init); + loop, + loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP), + init, + [] {}); } //! Given listening socket file descriptor and an init object, handle incoming //! connections and requests by calling methods on the Init object. template -void ListenConnections(EventLoop& loop, int fd, InitImpl& init) +void ListenConnections(EventLoop& loop, int fd, InitImpl& init, std::optional max_connections = std::nullopt) { loop.sync([&]() { - _Listen(loop, + auto listener{std::make_shared( loop.m_io_context.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP), - init); + max_connections)}; + _Listen(listener, loop, init); }); } diff --git a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/version.h b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/version.h index 423ed460..4587a288 100644 --- a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/version.h +++ b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/include/mp/version.h @@ -24,7 +24,7 @@ //! pointing at the prior merge commit. The /doc/versions.md file should also be //! updated, noting any significant or incompatible changes made since the //! previous version. -#define MP_MAJOR_VERSION 11 +#define MP_MAJOR_VERSION 12 //! Minor version number. Should be incremented in stable branches after //! backporting changes. The /doc/versions.md file should also be updated to diff --git a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/CMakeLists.txt b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/CMakeLists.txt index 1f21ba44..13246293 100644 --- a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/CMakeLists.txt +++ b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/CMakeLists.txt @@ -26,6 +26,7 @@ if(BUILD_TESTING AND TARGET CapnProto::kj-test) ${MP_PROXY_HDRS} mp/test/foo-types.h mp/test/foo.h + mp/test/listen_tests.cpp mp/test/spawn_tests.cpp mp/test/test.cpp ) diff --git a/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/mp/test/listen_tests.cpp b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/mp/test/listen_tests.cpp new file mode 100644 index 00000000..4e579d99 --- /dev/null +++ b/libbitcoinkernel-sys/bitcoin/src/ipc/libmultiprocess/test/mp/test/listen_tests.cpp @@ -0,0 +1,294 @@ +// Copyright (c) The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include + +namespace mp { +namespace test { +namespace { + +constexpr auto FAILURE_TIMEOUT = std::chrono::seconds{30}; + +//! Owns a temporary Unix-domain listening socket used by ListenSetup. Tests call +//! Connect() to create client socket FDs and release() to transfer the listening +//! FD to ListenConnections(). +class UnixListener +{ +public: + UnixListener() + { + std::string dir_template = (std::filesystem::temp_directory_path() / "mptest-listener-XXXXXX").string(); + char* dir = mkdtemp(dir_template.data()); + KJ_REQUIRE(dir != nullptr); + m_dir = dir; + m_path = m_dir + "/socket"; + + m_fd = socket(AF_UNIX, SOCK_STREAM, 0); + KJ_REQUIRE(m_fd >= 0); + + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + KJ_REQUIRE(m_path.size() < sizeof(addr.sun_path)); + std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1); + KJ_REQUIRE(bind(m_fd, reinterpret_cast(&addr), sizeof(addr)) == 0); + KJ_REQUIRE(listen(m_fd, SOMAXCONN) == 0); + } + + ~UnixListener() + { + if (m_fd >= 0) close(m_fd); + if (!m_path.empty()) unlink(m_path.c_str()); + if (!m_dir.empty()) rmdir(m_dir.c_str()); + } + + int release() + { + assert(m_fd >= 0); + int fd = m_fd; + m_fd = -1; + return fd; + } + + int MakeConnectedSocket() const + { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + KJ_REQUIRE(fd >= 0); + + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + KJ_REQUIRE(m_path.size() < sizeof(addr.sun_path)); + std::strncpy(addr.sun_path, m_path.c_str(), sizeof(addr.sun_path) - 1); + KJ_REQUIRE(connect(fd, reinterpret_cast(&addr), sizeof(addr)) == 0); + return fd; + } + +private: + int m_fd{-1}; + std::string m_dir; + std::string m_path; +}; + +//! Runs a client EventLoop on its own thread and connects one socket FD to the +//! server. The constructed ProxyClient can be used by the test thread to make +//! calls over that connection. +class ClientSetup +{ +public: + explicit ClientSetup(int fd) + : thread([this, fd] { + EventLoop loop("mptest-client", [](mp::LogMessage log) { + KJ_LOG(INFO, log.level, log.message); + if (log.level == mp::Log::Raise) throw std::runtime_error(log.message); + }); + client_promise.set_value(ConnectStream(loop, fd)); + loop.loop(); + }) + { + client = client_promise.get_future().get(); + } + + ~ClientSetup() + { + client.reset(); + thread.join(); + } + + std::promise>> client_promise; + std::unique_ptr> client; + + //! Thread variable should be after other struct members so the thread does + //! not start until the other members are initialized. + std::thread thread; +}; + +//! Runs a server EventLoop on its own thread, starts ListenConnections() on a +//! UnixListener socket, and records connection/disconnection counts through +//! EventLoop test hooks +class ListenSetup +{ +public: + explicit ListenSetup(std::optional max_connections = std::nullopt) + : thread([this, max_connections] { + EventLoop loop("mptest-server", [](mp::LogMessage log) { + KJ_LOG(INFO, log.level, log.message); + if (log.level == mp::Log::Raise) throw std::runtime_error(log.message); + }); + loop.testing_hook_disconnected = [&] { + Lock lock(counter_mutex); + ++disconnected_count; + counter_cv.notify_all(); + }; + loop.testing_hook_connected = [&] { + Lock lock(counter_mutex); + ++connected_count; + counter_cv.notify_all(); + }; + m_loop_ref.emplace(loop); + FooImplementation foo; + ListenConnections(loop, listener.release(), foo, max_connections); + ready_promise.set_value(); + loop.loop(); + }) + { + ready_promise.get_future().get(); + } + + ~ListenSetup() + { + m_loop_ref.reset(); + thread.join(); + } + + size_t ConnectedCount() + { + Lock lock(counter_mutex); + return connected_count; + } + + size_t DisconnectedCount() + { + Lock lock(counter_mutex); + return disconnected_count; + } + + void WaitForConnectedCount(size_t expected_count) + { + Lock lock(counter_mutex); + const auto deadline = std::chrono::steady_clock::now() + FAILURE_TIMEOUT; + const bool matched = counter_cv.wait_until(lock.m_lock, deadline, [&]() MP_REQUIRES(counter_mutex) { + return connected_count >= expected_count; + }); + KJ_REQUIRE(matched); + } + + void WaitForDisconnectedCount(size_t expected_count) + { + Lock lock(counter_mutex); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + const bool matched = counter_cv.wait_until(lock.m_lock, deadline, [&]() MP_REQUIRES(counter_mutex) { + return disconnected_count >= expected_count; + }); + KJ_REQUIRE(matched); + } + + UnixListener listener; + std::promise ready_promise; + std::optional m_loop_ref; + Mutex counter_mutex; + std::condition_variable counter_cv; + size_t connected_count MP_GUARDED_BY(counter_mutex) {0}; + size_t disconnected_count MP_GUARDED_BY(counter_mutex) {0}; + //! Thread variable should be after other struct members so the thread does + //! not start until the other members are initialized. + std::thread thread; +}; + +KJ_TEST("ListenConnections accepts incoming connections") +{ + ListenSetup server; + KJ_EXPECT(server.ConnectedCount() == 0) + auto client = std::make_unique(server.listener.MakeConnectedSocket()); + + server.WaitForConnectedCount(1); + KJ_EXPECT(client->client->add(1, 2) == 3); +} + +KJ_TEST("ListenConnections enforces a local connection limit") +{ + // With max-connections=1, the second socket can connect to the kernel + // backlog, but ListenConnections should not accept or serve it until the + // first accepts clients disconnects. + + ListenSetup server(/*max_connections=*/1); + + KJ_EXPECT(server.ConnectedCount() == 0) + auto client1 = std::make_unique(server.listener.MakeConnectedSocket()); + server.WaitForConnectedCount(1); + + KJ_EXPECT(client1->client->add(1, 2) == 3); + + auto client2 = std::make_unique(server.listener.MakeConnectedSocket()); + // Without this sync, ConnectedCount() == 1 might pass even if + // max_connections was not enforced because the event loop has not accepted + // client2 yet. + (**server.m_loop_ref).sync([] {}); + + KJ_EXPECT(server.ConnectedCount() == 1); + KJ_EXPECT(server.DisconnectedCount() == 0); + client1.reset(); + server.WaitForDisconnectedCount(1); + server.WaitForConnectedCount(2); + + KJ_EXPECT(client2->client->add(2, 3) == 5); + + KJ_EXPECT(server.DisconnectedCount() == 1); + client2.reset(); + server.WaitForDisconnectedCount(2); + + KJ_EXPECT(server.DisconnectedCount() == 2); + auto client3 = std::make_unique(server.listener.MakeConnectedSocket()); + server.WaitForConnectedCount(3); + KJ_EXPECT(client3->client->add(3, 4) == 7); +} + +KJ_TEST("ListenConnections accepts multiple connections") +{ + // With max-connections=2, two clients should be accepted and usable at the + // same time, while a third waits until one active client disconnects. + + ListenSetup server(/*max_connections=*/2); + + KJ_EXPECT(server.ConnectedCount() == 0); + auto client1 = std::make_unique(server.listener.MakeConnectedSocket()); + auto client2 = std::make_unique(server.listener.MakeConnectedSocket()); + server.WaitForConnectedCount(2); + + KJ_EXPECT(client1->client->add(1, 2) == 3); + KJ_EXPECT(client2->client->add(2, 3) == 5); + + auto client3 = std::make_unique(server.listener.MakeConnectedSocket()); + // Without this sync, ConnectedCount() == 2 might pass even if + // max_connections was not enforced because the event loop has not accepted + // client3 yet. + (**server.m_loop_ref).sync([] {}); + + KJ_EXPECT(server.ConnectedCount() == 2); + KJ_EXPECT(server.DisconnectedCount() == 0); + client1.reset(); + server.WaitForDisconnectedCount(1); + server.WaitForConnectedCount(3); + + KJ_EXPECT(client3->client->add(3, 4) == 7); +} + +} // namespace +} // namespace test +} // namespace mp diff --git a/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.cpp b/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.cpp index 152f778b..ba7e520d 100644 --- a/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.cpp +++ b/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.cpp @@ -28,10 +28,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -44,12 +46,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -506,6 +510,41 @@ struct btck_PrecomputedTransactionData : Handle {}; struct btck_ConsensusParams: Handle {}; +class CoinsViewBlock : public CCoinsViewCache +{ +private: + std::map m_coins; + // Omit any queries for the BIP30 checks - we don't have enough information for them anyway. + std::unordered_set m_block_txids; + + std::optional FetchCoinFromBase(const COutPoint& outpoint) const override + { + if (m_block_txids.contains(outpoint.hash)) return std::nullopt; + if (auto it{m_coins.find(outpoint)}; it != m_coins.end()) return it->second; + return std::nullopt; + } + +public: + CoinsViewBlock(const CBlock& block, + const btck_TransactionOutPoint* const* out_points, + const btck_Coin* const* coins, + size_t len) + : CCoinsViewCache(&CoinsViewEmpty::Get()) + { + SetBestBlock(block.hashPrevBlock); + + m_block_txids.reserve(block.vtx.size()); + for (const auto& tx : block.vtx) m_block_txids.insert(tx->GetHash()); + + for (size_t i{0}; i < len; ++i) { + const COutPoint& outpoint{btck_TransactionOutPoint::get(out_points[i])}; + const Coin& coin{btck_Coin::get(coins[i])}; + if (m_block_txids.contains(outpoint.hash)) continue; + m_coins.try_emplace(outpoint, coin); + } + } +}; + btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len) { assert(raw_transaction != nullptr || raw_transaction_len == 0); @@ -550,6 +589,11 @@ const btck_Txid* btck_transaction_get_txid(const btck_Transaction* transaction) return btck_Txid::ref(&btck_Transaction::get(transaction)->GetHash()); } +int btck_transaction_is_coinbase(const btck_Transaction* transaction) +{ + return btck_Transaction::get(transaction)->IsCoinBase() ? 1 : 0; +} + btck_Transaction* btck_transaction_copy(const btck_Transaction* transaction) { return btck_Transaction::copy(transaction); @@ -712,6 +756,11 @@ void btck_transaction_input_destroy(btck_TransactionInput* input) delete input; } +btck_TransactionOutPoint* btck_transaction_out_point_create(const btck_Txid* txid, uint32_t index) +{ + return btck_TransactionOutPoint::create(COutPoint{btck_Txid::get(txid), index}); +} + btck_TransactionOutPoint* btck_transaction_out_point_copy(const btck_TransactionOutPoint* out_point) { return btck_TransactionOutPoint::copy(out_point); @@ -1310,6 +1359,11 @@ const btck_Coin* btck_transaction_spent_outputs_get_coin_at(const btck_Transacti return btck_Coin::ref(coin); } +btck_Coin* btck_coin_create(const btck_TransactionOutput* output, uint32_t height, int is_coinbase) +{ + return btck_Coin::create(btck_TransactionOutput::get(output), height, is_coinbase == 1); +} + btck_Coin* btck_coin_copy(const btck_Coin* coin) { return btck_Coin::copy(coin); @@ -1365,6 +1419,35 @@ btck_BlockValidationState* btck_chainstate_manager_process_block_header( } } +int btck_chainstate_manager_validate_block( + btck_ChainstateManager* chainstate_manager, + const btck_Block* _block, + const btck_BlockTreeEntry* entry, + const btck_TransactionOutPoint* const* spent_out_points, + const btck_Coin* const* spent_coins, + size_t spent_outputs_len, + btck_BlockValidationState* state) +{ + assert(spent_out_points != nullptr || spent_outputs_len == 0); + assert(spent_coins != nullptr || spent_outputs_len == 0); + for (size_t i{0}; i < spent_outputs_len; ++i) { + assert(spent_out_points[i] != nullptr); + assert(spent_coins[i] != nullptr); + } + + const CBlock& block{*btck_Block::get(_block)}; + + try { + CoinsViewBlock coins{block, spent_out_points, spent_coins, spent_outputs_len}; + auto& chainman{*btck_ChainstateManager::get(chainstate_manager).m_chainman}; + btck_BlockValidationState::get(state) = chainman.ValidateBlock(block, btck_BlockTreeEntry::get(entry), coins); + } catch (const std::exception& e) { + LogError("Failed to validate block: %s", e.what()); + btck_BlockValidationState::get(state).Error(strprintf("Exception in ValidateBlock: %s", e.what())); + } + return btck_BlockValidationState::get(state).IsValid() ? 0 : -1; +} + const btck_Chain* btck_chainstate_manager_get_active_chain(const btck_ChainstateManager* chainman) { return btck_Chain::ref(&WITH_LOCK(btck_ChainstateManager::get(chainman).m_chainman->GetMutex(), return btck_ChainstateManager::get(chainman).m_chainman->ActiveChain())); diff --git a/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.h b/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.h index 6d646b30..33d0465c 100644 --- a/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.h +++ b/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.h @@ -695,6 +695,15 @@ BITCOINKERNEL_API int btck_transaction_check( const btck_Transaction* tx, btck_TxValidationState* validation_state) BITCOINKERNEL_ARG_NONNULL(1, 2); +/** + * @brief Check if this is a coinbase transaction. + * + * @param[in] transaction Non-null. + * @return 1 if it is coinbase, 0 if it is not. + */ +BITCOINKERNEL_API int btck_transaction_is_coinbase( + const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1); + /** * Destroy the transaction. */ @@ -1269,6 +1278,37 @@ BITCOINKERNEL_API btck_BlockValidationState* BITCOINKERNEL_WARN_UNUSED_RESULT bt btck_ChainstateManager* chainstate_manager, const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1, 2); +/** + * @brief Validate the passed in block. + * + * This validates the block against the caller's supplied spent coins without + * requiring a full UTXO set or mutating the chainstate. Coins created and + * consumed within the same block are ignored. For duplicate entries only the + * first occurrence is used. BIP-30 violating transactions cannot be detected + * through this function. + * + * The spent_out_points and spent_coins must be matching pairs by their index. + * Their order is however irrelevant. + * + * @param[in] chainstate_manager Non-null. + * @param[in] block Non-null. + * @param[in] block_tree_entry Non-null. The entry must be associated with the block. + * @param[in] spent_out_points Array of spent_outputs_len non-null outpoints consumed by this block. + * @param[in] spent_coins Array of spent_outputs_len non-null coins spent by this block, + * matching the spent_out_points. + * @param[in] spent_outputs_len Number of entries in the spent_out_points and spent_coins arrays + * @param[out] block_validation_state The result of the block validation. + * @return 0 if the block is valid. + */ +BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_chainstate_manager_validate_block( + btck_ChainstateManager* chainstate_manager, + const btck_Block* block, + const btck_BlockTreeEntry* block_tree_entry, + const btck_TransactionOutPoint* const* spent_out_points, + const btck_Coin* const* spent_coins, + size_t spent_outputs_len, + btck_BlockValidationState* block_validation_state) BITCOINKERNEL_ARG_NONNULL(1, 2, 3, 7); + /** * @brief Triggers the start of a reindex if the wipe options were previously * set for the chainstate manager. Can also import an array of existing block @@ -1703,6 +1743,17 @@ BITCOINKERNEL_API void btck_transaction_input_destroy(btck_TransactionInput* tra */ ///@{ +/** + * @brief Create a transaction out point from a txid and an output index. + * + * @param[in] txid Non-null. + * @param[in] index The output index. + * @return The allocated transaction out point. Must be freed with + * btck_transaction_out_point_destroy. + */ +BITCOINKERNEL_API btck_TransactionOutPoint* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_out_point_create( + const btck_Txid* txid, uint32_t index) BITCOINKERNEL_ARG_NONNULL(1); + /** * @brief Copy a transaction out point. * @@ -1783,6 +1834,17 @@ BITCOINKERNEL_API void btck_txid_destroy(btck_Txid* txid); */ ///@{ +/** + * @brief Create a coin. + * + * @param[in] output Non-null. + * @param[in] height The height the coin was confirmed in. + * @param[in] is_coinbase Set to 1 if the coin is from a coinbase output, set to 0 otherwise. + * @return The coin. + */ +BITCOINKERNEL_API btck_Coin* BITCOINKERNEL_WARN_UNUSED_RESULT btck_coin_create( + const btck_TransactionOutput* output, uint32_t height, int is_coinbase) BITCOINKERNEL_ARG_NONNULL(1); + /** * @brief Copy a coin. * diff --git a/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel_wrapper.h b/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel_wrapper.h index b00328af..03e74c18 100644 --- a/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel_wrapper.h +++ b/libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel_wrapper.h @@ -562,6 +562,11 @@ class OutPoint : public Handle @@ -616,6 +621,11 @@ class TransactionApi return btck_transaction_count_outputs(impl()); } + bool IsCoinbase() const + { + return btck_transaction_is_coinbase(impl()) != 0; + } + size_t CountInputs() const { return btck_transaction_count_inputs(impl()); @@ -1225,6 +1235,8 @@ class CoinView : public View, public CoinApi class Coin : public Handle, public CoinApi { public: + Coin(const TransactionOutput& output, uint32_t height, bool is_coinbase) : Handle{btck_coin_create(output.get(), height, is_coinbase)} {} + Coin(btck_Coin* coin) : Handle{coin} {} Coin(const CoinView& view) : Handle{view} {} @@ -1328,6 +1340,23 @@ class ChainMan : UniqueHandle> spent_outputs, + BlockValidationState& state) + { + std::vector out_points; + std::vector coins; + out_points.reserve(spent_outputs.size()); + coins.reserve(spent_outputs.size()); + for (const auto& [out_point, coin] : spent_outputs) { + out_points.push_back(out_point.get()); + coins.push_back(coin.get()); + } + + return btck_chainstate_manager_validate_block(get(), block.get(), entry.get(), out_points.data(), coins.data(), out_points.size(), state.get()) == 0; + } + ChainView GetChain() const { return ChainView{btck_chainstate_manager_get_active_chain(get())}; diff --git a/libbitcoinkernel-sys/bitcoin/src/kernel/chainparams.cpp b/libbitcoinkernel-sys/bitcoin/src/kernel/chainparams.cpp index cc77a373..352434da 100644 --- a/libbitcoinkernel-sys/bitcoin/src/kernel/chainparams.cpp +++ b/libbitcoinkernel-sys/bitcoin/src/kernel/chainparams.cpp @@ -165,7 +165,6 @@ class CMainParams : public CChainParams { // This is fine at runtime as we'll fall back to using them as an addrfetch if they don't support the // service bits we want, but we should get them updated to support all service bits wanted by any // release ASAP to avoid it where possible. - vSeeds.emplace_back("seed.bitcoin.sipa.be."); // Pieter Wuille, only supports x1, x5, x9, and xd vSeeds.emplace_back("dnsseed.bluematt.me."); // Matt Corallo, only supports x9 vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch."); // Jonas Schnelli, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.btc.petertodd.net."); // Peter Todd, only supports x1, x5, x9, and xd diff --git a/libbitcoinkernel-sys/bitcoin/src/test/CMakeLists.txt b/libbitcoinkernel-sys/bitcoin/src/test/CMakeLists.txt index e5619064..b5a12f72 100644 --- a/libbitcoinkernel-sys/bitcoin/src/test/CMakeLists.txt +++ b/libbitcoinkernel-sys/bitcoin/src/test/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(test_bitcoin base32_tests.cpp base58_tests.cpp base64_tests.cpp + baseindex_tests.cpp bech32_tests.cpp bip32_tests.cpp bip324_tests.cpp @@ -63,7 +64,6 @@ add_executable(test_bitcoin miniminer_tests.cpp miniscript_tests.cpp minisketch_tests.cpp - mock_process.cpp multisig_tests.cpp bip328_tests.cpp net_peer_connection_tests.cpp @@ -191,8 +191,6 @@ function(add_boost_test source_file) list(TRANSFORM test_suite_macro REPLACE "(BOOST_FIXTURE_TEST_SUITE|BOOST_AUTO_TEST_SUITE)\\(" "" ) - # The mock_process test suite does not contain unit tests. - list(REMOVE_ITEM test_suite_macro "mock_process") foreach(test_suite_name IN LISTS test_suite_macro) add_test(NAME ${test_suite_name} COMMAND test_bitcoin --run_test=${test_suite_name} --catch_system_error=no --log_level=test_suite -- -printtoconsole=1 diff --git a/libbitcoinkernel-sys/bitcoin/src/test/baseindex_tests.cpp b/libbitcoinkernel-sys/bitcoin/src/test/baseindex_tests.cpp new file mode 100644 index 00000000..16cbab19 --- /dev/null +++ b/libbitcoinkernel-sys/bitcoin/src/test/baseindex_tests.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2020-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include