mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-13 14:13:52 +02:00
Merge bitcoin/bitcoin#35331: [31.x] Backports
c058c29831doc: update manual pages for v31.1rc1 (fanquake)1c9d24fdddbuild: bump version to v31.1rc1 (fanquake)d813722ef2doc: update release notes for v31.1rc1 (fanquake)ea3b318d8dcoins: compact chainstate in background (Lőrinc)711065a3b9validation: randomly compact chainstate (Lőrinc)fef6c8a4f2coins: test chainstate flush baseline (Lőrinc)ca00827fabutil: Check write failures before renaming settings.json (Shrey)1ee11d8ba6lint: disable leveldb subtree check (fanquake)ccb99122f9net: un-default the OpenNetworkConnection()'s proxy_override argument (Eugene Siegel)13df77d13btest: add a regression test for private broadcast v1 retries (Vasil Dimov)6c08cb7323test: make reusable filling of a node's addrman (Vasil Dimov)70a8687d9dtest: make reusable starting a standalone P2P listener (Vasil Dimov)ef20249568test: make reusable SOCKS5 server starting (Vasil Dimov)66377c3c84net: ensure no direct private broadcast connections (Vasil Dimov)2c7986b3eenet: use the proxy if overriden when doing v2->v1 reconnections (Vasil Dimov)70000a560bci: use Warp cache for Docker layers (will)25506ed6d9ci: Add dynamic cache switching to warp cache (will)39f8e077c8ci: use ubuntu-latest instead of ubuntu-24.04 (fanquake)1f55b3e463doc: remove reference to cirrus (fanquake)8f13bb1ea0crypto: disable ASan instrumentation of SSE4 SHA256 for GCC (deadmanoz)6caf6de0a1ci: switch runners from cirrus to warpbuild (will)78714f6d4fDisable seek compaction (Andrew Toth)3440027b7dci: switch to GitHub cache for all runners (willcl-ark)d61687a2acmusig: Reject empty pubkey list in GetMuSig2KeyAggCache (nervana21)671e6c2c33wallet: use outpoint when estimating input size (Lőrinc)101071722epsbt, test: remove address type restrictions in test (rkrux) Pull request description: Backports: * #34953 * #35228 * #35279 * #35313 (only https://github.com/bitcoin-core/leveldb-subtree/pull/61) * #35316 * #35378 * #35348 * #35384 * #35408 * #35410 * #35430 * #35447 * #35465 ACKs for top commit: marcofleon: ACKc058c29831sedited: ACKc058c29831Tree-SHA512: a04909c1ce82d6f3412655ee7b52f4c482f5a175f9ec4e1468a84a4d488f935ab0ab333a3dc4d75f68fe2a9e7bae169d624c9061308435527b29b5a014c43dc3
This commit is contained in:
@@ -13,7 +13,7 @@ trim_trailing_whitespace = true
|
||||
[*.{h,cpp,rs,py,sh}]
|
||||
indent_size = 4
|
||||
|
||||
# .cirrus.yml, etc.
|
||||
# ci.yml, etc.
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
name: 'Restore Caches'
|
||||
description: 'Restore ccache, depends sources, and built depends caches'
|
||||
inputs:
|
||||
provider:
|
||||
description: 'The cache provider to use'
|
||||
required: true
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Restore Ccache cache
|
||||
id: ccache-cache
|
||||
uses: cirruslabs/cache/restore@v5
|
||||
uses: ./.github/actions/cache/restore/internal
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ env.CONTAINER_NAME }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
ccache-${{ env.CONTAINER_NAME }}-
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: Restore depends sources cache
|
||||
id: depends-sources
|
||||
uses: cirruslabs/cache/restore@v5
|
||||
uses: ./.github/actions/cache/restore/internal
|
||||
with:
|
||||
path: ${{ env.SOURCES_PATH }}
|
||||
key: depends-sources-${{ env.CONTAINER_NAME }}-${{ env.DEPENDS_HASH }}
|
||||
restore-keys: |
|
||||
depends-sources-${{ env.CONTAINER_NAME }}-
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: Restore built depends cache
|
||||
id: depends-built
|
||||
uses: cirruslabs/cache/restore@v5
|
||||
uses: ./.github/actions/cache/restore/internal
|
||||
with:
|
||||
path: ${{ env.BASE_CACHE }}
|
||||
key: depends-built-${{ env.CONTAINER_NAME }}-${{ env.DEPENDS_HASH }}
|
||||
restore-keys: |
|
||||
depends-built-${{ env.CONTAINER_NAME }}-
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: Restore previous releases cache
|
||||
id: previous-releases
|
||||
uses: cirruslabs/cache/restore@v5
|
||||
uses: ./.github/actions/cache/restore/internal
|
||||
with:
|
||||
path: ${{ env.PREVIOUS_RELEASES_DIR }}
|
||||
key: previous-releases-${{ env.CONTAINER_NAME }}-${{ env.PREVIOUS_RELEASES_HASH }}
|
||||
restore-keys: |
|
||||
previous-releases-${{ env.CONTAINER_NAME }}-
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: export cache hits
|
||||
shell: bash
|
||||
43
.github/actions/cache/restore/internal/action.yml
vendored
Normal file
43
.github/actions/cache/restore/internal/action.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: 'Cache Restore'
|
||||
description: 'Restore a cache with WarpBuild on Warp runners and GitHub Actions cache otherwise'
|
||||
inputs:
|
||||
path:
|
||||
description: 'A list of files, directories, and wildcard patterns to restore'
|
||||
required: true
|
||||
key:
|
||||
description: 'An explicit key for restoring the cache'
|
||||
required: true
|
||||
restore-keys:
|
||||
description: 'An ordered multiline string listing prefix-matched restore keys'
|
||||
required: false
|
||||
default: ''
|
||||
provider:
|
||||
description: 'The cache provider to use'
|
||||
required: true
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: 'A boolean value to indicate an exact match was found for the primary key'
|
||||
value: ${{ steps.warp.outputs.cache-hit || steps.gha.outputs.cache-hit }}
|
||||
cache-primary-key:
|
||||
description: 'The primary key used to restore the cache'
|
||||
value: ${{ steps.warp.outputs.cache-primary-key || steps.gha.outputs.cache-primary-key }}
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Restore cache with WarpBuild
|
||||
id: warp
|
||||
if: ${{ inputs.provider == 'warp' }}
|
||||
uses: WarpBuilds/cache/restore@v1
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
|
||||
- name: Restore cache with GitHub Actions
|
||||
id: gha
|
||||
if: ${{ inputs.provider == 'gha' }}
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
restore-keys: ${{ inputs.restore-keys }}
|
||||
@@ -1,5 +1,9 @@
|
||||
name: 'Save Caches'
|
||||
description: 'Save ccache, depends sources, and built depends caches'
|
||||
inputs:
|
||||
provider:
|
||||
description: 'The cache provider to use'
|
||||
required: true
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
@@ -11,29 +15,33 @@ runs:
|
||||
echo "previous releases direct cache hit to primary key: ${{ env.previous-releases-cache-hit }}"
|
||||
|
||||
- name: Save Ccache cache
|
||||
uses: cirruslabs/cache/save@v5
|
||||
uses: ./.github/actions/cache/save/internal
|
||||
if: ${{ (github.event_name == 'push') && (github.ref_name == github.event.repository.default_branch) }}
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ env.CONTAINER_NAME }}-${{ github.run_id }}
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: Save depends sources cache
|
||||
uses: cirruslabs/cache/save@v5
|
||||
uses: ./.github/actions/cache/save/internal
|
||||
if: ${{ (github.event_name == 'push') && (github.ref_name == github.event.repository.default_branch) && (env.depends-sources-cache-hit != 'true') }}
|
||||
with:
|
||||
path: ${{ env.SOURCES_PATH }}
|
||||
key: depends-sources-${{ env.CONTAINER_NAME }}-${{ env.DEPENDS_HASH }}
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: Save built depends cache
|
||||
uses: cirruslabs/cache/save@v5
|
||||
uses: ./.github/actions/cache/save/internal
|
||||
if: ${{ (github.event_name == 'push') && (github.ref_name == github.event.repository.default_branch) && (env.depends-built-cache-hit != 'true' )}}
|
||||
with:
|
||||
path: ${{ env.BASE_CACHE }}
|
||||
key: depends-built-${{ env.CONTAINER_NAME }}-${{ env.DEPENDS_HASH }}
|
||||
provider: ${{ inputs.provider }}
|
||||
|
||||
- name: Save previous releases cache
|
||||
uses: cirruslabs/cache/save@v5
|
||||
uses: ./.github/actions/cache/save/internal
|
||||
if: ${{ (github.event_name == 'push') && (github.ref_name == github.event.repository.default_branch) && (env.previous-releases-cache-hit != 'true' )}}
|
||||
with:
|
||||
path: ${{ env.PREVIOUS_RELEASES_DIR }}
|
||||
key: previous-releases-${{ env.CONTAINER_NAME }}-${{ env.PREVIOUS_RELEASES_HASH }}
|
||||
provider: ${{ inputs.provider }}
|
||||
28
.github/actions/cache/save/internal/action.yml
vendored
Normal file
28
.github/actions/cache/save/internal/action.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: 'Cache Save'
|
||||
description: 'Save a cache with WarpBuild on Warp runners and GitHub Actions cache otherwise'
|
||||
inputs:
|
||||
path:
|
||||
description: 'A list of files, directories, and wildcard patterns to cache'
|
||||
required: true
|
||||
key:
|
||||
description: 'An explicit key for saving the cache'
|
||||
required: true
|
||||
provider:
|
||||
description: 'The cache provider to use'
|
||||
required: true
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Save cache with WarpBuild
|
||||
if: ${{ inputs.provider == 'warp' }}
|
||||
uses: WarpBuilds/cache/save@v1
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
|
||||
- name: Save cache with GitHub Actions
|
||||
if: ${{ inputs.provider == 'gha' }}
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ inputs.key }}
|
||||
49
.github/actions/configure-docker/action.yml
vendored
49
.github/actions/configure-docker/action.yml
vendored
@@ -1,27 +1,27 @@
|
||||
name: 'Configure Docker'
|
||||
description: 'Set up Docker build driver and configure build cache args'
|
||||
inputs:
|
||||
cache-provider:
|
||||
description: 'gha or cirrus cache provider'
|
||||
required: true
|
||||
provider:
|
||||
description: 'The cache provider to use'
|
||||
required: false
|
||||
default: 'gha'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Check inputs
|
||||
shell: python
|
||||
run: |
|
||||
# We expect only gha or cirrus as inputs to cache-provider
|
||||
if "${{ inputs.cache-provider }}" not in ("gha", "cirrus"):
|
||||
print("::warning title=Unknown input to configure docker action::Provided value was ${{ inputs.cache-provider }}")
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx for Warp cache
|
||||
if: ${{ inputs.provider == 'warp' }}
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
# Use host network to allow access to cirrus gha cache running on the host
|
||||
driver-opts: |
|
||||
network=host
|
||||
|
||||
# This is required to allow buildkit to access the actions cache
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ inputs.provider != 'warp' }}
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
# This is required when using the gha cache backend with a manual docker buildx invocation.
|
||||
# Docker will check for variables $ACTIONS_CACHE_URL, $ACTIONS_RESULTS_URL and $ACTIONS_RUNTIME_TOKEN
|
||||
# which are set automatically when running on GitHub infra: https://docs.docker.com/build/cache/backends/gha/#synopsis
|
||||
- name: Expose actions cache variables
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
@@ -36,25 +36,18 @@ runs:
|
||||
- name: Construct docker build cache args
|
||||
shell: bash
|
||||
run: |
|
||||
# Configure docker build cache backend
|
||||
#
|
||||
# On forks the gha cache will work but will use Github's cache backend.
|
||||
# Docker will check for variables $ACTIONS_CACHE_URL, $ACTIONS_RESULTS_URL and $ACTIONS_RUNTIME_TOKEN
|
||||
# which are set automatically when running on GitHub infra: https://docs.docker.com/build/cache/backends/gha/#synopsis
|
||||
|
||||
# Use cirrus cache host
|
||||
if [[ ${{ inputs.cache-provider }} == 'cirrus' ]]; then
|
||||
url_args="url=${CIRRUS_CACHE_HOST},url_v2=${CIRRUS_CACHE_HOST}"
|
||||
else
|
||||
url_args=""
|
||||
cache_options="scope=${CONTAINER_NAME}"
|
||||
if [[ "${{ inputs.provider }}" == "warp" ]]; then
|
||||
cache_options="url=http://127.0.0.1:49160/,version=1,${cache_options}"
|
||||
fi
|
||||
|
||||
# Configure docker build cache backend
|
||||
# Always optimistically --cache‑from in case a cache blob exists
|
||||
args=(--cache-from "type=gha${url_args:+,${url_args}},scope=${CONTAINER_NAME}")
|
||||
args=(--cache-from "type=gha,${cache_options}")
|
||||
|
||||
# Only add --cache-to when using the Cirrus cache provider and pushing to the default branch.
|
||||
if [[ ${{ inputs.cache-provider }} == 'cirrus' && ${{ github.event_name }} == "push" && ${{ github.ref_name }} == ${{ github.event.repository.default_branch }} ]]; then
|
||||
args+=(--cache-to "type=gha${url_args:+,${url_args}},mode=max,ignore-error=true,scope=${CONTAINER_NAME}")
|
||||
# Only add --cache-to when pushing to the default branch.
|
||||
if [[ ${{ github.event_name }} == "push" && ${{ github.ref_name }} == ${{ github.event.repository.default_branch }} ]]; then
|
||||
args+=(--cache-to "type=gha,mode=max,ignore-error=true,${cache_options}")
|
||||
fi
|
||||
|
||||
# Always `--load` into docker images (needed when using the `docker-container` build driver).
|
||||
|
||||
93
.github/workflows/ci.yml
vendored
93
.github/workflows/ci.yml
vendored
@@ -19,8 +19,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
CI_FAILFAST_TEST_LEAVE_DANGLING: 1 # GHA does not care about dangling processes and setting this variable avoids killing the CI script itself on error
|
||||
CIRRUS_CACHE_HOST: http://127.0.0.1:12321/ # When using Cirrus Runners this host can be used by the docker `gha` build cache type.
|
||||
REPO_USE_CIRRUS_RUNNERS: 'bitcoin/bitcoin' # Use cirrus runners and cache for this repo, instead of falling back to the slow GHA runners
|
||||
REPO_USE_WARP_RUNNERS: 'bitcoin/bitcoin' # Use warp runners for this repo, instead of falling back to the slow GHA runners
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -47,9 +46,9 @@ jobs:
|
||||
fi
|
||||
- id: runners
|
||||
run: |
|
||||
if [[ "${REPO_USE_CIRRUS_RUNNERS}" == "${{ github.repository }}" ]]; then
|
||||
echo "provider=cirrus" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice title=Runner Selection::Using Cirrus Runners"
|
||||
if [[ "${REPO_USE_WARP_RUNNERS}" == "${{ github.repository }}" ]]; then
|
||||
echo "provider=warp" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice title=Runner Selection::Using Warp Runners"
|
||||
else
|
||||
echo "provider=gha" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice title=Runner Selection::Using GitHub-hosted runners"
|
||||
@@ -58,9 +57,9 @@ jobs:
|
||||
test-each-commit:
|
||||
name: 'test ancestor commits'
|
||||
needs: runners
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'cirrus' && 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'warp' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
|
||||
env:
|
||||
TEST_RUNNER_PORT_MIN: "14000" # Use a larger port, to avoid colliding with CIRRUS_CACHE_HOST port 12321.
|
||||
TEST_RUNNER_PORT_MIN: "14000" # Use a larger port range to avoid colliding with other CI services.
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.commits != 1
|
||||
timeout-minutes: 360 # Use maximum time, see https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes.
|
||||
steps:
|
||||
@@ -332,7 +331,7 @@ jobs:
|
||||
windows-cross:
|
||||
name: 'Windows-cross to x86_64, ${{ matrix.crt }}'
|
||||
needs: [runners, record-frozen-commit]
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'cirrus' && 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-sm' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'warp' && 'warp-ubuntu-latest-x64-4x' || 'ubuntu-latest' }}
|
||||
if: ${{ vars.SKIP_BRANCH_PUSH != 'true' || github.event_name == 'pull_request' }}
|
||||
|
||||
strategy:
|
||||
@@ -364,18 +363,22 @@ jobs:
|
||||
|
||||
- name: Restore caches
|
||||
id: restore-cache
|
||||
uses: ./.github/actions/restore-caches
|
||||
uses: ./.github/actions/cache/restore
|
||||
with:
|
||||
provider: ${{ needs.runners.outputs.provider }}
|
||||
|
||||
- name: Configure Docker
|
||||
uses: ./.github/actions/configure-docker
|
||||
with:
|
||||
cache-provider: ${{ needs.runners.outputs.provider }}
|
||||
provider: ${{ needs.runners.outputs.provider }}
|
||||
|
||||
- name: CI script
|
||||
run: ./ci/test_run_all.sh
|
||||
|
||||
- name: Save caches
|
||||
uses: ./.github/actions/save-caches
|
||||
uses: ./.github/actions/cache/save
|
||||
with:
|
||||
provider: ${{ needs.runners.outputs.provider }}
|
||||
|
||||
- name: Upload built executables
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -443,7 +446,7 @@ jobs:
|
||||
ci-matrix:
|
||||
name: ${{ matrix.name }}
|
||||
needs: runners
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'cirrus' && matrix.cirrus-runner || matrix.fallback-runner }}
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'warp' && matrix.warp-runner || matrix.fallback-runner }}
|
||||
if: ${{ vars.SKIP_BRANCH_PUSH != 'true' || github.event_name == 'pull_request' }}
|
||||
timeout-minutes: ${{ matrix.timeout-minutes }}
|
||||
|
||||
@@ -456,87 +459,87 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: 'iwyu'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_iwyu.sh'
|
||||
|
||||
- name: '32 bit ARM'
|
||||
cirrus-runner: 'ubuntu-24.04-arm' # Cirrus' Arm runners are Apple (with virtual Linux aarch64), which doesn't support 32-bit mode
|
||||
warp-runner: 'ubuntu-24.04-arm' # Warp's Arm runners don't support 32-bit mode currently
|
||||
fallback-runner: 'ubuntu-24.04-arm'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_arm.sh'
|
||||
provider: 'gha'
|
||||
|
||||
- name: 'ASan + LSan + UBSan + integer'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md' # has to match container in ci/test/00_setup_env_native_asan.sh for tracing tools
|
||||
warp-runner: 'warp-ubuntu-2404-x64-8x' # has to match container in ci/test/00_setup_env_native_asan.sh for tracing tools
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_asan.sh'
|
||||
|
||||
- name: 'macOS-cross to arm64'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-sm'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-4x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_mac_cross.sh'
|
||||
|
||||
- name: 'macOS-cross to x86_64'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-sm'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-4x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_mac_cross_intel.sh'
|
||||
|
||||
- name: 'No wallet'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-sm'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-4x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_nowallet.sh'
|
||||
|
||||
- name: 'i686, no IPC'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_i686_no_ipc.sh'
|
||||
|
||||
- name: 'fuzzer,address,undefined,integer'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-lg'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-16x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 240
|
||||
file-env: './ci/test/00_setup_env_native_fuzz.sh'
|
||||
|
||||
- name: 'previous releases'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_previous_releases.sh'
|
||||
|
||||
- name: 'Alpine (musl)'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_alpine_musl.sh'
|
||||
|
||||
- name: 'tidy'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_tidy.sh'
|
||||
|
||||
- name: 'TSan'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_tsan.sh'
|
||||
|
||||
- name: 'MSan, fuzz'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-md'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-8x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 150
|
||||
file-env: './ci/test/00_setup_env_native_fuzz_with_msan.sh'
|
||||
|
||||
- name: 'MSan'
|
||||
cirrus-runner: 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-lg'
|
||||
fallback-runner: 'ubuntu-24.04'
|
||||
warp-runner: 'warp-ubuntu-latest-x64-16x'
|
||||
fallback-runner: 'ubuntu-latest'
|
||||
timeout-minutes: 120
|
||||
file-env: './ci/test/00_setup_env_native_msan.sh'
|
||||
|
||||
@@ -550,12 +553,14 @@ jobs:
|
||||
|
||||
- name: Restore caches
|
||||
id: restore-cache
|
||||
uses: ./.github/actions/restore-caches
|
||||
uses: ./.github/actions/cache/restore
|
||||
with:
|
||||
provider: ${{ matrix.provider || needs.runners.outputs.provider }}
|
||||
|
||||
- name: Configure Docker
|
||||
uses: ./.github/actions/configure-docker
|
||||
with:
|
||||
cache-provider: ${{ matrix.provider || needs.runners.outputs.provider }}
|
||||
provider: ${{ matrix.provider || needs.runners.outputs.provider }}
|
||||
|
||||
- name: Clear unnecessary files
|
||||
if: ${{ needs.runners.outputs.provider == 'gha' && true || false }} # Only needed on GHA runners
|
||||
@@ -576,12 +581,14 @@ jobs:
|
||||
run: ./ci/test_run_all.sh
|
||||
|
||||
- name: Save caches
|
||||
uses: ./.github/actions/save-caches
|
||||
uses: ./.github/actions/cache/save
|
||||
with:
|
||||
provider: ${{ matrix.provider || needs.runners.outputs.provider }}
|
||||
|
||||
lint:
|
||||
name: 'lint'
|
||||
needs: runners
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'cirrus' && 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-xs' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ needs.runners.outputs.provider == 'warp' && 'warp-ubuntu-latest-x64-2x' || 'ubuntu-latest' }}
|
||||
if: ${{ vars.SKIP_BRANCH_PUSH != 'true' || github.event_name == 'pull_request' }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
@@ -598,7 +605,7 @@ jobs:
|
||||
- name: Configure Docker
|
||||
uses: ./.github/actions/configure-docker
|
||||
with:
|
||||
cache-provider: ${{ needs.runners.outputs.provider }}
|
||||
provider: ${{ needs.runners.outputs.provider }}
|
||||
|
||||
- name: CI script
|
||||
run: |
|
||||
|
||||
@@ -28,9 +28,9 @@ get_directory_property(precious_variables CACHE_VARIABLES)
|
||||
#=============================
|
||||
set(CLIENT_NAME "Bitcoin Core")
|
||||
set(CLIENT_VERSION_MAJOR 31)
|
||||
set(CLIENT_VERSION_MINOR 0)
|
||||
set(CLIENT_VERSION_MINOR 1)
|
||||
set(CLIENT_VERSION_BUILD 0)
|
||||
set(CLIENT_VERSION_RC 0)
|
||||
set(CLIENT_VERSION_RC 1)
|
||||
set(CLIENT_VERSION_IS_RELEASE "true")
|
||||
set(COPYRIGHT_YEAR "2026")
|
||||
|
||||
|
||||
18
ci/README.md
18
ci/README.md
@@ -80,20 +80,22 @@ trigger cache-invalidation and rebuilds as necessary.
|
||||
|
||||
To configure the primary repository, follow these steps:
|
||||
|
||||
1. Register with [Cirrus Runners](https://cirrus-runners.app/) and purchase runners.
|
||||
2. Install the Cirrus Runners GitHub app against the GitHub organization.
|
||||
1. Register with [WarpBuild](https://www.warpbuild.com/) and purchase runners.
|
||||
2. Install the WarpBuild GitHub app against the GitHub organization.
|
||||
3. Enable organisation-level runners to be used in public repositories:
|
||||
1. `Org settings -> Actions -> Runner Groups -> Default -> Allow public repos`
|
||||
4. Permit the following actions to run:
|
||||
1. cirruslabs/cache/restore@\*
|
||||
1. cirruslabs/cache/save@\*
|
||||
1. docker/setup-buildx-action@\*
|
||||
1. actions/cache/restore@\*
|
||||
1. actions/cache/save@\*
|
||||
1. actions/github-script@\*
|
||||
1. docker/setup-buildx-action@\*
|
||||
1. warpbuilds/cache/restore@\*
|
||||
1. warpbuilds/cache/save@\*
|
||||
|
||||
### Forked repositories
|
||||
|
||||
When used in a fork the CI will run on GitHub's free hosted runners by default.
|
||||
In this case, due to GitHub's 10GB-per-repo cache size limitations caches will be frequently evicted and missed, but the workflows will run (slowly).
|
||||
In this case, GitHub's cache size limitations may cause caches to be frequently evicted and missed, but the workflows will run (slowly).
|
||||
|
||||
It is also possible to use your own Cirrus Runners in your own fork with an appropriate patch to the `REPO_USE_CIRRUS_RUNNERS` variable in ../.github/workflows/ci.yml
|
||||
NB that Cirrus Runners only work at an organisation level, therefore in order to use your own Cirrus Runners, *the fork must be within your own organisation*.
|
||||
It is also possible to use your own WarpBuild Runners in your own fork with an appropriate patch to the `REPO_USE_WARP_RUNNERS` variable in ../.github/workflows/ci.yml
|
||||
NB that WarpBuild Runners only work at an organisation level, therefore in order to use your own WarpBuild Runners, *the fork must be within your own organisation*.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIN-CLI "1" "April 2026" "bitcoin-cli v31.0.0" "User Commands"
|
||||
.TH BITCOIN-CLI "1" "June 2026" "bitcoin-cli v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoin-cli \- manual page for bitcoin-cli v31.0.0
|
||||
bitcoin-cli \- manual page for bitcoin-cli v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoin-cli
|
||||
[\fI\,options\/\fR] \fI\,<command> \/\fR[\fI\,params\/\fR]
|
||||
@@ -15,7 +15,7 @@ bitcoin-cli \- manual page for bitcoin-cli v31.0.0
|
||||
.B bitcoin-cli
|
||||
[\fI\,options\/\fR] \fI\,help <command>\/\fR
|
||||
.SH DESCRIPTION
|
||||
Bitcoin Core RPC client version v31.0.0
|
||||
Bitcoin Core RPC client version v31.1.0rc1
|
||||
.PP
|
||||
The bitcoin\-cli utility provides a command line interface to interact with a Bitcoin Core RPC server.
|
||||
.PP
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIN-QT "1" "April 2026" "bitcoin-qt v31.0.0" "User Commands"
|
||||
.TH BITCOIN-QT "1" "June 2026" "bitcoin-qt v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoin-qt \- manual page for bitcoin-qt v31.0.0
|
||||
bitcoin-qt \- manual page for bitcoin-qt v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoin-qt
|
||||
[\fI\,options\/\fR] [\fI\,URI\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Bitcoin Core version v31.0.0
|
||||
Bitcoin Core version v31.1.0rc1
|
||||
.PP
|
||||
The bitcoin\-qt application provides a graphical interface for interacting with Bitcoin Core.
|
||||
.PP
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIN-TX "1" "April 2026" "bitcoin-tx v31.0.0" "User Commands"
|
||||
.TH BITCOIN-TX "1" "June 2026" "bitcoin-tx v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoin-tx \- manual page for bitcoin-tx v31.0.0
|
||||
bitcoin-tx \- manual page for bitcoin-tx v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoin-tx
|
||||
[\fI\,options\/\fR] \fI\,<hex-tx> \/\fR[\fI\,commands\/\fR]
|
||||
@@ -9,7 +9,7 @@ bitcoin-tx \- manual page for bitcoin-tx v31.0.0
|
||||
.B bitcoin-tx
|
||||
[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Bitcoin Core bitcoin\-tx utility version v31.0.0
|
||||
Bitcoin Core bitcoin\-tx utility version v31.1.0rc1
|
||||
.PP
|
||||
The bitcoin\-tx tool is used for creating and modifying bitcoin transactions.
|
||||
.PP
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIN-UTIL "1" "April 2026" "bitcoin-util v31.0.0" "User Commands"
|
||||
.TH BITCOIN-UTIL "1" "June 2026" "bitcoin-util v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoin-util \- manual page for bitcoin-util v31.0.0
|
||||
bitcoin-util \- manual page for bitcoin-util v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoin-util
|
||||
[\fI\,options\/\fR] [\fI\,command\/\fR]
|
||||
@@ -9,7 +9,7 @@ bitcoin-util \- manual page for bitcoin-util v31.0.0
|
||||
.B bitcoin-util
|
||||
[\fI\,options\/\fR] \fI\,grind <hex-block-header>\/\fR
|
||||
.SH DESCRIPTION
|
||||
Bitcoin Core bitcoin\-util utility version v31.0.0
|
||||
Bitcoin Core bitcoin\-util utility version v31.1.0rc1
|
||||
.PP
|
||||
The bitcoin\-util tool provides bitcoin related functionality that does not rely on the ability to access a running node. Available [commands] are listed below.
|
||||
.SH OPTIONS
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIN-WALLET "1" "April 2026" "bitcoin-wallet v31.0.0" "User Commands"
|
||||
.TH BITCOIN-WALLET "1" "June 2026" "bitcoin-wallet v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoin-wallet \- manual page for bitcoin-wallet v31.0.0
|
||||
bitcoin-wallet \- manual page for bitcoin-wallet v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoin-wallet
|
||||
[\fI\,options\/\fR] \fI\,<command>\/\fR
|
||||
.SH DESCRIPTION
|
||||
Bitcoin Core bitcoin\-wallet utility version v31.0.0
|
||||
Bitcoin Core bitcoin\-wallet utility version v31.1.0rc1
|
||||
.PP
|
||||
bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files.
|
||||
.PP
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIN "1" "April 2026" "bitcoin v31.0.0" "User Commands"
|
||||
.TH BITCOIN "1" "June 2026" "bitcoin v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoin \- manual page for bitcoin v31.0.0
|
||||
bitcoin \- manual page for bitcoin v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoin
|
||||
[\fI\,OPTIONS\/\fR] \fI\,COMMAND\/\fR...
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH BITCOIND "1" "April 2026" "bitcoind v31.0.0" "User Commands"
|
||||
.TH BITCOIND "1" "June 2026" "bitcoind v31.1.0rc1" "User Commands"
|
||||
.SH NAME
|
||||
bitcoind \- manual page for bitcoind v31.0.0
|
||||
bitcoind \- manual page for bitcoind v31.1.0rc1
|
||||
.SH SYNOPSIS
|
||||
.B bitcoind
|
||||
[\fI\,options\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Bitcoin Core daemon version v31.0.0 bitcoind
|
||||
Bitcoin Core daemon version v31.1.0rc1 bitcoind
|
||||
.PP
|
||||
The Bitcoin Core daemon (bitcoind) is a headless program that connects to the Bitcoin network to validate and relay transactions and blocks, as well as relaying addresses.
|
||||
.PP
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
v31.x Release Notes
|
||||
v31.1rc1 Release Notes
|
||||
===================
|
||||
|
||||
Bitcoin Core version 31.x is now available from:
|
||||
Bitcoin Core version 31.1rc1 is now available from:
|
||||
|
||||
<https://bitcoincore.org/bin/bitcoin-core-31.x/>
|
||||
<https://bitcoincore.org/bin/bitcoin-core-31.1/test.rc1/>
|
||||
|
||||
This release includes new features, various bug fixes and performance
|
||||
improvements, as well as updated translations.
|
||||
@@ -39,17 +39,35 @@ on them. It is not recommended to use Bitcoin Core on unsupported systems.
|
||||
Notable changes
|
||||
===============
|
||||
|
||||
### PrivateBroadcast
|
||||
|
||||
This release fixes an ip address leak when using the -privatebroadcast feature.
|
||||
Under certain circumstances connections were being made over clearnet rather than
|
||||
the enabled privacy network.
|
||||
|
||||
|
||||
### Validation
|
||||
|
||||
- #35209 validation: correct lifetime of precomputed tx data
|
||||
- #35465 coins: compact chainstate regularly
|
||||
|
||||
### Leveldb
|
||||
|
||||
- #61(bitcoin-core/leveldb): Disable seek compaction
|
||||
|
||||
### P2P
|
||||
|
||||
- #35032 net_processing: don't modify addrman for private broadcast connections
|
||||
- #35410 net: use the proxy if overriden when doing v2->v1 reconnections
|
||||
|
||||
### Wallet
|
||||
|
||||
- #35227 wallet: check the final BDB page LSN during migration
|
||||
- #35228 wallet: use outpoint when estimating input size
|
||||
|
||||
### Musig
|
||||
|
||||
- #35316 musig: Reject empty pubkey list in GetMuSig2KeyAggCache
|
||||
|
||||
### Build
|
||||
|
||||
@@ -62,11 +80,16 @@ Notable changes
|
||||
- #34991 test: fix feature_index_prune.py bug when using --usecli
|
||||
- #35080 test: Add missing self.options.timeout_factor scale in tool_bitcoin_chainstate.py
|
||||
- #35218 test: fix P2SH script in coins cache fuzz target
|
||||
- #35279 psbt, test: remove address type restrictions in test
|
||||
|
||||
### Fuzz
|
||||
|
||||
- #35289 fuzz: Fix timeout in txorphan
|
||||
|
||||
### Util
|
||||
|
||||
- #35384 util: Check write failures before renaming settings.json
|
||||
|
||||
### Docs
|
||||
|
||||
- #35283 doc: mention -DWITH_ZMQ=ON in BSD build guides
|
||||
@@ -75,29 +98,43 @@ Notable changes
|
||||
|
||||
- #35202 ci: restore sockets in i686, no IPC job
|
||||
- #35230 ci: Move --usecli --extended from i386 task to alpine task
|
||||
- #35348 ci: switch to GitHub cache for all runners
|
||||
- #35378 ci: switch runners from cirrus to warpbuild
|
||||
- #35408 ci: 35378 followups
|
||||
- #35430 ci: use warp caching on warp runners
|
||||
- #35447 ci: use warpbuild cache for docker buildkit cache
|
||||
|
||||
### Misc
|
||||
|
||||
- #35044 contrib: Fix NameError in signet miner gbt()
|
||||
- #35175 multi_index: fix compilation failure with boost >= 1.91
|
||||
- #34953 crypto: disable ASan instrumentation of SSE4 SHA256 for GCC
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Thanks to everyone who directly contributed to this release:
|
||||
|
||||
- andrewtoth
|
||||
- Cory Fields
|
||||
- Crypt-iQ
|
||||
- darosior
|
||||
- deadmanoz
|
||||
- fanquake
|
||||
- Greg Sanders
|
||||
- Hennadii Stepanov
|
||||
- junbyjun1238
|
||||
- Lőrinc
|
||||
- MarcoFalke
|
||||
- marcofleon
|
||||
- nervana21
|
||||
- optout21
|
||||
- Pol Espinasa
|
||||
- rkrux
|
||||
- Shrey
|
||||
- Torkel Rogstad
|
||||
- Vasil Dimov
|
||||
- willcl-ark
|
||||
|
||||
As well as to everyone that helped with translations on
|
||||
[Transifex](https://explore.transifex.com/bitcoin/bitcoin/).
|
||||
|
||||
@@ -86,7 +86,7 @@ bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& va
|
||||
|
||||
SettingsValue in;
|
||||
if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
|
||||
errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
|
||||
errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error, "
|
||||
"and can be fixed by removing the file, which will reset settings to default values.",
|
||||
fs::PathToString(path)));
|
||||
return false;
|
||||
@@ -139,7 +139,15 @@ bool WriteSettings(const fs::path& path,
|
||||
return false;
|
||||
}
|
||||
file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
|
||||
if (file.fail()) {
|
||||
errors.emplace_back(strprintf("Error: Unable to write settings file %s", fs::PathToString(path)));
|
||||
return false;
|
||||
}
|
||||
file.close();
|
||||
if (file.fail()) {
|
||||
errors.emplace_back(strprintf("Error: Unable to close settings file %s", fs::PathToString(path)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,18 +12,23 @@
|
||||
|
||||
namespace sha256_sse4
|
||||
{
|
||||
void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks)
|
||||
#if defined(__clang__)
|
||||
/*
|
||||
clang is unable to compile this with -O0 and -fsanitize=address.
|
||||
See upstream bug: https://github.com/llvm/llvm-project/issues/92182.
|
||||
This also fails to compile with -O2, -fcf-protection & -fsanitize=address.
|
||||
See https://github.com/bitcoin/bitcoin/issues/31913.
|
||||
*/
|
||||
#if __has_feature(address_sanitizer)
|
||||
/*
|
||||
Both Clang and GCC fail with ASan on this inline assembly:
|
||||
- Clang: compile failure with -O0 or -O2 + -fcf-protection under ASan.
|
||||
See https://github.com/llvm/llvm-project/issues/92182
|
||||
and https://github.com/bitcoin/bitcoin/issues/31913.
|
||||
- GCC: runtime SEGV during SHA256AutoDetect()'s self-test under ASan,
|
||||
regardless of optimization level.
|
||||
See https://github.com/bitcoin/bitcoin/issues/34881.
|
||||
*/
|
||||
#if defined(__SANITIZE_ADDRESS__)
|
||||
__attribute__((no_sanitize("address")))
|
||||
#elif defined(__clang__)
|
||||
#if __has_feature(address_sanitizer) // fallback can be removed once support for Clang 21 is dropped
|
||||
__attribute__((no_sanitize("address")))
|
||||
#endif
|
||||
#endif
|
||||
void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks)
|
||||
{
|
||||
static const uint32_t K256 alignas(16) [] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
|
||||
@@ -246,7 +246,7 @@ CDBWrapper::CDBWrapper(const DBParams& params)
|
||||
|
||||
if (params.options.force_compact) {
|
||||
LogInfo("Starting database compaction of %s", fs::PathToString(params.path));
|
||||
DBContext().pdb->CompactRange(nullptr, nullptr);
|
||||
CompactFull();
|
||||
LogInfo("Finished database compaction of %s", fs::PathToString(params.path));
|
||||
}
|
||||
|
||||
@@ -291,11 +291,18 @@ void CDBWrapper::WriteBatch(CDBBatch& batch, bool fSync)
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> CDBWrapper::GetProperty(const std::string& property) const
|
||||
{
|
||||
if (std::string value; DBContext().pdb->GetProperty(property, &value)) return value;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void CDBWrapper::CompactFull() { DBContext().pdb->CompactRange(nullptr, nullptr); }
|
||||
|
||||
size_t CDBWrapper::DynamicMemoryUsage() const
|
||||
{
|
||||
std::string memory;
|
||||
std::optional<size_t> parsed;
|
||||
if (!DBContext().pdb->GetProperty("leveldb.approximate-memory-usage", &memory) || !(parsed = ToIntegral<size_t>(memory))) {
|
||||
if (auto memory{GetProperty("leveldb.approximate-memory-usage")}; !memory || !(parsed = ToIntegral<size_t>(*memory))) {
|
||||
LogDebug(BCLog::LEVELDB, "Failed to get approximate-memory-usage property\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -250,6 +250,12 @@ public:
|
||||
|
||||
void WriteBatch(CDBBatch& batch, bool fSync = false);
|
||||
|
||||
//! Perform a blocking full compaction of the underlying LevelDB.
|
||||
void CompactFull();
|
||||
|
||||
//! Return a LevelDB property value, if available.
|
||||
std::optional<std::string> GetProperty(const std::string& property) const;
|
||||
|
||||
// Get an estimate of LevelDB memory usage (in bytes).
|
||||
size_t DynamicMemoryUsage() const;
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ static const int kValueSize = 200 * 1024;
|
||||
static const int kTotalSize = 100 * 1024 * 1024;
|
||||
static const int kCount = kTotalSize / kValueSize;
|
||||
|
||||
// Read through the first n keys repeatedly and check that they get
|
||||
// compacted (verified by checking the size of the key space).
|
||||
// Read through the first n keys repeatedly and check that reads do NOT
|
||||
// trigger compaction (seek compaction is disabled in this fork).
|
||||
void AutoCompactTest::DoReads(int n) {
|
||||
std::string value(kValueSize, 'x');
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
@@ -76,25 +76,23 @@ void AutoCompactTest::DoReads(int n) {
|
||||
const int64_t initial_size = Size(Key(0), Key(n));
|
||||
const int64_t initial_other_size = Size(Key(n), Key(kCount));
|
||||
|
||||
// Read until size drops significantly.
|
||||
// Read repeatedly. The size of the read range must NOT shrink: with
|
||||
// seek compaction disabled, reads never schedule a compaction.
|
||||
std::string limit_key = Key(n);
|
||||
for (int read = 0; true; read++) {
|
||||
ASSERT_LT(read, 100) << "Taking too long to compact";
|
||||
for (int read = 0; read < 100; read++) {
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
for (iter->SeekToFirst();
|
||||
iter->Valid() && iter->key().ToString() < limit_key; iter->Next()) {
|
||||
// Drop data
|
||||
}
|
||||
delete iter;
|
||||
// Wait a little bit to allow any triggered compactions to complete.
|
||||
Env::Default()->SleepForMicroseconds(1000000);
|
||||
uint64_t size = Size(Key(0), Key(n));
|
||||
fprintf(stderr, "iter %3d => %7.3f MB [other %7.3f MB]\n", read + 1,
|
||||
size / 1048576.0, Size(Key(n), Key(kCount)) / 1048576.0);
|
||||
if (size <= initial_size / 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Give any background work a chance to run, even though none should.
|
||||
Env::Default()->SleepForMicroseconds(1000000);
|
||||
ASSERT_EQ(Size(Key(0), Key(n)), static_cast<uint64_t>(initial_size));
|
||||
|
||||
// Verify that the size of the key space not touched by the reads
|
||||
// is pretty much unchanged.
|
||||
|
||||
@@ -735,15 +735,14 @@ TEST(DBTest, GetPicksCorrectFile) {
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, GetEncountersEmptyLevel) {
|
||||
TEST(DBTest, GetDoesNotTriggerSeekCompaction) {
|
||||
do {
|
||||
// Arrange for the following to happen:
|
||||
// * sstable A in level 0
|
||||
// * nothing in level 1
|
||||
// * sstable B in level 2
|
||||
// Then do enough Get() calls to arrange for an automatic compaction
|
||||
// of sstable A. A bug would cause the compaction to be marked as
|
||||
// occurring at level 1 (instead of the correct level 0).
|
||||
// Seek compaction is disabled in this fork, so repeated reads must
|
||||
// not change the level layout. A manual compaction must still work.
|
||||
|
||||
// Step 1: First place sstables in levels 0 and 2
|
||||
int compaction_count = 0;
|
||||
@@ -761,14 +760,17 @@ TEST(DBTest, GetEncountersEmptyLevel) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 1);
|
||||
|
||||
// Step 3: read a bunch of times
|
||||
// Step 3: many read misses must not schedule any compaction.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("missing"));
|
||||
}
|
||||
|
||||
// Step 4: Wait for compaction to finish
|
||||
DelayMilliseconds(1000);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 1);
|
||||
|
||||
// Step 4: a manual compaction still moves the L0 file down.
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
@@ -400,16 +400,11 @@ Status Version::Get(const ReadOptions& options, const LookupKey& k,
|
||||
return state.found ? state.s : Status::NotFound(Slice());
|
||||
}
|
||||
|
||||
bool Version::UpdateStats(const GetStats& stats) {
|
||||
FileMetaData* f = stats.seek_file;
|
||||
if (f != nullptr) {
|
||||
f->allowed_seeks--;
|
||||
if (f->allowed_seeks <= 0 && file_to_compact_ == nullptr) {
|
||||
file_to_compact_ = f;
|
||||
file_to_compact_level_ = stats.seek_file_level;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool Version::UpdateStats(const GetStats& /*stats*/) {
|
||||
// Disable automatic compactions triggered by read seek counters.
|
||||
// The heuristic was tuned for expensive random seeks and can create
|
||||
// severe write amplification on large random-key databases.
|
||||
// Size and manual compactions still run.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -661,6 +656,8 @@ class VersionSet::Builder {
|
||||
// same as the compaction of 40KB of data. We are a little
|
||||
// conservative and allow approximately one seek for every 16KB
|
||||
// of data before triggering a compaction.
|
||||
//
|
||||
// Note: seek compactions are disabled. See Version::UpdateStats.
|
||||
f->allowed_seeks = static_cast<int>((f->file_size / 16384U));
|
||||
if (f->allowed_seeks < 100) f->allowed_seeks = 100;
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ constexpr uint256 MUSIG_CHAINCODE{
|
||||
|
||||
static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
|
||||
{
|
||||
if (pubkeys.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the pubkeys
|
||||
std::vector<secp256k1_pubkey> secp_pubkeys;
|
||||
std::vector<const secp256k1_pubkey*> pubkey_ptrs;
|
||||
|
||||
55
src/net.cpp
55
src/net.cpp
@@ -487,8 +487,11 @@ CNode* CConnman::ConnectNode(CAddress addrConnect,
|
||||
LogDebug(BCLog::PROXY, "Using proxy: %s to connect to %s\n", proxy.ToString(), target_addr.ToStringAddrPort());
|
||||
sock = ConnectThroughProxy(proxy, target_addr.ToStringAddr(), target_addr.GetPort(), proxyConnectionFailed);
|
||||
} else {
|
||||
// no proxy needed (none set for target network)
|
||||
sock = ConnectDirectly(target_addr, conn_type == ConnectionType::MANUAL);
|
||||
// No proxy needed (none set for target network). Private broadcast connections
|
||||
// must always use a proxy, otherwise they would leak the originator's IP address.
|
||||
if (Assume(conn_type != ConnectionType::PRIVATE_BROADCAST)) {
|
||||
sock = ConnectDirectly(target_addr, conn_type == ConnectionType::MANUAL);
|
||||
}
|
||||
}
|
||||
if (!proxyConnectionFailed) {
|
||||
// If a connection to the node was attempted, and failure (if any) is not caused by a problem connecting to
|
||||
@@ -536,6 +539,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect,
|
||||
network_id,
|
||||
CNodeOptions{
|
||||
.permission_flags = permission_flags,
|
||||
.proxy_override = proxy_override,
|
||||
.i2p_sam_session = std::move(i2p_transient_session),
|
||||
.recv_flood_size = nReceiveFloodSize,
|
||||
.use_v2transport = use_v2transport,
|
||||
@@ -1902,7 +1906,13 @@ bool CConnman::AddConnection(const std::string& address, ConnectionType conn_typ
|
||||
CountingSemaphoreGrant<> grant(*semOutbound, true);
|
||||
if (!grant) return false;
|
||||
|
||||
OpenNetworkConnection(CAddress(), false, std::move(grant), address.c_str(), conn_type, /*use_v2transport=*/use_v2transport);
|
||||
OpenNetworkConnection(/*addrConnect=*/CAddress{},
|
||||
/*fCountFailure=*/false,
|
||||
/*grant_outbound=*/std::move(grant),
|
||||
/*pszDest=*/address.c_str(),
|
||||
/*conn_type=*/conn_type,
|
||||
/*use_v2transport=*/use_v2transport,
|
||||
/*proxy_override=*/std::nullopt);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1943,6 +1953,7 @@ void CConnman::DisconnectNodes()
|
||||
// and we don't want to hold up the socket handler thread for that long.
|
||||
if (network_active && pnode->m_transport->ShouldReconnectV1()) {
|
||||
reconnections_to_add.push_back({
|
||||
.proxy_override = pnode->m_proxy_override,
|
||||
.addr_connect = pnode->addr,
|
||||
.grant = std::move(pnode->grantOutbound),
|
||||
.destination = pnode->m_dest,
|
||||
@@ -2419,7 +2430,13 @@ void CConnman::ProcessAddrFetch()
|
||||
CAddress addr;
|
||||
CountingSemaphoreGrant<> grant(*semOutbound, /*fTry=*/true);
|
||||
if (grant) {
|
||||
OpenNetworkConnection(addr, false, std::move(grant), strDest.c_str(), ConnectionType::ADDR_FETCH, use_v2transport);
|
||||
OpenNetworkConnection(/*addrConnect=*/addr,
|
||||
/*fCountFailure=*/false,
|
||||
/*grant_outbound=*/std::move(grant),
|
||||
/*pszDest=*/strDest.c_str(),
|
||||
/*conn_type=*/ConnectionType::ADDR_FETCH,
|
||||
/*use_v2transport=*/use_v2transport,
|
||||
/*proxy_override=*/std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2537,8 +2554,13 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, std
|
||||
{
|
||||
for (const std::string& strAddr : connect)
|
||||
{
|
||||
CAddress addr(CService(), NODE_NONE);
|
||||
OpenNetworkConnection(addr, false, {}, strAddr.c_str(), ConnectionType::MANUAL, /*use_v2transport=*/use_v2transport);
|
||||
OpenNetworkConnection(/*addrConnect=*/CAddress{CService{}, NODE_NONE},
|
||||
/*fCountFailure=*/false,
|
||||
/*grant_outbound=*/{},
|
||||
/*pszDest=*/strAddr.c_str(),
|
||||
/*conn_type=*/ConnectionType::MANUAL,
|
||||
/*use_v2transport=*/use_v2transport,
|
||||
/*proxy_override=*/std::nullopt);
|
||||
for (int i = 0; i < 10 && i < nLoop; i++)
|
||||
{
|
||||
if (!m_interrupt_net->sleep_for(500ms)) {
|
||||
@@ -2888,7 +2910,13 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, std
|
||||
const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(m_max_automatic_connections - 1, 2)};
|
||||
// Use BIP324 transport when both us and them have NODE_V2_P2P set.
|
||||
const bool use_v2transport(addrConnect.nServices & GetLocalServices() & NODE_P2P_V2);
|
||||
OpenNetworkConnection(addrConnect, count_failures, std::move(grant), /*pszDest=*/nullptr, conn_type, use_v2transport);
|
||||
OpenNetworkConnection(/*addrConnect=*/addrConnect,
|
||||
/*fCountFailure=*/count_failures,
|
||||
/*grant_outbound=*/std::move(grant),
|
||||
/*pszDest=*/nullptr,
|
||||
/*conn_type=*/conn_type,
|
||||
/*use_v2transport=*/use_v2transport,
|
||||
/*proxy_override=*/std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2982,8 +3010,13 @@ void CConnman::ThreadOpenAddedConnections()
|
||||
break;
|
||||
}
|
||||
tried = true;
|
||||
CAddress addr(CService(), NODE_NONE);
|
||||
OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport);
|
||||
OpenNetworkConnection(/*addrConnect=*/CAddress{CService{}, NODE_NONE},
|
||||
/*fCountFailure=*/false,
|
||||
/*grant_outbound=*/std::move(grant),
|
||||
/*pszDest=*/info.m_params.m_added_node.c_str(),
|
||||
/*conn_type=*/ConnectionType::MANUAL,
|
||||
/*use_v2transport=*/info.m_params.m_use_v2transport,
|
||||
/*proxy_override=*/std::nullopt);
|
||||
if (!m_interrupt_net->sleep_for(500ms)) return;
|
||||
grant = CountingSemaphoreGrant<>(*semAddnode, /*fTry=*/true);
|
||||
}
|
||||
@@ -3980,6 +4013,7 @@ CNode::CNode(NodeId idIn,
|
||||
m_permission_flags{node_opts.permission_flags},
|
||||
m_sock{sock},
|
||||
m_connected{GetTime<std::chrono::seconds>()},
|
||||
m_proxy_override{std::move(node_opts.proxy_override)},
|
||||
addr{addrIn},
|
||||
addrBind{addrBindIn},
|
||||
m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn},
|
||||
@@ -4163,7 +4197,8 @@ void CConnman::PerformReconnections()
|
||||
std::move(item.grant),
|
||||
item.destination.empty() ? nullptr : item.destination.c_str(),
|
||||
item.conn_type,
|
||||
item.use_v2transport);
|
||||
item.use_v2transport,
|
||||
item.proxy_override);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -669,6 +669,7 @@ public:
|
||||
struct CNodeOptions
|
||||
{
|
||||
NetPermissionFlags permission_flags = NetPermissionFlags::None;
|
||||
std::optional<Proxy> proxy_override = {};
|
||||
std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr;
|
||||
bool prefer_evict = false;
|
||||
size_t recv_flood_size{DEFAULT_MAXRECEIVEBUFFER * 1000};
|
||||
@@ -711,6 +712,10 @@ public:
|
||||
std::atomic<std::chrono::seconds> m_last_recv{0s};
|
||||
//! Unix epoch time at peer connection
|
||||
const std::chrono::seconds m_connected;
|
||||
|
||||
//! Proxy to use regardless of global proxy settings if reconnecting to this node.
|
||||
const std::optional<Proxy> m_proxy_override;
|
||||
|
||||
// Address of this peer
|
||||
const CAddress addr;
|
||||
// Bind address of our side of the connection
|
||||
@@ -1186,7 +1191,7 @@ public:
|
||||
const char* pszDest,
|
||||
ConnectionType conn_type,
|
||||
bool use_v2transport,
|
||||
const std::optional<Proxy>& proxy_override = std::nullopt)
|
||||
const std::optional<Proxy>& proxy_override)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex);
|
||||
|
||||
/// Group of private broadcast related members.
|
||||
@@ -1778,6 +1783,7 @@ private:
|
||||
/** Struct for entries in m_reconnections. */
|
||||
struct ReconnectionInfo
|
||||
{
|
||||
std::optional<Proxy> proxy_override;
|
||||
CAddress addr_connect;
|
||||
CountingSemaphoreGrant<> grant;
|
||||
std::string destination;
|
||||
|
||||
@@ -356,7 +356,13 @@ static RPCHelpMan addnode()
|
||||
if (command == "onetry")
|
||||
{
|
||||
CAddress addr;
|
||||
connman.OpenNetworkConnection(addr, /*fCountFailure=*/false, /*grant_outbound=*/{}, std::string{node_arg}.c_str(), ConnectionType::MANUAL, use_v2transport);
|
||||
connman.OpenNetworkConnection(/*addrConnect=*/addr,
|
||||
/*fCountFailure=*/false,
|
||||
/*grant_outbound=*/{},
|
||||
/*pszDest=*/std::string{node_arg}.c_str(),
|
||||
/*conn_type=*/ConnectionType::MANUAL,
|
||||
/*use_v2transport=*/use_v2transport,
|
||||
/*proxy_override=*/std::nullopt);
|
||||
return UniValue::VNULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,12 @@ BOOST_AUTO_TEST_CASE(valid_keys)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_pubkey_list)
|
||||
{
|
||||
const std::optional<CPubKey> aggregate_pubkey{MuSig2AggregatePubkeys({})};
|
||||
BOOST_CHECK(!aggregate_pubkey.has_value());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(invalid_key)
|
||||
{
|
||||
std::vector<std::string> test_vectors = {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <txdb.h>
|
||||
#include <uint256.h>
|
||||
#include <undo.h>
|
||||
#include <util/check.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <map>
|
||||
@@ -1060,6 +1061,30 @@ BOOST_FIXTURE_TEST_CASE(ccoins_flush_behavior, FlushTest)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(coins_db_leveldb_layout, FlushTest)
|
||||
{
|
||||
auto level2_files{[](CCoinsViewDB& base) {
|
||||
return *Assert(ToIntegral<int>(*Assert(base.GetDBProperty("leveldb.num-files-at-level2"))));
|
||||
}};
|
||||
const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), 0};
|
||||
const Coin coin{MakeCoin()};
|
||||
const uint256 block_hash{m_rng.rand256()};
|
||||
|
||||
CCoinsViewDB base{{.path = m_args.GetDataDirBase() / "coins_db_leveldb_layout", .cache_bytes = 1_MiB, .wipe_data = true}, {}};
|
||||
CCoinsViewCache cache{&base};
|
||||
|
||||
cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin});
|
||||
cache.SetBestBlock(block_hash);
|
||||
cache.Sync();
|
||||
|
||||
BOOST_CHECK_EQUAL(level2_files(base), 0);
|
||||
WITH_LOCK(::cs_main, return base.CompactFull()).wait();
|
||||
BOOST_CHECK_EQUAL(level2_files(base), 1);
|
||||
|
||||
BOOST_CHECK(*Assert(base.GetCoin(outpoint)) == coin);
|
||||
BOOST_CHECK_EQUAL(base.GetBestBlock(), block_hash);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(coins_resource_is_used)
|
||||
{
|
||||
CCoinsMapMemoryResource resource;
|
||||
|
||||
@@ -173,13 +173,19 @@ FUZZ_TARGET(connman, .init = initialize_connman)
|
||||
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
|
||||
}
|
||||
|
||||
std::optional<Proxy> proxy_override;
|
||||
if (conn_type == ConnectionType::PRIVATE_BROADCAST || fuzzed_data_provider.ConsumeBool()) {
|
||||
proxy_override.emplace(ConsumeService(fuzzed_data_provider));
|
||||
}
|
||||
|
||||
connman.OpenNetworkConnection(
|
||||
/*addrConnect=*/random_address,
|
||||
/*fCountFailure=*/fuzzed_data_provider.ConsumeBool(),
|
||||
/*grant_outbound=*/{},
|
||||
/*pszDest=*/fuzzed_data_provider.ConsumeBool() ? nullptr : random_string.c_str(),
|
||||
/*conn_type=*/conn_type,
|
||||
/*use_v2transport=*/fuzzed_data_provider.ConsumeBool());
|
||||
/*use_v2transport=*/fuzzed_data_provider.ConsumeBool(),
|
||||
/*proxy_override=*/proxy_override);
|
||||
},
|
||||
[&] {
|
||||
connman.SetNetworkActive(fuzzed_data_provider.ConsumeBool());
|
||||
|
||||
@@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(ReadWrite)
|
||||
// Check invalid json not allowed
|
||||
WriteText(path, R"(invalid json)");
|
||||
BOOST_CHECK(!common::ReadSettings(path, values, errors));
|
||||
std::vector<std::string> fail_parse = {strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
|
||||
std::vector<std::string> fail_parse = {strprintf("Settings file %s does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error, "
|
||||
"and can be fixed by removing the file, which will reset settings to default values.",
|
||||
fs::PathToString(path))};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());
|
||||
|
||||
39
src/txdb.cpp
39
src/txdb.cpp
@@ -13,10 +13,14 @@
|
||||
#include <serialize.h>
|
||||
#include <uint256.h>
|
||||
#include <util/log.h>
|
||||
#include <util/threadnames.h>
|
||||
#include <util/vector.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
@@ -55,11 +59,22 @@ CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
|
||||
m_options{std::move(options)},
|
||||
m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
|
||||
|
||||
CCoinsViewDB::~CCoinsViewDB()
|
||||
{
|
||||
if (m_compaction.valid()) {
|
||||
if (m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
|
||||
LogInfo("Waiting for background chainstate compaction of %s", fs::PathToString(m_db_params.path));
|
||||
}
|
||||
m_compaction.wait();
|
||||
}
|
||||
}
|
||||
|
||||
void CCoinsViewDB::ResizeCache(size_t new_cache_size)
|
||||
{
|
||||
// We can't do this operation with an in-memory DB since we'll lose all the coins upon
|
||||
// reset.
|
||||
if (!m_db_params.memory_only) {
|
||||
LOCK(m_db_mutex);
|
||||
// Have to do a reset first to get the original `m_db` state to release its
|
||||
// filesystem lock.
|
||||
m_db.reset();
|
||||
@@ -168,6 +183,30 @@ size_t CCoinsViewDB::EstimateSize() const
|
||||
return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
|
||||
}
|
||||
|
||||
std::optional<std::string> CCoinsViewDB::GetDBProperty(const std::string& property)
|
||||
{
|
||||
return m_db->GetProperty(property);
|
||||
}
|
||||
|
||||
std::shared_future<void> CCoinsViewDB::CompactFull()
|
||||
{
|
||||
AssertLockHeld(::cs_main);
|
||||
if (m_compaction.valid() && m_compaction.wait_for(std::chrono::seconds{0}) != std::future_status::ready) return m_compaction;
|
||||
m_compaction = std::async(std::launch::async, [this] {
|
||||
try {
|
||||
util::ThreadRename("utxocompact");
|
||||
LOCK(m_db_mutex);
|
||||
|
||||
LogDebug(BCLog::COINDB, "Starting chainstate compaction of %s", fs::PathToString(m_db_params.path));
|
||||
m_db->CompactFull();
|
||||
LogDebug(BCLog::COINDB, "Finished chainstate compaction of %s", fs::PathToString(m_db_params.path));
|
||||
} catch (const std::exception& e) {
|
||||
LogWarning("Failed chainstate compaction (%s)", e.what());
|
||||
}
|
||||
}).share();
|
||||
return m_compaction;
|
||||
}
|
||||
|
||||
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
|
||||
class CCoinsViewDBCursor: public CCoinsViewCursor
|
||||
{
|
||||
|
||||
14
src/txdb.h
14
src/txdb.h
@@ -15,8 +15,10 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class COutPoint;
|
||||
@@ -36,9 +38,13 @@ class CCoinsViewDB final : public CCoinsView
|
||||
protected:
|
||||
DBParams m_db_params;
|
||||
CoinsViewOptions m_options;
|
||||
//! Prevents CompactFull() from using m_db while ResizeCache() replaces it.
|
||||
Mutex m_db_mutex;
|
||||
std::unique_ptr<CDBWrapper> m_db;
|
||||
std::shared_future<void> m_compaction;
|
||||
public:
|
||||
explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options);
|
||||
~CCoinsViewDB() override;
|
||||
|
||||
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
|
||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||
@@ -52,7 +58,13 @@ public:
|
||||
size_t EstimateSize() const override;
|
||||
|
||||
//! Dynamically alter the underlying leveldb cache size.
|
||||
void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_db_mutex);
|
||||
|
||||
//! Perform a full compaction of the underlying LevelDB on a one-shot background thread.
|
||||
std::shared_future<void> CompactFull() EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_db_mutex);
|
||||
|
||||
//! Return an underlying LevelDB property value, if available.
|
||||
std::optional<std::string> GetDBProperty(const std::string& property);
|
||||
};
|
||||
|
||||
#endif // BITCOIN_TXDB_H
|
||||
|
||||
@@ -112,6 +112,13 @@ const std::vector<std::string> CHECKLEVEL_DOC {
|
||||
* */
|
||||
static constexpr int PRUNE_LOCK_BUFFER{10};
|
||||
|
||||
// Return whether the completed full flush should compact chainstate
|
||||
static bool ShouldCompactChainstate(bool in_ibd)
|
||||
{
|
||||
static constexpr uint32_t flush_ratio{320}; // Roughly every 2 weeks with hourly flushes
|
||||
return !in_ibd && FastRandomContext().randrange(flush_ratio) == 0;
|
||||
}
|
||||
|
||||
TRACEPOINT_SEMAPHORE(validation, block_connected);
|
||||
TRACEPOINT_SEMAPHORE(utxocache, flush);
|
||||
TRACEPOINT_SEMAPHORE(mempool, replaced);
|
||||
@@ -2824,9 +2831,19 @@ bool Chainstate::FlushStateToDisk(
|
||||
m_next_write = FastRandomContext().rand_uniform_delay(NodeClock::now() + DATABASE_WRITE_INTERVAL_MIN, range);
|
||||
}
|
||||
}
|
||||
if (full_flush_completed && m_chainman.m_options.signals) {
|
||||
// Update best block in wallet (so we can detect restored wallets).
|
||||
m_chainman.m_options.signals->ChainStateFlushed(this->GetRole(), GetLocator(m_chain.Tip()));
|
||||
if (full_flush_completed) {
|
||||
if (m_chainman.m_options.signals) {
|
||||
// Update best block in wallet (so we can detect restored wallets).
|
||||
m_chainman.m_options.signals->ChainStateFlushed(this->GetRole(), GetLocator(m_chain.Tip()));
|
||||
}
|
||||
|
||||
if (!m_chainman.m_interrupt && ShouldCompactChainstate(m_chainman.IsInitialBlockDownload())) {
|
||||
try {
|
||||
CoinsDB().CompactFull();
|
||||
} catch (const std::exception& e) {
|
||||
LogWarning("Failed to start chainstate compaction (%s)", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::runtime_error& e) {
|
||||
return FatalError(m_chainman.GetNotifications(), state, strprintf(_("System error while flushing: %s"), e.what()));
|
||||
|
||||
@@ -94,7 +94,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoin
|
||||
if (!provider) return -1;
|
||||
|
||||
if (const auto desc = InferDescriptor(txout.scriptPubKey, *provider)) {
|
||||
if (const auto weight = MaxInputWeight(*desc, {}, coin_control, true, can_grind_r)) {
|
||||
if (const auto weight = MaxInputWeight(*desc, CTxIn{outpoint}, coin_control, true, can_grind_r)) {
|
||||
return static_cast<int>(GetVirtualTransactionSize(*weight, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <consensus/amount.h>
|
||||
#include <key.h>
|
||||
#include <policy/fees/block_policy_estimator.h>
|
||||
#include <script/solver.h>
|
||||
#include <validation.h>
|
||||
@@ -16,6 +17,22 @@
|
||||
namespace wallet {
|
||||
BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(max_signed_input_size_uses_external_outpoint)
|
||||
{
|
||||
const CKey key{GenerateRandomKey()};
|
||||
FillableSigningProvider provider;
|
||||
BOOST_REQUIRE(provider.AddKey(key));
|
||||
|
||||
const CTxOut txout{COIN, GetScriptForDestination(PKHash{key.GetPubKey()})};
|
||||
const COutPoint outpoint{Txid{}, 0};
|
||||
CCoinControl coin_control;
|
||||
coin_control.Select(outpoint).SetTxOut(txout);
|
||||
|
||||
const int low_r{CalculateMaximumSignedInputSize(txout, COutPoint{}, &provider, /*can_grind_r=*/true, &coin_control)};
|
||||
const int high_r{CalculateMaximumSignedInputSize(txout, outpoint, &provider, /*can_grind_r=*/true, &coin_control)};
|
||||
BOOST_CHECK_EQUAL(high_r, low_r + 1);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
|
||||
{
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
|
||||
@@ -73,7 +73,7 @@ class SettingsTest(BitcoinTestFramework):
|
||||
# Test invalid json
|
||||
with settings.open("w") as fp:
|
||||
fp.write("invalid json")
|
||||
node.assert_start_raises_init_error(expected_msg='does not contain valid JSON. This is probably caused by disk corruption or a crash', match=ErrorMatch.PARTIAL_REGEX)
|
||||
node.assert_start_raises_init_error(expected_msg='does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error', match=ErrorMatch.PARTIAL_REGEX)
|
||||
|
||||
# Test invalid json object
|
||||
with settings.open("w") as fp:
|
||||
|
||||
@@ -15,6 +15,7 @@ from test_framework.p2p import (
|
||||
P2PInterface,
|
||||
P2P_SERVICES,
|
||||
P2P_VERSION,
|
||||
start_p2p_listener,
|
||||
)
|
||||
from test_framework.messages import (
|
||||
CAddress,
|
||||
@@ -29,8 +30,7 @@ from test_framework.netutil import (
|
||||
)
|
||||
from test_framework.script_util import build_malleated_tx_package
|
||||
from test_framework.socks5 import (
|
||||
Socks5Configuration,
|
||||
Socks5Server,
|
||||
start_socks5_server,
|
||||
)
|
||||
from test_framework.test_framework import (
|
||||
BitcoinTestFramework,
|
||||
@@ -40,7 +40,6 @@ from test_framework.util import (
|
||||
assert_greater_than_or_equal,
|
||||
assert_not_equal,
|
||||
assert_raises_rpc_error,
|
||||
p2p_port,
|
||||
tor_port,
|
||||
)
|
||||
from test_framework.wallet import (
|
||||
@@ -49,116 +48,6 @@ from test_framework.wallet import (
|
||||
|
||||
NUM_PRIVATE_BROADCAST_PER_TX = 3
|
||||
|
||||
# Fill addrman with these addresses. Must have enough Tor addresses, so that even
|
||||
# if all 10 default connections are opened to a Tor address (!?) there must be more
|
||||
# for private broadcast.
|
||||
ADDRMAN_ADDRESSES = [
|
||||
"20.0.0.1",
|
||||
"30.0.0.1",
|
||||
"40.0.0.1",
|
||||
"50.0.0.1",
|
||||
"60.0.0.1",
|
||||
"70.0.0.1",
|
||||
"80.0.0.1",
|
||||
"90.0.0.1",
|
||||
"100.0.0.1",
|
||||
"110.0.0.1",
|
||||
"120.0.0.1",
|
||||
"130.0.0.1",
|
||||
"140.0.0.1",
|
||||
"150.0.0.1",
|
||||
"160.0.0.1",
|
||||
"170.0.0.1",
|
||||
"180.0.0.1",
|
||||
"190.0.0.1",
|
||||
"200.0.0.1",
|
||||
"210.0.0.1",
|
||||
|
||||
"[20::1]",
|
||||
"[30::1]",
|
||||
"[40::1]",
|
||||
"[50::1]",
|
||||
"[60::1]",
|
||||
"[70::1]",
|
||||
"[80::1]",
|
||||
"[90::1]",
|
||||
"[100::1]",
|
||||
"[110::1]",
|
||||
"[120::1]",
|
||||
"[130::1]",
|
||||
"[140::1]",
|
||||
"[150::1]",
|
||||
"[160::1]",
|
||||
"[170::1]",
|
||||
"[180::1]",
|
||||
"[190::1]",
|
||||
"[200::1]",
|
||||
"[210::1]",
|
||||
|
||||
"testonlyad777777777777777777777777777777777777777775b6qd.onion",
|
||||
"testonlyah77777777777777777777777777777777777777777z7ayd.onion",
|
||||
"testonlyal77777777777777777777777777777777777777777vp6qd.onion",
|
||||
"testonlyap77777777777777777777777777777777777777777r5qad.onion",
|
||||
"testonlyat77777777777777777777777777777777777777777udsid.onion",
|
||||
"testonlyax77777777777777777777777777777777777777777yciid.onion",
|
||||
"testonlya777777777777777777777777777777777777777777rhgyd.onion",
|
||||
"testonlybd77777777777777777777777777777777777777777rs4ad.onion",
|
||||
"testonlybp77777777777777777777777777777777777777777zs2ad.onion",
|
||||
"testonlybt777777777777777777777777777777777777777777x6id.onion",
|
||||
"testonlybx777777777777777777777777777777777777777775styd.onion",
|
||||
"testonlyb3777777777777777777777777777777777777777774ckid.onion",
|
||||
"testonlycd77777777777777777777777777777777777777777733id.onion",
|
||||
"testonlych77777777777777777777777777777777777777777t6kid.onion",
|
||||
"testonlycl77777777777777777777777777777777777777777tt3ad.onion",
|
||||
"testonlyct77777777777777777777777777777777777777777wvhyd.onion",
|
||||
"testonlycx7777777777777777777777777777777777777777774bad.onion",
|
||||
"testonlyc377777777777777777777777777777777777777777u6aid.onion",
|
||||
"testonlydd777777777777777777777777777777777777777777u5ad.onion",
|
||||
"testonlydh77777777777777777777777777777777777777777wgnyd.onion",
|
||||
|
||||
"testonlyad77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyah77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyap77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyat77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyax77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlya377777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlya777777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybd77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybh77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybl77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybp77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybt77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybx77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyb777777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlych77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlycp77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyct77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlycx77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyc377777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyc777777777777777777777777777777777777777777q.b32.i2p",
|
||||
|
||||
"[fc00::1]",
|
||||
"[fc00::2]",
|
||||
"[fc00::3]",
|
||||
"[fc00::5]",
|
||||
"[fc00::6]",
|
||||
"[fc00::7]",
|
||||
"[fc00::8]",
|
||||
"[fc00::9]",
|
||||
"[fc00::10]",
|
||||
"[fc00::11]",
|
||||
"[fc00::12]",
|
||||
"[fc00::13]",
|
||||
"[fc00::15]",
|
||||
"[fc00::16]",
|
||||
"[fc00::17]",
|
||||
"[fc00::18]",
|
||||
"[fc00::19]",
|
||||
"[fc00::20]",
|
||||
"[fc00::22]",
|
||||
"[fc00::23]",
|
||||
]
|
||||
|
||||
|
||||
class P2PPrivateBroadcast(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@@ -166,18 +55,6 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
|
||||
self.num_nodes = 2
|
||||
|
||||
def setup_nodes(self):
|
||||
# Start a SOCKS5 proxy server.
|
||||
socks5_server_config = Socks5Configuration()
|
||||
# self.nodes[0] listens on p2p_port(0),
|
||||
# self.nodes[1] listens on p2p_port(1),
|
||||
# thus we tell the SOCKS5 server to listen on p2p_port(self.num_nodes) (self.num_nodes is 2)
|
||||
socks5_server_config.addr = ("127.0.0.1", p2p_port(self.num_nodes))
|
||||
socks5_server_config.unauth = True
|
||||
socks5_server_config.auth = True
|
||||
|
||||
self.socks5_server = Socks5Server(socks5_server_config)
|
||||
self.socks5_server.start()
|
||||
|
||||
self.destinations = []
|
||||
|
||||
self.destinations_lock = threading.Lock()
|
||||
@@ -235,22 +112,7 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
|
||||
listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)
|
||||
listener.peer_connect_send_version(services=P2P_SERVICES)
|
||||
|
||||
def on_listen_done(addr, port):
|
||||
nonlocal actual_to_addr
|
||||
nonlocal actual_to_port
|
||||
actual_to_addr = addr
|
||||
actual_to_port = port
|
||||
|
||||
# Use port=0 to let the OS assign an available port. This
|
||||
# avoids "address already in use" errors when tests run
|
||||
# concurrently or ports are still in TIME_WAIT state.
|
||||
self.network_thread.listen(
|
||||
addr="127.0.0.1",
|
||||
port=0,
|
||||
p2p=listener,
|
||||
callback=on_listen_done)
|
||||
# Wait until the callback has been called.
|
||||
self.wait_until(lambda: actual_to_port != 0)
|
||||
actual_to_addr, actual_to_port = start_p2p_listener(self.network_thread, listener)
|
||||
|
||||
self.log.debug(f"Instructing the SOCKS5 proxy to redirect connection i={i} ({conn_type}) for "
|
||||
f"{format_addr_port(requested_to_addr, requested_to_port)} to "
|
||||
@@ -268,7 +130,7 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
|
||||
"actual_to_port": actual_to_port,
|
||||
}
|
||||
|
||||
self.socks5_server.conf.destinations_factory = destinations_factory
|
||||
self.socks5_server = start_socks5_server(destinations_factory)
|
||||
|
||||
self.extra_args = [
|
||||
[
|
||||
@@ -279,7 +141,7 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
|
||||
"-v2transport=0",
|
||||
"-test=addrman",
|
||||
"-privatebroadcast",
|
||||
f"-proxy={socks5_server_config.addr[0]}:{socks5_server_config.addr[1]}",
|
||||
f"-proxy={self.socks5_server.conf.addr[0]}:{self.socks5_server.conf.addr[1]}",
|
||||
# To increase coverage, make it think that the I2P network is reachable so that it
|
||||
# selects such addresses as well. Pick a proxy address where nobody is listening
|
||||
# and connection attempts fail quickly.
|
||||
@@ -361,13 +223,9 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
|
||||
tx_receiver = self.nodes[1]
|
||||
far_observer = tx_receiver.add_p2p_connection(P2PInterface())
|
||||
|
||||
wallet = MiniWallet(tx_originator)
|
||||
self.fill_node_addrman(node_index=0, address_types_to_add=[CAddress.NET_IPV4, CAddress.NET_IPV6, CAddress.NET_TORV3, CAddress.NET_I2P, CAddress.NET_CJDNS])
|
||||
|
||||
# Fill tx_originator's addrman.
|
||||
for addr in ADDRMAN_ADDRESSES:
|
||||
res = tx_originator.addpeeraddress(address=addr, port=0 if addr.endswith(".i2p") else 8333, tried=False)
|
||||
if not res["success"]:
|
||||
self.log.debug(f"Could not add {addr} to tx_originator's addrman (collision?)")
|
||||
wallet = MiniWallet(tx_originator)
|
||||
|
||||
txs = wallet.create_self_transfer_chain(chain_length=3)
|
||||
self.log.info(f"Created txid={txs[0]['txid']}: for basic test")
|
||||
|
||||
199
test/functional/p2p_private_broadcast_retry_v1.py
Executable file
199
test/functional/p2p_private_broadcast_retry_v1.py
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2026-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.
|
||||
"""
|
||||
Ensure that when v2 private broadcast connection to IPv4 fails the v1 retry
|
||||
will also be made through the Tor proxy.
|
||||
|
||||
The test does:
|
||||
* Add a bunch of IPv4 addresses to the node's addrman (they will be added without P2P_V2 flag).
|
||||
* Get them to report P2P_V2 in their service flags and connect to each one, so that the flags
|
||||
in addrman are updated to contain P2P_V2.
|
||||
* Get one successful connection to a Tor peer (.onion) so that bitcoind assumes the configured
|
||||
Tor proxy works and is indeed a proxy to the Tor network. This will make it open private
|
||||
broadcast connections also to IPv4 addresses via that proxy.
|
||||
* Start some private broadcast connections.
|
||||
* Remember the destination IPv4 address of the first connection and get it to fail the v2
|
||||
transport.
|
||||
* Wait for a subsequent connection also through the Tor proxy to the same IPv4 and expect
|
||||
it to be v1, i.e. the v2->v1 downgrade retry.
|
||||
"""
|
||||
|
||||
from test_framework.netutil import (
|
||||
format_addr_port
|
||||
)
|
||||
from test_framework.p2p import (
|
||||
P2PConnection,
|
||||
P2PInterface,
|
||||
P2P_SERVICES,
|
||||
start_p2p_listener,
|
||||
)
|
||||
from test_framework.messages import (
|
||||
CAddress,
|
||||
NODE_P2P_V2,
|
||||
)
|
||||
from test_framework.socks5 import (
|
||||
start_socks5_server,
|
||||
)
|
||||
from test_framework.test_framework import (
|
||||
BitcoinTestFramework,
|
||||
)
|
||||
from test_framework.v2_p2p import (
|
||||
EncryptedP2PState,
|
||||
)
|
||||
from test_framework.wallet import (
|
||||
MiniWallet,
|
||||
)
|
||||
|
||||
class P2PDetermineV2or1AndClose(P2PConnection):
|
||||
def __init__(self, on_v2or1_determined):
|
||||
super().__init__()
|
||||
self.on_v2or1_determined = on_v2or1_determined
|
||||
|
||||
# https://docs.python.org/3/library/asyncio-protocol.html#asyncio.Protocol.data_received
|
||||
def data_received(self, data):
|
||||
self.recvbuf += data
|
||||
if len(self.recvbuf) >= 4:
|
||||
self.on_v2or1_determined(1 if self.recvbuf[0:4] == self.magic_bytes else 2)
|
||||
self.peer_disconnect()
|
||||
|
||||
def on_open(self):
|
||||
pass
|
||||
|
||||
def on_close(self):
|
||||
pass
|
||||
|
||||
class P2PPrivateBroadcastRetryV1(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.disable_autoconnect = False
|
||||
self.num_nodes = 1
|
||||
|
||||
def ipv4_via_tor_proxy_conn_versions_append(self, v2or1):
|
||||
"""
|
||||
Add to the transport versions (v2 or v1) tried towards the first IPv4 which
|
||||
nodes[0] tries to connect to via the Tor proxy.
|
||||
"""
|
||||
self.ipv4_via_tor_proxy_conn_versions.append(v2or1)
|
||||
|
||||
def setup_nodes(self):
|
||||
def destinations_factory_all_proxy(requested_to_addr, requested_to_port):
|
||||
"""
|
||||
Instruct the SOCKS5 proxy to redirect all connections to newly created P2PInterface
|
||||
objects that claim support for P2P_V2.
|
||||
"""
|
||||
listener = P2PInterface()
|
||||
listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)
|
||||
listener.peer_connect_send_version(services=P2P_SERVICES | NODE_P2P_V2)
|
||||
|
||||
actual_to_addr, actual_to_port = start_p2p_listener(self.network_thread, listener)
|
||||
|
||||
self.log.debug("Instructing the common proxy to redirect connection for "
|
||||
f"{format_addr_port(requested_to_addr, requested_to_port)} to "
|
||||
f"{format_addr_port(actual_to_addr, actual_to_port)} (Python {type(listener).__name__})")
|
||||
|
||||
return {
|
||||
"actual_to_addr": actual_to_addr,
|
||||
"actual_to_port": actual_to_port,
|
||||
}
|
||||
|
||||
self.all_proxy = start_socks5_server(destinations_factory_all_proxy)
|
||||
|
||||
self.ipv4_via_tor_proxy_addr_port = None # Remember the first IPv4 address connected to via the Tor proxy.
|
||||
self.ipv4_via_tor_proxy_conn_versions = [] # Transport versions tried on that address.
|
||||
|
||||
def destinations_factory_tor_proxy(requested_to_addr, requested_to_port):
|
||||
"""
|
||||
Instruct the SOCKS5 proxy to redirect all connections to newly created P2PInterface,
|
||||
except the first connection to an IPv4 address and all subsequent connections to that
|
||||
address which are redirected to P2PDetermineV2or1AndClose.
|
||||
"""
|
||||
requested_to = format_addr_port(requested_to_addr, requested_to_port)
|
||||
|
||||
if not requested_to_addr.endswith(".onion") and self.ipv4_via_tor_proxy_addr_port is None: # First IPv4
|
||||
self.ipv4_via_tor_proxy_addr_port = requested_to
|
||||
|
||||
if self.ipv4_via_tor_proxy_addr_port == requested_to:
|
||||
# This is either the first (v2) or the second (the expected v1 retry) connection to requested_to.
|
||||
listener = P2PDetermineV2or1AndClose(self.ipv4_via_tor_proxy_conn_versions_append)
|
||||
listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)
|
||||
else:
|
||||
listener = P2PInterface()
|
||||
listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor)
|
||||
listener.peer_connect_send_version(services=P2P_SERVICES | NODE_P2P_V2)
|
||||
if not requested_to_addr.endswith(".onion"):
|
||||
listener.v2_state = EncryptedP2PState(initiating=False, net=self.chain)
|
||||
|
||||
actual_to_addr, actual_to_port = start_p2p_listener(self.network_thread, listener)
|
||||
|
||||
self.log.debug(f"Instructing the Tor proxy to redirect connection for {requested_to} to "
|
||||
f"{format_addr_port(actual_to_addr, actual_to_port)} (Python {type(listener).__name__})")
|
||||
|
||||
return {
|
||||
"actual_to_addr": actual_to_addr,
|
||||
"actual_to_port": actual_to_port,
|
||||
}
|
||||
|
||||
self.tor_proxy = start_socks5_server(destinations_factory_tor_proxy)
|
||||
|
||||
self.extra_args = [
|
||||
[
|
||||
"-privatebroadcast=1",
|
||||
f"-proxy={self.all_proxy.conf.addr[0]}:{self.all_proxy.conf.addr[1]}",
|
||||
f"-onion={self.tor_proxy.conf.addr[0]}:{self.tor_proxy.conf.addr[1]}",
|
||||
"-test=addrman",
|
||||
"-v2transport=0",
|
||||
],
|
||||
]
|
||||
|
||||
super().setup_nodes()
|
||||
|
||||
def setup_network(self):
|
||||
self.setup_nodes()
|
||||
|
||||
def run_test(self):
|
||||
node0 = self.nodes[0]
|
||||
|
||||
self.log.info("Filling node0's addrman with addresses")
|
||||
self.fill_node_addrman(node_index=0, address_types_to_add=[CAddress.NET_IPV4])
|
||||
|
||||
self.log.info("Opening manual connections to all IPv4 addresses to add P2P_V2 flag to addrman entries")
|
||||
for a in node0.getnodeaddresses(count=0, network="ipv4"):
|
||||
node0.addnode(node=format_addr_port(a["address"], a["port"]), command="onetry", v2transport=False)
|
||||
|
||||
self.log.info("Waiting for all IPv4 addresses to get P2P_V2 as a result of peers advertising support")
|
||||
self.wait_until(lambda: all(a["services"] & NODE_P2P_V2 != 0 for a in node0.getnodeaddresses(count=0, network="ipv4")))
|
||||
|
||||
# The destinations behind the -proxy= don't actually support v2. When bitcoind runs with -v2transport=1
|
||||
# and tries v2 on them they would print benign "magic byte mismatch" warnings.
|
||||
# Disable those since none of them are needed anymore.
|
||||
self.all_proxy.conf.destinations_factory = None
|
||||
|
||||
self.restart_node(0, extra_args=self.extra_args[0] + ["-v2transport=1"])
|
||||
|
||||
self.log.info("Opening a connection to a Tor addresses, so bitcoind considers -onion= is a real Tor proxy")
|
||||
node0.addnode(node="testonlyad777777777777777777777777777777777777777775b6qd.onion:1234", command="onetry", v2transport=False)
|
||||
|
||||
self.log.info("Waiting for at least one Tor connection")
|
||||
self.wait_until(lambda: any(p["network"] == "onion" for p in node0.getpeerinfo()))
|
||||
|
||||
self.log.info("Starting private broadcast connections")
|
||||
wallet = MiniWallet(node0)
|
||||
tx = wallet.create_self_transfer()
|
||||
node0.sendrawtransaction(hexstring=tx["hex"])
|
||||
|
||||
self.log.info("Tor proxy: waiting for connection to an IPv4 address")
|
||||
self.wait_until(lambda: self.ipv4_via_tor_proxy_addr_port is not None)
|
||||
self.log.info(f"Tor proxy: got {self.ipv4_via_tor_proxy_addr_port}, waiting for v2")
|
||||
self.wait_until(lambda: 2 in self.ipv4_via_tor_proxy_conn_versions)
|
||||
self.log.info(f"Tor proxy: got {self.ipv4_via_tor_proxy_addr_port} v2, waiting for v1")
|
||||
self.wait_until(lambda: 1 in self.ipv4_via_tor_proxy_conn_versions)
|
||||
self.log.info(f"Tor proxy: got {self.ipv4_via_tor_proxy_addr_port} v2, v1")
|
||||
|
||||
self.stop_node(0)
|
||||
self.all_proxy.stop()
|
||||
self.tor_proxy.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
P2PPrivateBroadcastRetryV1(__file__).main()
|
||||
@@ -65,7 +65,7 @@ class PSBTTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 3
|
||||
self.extra_args = [
|
||||
["-walletrbf=1", "-addresstype=bech32", "-changetype=bech32"], #TODO: Remove address type restrictions once taproot has psbt extensions
|
||||
["-walletrbf=1"],
|
||||
["-walletrbf=0", "-changetype=legacy"],
|
||||
[]
|
||||
]
|
||||
|
||||
@@ -973,3 +973,27 @@ class P2PTxInvStore(P2PInterface):
|
||||
self.wait_until(lambda: set(self.tx_invs_received.keys()) == set([int(tx, 16) for tx in txns]), timeout=timeout)
|
||||
# Flush messages and wait for the getdatas to be processed
|
||||
self.sync_with_ping()
|
||||
|
||||
def start_p2p_listener(network_thread, listener):
|
||||
listen_addr = ""
|
||||
listen_port = 0
|
||||
|
||||
def on_listen_done(addr, port):
|
||||
nonlocal listen_addr
|
||||
nonlocal listen_port
|
||||
listen_addr = addr
|
||||
listen_port = port
|
||||
|
||||
# Use port=0 to let the OS assign an available port. This
|
||||
# avoids "address already in use" errors when tests run
|
||||
# concurrently or ports are still in TIME_WAIT state.
|
||||
network_thread.listen(
|
||||
addr="127.0.0.1",
|
||||
port=0,
|
||||
p2p=listener,
|
||||
callback=on_listen_done)
|
||||
|
||||
# Wait until the callback has been called.
|
||||
wait_until_helper_internal(lambda: listen_port != 0)
|
||||
|
||||
return listen_addr, listen_port
|
||||
|
||||
@@ -327,3 +327,15 @@ class Socks5Server():
|
||||
logger.debug(f"Stop(): Handler {i} thread joined")
|
||||
else:
|
||||
logger.warning(f"Stop(): Handler thread {i} didn't finish after force close")
|
||||
|
||||
def start_socks5_server(destinations_factory):
|
||||
config = Socks5Configuration()
|
||||
config.addr = ("127.0.0.1", 0) # Use port=0 to let the OS pick one. The actual port is later in server.conf.addr[1].
|
||||
config.unauth = True
|
||||
config.auth = True
|
||||
config.destinations_factory = destinations_factory
|
||||
|
||||
server = Socks5Server(config)
|
||||
server.start()
|
||||
|
||||
return server
|
||||
|
||||
@@ -23,6 +23,7 @@ import time
|
||||
from .address import create_deterministic_address_bcrt1_p2tr_op_true
|
||||
from .authproxy import JSONRPCException
|
||||
from . import coverage
|
||||
from .messages import CAddress
|
||||
from .p2p import NetworkThread
|
||||
from .test_node import TestNode
|
||||
from .util import (
|
||||
@@ -728,6 +729,125 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
def wait_until(self, test_function, timeout=60, check_interval=0.05):
|
||||
return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.options.timeout_factor, check_interval=check_interval)
|
||||
|
||||
def fill_node_addrman(self, *, node_index, address_types_to_add):
|
||||
ADDRESSES = {
|
||||
CAddress.NET_IPV4: [
|
||||
"20.0.0.1",
|
||||
"30.0.0.1",
|
||||
"40.0.0.1",
|
||||
"50.0.0.1",
|
||||
"60.0.0.1",
|
||||
"70.0.0.1",
|
||||
"80.0.0.1",
|
||||
"90.0.0.1",
|
||||
"100.0.0.1",
|
||||
"110.0.0.1",
|
||||
"120.0.0.1",
|
||||
"130.0.0.1",
|
||||
"140.0.0.1",
|
||||
"150.0.0.1",
|
||||
"160.0.0.1",
|
||||
"170.0.0.1",
|
||||
"180.0.0.1",
|
||||
"190.0.0.1",
|
||||
"200.0.0.1",
|
||||
"210.0.0.1",
|
||||
],
|
||||
CAddress.NET_IPV6: [
|
||||
"[20::1]",
|
||||
"[30::1]",
|
||||
"[40::1]",
|
||||
"[50::1]",
|
||||
"[60::1]",
|
||||
"[70::1]",
|
||||
"[80::1]",
|
||||
"[90::1]",
|
||||
"[100::1]",
|
||||
"[110::1]",
|
||||
"[120::1]",
|
||||
"[130::1]",
|
||||
"[140::1]",
|
||||
"[150::1]",
|
||||
"[160::1]",
|
||||
"[170::1]",
|
||||
"[180::1]",
|
||||
"[190::1]",
|
||||
"[200::1]",
|
||||
"[210::1]",
|
||||
],
|
||||
CAddress.NET_TORV3: [
|
||||
"testonlyad777777777777777777777777777777777777777775b6qd.onion",
|
||||
"testonlyah77777777777777777777777777777777777777777z7ayd.onion",
|
||||
"testonlyal77777777777777777777777777777777777777777vp6qd.onion",
|
||||
"testonlyap77777777777777777777777777777777777777777r5qad.onion",
|
||||
"testonlyat77777777777777777777777777777777777777777udsid.onion",
|
||||
"testonlyax77777777777777777777777777777777777777777yciid.onion",
|
||||
"testonlya777777777777777777777777777777777777777777rhgyd.onion",
|
||||
"testonlybd77777777777777777777777777777777777777777rs4ad.onion",
|
||||
"testonlybp77777777777777777777777777777777777777777zs2ad.onion",
|
||||
"testonlybt777777777777777777777777777777777777777777x6id.onion",
|
||||
"testonlybx777777777777777777777777777777777777777775styd.onion",
|
||||
"testonlyb3777777777777777777777777777777777777777774ckid.onion",
|
||||
"testonlycd77777777777777777777777777777777777777777733id.onion",
|
||||
"testonlych77777777777777777777777777777777777777777t6kid.onion",
|
||||
"testonlycl77777777777777777777777777777777777777777tt3ad.onion",
|
||||
"testonlyct77777777777777777777777777777777777777777wvhyd.onion",
|
||||
"testonlycx7777777777777777777777777777777777777777774bad.onion",
|
||||
"testonlyc377777777777777777777777777777777777777777u6aid.onion",
|
||||
"testonlydd777777777777777777777777777777777777777777u5ad.onion",
|
||||
"testonlydh77777777777777777777777777777777777777777wgnyd.onion",
|
||||
],
|
||||
CAddress.NET_I2P: [
|
||||
"testonlyad77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyah77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyap77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyat77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyax77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlya377777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlya777777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybd77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybh77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybl77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybp77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybt77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlybx77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyb777777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlych77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlycp77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyct77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlycx77777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyc377777777777777777777777777777777777777777q.b32.i2p",
|
||||
"testonlyc777777777777777777777777777777777777777777q.b32.i2p",
|
||||
],
|
||||
CAddress.NET_CJDNS: [
|
||||
"[fc00::1]",
|
||||
"[fc00::2]",
|
||||
"[fc00::3]",
|
||||
"[fc00::5]",
|
||||
"[fc00::6]",
|
||||
"[fc00::7]",
|
||||
"[fc00::8]",
|
||||
"[fc00::9]",
|
||||
"[fc00::10]",
|
||||
"[fc00::11]",
|
||||
"[fc00::12]",
|
||||
"[fc00::13]",
|
||||
"[fc00::15]",
|
||||
"[fc00::16]",
|
||||
"[fc00::17]",
|
||||
"[fc00::18]",
|
||||
"[fc00::19]",
|
||||
"[fc00::20]",
|
||||
"[fc00::22]",
|
||||
"[fc00::23]",
|
||||
],
|
||||
}
|
||||
for addr_type in address_types_to_add:
|
||||
for addr in ADDRESSES[addr_type]:
|
||||
res = self.nodes[node_index].addpeeraddress(address=addr, port=0 if addr.endswith(".i2p") else 8333, tried=False)
|
||||
if not res["success"]:
|
||||
self.log.debug(f"Could not add {addr} to nodes[{node_index}]'s addrman (collision?)")
|
||||
|
||||
# Private helper methods. These should not be accessed by the subclass test scripts.
|
||||
|
||||
def _start_logging(self):
|
||||
|
||||
@@ -367,6 +367,7 @@ BASE_SCRIPTS = [
|
||||
'p2p_permissions.py',
|
||||
'feature_blocksdir.py',
|
||||
'wallet_startup.py',
|
||||
'p2p_private_broadcast_retry_v1.py',
|
||||
'feature_remove_pruned_files_on_startup.py',
|
||||
'p2p_i2p_ports.py',
|
||||
'p2p_i2p_sessions.py',
|
||||
|
||||
@@ -66,7 +66,7 @@ pub fn get_subtrees() -> Vec<&'static str> {
|
||||
"src/crc32c",
|
||||
"src/crypto/ctaes",
|
||||
"src/ipc/libmultiprocess",
|
||||
"src/leveldb",
|
||||
//"src/leveldb", No longer a subtree in this release branch, due to direct cherry-picks
|
||||
"src/minisketch",
|
||||
"src/secp256k1",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user