Files
bitcoin/ci/test/03_test_script.sh
Ryan Ofsky a4157fc24a Merge bitcoin/bitcoin#33966: refactor: disentangle miner startup defaults from runtime options
1e5d3b4f0d doc: add release note for mining option validation (Sjors Provoost)
0317f52022 ci: enforce iwyu for touched files (Sjors Provoost)
8c58f63578 refactor: have mining files include what they use (Sjors Provoost)
3bb6498fb0 mining: store block create options in NodeContext (Sjors Provoost)
4637cd157d mining: reject invalid block create options (Sjors Provoost)
8daac1d6eb mining: add block create option helpers (Sjors Provoost)
128da7c3ff miner: add block_max_weight to BlockCreateOptions (Sjors Provoost)
fa81e51eae mining: parse block creation args in mining_args (Sjors Provoost)
020166080c mining: use interface for tests, bench and fuzzers (Sjors Provoost)
44082bea47 interfaces: make Mining use const NodeContext (Sjors Provoost)
d4368e059c move-only: add node/mining_types.h (Sjors Provoost)
6aeb1fbea2 test: cover IPC blockmaxweight policy (Sjors Provoost)
63b23ea1e9 test: regression test for waitNext mining policy (Sjors Provoost)
24750f8b31 test: add createNewBlock failure helper (Sjors Provoost)
63ee9cd15b test: misc interface_ipc_mining.py improvements (Sjors Provoost)

Pull request description:

  Although this PR is primarily a refactor, _there are behavior changes_ documented in the release note:
  - the IPC mining interface now rejects out-of-range block template options instead of silently clamping them;
  - startup now rejects `-blockmaxweight` values lower than `-blockreservedweight`, instead of allowing them to be clamped later.

  The interaction between node startup options like `-blockreservedweight` and runtime options, especially those passed via IPC, is confusing.

  They're combined in `BlockAssembler::Options`, which this PR gets rid of in favour of `BlockCreateOptions`.

  `BlockCreateOptions` is used by interface clients. As before, IPC clients have access to a safe / sane subset, whereas RPC and test code can use all fields. The same type is also used to store mining defaults parsed once during node startup in `NodeContext`.

  The maximum block weight setting (`block_max_weight`) is optional. When read from startup options it matches `-blockmaxweight`; when provided by callers it is a runtime override. `Merge()` fills unset fields from startup defaults while preserving caller-provided values.

  This all happens in commits `mining: add block create option helpers` and `mining: store block create options in NodeContext`, and requires some preparation to keep things easy to review.

  We get rid of `BlockAssembler::Options` but this is used in many tests. Since large churn is inevitable, we might as well switch all tests, bench and fuzzers over to the Mining interface. The `mining: use interface for tests, bench and fuzzers` commit does that, dramatically reducing direct use of `BlockAssembler`. Two exceptions are documented in the commit message. Because `test_block_validity` wasn't available via the interface and the block_assemble benchmark needs it, it's moved from `BlockAssembler::Options` to `BlockCreateOptions` (still not exposed via IPC).

  We need access to mining related structs from both the miner and node initialization code. To avoid having to pull in all of `BlockAssembler` for the latter, the `move-only: add node/mining_types.h` commit introduces `node/mining_types.h` and moves `BlockCreateOptions`, `BlockWaitOptions` and `BlockCheckOptions` there from `src/node/types.h`.

  I considered also moving `DEFAULT_BLOCK_MAX_WEIGHT`, `DEFAULT_BLOCK_RESERVED_WEIGHT`, `MINIMUM_BLOCK_RESERVED_WEIGHT` and `DEFAULT_BLOCK_MIN_TX_FEE` there from `policy.h`, since they are distinct from relay policy and not needed by the kernel. But this seems more appropriate for a follow-up and requires additional discussion.

  ---

  I kept variable renaming and other formatting changes to a minimum to ease review with `--color-moved=dimmed-zebra`.

  ## Commit summary

  Tests and test cleanup:
  - `test: misc interface_ipc_mining.py improvements`
  - `test: add assert_create_fails helper`
  - `test: regression test for waitNext mining policy`
  - `test: cover IPC blockmaxweight policy`

  Refactoring test/bench/fuzz callers:
  - `interfaces: make Mining use const NodeContext`
  - `mining: use interface for tests, bench and fuzzers`

  Moving mining interface types:
  - `move-only: add node/mining_types.h`

  Separating startup defaults from runtime options:
  - `mining: parse block creation args in mining_args`: adds `node/mining_args.{h,cpp}` and moves mining option parsing out of `init.cpp`, without storing the parsed values yet.
  - `miner: add block_max_weight to BlockCreateOptions`: moves the runtime maximum block weight setting into `BlockCreateOptions` as an optional value, so it can later be defaulted from startup args when unset.
  - `mining: add block create option helpers`: centralizes block template option defaulting and merging, removes `BlockAssembler::Options`, and preserves behavior except for dropping the `Specified ` prefix from startup option error messages.
  - `mining: reject invalid block create options`: checks typed `BlockCreateOptions` before block template creation, so invalid runtime options are rejected instead of silently clamped. Startup validation also rejects `-blockmaxweight` values lower than `-blockreservedweight`.
  - `mining: store block create options in NodeContext`: stores the startup mining options in `NodeContext` as `BlockCreateOptions`, so startup defaults and runtime overrides can be merged with the same option type.

  Include hygiene, CI and release note:
  - `refactor: have mining files include what they use`
  - `ci: enforce iwyu for touched files`
  - `doc: add release note for mining option validation`

ACKs for top commit:
  w0xlt:
    reACK 1e5d3b4f0d
  sedited:
    ACK 1e5d3b4f0d
  ryanofsky:
    Code review ACK 1e5d3b4f0d. Looks good, thanks for the updates!

Tree-SHA512: 28c715023cb78f02775caa787b243c994bd0f8ce4559afc8db9301e93400ebbc74963626a4afe65ae15bcc16b9192d051a745839f4c804848d50746ea5a224b4
2026-05-26 08:39:03 -04:00

272 lines
9.8 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Copyright (c) 2018-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.
export LC_ALL=C.UTF-8
set -o errexit -o xtrace -o pipefail
if [ "${DANGER_RUN_CI_ON_HOST}" != "1" ]; then
echo "This script will make unsafe local and global modifications, so it can only be run inside a container and requires DANGER_RUN_CI_ON_HOST=1"
exit 1
fi
cd "${BASE_ROOT_DIR}"
export PATH="/path_with space:${PATH}"
export ASAN_OPTIONS="detect_leaks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1"
export LSAN_OPTIONS="suppressions=${BASE_ROOT_DIR}/test/sanitizer_suppressions/lsan"
export TSAN_OPTIONS="suppressions=${BASE_ROOT_DIR}/test/sanitizer_suppressions/tsan:halt_on_error=1:second_deadlock_stack=1"
export UBSAN_OPTIONS="suppressions=${BASE_ROOT_DIR}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1"
echo "Number of available processing units: $(nproc)"
if [ "$CI_OS_NAME" == "macos" ]; then
top -l 1 -s 0 | awk ' /PhysMem/ {print}'
else
free -m -h
echo "System info: $(uname --kernel-name --kernel-release)"
lscpu
fi
echo "Free disk space:"
df -h
# We force an install of linux-headers again here via $PACKAGES to fix any
# kernel mismatch between a cached docker image and the underlying host.
# This can happen occasionally on hosted runners if the runner image is updated.
if [[ "$CONTAINER_NAME" == "ci_native_asan" ]]; then
$CI_RETRY_EXE apt-get update
${CI_RETRY_EXE} bash -c "apt-get install --no-install-recommends --no-upgrade -y $PACKAGES"
fi
# What host to compile for. See also ./depends/README.md
# Tests that need cross-compilation export the appropriate HOST.
# Tests that run natively guess the host
export HOST=${HOST:-$("$BASE_ROOT_DIR/depends/config.guess")}
echo "=== BEGIN env ==="
env
echo "=== END env ==="
# The CI framework should be flexible where it is run from. For example, from
# a git-archive, a git-worktree, or a normal git repo.
# The iwyu task requires a working git repo, which may not always be
# available, so initialize one with force.
if [[ "${RUN_IWYU}" == true ]]; then
mv .git .git_ci_backup || true
git init
git add ./src # the git diff command used later for iwyu only cares about ./src
git config user.email "ci@ci"
git config user.name "CI"
git commit -m "dummy CI ./src init for IWYU"
fi
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_corpora/
if [ ! -d "$DIR_FUZZ_IN" ]; then
${CI_RETRY_EXE} git clone --depth=1 https://github.com/bitcoin-core/qa-assets "${DIR_QA_ASSETS}"
fi
(
cd "${DIR_QA_ASSETS}"
echo "Using qa-assets repo from commit ..."
git log -1
)
elif [ "$RUN_UNIT_TESTS" = "true" ]; then
export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
if [ ! -d "$DIR_UNIT_TEST_DATA" ]; then
mkdir -p "$DIR_UNIT_TEST_DATA"
${CI_RETRY_EXE} curl --location --fail https://github.com/bitcoin-core/qa-assets/raw/main/unit_test_data/script_assets_test.json -o "${DIR_UNIT_TEST_DATA}/script_assets_test.json"
fi
fi
# Make sure default datadir does not exist and is never read by creating a dummy file
if [ "$CI_OS_NAME" == "macos" ]; then
echo > "${HOME}/Library/Application Support/Bitcoin"
else
echo > "${HOME}/.bitcoin"
fi
if [ -z "$NO_DEPENDS" ]; then
if [[ $CI_IMAGE_NAME_TAG == *alpine* ]]; then
SHELL_OPTS="CONFIG_SHELL=/usr/bin/dash"
else
SHELL_OPTS="CONFIG_SHELL="
fi
bash -c "$SHELL_OPTS make $MAKEJOBS -C depends HOST=$HOST $DEP_OPTS LOG=1"
fi
if [ "$DOWNLOAD_PREVIOUS_RELEASES" = "true" ]; then
test/get_previous_releases.py --target-dir "$PREVIOUS_RELEASES_DIR"
fi
BITCOIN_CONFIG_ALL="-DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON"
if [ -z "$NO_DEPENDS" ]; then
BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} -DCMAKE_TOOLCHAIN_FILE=$DEPENDS_DIR/$HOST/toolchain.cmake"
fi
ccache --zero-stats
# Folder where the build is done.
BASE_BUILD_DIR=${BASE_BUILD_DIR:-$BASE_SCRATCH_DIR/build-$HOST}
BITCOIN_CONFIG_ALL="$BITCOIN_CONFIG_ALL '-DCMAKE_INSTALL_PREFIX=$BASE_OUTDIR' -Werror=dev"
if [[ "${RUN_IWYU}" == true || "${RUN_TIDY}" == true ]]; then
BITCOIN_CONFIG_ALL="$BITCOIN_CONFIG_ALL -DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
fi
eval "CMAKE_ARGS=($BITCOIN_CONFIG_ALL $BITCOIN_CONFIG)"
cmake -S "$BASE_ROOT_DIR" -B "$BASE_BUILD_DIR" "${CMAKE_ARGS[@]}" || (
cd "${BASE_BUILD_DIR}"
# shellcheck disable=SC2046
cat $(cmake -P "${BASE_ROOT_DIR}/ci/test/GetCMakeLogFiles.cmake")
false
)
if [[ "${GOAL}" != all && "${GOAL}" != codegen ]]; then
GOAL="all ${GOAL}"
fi
# shellcheck disable=SC2086
cmake --build "${BASE_BUILD_DIR}" "$MAKEJOBS" --target $GOAL || (
echo "Build failure. Verbose build follows."
# shellcheck disable=SC2086
cmake --build "${BASE_BUILD_DIR}" -j1 --target $GOAL --verbose
false
)
ccache --version | head -n 1 && ccache --show-stats --verbose
ccache --print-stats | python3 -c '
import os
import sys
for line in sys.stdin:
key, value = line.split("\t", 1)
# "primary storage" fallback only needed for ccache version 4.5.1
if key in ("local_storage_hit", "primary_storage_hit"):
hits = int(value)
elif key in ("local_storage_miss", "primary_storage_miss"):
miss = int(value)
calls = hits + miss
# codegen has no calls, so skip that here
if calls:
rate = (hits / calls * 100)
print(f"{rate:.2f}")
if rate < 75:
container = os.environ["CONTAINER_NAME"]
print(
"::notice title=low ccache hitrate::"
f"Ccache hit-rate in {container} was {rate:.2f}%"
)
'
du -sh "${DEPENDS_DIR}"/*/
du -sh "${PREVIOUS_RELEASES_DIR}"
if [ -n "${CI_LIMIT_STACK_SIZE}" ]; then
ulimit -s 512
fi
if [ -n "$USE_VALGRIND" ]; then
"${BASE_ROOT_DIR}/ci/test/wrap-valgrind.sh"
fi
if [ "$RUN_CHECK_DEPS" = "true" ]; then
"${BASE_ROOT_DIR}/contrib/devtools/check-deps.sh" "${BASE_BUILD_DIR}"
fi
if [[ "$CI_OS_NAME" == "macos" && "${GOAL}" = "install deploy" ]]; then
unzip "${BASE_BUILD_DIR}/bitcoin-macos-app.zip" -d "${BASE_BUILD_DIR}/deploy"
if ! ( codesign --verify "${BASE_BUILD_DIR}/deploy/Bitcoin-Qt.app" ); then
echo "Codesigning failed."
false
fi
fi
if [ "$RUN_UNIT_TESTS" = "true" ]; then
DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" \
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
CTEST_OUTPUT_ON_FAILURE=ON \
ctest --test-dir "${BASE_BUILD_DIR}" \
--stop-on-failure \
"${MAKEJOBS}" \
--timeout $(( TEST_RUNNER_TIMEOUT_FACTOR * 60 ))
fi
if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
# parses TEST_RUNNER_EXTRA as an array which allows for multiple arguments such as TEST_RUNNER_EXTRA='--exclude "rpc_bind.py --ipv6"'
eval "TEST_RUNNER_EXTRA=($TEST_RUNNER_EXTRA)"
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
"${BASE_BUILD_DIR}/test/functional/test_runner.py" \
"${MAKEJOBS}" \
--tmpdirprefix "${BASE_SCRATCH_DIR}/test_runner/" \
--ansi \
--combinedlogslen=99999999 \
--timeout-factor="${TEST_RUNNER_TIMEOUT_FACTOR}" \
"${TEST_RUNNER_EXTRA[@]}" \
--quiet \
--failfast
fi
if [ "${RUN_TIDY}" = "true" ]; then
cmake -B /tidy-build -DLLVM_DIR=/usr/lib/llvm-"${TIDY_LLVM_V}"/cmake -DCMAKE_BUILD_TYPE=Release -S "${BASE_ROOT_DIR}"/contrib/devtools/bitcoin-tidy
cmake --build /tidy-build "$MAKEJOBS"
cmake --build /tidy-build --target bitcoin-tidy-tests "$MAKEJOBS"
set -eo pipefail
# Filter out:
# * qt qrc and moc generated files
jq 'map(select(.file | test("src/qt/.*_autogen/.*\\.cpp$") | not))' "${BASE_BUILD_DIR}/compile_commands.json" > tmp.json
mv tmp.json "${BASE_BUILD_DIR}/compile_commands.json"
cd "${BASE_BUILD_DIR}/src/"
if ! ( run-clang-tidy-"${TIDY_LLVM_V}" -config-file="${BASE_ROOT_DIR}/src/.clang-tidy" -quiet -load="/tidy-build/libbitcoin-tidy.so" "${MAKEJOBS}" | tee tmp.tidy-out.txt ); then
grep -C5 "error: " tmp.tidy-out.txt
echo "^^^ ⚠️ Failure generated from clang-tidy"
false
fi
fi
if [[ "${RUN_IWYU}" == true ]]; then
# TODO: Consider enforcing IWYU across the entire codebase.
FILES_WITH_ENFORCED_IWYU="/src/(((crypto|index|kernel|primitives|univalue/(lib|test)|util|zmq)/.*|bench/(block_assemble|connectblock)|common/license_info|node/(blockstorage|interfaces|miner|mining_args|utxo_snapshot)|rpc/mining|clientversion|core_io|signet|init)\\.cpp)"
jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns)))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_errors.json"
jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns) | not))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_warnings.json"
cd "${BASE_ROOT_DIR}"
run_iwyu() {
mv "${BASE_BUILD_DIR}/$1" "${BASE_BUILD_DIR}/compile_commands.json"
{
python3 "/include-what-you-use/iwyu_tool.py" \
-p "${BASE_BUILD_DIR}" "${MAKEJOBS}" \
-- -Xiwyu --cxx17ns -Xiwyu --mapping_file="${BASE_ROOT_DIR}/contrib/devtools/iwyu/bitcoin.core.imp" \
-Xiwyu --max_line_length=160 \
-Xiwyu --check_also="*/primitives/*.h" \
2>&1 || true
} | tee /tmp/iwyu_ci.out
python3 "/include-what-you-use/fix_includes.py" --nosafe_headers < /tmp/iwyu_ci.out
git diff -U1 | ./contrib/devtools/clang-format-diff.py -binary="clang-format-${IWYU_LLVM_V}" -p1 -i -v
}
run_iwyu "compile_commands_iwyu_errors.json"
if ! ( git --no-pager diff --exit-code ); then
echo "^^^ ⚠️ Failure generated from IWYU"
false
fi
run_iwyu "compile_commands_iwyu_warnings.json"
git --no-pager diff
fi
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
# shellcheck disable=SC2086
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
"${BASE_BUILD_DIR}/test/fuzz/test_runner.py" \
${FUZZ_TESTS_CONFIG} \
"${MAKEJOBS}" \
-l DEBUG \
"${DIR_FUZZ_IN}" \
--empty_min_time=60
fi