+
+ `;
+
+ fs.writeFileSync('index.html', mainIndexHtml);
+
+ // Set outputs for use in PR comment
+ const resultUrl = `https://${context.repo.owner}.github.io/${context.repo.name}/results/pr-${prNumber}/${runId}/index.html`;
+ const speedupString = Object.entries(combinedResults.speedups)
+ .map(([network, speedup]) => `${network}: ${speedup}%`)
+ .join(', ');
+
+ core.setOutput('result-url', resultUrl);
+ core.setOutput('speedups', speedupString);
+ core.setOutput('pr-number', prNumber);
+ return { url: resultUrl, speedups: speedupString };
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: results
+ - name: Commit and push to gh-pages
+ run: |
+ git config --global user.name "github-actions[bot]"
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git add results/ index.html
+ git commit -m "Update benchmark results from run ${{ github.event.workflow_run.id }}"
+ git push origin gh-pages
+ comment-pr:
+ needs: build
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ actions: read
+ steps:
+ - name: Comment on PR
+ if: ${{ needs.build.outputs.pr-number != 'main' }}
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh pr comment ${{ needs.build.outputs.pr-number }} \
+ --repo ${{ github.repository }} \
+ --body "๐ Benchmark results for this run (${{ github.event.workflow_run.id }}) will be available at: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/results/pr-${{ needs.build.outputs.pr-number }}/${{ github.event.workflow_run.id }}/index.html after the github pages \"build and deployment\" action has completed.
+ ๐ Speedups: ${{ needs.build.outputs.speedups }}"
diff --git a/.github/workflows/sync_upstream.yml b/.github/workflows/sync_upstream.yml
new file mode 100644
index 0000000000..11d9cfafc2
--- /dev/null
+++ b/.github/workflows/sync_upstream.yml
@@ -0,0 +1,32 @@
+name: Sync with Upstream
+on:
+ schedule:
+ - cron: '0 3 * * *' # 03:00 UTC daily
+ workflow_dispatch:
+permissions:
+ contents: write # Required for pushing to master
+jobs:
+ sync:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Add upstream remote
+ run: |
+ git remote add upstream https://github.com/bitcoin/bitcoin.git
+ git remote -v
+ - name: Fetch upstream
+ run: git fetch upstream
+ - name: Configure Git
+ run: |
+ git config user.name github-actions
+ git config user.email github-actions@github.com
+ - name: Rebase onto upstream
+ run: |
+ git checkout master
+ git rebase upstream/master
+ - name: Push changes
+ run: git push --force-with-lease origin master
diff --git a/.tx/config b/.tx/config
index 02d80379d1..b5a9abaae3 100644
--- a/.tx/config
+++ b/.tx/config
@@ -1,7 +1,7 @@
[main]
host = https://www.transifex.com
-[o:bitcoin:p:bitcoin:r:qt-translation-028x]
+[o:bitcoin:p:bitcoin:r:qt-translation-029x]
file_filter = src/qt/locale/bitcoin_.xlf
source_file = src/qt/locale/bitcoin_en.xlf
source_lang = en
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b002641415..f53a458d34 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -609,7 +609,12 @@ message("Configure summary")
message("=================")
message("Executables:")
message(" bitcoind ............................ ${BUILD_DAEMON}")
-message(" bitcoin-node (multiprocess) ......... ${WITH_MULTIPROCESS}")
+if(BUILD_DAEMON AND WITH_MULTIPROCESS)
+ set(bitcoin_daemon_status ON)
+else()
+ set(bitcoin_daemon_status OFF)
+endif()
+message(" bitcoin-node (multiprocess) ......... ${bitcoin_daemon_status}")
message(" bitcoin-qt (GUI) .................... ${BUILD_GUI}")
if(BUILD_GUI AND WITH_MULTIPROCESS)
set(bitcoin_gui_status ON)
diff --git a/bench-ci/build_binaries.sh b/bench-ci/build_binaries.sh
new file mode 100755
index 0000000000..9a396a0065
--- /dev/null
+++ b/bench-ci/build_binaries.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+set -euxo pipefail
+
+if [ $# -ne 2 ]; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+# Save current state of git
+initial_ref=$(git symbolic-ref -q HEAD || git rev-parse HEAD)
+if git symbolic-ref -q HEAD >/dev/null; then
+ initial_state="branch"
+ initial_branch=${initial_ref#refs/heads/}
+else
+ initial_state="detached"
+fi
+
+base_commit="$1"
+head_commit="$2"
+
+mkdir -p binaries/base
+mkdir -p binaries/head
+
+for build in "base:${base_commit}" "head:${head_commit}"; do
+ name="${build%%:*}"
+ commit="${build#*:}"
+ git checkout "$commit"
+ # Use environment variables if set, otherwise use defaults
+ HOSTS="${HOSTS:-x86_64-linux-gnu}" \
+ SOURCES_PATH="${SOURCES_PATH:-/data/SOURCES_PATH}" \
+ BASE_CACHE="${BASE_CACHE:-/data/BASE_CACHE}" \
+ taskset -c 2-15 chrt -f 1 bench-ci/guix/guix-build
+
+ # Truncate commit hash to 12 characters
+ short_commit=$(echo "$commit" | cut -c 1-12)
+
+ # Extract the Guix output
+ tar -xzf "guix-build-${short_commit}/output/x86_64-linux-gnu/bitcoin-${short_commit}-x86_64-linux-gnu.tar.gz"
+
+ # Copy the binary to our binaries directory
+ cp "bitcoin-${short_commit}/bin/bitcoind" "binaries/${name}/bitcoind"
+
+ # Cleanup extracted files
+ rm -rf "bitcoin-${short_commit}"
+done
+
+# Restore initial git state
+if [ "$initial_state" = "branch" ]; then
+ git checkout "$initial_branch"
+else
+ git checkout "$initial_ref"
+fi
diff --git a/bench-ci/guix/INSTALL.md b/bench-ci/guix/INSTALL.md
new file mode 100644
index 0000000000..10f5835bb8
--- /dev/null
+++ b/bench-ci/guix/INSTALL.md
@@ -0,0 +1,814 @@
+# Guix Installation and Setup
+
+This only needs to be done once per machine. If you have already completed the
+installation and setup, please proceed to [perform a build](./README.md).
+
+Otherwise, you may choose from one of the following options to install Guix:
+
+1. Using the official **shell installer script** [โค skip to section][install-script]
+ - Maintained by Guix developers
+ - Easiest (automatically performs *most* setup)
+ - Works on nearly all Linux distributions
+ - Only installs latest release
+ - Binary installation only, requires high level of trust
+ - Note: The script needs to be run as root, so it should be inspected before it's run
+2. Using the official **binary tarball** [โค skip to section][install-bin-tarball]
+ - Maintained by Guix developers
+ - Normal difficulty (full manual setup required)
+ - Works on nearly all Linux distributions
+ - Installs any release
+ - Binary installation only, requires high level of trust
+3. Using fanquake's **Docker image** [โ๏ธ external instructions][install-fanquake-docker]
+ - Maintained by fanquake
+ - Easy (automatically performs *some* setup)
+ - Works wherever Docker images work
+ - Installs any release
+ - Binary installation only, requires high level of trust
+4. Using a **distribution-maintained package** [โค skip to section][install-distro-pkg]
+ - Maintained by distribution's Guix package maintainer
+ - Normal difficulty (manual setup required)
+ - Works only on distributions with Guix packaged, see: https://repology.org/project/guix/versions
+ - Installs a release decided on by package maintainer
+ - Source or binary installation depending on the distribution
+5. Building **from source** [โค skip to section][install-source]
+ - Maintained by you
+ - Hard, but rewarding
+ - Can be made to work on most Linux distributions
+ - Installs any commit (more granular)
+ - Source installation, requires lower level of trust
+
+## Options 1 and 2: Using the official shell installer script or binary tarball
+
+The installation instructions for both the official shell installer script and
+the binary tarballs can be found in the GNU Guix Manual's [Binary Installation
+section](https://guix.gnu.org/manual/en/html_node/Binary-Installation.html).
+
+Note that running through the binary tarball installation steps is largely
+equivalent to manually performing what the shell installer script does.
+
+Note that at the time of writing (July 5th, 2021), the shell installer script
+automatically creates an `/etc/profile.d` entry which the binary tarball
+installation instructions do not ask you to create. However, you will likely
+need this entry for better desktop integration. Please see [this
+section](#add-an-etcprofiled-entry) for instructions on how to add a
+`/etc/profile.d/guix.sh` entry.
+
+Regardless of which installation option you chose, the changes to
+`/etc/profile.d` will not take effect until the next shell or desktop session,
+so you should log out and log back in.
+
+## Option 3: Using fanquake's Docker image
+
+Please refer to fanquake's instructions
+[here](https://github.com/fanquake/core-review/tree/master/guix).
+
+## Option 4: Using a distribution-maintained package
+
+Note that this section is based on the distro packaging situation at the time of
+writing (July 2021). Guix is expected to be more widely packaged over time. For
+an up-to-date view on Guix's package status/version across distros, please see:
+https://repology.org/project/guix/versions
+
+### Debian / Ubuntu
+
+Guix is available as a distribution package in [Debian
+](https://packages.debian.org/search?keywords=guix) and [Ubuntu
+](https://packages.ubuntu.com/search?keywords=guix).
+
+To install:
+```sh
+sudo apt install guix
+```
+
+### Arch Linux
+
+Guix is available in the AUR as
+[`guix`](https://aur.archlinux.org/packages/guix/), please follow the
+installation instructions in the Arch Linux Wiki ([live
+link](https://wiki.archlinux.org/index.php/Guix#AUR_Package_Installation),
+[2021/03/30
+permalink](https://wiki.archlinux.org/index.php?title=Guix&oldid=637559#AUR_Package_Installation))
+to install Guix.
+
+At the time of writing (2021/03/30), the `check` phase will fail if the path to
+guix's build directory is longer than 36 characters due to an anachronistic
+character limit on the shebang line. Since the `check` phase happens after the
+`build` phase, which may take quite a long time, it is recommended that users
+either:
+
+1. Skip the `check` phase
+ - For `makepkg`: `makepkg --nocheck ...`
+ - For `yay`: `yay --mflags="--nocheck" ...`
+ - For `paru`: `paru --nocheck ...`
+2. Or, check their build directory's length beforehand
+ - For those building with `makepkg`: `pwd | wc -c`
+
+## Option 5: Building from source
+
+Building Guix from source is a rather involved process but a rewarding one for
+those looking to minimize trust and maximize customizability (e.g. building a
+particular commit of Guix). Previous experience with using autotools-style build
+systems to build packages from source will be helpful. *hic sunt dracones.*
+
+I strongly urge you to at least skim through the entire section once before you
+start issuing commands, as it will save you a lot of unnecessary pain and
+anguish.
+
+### Installing common build tools
+
+There are a few basic build tools that are required for most things we'll build,
+so let's install them now:
+
+Text transformation/i18n:
+- `autopoint` (sometimes packaged in `gettext`)
+- `help2man`
+- `po4a`
+- `texinfo`
+
+Build system tools:
+- `g++` w/ C++11 support
+- `libtool`
+- `autoconf`
+- `automake`
+- `pkg-config` (sometimes packaged as `pkgconf`)
+- `make`
+- `cmake`
+
+Miscellaneous:
+- `git`
+- `gnupg`
+- `python3`
+
+### Building and Installing Guix's dependencies
+
+In order to build Guix itself from source, we need to first make sure that the
+necessary dependencies are installed and discoverable. The most up-to-date list
+of Guix's dependencies is kept in the ["Requirements"
+section](https://guix.gnu.org/manual/en/html_node/Requirements.html) of the Guix
+Reference Manual.
+
+Depending on your distribution, most or all of these dependencies may already be
+packaged and installable without manually building and installing.
+
+For reference, the graphic below outlines Guix v1.3.0's dependency graph:
+
+
+
+If you do not care about building each dependency from source, and Guix is
+already packaged for your distribution, you can easily install only the build
+dependencies of Guix. For example, to enable deb-src and install the Guix build
+dependencies on Ubuntu/Debian:
+
+```sh
+sed -i 's|# deb-src|deb-src|g' /etc/apt/sources.list
+apt update
+apt-get build-dep -y guix
+```
+
+If this succeeded, you can likely skip to section
+["Building and Installing Guix itself"](#building-and-installing-guix-itself).
+
+#### Guile
+
+###### Corner case: Multiple versions of Guile on one system
+
+It is recommended to only install the required version of Guile, so that build systems do
+not get confused about which Guile to use.
+
+However, if you insist on having more versions of Guile installed on
+your system, then you need to **consistently** specify
+`GUILE_EFFECTIVE_VERSION=3.0` to all
+`./configure` invocations for Guix and its dependencies.
+
+##### Installing Guile
+
+If your distribution splits packages into `-dev`-suffixed and
+non-`-dev`-suffixed sub-packages (as is the case for Debian-derived
+distributions), please make sure to install both. For example, to install Guile
+v3.0 on Debian/Ubuntu:
+
+```sh
+apt install guile-3.0 guile-3.0-dev
+```
+
+#### Mixing distribution packages and source-built packages
+
+At the time of writing, most distributions have _some_ of Guix's dependencies
+packaged, but not all. This means that you may want to install the distribution
+package for some dependencies, and manually build-from-source for others.
+
+Distribution packages usually install to `/usr`, which is different from the
+default `./configure` prefix of source-built packages: `/usr/local`.
+
+This means that if you mix-and-match distribution packages and source-built
+packages and do not specify exactly `--prefix=/usr` to `./configure` for
+source-built packages, you will need to augment the `GUILE_LOAD_PATH` and
+`GUILE_LOAD_COMPILED_PATH` environment variables so that Guile will look
+under the right prefix and find your source-built packages.
+
+For example, if you are using Guile v3.0, and have Guile packages in the
+`/usr/local` prefix, either add the following lines to your `.profile` or
+`.bash_profile` so that the environment variable is properly set for all future
+shell logins, or paste the lines into a POSIX-style shell to temporarily modify
+the environment variables of your current shell session.
+
+```sh
+# Help Guile v3.0.x find packages in /usr/local
+export GUILE_LOAD_PATH="/usr/local/share/guile/site/3.0${GUILE_LOAD_PATH:+:}$GUILE_LOAD_PATH"
+export GUILE_LOAD_COMPILED_PATH="/usr/local/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_COMPILED_LOAD_PATH"
+```
+
+Note that these environment variables are used to check for packages during
+`./configure`, so they should be set as soon as possible should you want to use
+a prefix other than `/usr`.
+
+#### Building and installing source-built packages
+
+***IMPORTANT**: A few dependencies have non-obvious quirks/errata which are
+documented in the sub-sections immediately below. Please read these sections
+before proceeding to build and install these packages.*
+
+Although you should always refer to the README or INSTALL files for the most
+accurate information, most of these dependencies use autoconf-style build
+systems (check if there's a `configure.ac` file), and will likely do the right
+thing with the following:
+
+Clone the repository and check out the latest release:
+```sh
+git clone /.git
+cd
+git tag -l # check for the latest release
+git checkout
+```
+
+For autoconf-based build systems (if `./autogen.sh` or `configure.ac` exists at
+the root of the repository):
+
+```sh
+./autogen.sh || autoreconf -vfi
+./configure --prefix=
+make
+sudo make install
+```
+
+For CMake-based build systems (if `CMakeLists.txt` exists at the root of the
+repository):
+
+```sh
+mkdir build && cd build
+cmake .. -DCMAKE_INSTALL_PREFIX=
+sudo cmake --build . --target install
+```
+
+If you choose not to specify exactly `--prefix=/usr` to `./configure`, please
+make sure you've carefully read the [previous section] on mixing distribution
+packages and source-built packages.
+
+##### Binding packages require `-dev`-suffixed packages
+
+Relevant for:
+- Everyone
+
+When building bindings, the `-dev`-suffixed version of the original package
+needs to be installed. For example, building `Guile-zlib` on Debian-derived
+distributions requires that `zlib1g-dev` is installed.
+
+When using bindings, the `-dev`-suffixed version of the original package still
+needs to be installed. This is particularly problematic when distribution
+packages are mispackaged like `guile-sqlite3` is in Ubuntu Focal such that
+installing `guile-sqlite3` does not automatically install `libsqlite3-dev` as a
+dependency.
+
+Below is a list of relevant Guile bindings and their corresponding `-dev`
+packages in Debian at the time of writing.
+
+| Guile binding package | -dev Debian package |
+|-----------------------|---------------------|
+| guile-gcrypt | libgcrypt-dev |
+| guile-git | libgit2-dev |
+| guile-gnutls | (none) |
+| guile-json | (none) |
+| guile-lzlib | liblz-dev |
+| guile-ssh | libssh-dev |
+| guile-sqlite3 | libsqlite3-dev |
+| guile-zlib | zlib1g-dev |
+
+##### `guile-git` actually depends on `libgit2 >= 1.1`
+
+Relevant for:
+- Those building `guile-git` from source against `libgit2 < 1.1`
+- Those installing `guile-git` from their distribution where `guile-git` is
+ built against `libgit2 < 1.1`
+
+As of v0.5.2, `guile-git` claims to only require `libgit2 >= 0.28.0`, however,
+it actually requires `libgit2 >= 1.1`, otherwise, it will be confused by a
+reference of `origin/keyring`: instead of interpreting the reference as "the
+'keyring' branch of the 'origin' remote", the reference is interpreted as "the
+branch literally named 'origin/keyring'"
+
+This is especially notable because Ubuntu Focal packages `libgit2 v0.28.4`, and
+`guile-git` is built against it.
+
+Should you be in this situation, you need to build both `libgit2 v1.1.x` and
+`guile-git` from source.
+
+Source: https://logs.guix.gnu.org/guix/2020-11-12.log#232527
+
+### Building and Installing Guix itself
+
+Start by cloning Guix:
+
+```
+git clone https://git.savannah.gnu.org/git/guix.git
+cd guix
+```
+
+You will likely want to build the latest release.
+At the time of writing (November 2023), the latest release was `v1.4.0`.
+
+```
+git branch -a -l 'origin/version-*' # check for the latest release
+git checkout
+```
+
+Bootstrap the build system:
+```
+./bootstrap
+```
+
+Configure with the recommended `--localstatedir` flag:
+```
+./configure --localstatedir=/var
+```
+
+Note: If you intend to hack on Guix in the future, you will need to supply the
+same `--localstatedir=` flag for all future Guix `./configure` invocations. See
+the last paragraph of this
+[section](https://guix.gnu.org/manual/en/html_node/Requirements.html) for more
+details.
+
+Build Guix (this will take a while):
+```
+make -j$(nproc)
+```
+
+Install Guix:
+
+```
+sudo make install
+```
+
+### Post-"build from source" Setup
+
+#### Creating and starting a `guix-daemon-original` service with a fixed `argv[0]`
+
+At this point, guix will be installed to `${bindir}`, which is likely
+`/usr/local/bin` if you did not override directory variables at
+`./configure`-time. More information on standard Automake directory variables
+can be found
+[here](https://www.gnu.org/software/automake/manual/html_node/Standard-Directory-Variables.html).
+
+However, the Guix init scripts and service configurations for Upstart, systemd,
+SysV, and OpenRC are installed (in `${libdir}`) to launch
+`${localstatedir}/guix/profiles/per-user/root/current-guix/bin/guix-daemon`,
+which does not yet exist, and will only exist after [`root` performs their first
+`guix pull`](#guix-pull-as-root).
+
+We need to create a `-original` version of these init scripts that's pointed to
+the binaries we just built and `make install`'ed in `${bindir}` (normally,
+`/usr/local/bin`).
+
+Example for `systemd`, run as `root`:
+
+```sh
+# Create guix-daemon-original.service by modifying guix-daemon.service
+libdir=# set according to your PREFIX (default is /usr/local/lib)
+bindir="$(dirname $(command -v guix-daemon))"
+sed -E -e "s|/\S*/guix/profiles/per-user/root/current-guix/bin/guix-daemon|${bindir}/guix-daemon|" "${libdir}"/systemd/system/guix-daemon.service > /etc/systemd/system/guix-daemon-original.service
+chmod 664 /etc/systemd/system/guix-daemon-original.service
+
+# Make systemd recognize the new service
+systemctl daemon-reload
+
+# Make sure that the non-working guix-daemon.service is stopped and disabled
+systemctl stop guix-daemon
+systemctl disable guix-daemon
+
+# Make sure that the working guix-daemon-original.service is started and enabled
+systemctl enable guix-daemon-original
+systemctl start guix-daemon-original
+```
+
+#### Creating `guix-daemon` users / groups
+
+Please see the [relevant
+section](https://guix.gnu.org/manual/en/html_node/Build-Environment-Setup.html)
+in the Guix Reference Manual for more details.
+
+## Optional setup
+
+At this point, you are set up to [use Guix to build Bitcoin
+Core](./README.md#usage). However, if you want to polish your setup a bit and
+make it "what Guix intended", then read the next few subsections.
+
+### Add an `/etc/profile.d` entry
+
+This section definitely does not apply to you if you installed Guix using:
+1. The shell installer script
+2. fanquake's Docker image
+3. Debian's `guix` package
+
+#### Background
+
+Although Guix knows how to update itself and its packages, it does so in a
+non-invasive way (it does not modify `/usr/local/bin/guix`).
+
+Instead, it does the following:
+
+- After a `guix pull`, it updates
+ `/var/guix/profiles/per-user/$USER/current-guix`, and creates a symlink
+ targeting this directory at `$HOME/.config/guix/current`
+
+- After a `guix install`, it updates
+ `/var/guix/profiles/per-user/$USER/guix-profile`, and creates a symlink
+ targeting this directory at `$HOME/.guix-profile`
+
+Therefore, in order for these operations to affect your shell/desktop sessions
+(and for the principle of least astonishment to hold), their corresponding
+directories have to be added to well-known environment variables like `$PATH`,
+`$INFOPATH`, `$XDG_DATA_DIRS`, etc.
+
+In other words, if `$HOME/.config/guix/current/bin` does not exist in your
+`$PATH`, a `guix pull` will have no effect on what `guix` you are using. Same
+goes for `$HOME/.guix-profile/bin`, `guix install`, and installed packages.
+
+Helpfully, after a `guix pull` or `guix install`, a message will be printed like
+so:
+
+```
+hint: Consider setting the necessary environment variables by running:
+
+ GUIX_PROFILE="$HOME/.guix-profile"
+ . "$GUIX_PROFILE/etc/profile"
+
+Alternately, see `guix package --search-paths -p "$HOME/.guix-profile"'.
+```
+
+However, this is somewhat tedious to do for both `guix pull` and `guix install`
+for each user on the system that wants to properly use `guix`. I recommend that
+you instead add an entry to `/etc/profile.d` instead. This is done by default
+when installing the Debian package later than 1.2.0-4 and when using the shell
+script installer.
+
+#### Instructions
+
+Create `/etc/profile.d/guix.sh` with the following content:
+```sh
+# _GUIX_PROFILE: `guix pull` profile
+_GUIX_PROFILE="$HOME/.config/guix/current"
+if [ -L $_GUIX_PROFILE ]; then
+ export PATH="$_GUIX_PROFILE/bin${PATH:+:}$PATH"
+ # Export INFOPATH so that the updated info pages can be found
+ # and read by both /usr/bin/info and/or $GUIX_PROFILE/bin/info
+ # When INFOPATH is unset, add a trailing colon so that Emacs
+ # searches 'Info-default-directory-list'.
+ export INFOPATH="$_GUIX_PROFILE/share/info:$INFOPATH"
+fi
+
+# GUIX_PROFILE: User's default profile
+GUIX_PROFILE="$HOME/.guix-profile"
+[ -L $GUIX_PROFILE ] || return
+GUIX_LOCPATH="$GUIX_PROFILE/lib/locale"
+export GUIX_PROFILE GUIX_LOCPATH
+
+[ -f "$GUIX_PROFILE/etc/profile" ] && . "$GUIX_PROFILE/etc/profile"
+
+# set XDG_DATA_DIRS to include Guix installations
+export XDG_DATA_DIRS="$GUIX_PROFILE/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}"
+```
+
+Please note that this will not take effect until the next shell or desktop
+session (log out and log back in).
+
+### `guix pull` as root
+
+Before you do this, you need to read the section on [choosing your security
+model][security-model] and adjust `guix` and `guix-daemon` flags according to
+your choice, as invoking `guix pull` may pull substitutes from substitute
+servers (which you may not want).
+
+As mentioned in a previous section, Guix expects
+`${localstatedir}/guix/profiles/per-user/root/current-guix` to be populated with
+`root`'s Guix profile, `guix pull`-ed and built by some former version of Guix.
+However, this is not the case when we build from source. Therefore, we need to
+perform a `guix pull` as `root`:
+
+```sh
+sudo --login guix pull --branch=version-
+# or
+sudo --login guix pull --commit=
+```
+
+`guix pull` is quite a long process (especially if you're using
+`--no-substitutes`). If you encounter build problems, please refer to the
+[troubleshooting section](#troubleshooting).
+
+Note that running a bare `guix pull` with no commit or branch specified will
+pull the latest commit on Guix's master branch, which is likely fine, but not
+recommended.
+
+If you installed Guix from source, you may get an error like the following:
+```sh
+error: while creating symlink '/root/.config/guix/current' No such file or directory
+```
+To resolve this, simply:
+```
+sudo mkdir -p /root/.config/guix
+```
+Then try the `guix pull` command again.
+
+After the `guix pull` finishes successfully,
+`${localstatedir}/guix/profiles/per-user/root/current-guix` should be populated.
+
+#### Using the newly-pulled `guix` by restarting the daemon
+
+Depending on how you installed Guix, you should now make sure that your init
+scripts and service configurations point to the newly-pulled `guix-daemon`.
+
+##### If you built Guix from source
+
+If you followed the instructions for [fixing argv\[0\]][fix-argv0], you can now
+do the following:
+
+```sh
+systemctl stop guix-daemon-original
+systemctl disable guix-daemon-original
+
+systemctl enable guix-daemon
+systemctl start guix-daemon
+```
+
+Remember to set `--no-substitutes` in `$libdir/systemd/system/guix-daemon.service` and other customizations if you used them for `guix-daemon-original.service`.
+
+##### If you installed Guix via the Debian/Ubuntu distribution packages
+
+You will need to create a `guix-daemon-latest` service which points to the new
+`guix` rather than a pinned one.
+
+```sh
+# Create guix-daemon-latest.service by modifying guix-daemon.service
+sed -E -e "s|/usr/bin/guix-daemon|/var/guix/profiles/per-user/root/current-guix/bin/guix-daemon|" /etc/systemd/system/guix-daemon.service > /lib/systemd/system/guix-daemon-latest.service
+chmod 664 /lib/systemd/system/guix-daemon-latest.service
+
+# Make systemd recognize the new service
+systemctl daemon-reload
+
+# Make sure that the old guix-daemon.service is stopped and disabled
+systemctl stop guix-daemon
+systemctl disable guix-daemon
+
+# Make sure that the new guix-daemon-latest.service is started and enabled
+systemctl enable guix-daemon-latest
+systemctl start guix-daemon-latest
+```
+
+##### If you installed Guix via lantw44's Arch Linux AUR package
+
+At the time of writing (July 5th, 2021) the systemd unit for "updated Guix" is
+`guix-daemon-latest.service`, therefore, you should do the following:
+
+```sh
+systemctl stop guix-daemon
+systemctl disable guix-daemon
+
+systemctl enable guix-daemon-latest
+systemctl start guix-daemon-latest
+```
+
+##### Otherwise...
+
+Simply do:
+
+```sh
+systemctl restart guix-daemon
+```
+
+### Checking everything
+
+If you followed all the steps above to make your Guix setup "prim and proper,"
+you can check that you did everything properly by running through this
+checklist.
+
+1. `/etc/profile.d/guix.sh` should exist and be sourced at each shell login
+
+2. `guix describe` should not print `guix describe: error: failed to determine
+ origin`, but rather something like:
+
+ ```
+ Generation 38 Feb 22 2021 16:39:31 (current)
+ guix f350df4
+ repository URL: https://git.savannah.gnu.org/git/guix.git
+ branch: version-1.2.0
+ commit: f350df405fbcd5b9e27e6b6aa500da7f101f41e7
+ ```
+
+3. `guix-daemon` should be running from `${localstatedir}/guix/profiles/per-user/root/current-guix`
+
+# Troubleshooting
+
+## Derivation failed to build
+
+When you see a build failure like below:
+
+```
+building /gnu/store/...-foo-3.6.12.drv...
+/ 'check' phasenote: keeping build directory `/tmp/guix-build-foo-3.6.12.drv-0'
+builder for `/gnu/store/...-foo-3.6.12.drv' failed with exit code 1
+build of /gnu/store/...-foo-3.6.12.drv failed
+View build log at '/var/log/guix/drvs/../...-foo-3.6.12.drv.bz2'.
+cannot build derivation `/gnu/store/...-qux-7.69.1.drv': 1 dependencies couldn't be built
+cannot build derivation `/gnu/store/...-bar-3.16.5.drv': 1 dependencies couldn't be built
+cannot build derivation `/gnu/store/...-baz-2.0.5.drv': 1 dependencies couldn't be built
+guix time-machine: error: build of `/gnu/store/...-baz-2.0.5.drv' failed
+```
+
+It means that `guix` failed to build a package named `foo`, which was a
+dependency of `qux`, `bar`, and `baz`. Importantly, note that the last "failed"
+line is not necessarily the root cause, the first "failed" line is.
+
+Most of the time, the build failure is due to a spurious test failure or the
+package's build system/test suite breaking when running multi-threaded. To
+rebuild _just_ this derivation in a single-threaded fashion (please don't forget
+to add other `guix` flags like `--no-substitutes` as appropriate):
+
+```sh
+$ guix build --cores=1 /gnu/store/...-foo-3.6.12.drv
+```
+
+If the single-threaded rebuild did not succeed, you may need to dig deeper.
+You may view `foo`'s build logs in `less` like so (please replace paths with the
+path you see in the build failure output):
+
+```sh
+$ bzcat /var/log/guix/drvs/../...-foo-3.6.12.drv.bz2 | less
+```
+
+`foo`'s build directory is also preserved and available at
+`/tmp/guix-build-foo-3.6.12.drv-0`. However, if you fail to build `foo` multiple
+times, it may be `/tmp/...drv-1` or `/tmp/...drv-2`. Always consult the build
+failure output for the most accurate, up-to-date information.
+
+### python(-minimal): [Errno 84] Invalid or incomplete multibyte or wide character
+
+This error occurs when your `$TMPDIR` (default: /tmp) exists on a filesystem
+which rejects characters not present in the UTF-8 character code set. An example
+is ZFS with the utf8only=on option set.
+
+More information: https://github.com/python/cpython/issues/81765
+
+### openssl-1.1.1l and openssl-1.1.1n
+
+OpenSSL includes tests that will fail once some certificate has expired.
+The workarounds from the GnuTLS section immediately below can be used.
+
+For openssl-1.1.1l use 2022-05-01 as the date.
+
+### GnuTLS: test-suite FAIL: status-request-revoked
+
+*The derivation is likely identified by: `/gnu/store/vhphki5sg9xkdhh2pbc8gi6vhpfzryf0-gnutls-3.6.12.drv`*
+
+This unfortunate error is most common for non-substitute builders who installed
+Guix v1.2.0. The problem stems from the fact that one of GnuTLS's tests uses a
+hardcoded certificate which expired on 2020-10-24.
+
+What's more unfortunate is that this GnuTLS derivation is somewhat special in
+Guix's dependency graph and is not affected by the package transformation flags
+like `--without-tests=`.
+
+The easiest solution for those encountering this problem is to install a newer
+version of Guix. However, there are ways to work around this issue:
+
+#### Workaround 1: Using substitutes for this single derivation
+
+If you've authorized the official Guix build farm's key (more info
+[here](./README.md#step-1-authorize-the-signing-keys)), then you can use
+substitutes just for this single derivation by invoking the following:
+
+```sh
+guix build --substitute-urls="https://ci.guix.gnu.org" /gnu/store/vhphki5sg9xkdhh2pbc8gi6vhpfzryf0-gnutls-3.6.12.drv
+```
+
+See [this section](./README.md#removing-authorized-keys) for instructions on how
+to remove authorized keys if you don't want to keep the build farm's key
+authorized.
+
+#### Workaround 2: Temporarily setting the system clock back
+
+This workaround was described [here](https://issues.guix.gnu.org/44559#5).
+
+Basically:
+
+1. Turn off NTP
+2. Set system time to 2020-10-01
+3. guix build --no-substitutes /gnu/store/vhphki5sg9xkdhh2pbc8gi6vhpfzryf0-gnutls-3.6.12.drv
+4. Set system time back to accurate current time
+5. Turn NTP back on
+
+For example,
+
+```sh
+sudo timedatectl set-ntp no
+sudo date --set "01 oct 2020 15:00:00"
+guix build /gnu/store/vhphki5sg9xkdhh2pbc8gi6vhpfzryf0-gnutls-3.6.12.drv
+sudo timedatectl set-ntp yes
+```
+
+#### Workaround 3: Disable the tests in the Guix source code for this single derivation
+
+If all of the above workarounds fail, you can also disable the `tests` phase of
+the derivation via the `arguments` option, as described in the official
+[`package`
+reference](https://guix.gnu.org/manual/en/html_node/package-Reference.html).
+
+For example, to disable the openssl-1.1 check phase:
+
+```diff
+diff --git a/gnu/packages/tls.scm b/gnu/packages/tls.scm
+index f1e844b..1077c4b 100644
+--- a/gnu/packages/tls.scm
++++ b/gnu/packages/tls.scm
+@@ -494,4 +494,5 @@ (define-public openssl-1.1
+ (arguments
+ `(#:parallel-tests? #f
++ #:tests? #f
+ #:test-target "test"
+```
+
+### coreutils: FAIL: tests/tail-2/inotify-dir-recreate
+
+The inotify-dir-create test fails on "remote" filesystems such as overlayfs
+(Docker's default filesystem) due to the filesystem being mistakenly recognized
+as non-remote.
+
+A relatively easy workaround to this is to make sure that a somewhat traditional
+filesystem is mounted at `/tmp` (where `guix-daemon` performs its builds). For
+Docker users, this might mean [using a volume][docker/volumes], [binding
+mounting][docker/bind-mnt] from host, or (for those with enough RAM and swap)
+[mounting a tmpfs][docker/tmpfs] using the `--tmpfs` flag.
+
+Please see the following links for more details:
+
+- An upstream coreutils bug has been filed: [debbugs#47940](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=47940)
+- A Guix bug detailing the underlying problem has been filed: [guix-issues#47935](https://issues.guix.gnu.org/47935), [guix-issues#49985](https://issues.guix.gnu.org/49985#5)
+- A commit to skip this test in Guix has been merged into the core-updates branch:
+[savannah/guix@6ba1058](https://git.savannah.gnu.org/cgit/guix.git/commit/?id=6ba1058df0c4ce5611c2367531ae5c3cdc729ab4)
+
+
+[install-script]: #options-1-and-2-using-the-official-shell-installer-script-or-binary-tarball
+[install-bin-tarball]: #options-1-and-2-using-the-official-shell-installer-script-or-binary-tarball
+[install-fanquake-docker]: #option-3-using-fanquakes-docker-image
+[install-distro-pkg]: #option-4-using-a-distribution-maintained-package
+[install-source]: #option-5-building-from-source
+
+[fix-argv0]: #creating-and-starting-a-guix-daemon-original-service-with-a-fixed-argv0
+[security-model]: ./README.md#choosing-your-security-model
+
+[docker/volumes]: https://docs.docker.com/storage/volumes/
+[docker/bind-mnt]: https://docs.docker.com/storage/bind-mounts/
+[docker/tmpfs]: https://docs.docker.com/storage/tmpfs/
+
+# Purging/Uninstalling Guix
+
+In the extraordinarily rare case where you messed up your Guix installation in
+an irreversible way, you may want to completely purge Guix from your system and
+start over.
+
+1. Uninstall Guix itself according to the way you installed it (e.g. `sudo apt
+ purge guix` for Ubuntu packaging, `sudo make uninstall` for a build from source).
+2. Remove all build users and groups
+
+ You may check for relevant users and groups using:
+
+ ```
+ getent passwd | grep guix
+ getent group | grep guix
+ ```
+
+ Then, you may remove users and groups using:
+
+ ```
+ sudo userdel
+ sudo groupdel
+ ```
+
+3. Remove all possible Guix-related directories
+ - `/var/guix/`
+ - `/var/log/guix/`
+ - `/gnu/`
+ - `/etc/guix/`
+ - `/home/*/.config/guix/`
+ - `/home/*/.cache/guix/`
+ - `/home/*/.guix-profile/`
+ - `/root/.config/guix/`
+ - `/root/.cache/guix/`
+ - `/root/.guix-profile/`
diff --git a/bench-ci/guix/README.md b/bench-ci/guix/README.md
new file mode 100644
index 0000000000..f0ba490366
--- /dev/null
+++ b/bench-ci/guix/README.md
@@ -0,0 +1,436 @@
+# Bootstrappable Bitcoin Core Builds
+
+This directory contains the files necessary to perform bootstrappable Bitcoin
+Core builds.
+
+[Bootstrappability][b17e] furthers our binary security guarantees by allowing us
+to _audit and reproduce_ our toolchain instead of blindly _trusting_ binary
+downloads.
+
+We achieve bootstrappability by using Guix as a functional package manager.
+
+# Requirements
+
+Conservatively, you will need:
+
+- 16GB of free disk space on the partition that /gnu/store will reside in
+- 8GB of free disk space **per platform triple** you're planning on building
+ (see the `HOSTS` [environment variable description][env-vars-list])
+
+# Installation and Setup
+
+If you don't have Guix installed and set up, please follow the instructions in
+[INSTALL.md](./INSTALL.md)
+
+# Usage
+
+If you haven't considered your security model yet, please read [the relevant
+section](#choosing-your-security-model) before proceeding to perform a build.
+
+## Making the Xcode SDK available for macOS cross-compilation
+
+In order to perform a build for macOS (which is included in the default set of
+platform triples to build), you'll need to extract the macOS SDK tarball using
+tools found in the [`macdeploy` directory](../macdeploy/README.md#sdk-extraction).
+
+You can then either point to the SDK using the `SDK_PATH` environment variable:
+
+```sh
+# Extract the SDK tarball to /path/to/parent/dir/of/extracted/SDK/Xcode---extracted-SDK-with-libcxx-headers
+tar -C /path/to/parent/dir/of/extracted/SDK -xaf /path/to/Xcode---extracted-SDK-with-libcxx-headers.tar.gz
+
+# Indicate where to locate the SDK tarball
+export SDK_PATH=/path/to/parent/dir/of/extracted/SDK
+```
+
+or extract it into `depends/SDKs`:
+
+```sh
+mkdir -p depends/SDKs
+tar -C depends/SDKs -xaf /path/to/SDK/tarball
+```
+
+## Building
+
+*The author highly recommends at least reading over the [common usage patterns
+and examples](#common-guix-build-invocation-patterns-and-examples) section below
+before starting a build. For a full list of customization options, see the
+[recognized environment variables][env-vars-list] section.*
+
+To build Bitcoin Core reproducibly with all default options, invoke the
+following from the top of a clean repository:
+
+```sh
+./contrib/guix/guix-build
+```
+
+## Codesigning build outputs
+
+The `guix-codesign` command attaches codesignatures (produced by codesigners) to
+existing non-codesigned outputs. Please see the [release process
+documentation](/doc/release-process.md#codesigning) for more context.
+
+It respects many of the same environment variable flags as `guix-build`, with 2
+crucial differences:
+
+1. Since only Windows and macOS build outputs require codesigning, the `HOSTS`
+ environment variable will have a sane default value of `x86_64-w64-mingw32
+ x86_64-apple-darwin arm64-apple-darwin` instead of all the platforms.
+2. The `guix-codesign` command ***requires*** a `DETACHED_SIGS_REPO` flag.
+ * _**DETACHED_SIGS_REPO**_
+
+ Set the directory where detached codesignatures can be found for the current
+ Bitcoin Core version being built.
+
+ _REQUIRED environment variable_
+
+An invocation with all default options would look like:
+
+```
+env DETACHED_SIGS_REPO= ./contrib/guix/guix-codesign
+```
+
+## Cleaning intermediate work directories
+
+By default, `guix-build` leaves all intermediate files or "work directories"
+(e.g. `depends/work`, `guix-build-*/distsrc-*`) intact at the end of a build so
+that they are available to the user (to aid in debugging, etc.). However, these
+directories usually take up a large amount of disk space. Therefore, a
+`guix-clean` convenience script is provided which cleans the current `git`
+worktree to save disk space:
+
+```
+./contrib/guix/guix-clean
+```
+
+
+## Attesting to build outputs
+
+Much like how Gitian build outputs are attested to in a `gitian.sigs`
+repository, Guix build outputs are attested to in the [`guix.sigs`
+repository](https://github.com/bitcoin-core/guix.sigs).
+
+After you've cloned the `guix.sigs` repository, to attest to the current
+worktree's commit/tag:
+
+```
+env GUIX_SIGS_REPO= SIGNER= ./contrib/guix/guix-attest
+```
+
+See `./contrib/guix/guix-attest --help` for more information on the various ways
+`guix-attest` can be invoked.
+
+## Verifying build output attestations
+
+After at least one other signer has uploaded their signatures to the `guix.sigs`
+repository:
+
+```
+git -C pull
+env GUIX_SIGS_REPO= ./contrib/guix/guix-verify
+```
+
+
+## Common `guix-build` invocation patterns and examples
+
+### Keeping caches and SDKs outside of the worktree
+
+If you perform a lot of builds and have a bunch of worktrees, you may find it
+more efficient to keep the depends tree's download cache, build cache, and SDKs
+outside of the worktrees to avoid duplicate downloads and unnecessary builds. To
+help with this situation, the `guix-build` script honours the `SOURCES_PATH`,
+`BASE_CACHE`, and `SDK_PATH` environment variables and will pass them on to the
+depends tree so that you can do something like:
+
+```sh
+env SOURCES_PATH="$HOME/depends-SOURCES_PATH" BASE_CACHE="$HOME/depends-BASE_CACHE" SDK_PATH="$HOME/macOS-SDKs" ./contrib/guix/guix-build
+```
+
+Note that the paths that these environment variables point to **must be
+directories**, and **NOT symlinks to directories**.
+
+See the [recognized environment variables][env-vars-list] section for more
+details.
+
+### Building a subset of platform triples
+
+Sometimes you only want to build a subset of the supported platform triples, in
+which case you can override the default list by setting the space-separated
+`HOSTS` environment variable:
+
+```sh
+env HOSTS='x86_64-w64-mingw32 x86_64-apple-darwin' ./contrib/guix/guix-build
+```
+
+See the [recognized environment variables][env-vars-list] section for more
+details.
+
+### Controlling the number of threads used by `guix` build commands
+
+Depending on your system's RAM capacity, you may want to decrease the number of
+threads used to decrease RAM usage or vice versa.
+
+By default, the scripts under `./contrib/guix` will invoke all `guix` build
+commands with `--cores="$JOBS"`. Note that `$JOBS` defaults to `$(nproc)` if not
+specified. However, astute manual readers will also notice that `guix` build
+commands also accept a `--max-jobs=` flag (which defaults to 1 if unspecified).
+
+Here is the difference between `--cores=` and `--max-jobs=`:
+
+> Note: When I say "derivation," think "package"
+
+`--cores=`
+
+ - controls the number of CPU cores to build each derivation. This is the value
+ passed to `make`'s `--jobs=` flag.
+
+`--max-jobs=`
+
+ - controls how many derivations can be built in parallel
+ - defaults to 1
+
+Therefore, the default is for `guix` build commands to build one derivation at a
+time, utilizing `$JOBS` threads.
+
+Specifying the `$JOBS` environment variable will only modify `--cores=`, but you
+can also modify the value for `--max-jobs=` by specifying
+`$ADDITIONAL_GUIX_COMMON_FLAGS`. For example, if you have a LOT of memory, you
+may want to set:
+
+```sh
+export ADDITIONAL_GUIX_COMMON_FLAGS='--max-jobs=8'
+```
+
+Which allows for a maximum of 8 derivations to be built at the same time, each
+utilizing `$JOBS` threads.
+
+Or, if you'd like to avoid spurious build failures caused by issues with
+parallelism within a single package, but would still like to build multiple
+packages when the dependency graph allows for it, you may want to try:
+
+```sh
+export JOBS=1 ADDITIONAL_GUIX_COMMON_FLAGS='--max-jobs=8'
+```
+
+See the [recognized environment variables][env-vars-list] section for more
+details.
+
+## Recognized environment variables
+
+* _**HOSTS**_
+
+ Override the space-separated list of platform triples for which to perform a
+ bootstrappable build.
+
+ _(defaults to "x86\_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu
+ riscv64-linux-gnu powerpc64-linux-gnu powerpc64le-linux-gnu
+ x86\_64-w64-mingw32 x86\_64-apple-darwin arm64-apple-darwin")_
+
+* _**SOURCES_PATH**_
+
+ Set the depends tree download cache for sources. This is passed through to the
+ depends tree. Setting this to the same directory across multiple builds of the
+ depends tree can eliminate unnecessary redownloading of package sources.
+
+ The path that this environment variable points to **must be a directory**, and
+ **NOT a symlink to a directory**.
+
+* _**BASE_CACHE**_
+
+ Set the depends tree cache for built packages. This is passed through to the
+ depends tree. Setting this to the same directory across multiple builds of the
+ depends tree can eliminate unnecessary building of packages.
+
+ The path that this environment variable points to **must be a directory**, and
+ **NOT a symlink to a directory**.
+
+* _**SDK_PATH**_
+
+ Set the path where _extracted_ SDKs can be found. This is passed through to
+ the depends tree. Note that this should be set to the _parent_ directory of
+ the actual SDK (e.g. `SDK_PATH=$HOME/Downloads/macOS-SDKs` instead of
+ `$HOME/Downloads/macOS-SDKs/Xcode-12.2-12B45b-extracted-SDK-with-libcxx-headers`).
+
+ The path that this environment variable points to **must be a directory**, and
+ **NOT a symlink to a directory**.
+
+* _**JOBS**_
+
+ Override the number of jobs to run simultaneously, you might want to do so on
+ a memory-limited machine. This may be passed to:
+
+ - `guix` build commands as in `guix shell --cores="$JOBS"`
+ - `make` as in `make --jobs="$JOBS"`
+ - `cmake` as in `cmake --build build -j "$JOBS"`
+ - `xargs` as in `xargs -P"$JOBS"`
+
+ See [here](#controlling-the-number-of-threads-used-by-guix-build-commands) for
+ more details.
+
+ _(defaults to the value of `nproc` outside the container)_
+
+* _**SOURCE_DATE_EPOCH**_
+
+ Override the reference UNIX timestamp used for bit-for-bit reproducibility,
+ the variable name conforms to [standard][r12e/source-date-epoch].
+
+ _(defaults to the output of `$(git log --format=%at -1)`)_
+
+* _**V**_
+
+ If non-empty, will pass `V=1` to all `make` invocations, making `make` output
+ verbose.
+
+ Note that any given value is ignored. The variable is only checked for
+ emptiness. More concretely, this means that `V=` (setting `V` to the empty
+ string) is interpreted the same way as not setting `V` at all, and that `V=0`
+ has the same effect as `V=1`.
+
+* _**SUBSTITUTE_URLS**_
+
+ A whitespace-delimited list of URLs from which to download pre-built packages.
+ A URL is only used if its signing key is authorized (refer to the [substitute
+ servers section](#option-1-building-with-substitutes) for more details).
+
+* _**ADDITIONAL_GUIX_COMMON_FLAGS**_
+
+ Additional flags to be passed to all `guix` commands.
+
+* _**ADDITIONAL_GUIX_TIMEMACHINE_FLAGS**_
+
+ Additional flags to be passed to `guix time-machine`.
+
+* _**ADDITIONAL_GUIX_ENVIRONMENT_FLAGS**_
+
+ Additional flags to be passed to the invocation of `guix shell` inside
+ `guix time-machine`.
+
+# Choosing your security model
+
+No matter how you installed Guix, you need to decide on your security model for
+building packages with Guix.
+
+Guix allows us to achieve better binary security by using our CPU time to build
+everything from scratch. However, it doesn't sacrifice user choice in pursuit of
+this: users can decide whether or not to use **substitutes** (pre-built
+packages).
+
+## Option 1: Building with substitutes
+
+### Step 1: Authorize the signing keys
+
+Depending on the installation procedure you followed, you may have already
+authorized the Guix build farm key. In particular, the official shell installer
+script asks you if you want the key installed, and the debian distribution
+package authorized the key during installation.
+
+You can check the current list of authorized keys at `/etc/guix/acl`.
+
+At the time of writing, a `/etc/guix/acl` with just the Guix build farm key
+authorized looks something like:
+
+```lisp
+(acl
+ (entry
+ (public-key
+ (ecc
+ (curve Ed25519)
+ (q #8D156F295D24B0D9A86FA5741A840FF2D24F60F7B6C4134814AD55625971B394#)
+ )
+ )
+ (tag
+ (guix import)
+ )
+ )
+ )
+```
+
+If you've determined that the official Guix build farm key hasn't been
+authorized, and you would like to authorize it, run the following as root:
+
+```
+guix archive --authorize < /var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub
+```
+
+If
+`/var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub`
+doesn't exist, try:
+
+```sh
+guix archive --authorize < /share/guix/ci.guix.gnu.org.pub
+```
+
+Where `` is likely:
+- `/usr` if you installed from a distribution package
+- `/usr/local` if you installed Guix from source and didn't supply any
+ prefix-modifying flags to Guix's `./configure`
+
+For dongcarl's substitute server at https://guix.carldong.io, run as root:
+
+```sh
+wget -qO- 'https://guix.carldong.io/signing-key.pub' | guix archive --authorize
+```
+
+#### Removing authorized keys
+
+To remove previously authorized keys, simply edit `/etc/guix/acl` and remove the
+`(entry (public-key ...))` entry.
+
+### Step 2: Specify the substitute servers
+
+Once its key is authorized, the official Guix build farm at
+https://ci.guix.gnu.org is automatically used unless the `--no-substitutes` flag
+is supplied. This default list of substitute servers is overridable both on a
+`guix-daemon` level and when you invoke `guix` commands. See examples below for
+the various ways of adding dongcarl's substitute server after having [authorized
+his signing key](#step-1-authorize-the-signing-keys).
+
+Change the **default list** of substitute servers by starting `guix-daemon` with
+the `--substitute-urls` option (you will likely need to edit your init script):
+
+```sh
+guix-daemon --substitute-urls='https://guix.carldong.io https://ci.guix.gnu.org'
+```
+
+Override the default list of substitute servers by passing the
+`--substitute-urls` option for invocations of `guix` commands:
+
+```sh
+guix --substitute-urls='https://guix.carldong.io https://ci.guix.gnu.org'
+```
+
+For scripts under `./contrib/guix`, set the `SUBSTITUTE_URLS` environment
+variable:
+
+```sh
+export SUBSTITUTE_URLS='https://guix.carldong.io https://ci.guix.gnu.org'
+```
+
+## Option 2: Disabling substitutes on an ad-hoc basis
+
+If you prefer not to use any substitutes, make sure to supply `--no-substitutes`
+like in the following snippet. The first build will take a while, but the
+resulting packages will be cached for future builds.
+
+For direct invocations of `guix`:
+```sh
+guix --no-substitutes
+```
+
+For the scripts under `./contrib/guix/`:
+```sh
+export ADDITIONAL_GUIX_COMMON_FLAGS='--no-substitutes'
+```
+
+## Option 3: Disabling substitutes by default
+
+`guix-daemon` accepts a `--no-substitutes` flag, which will make sure that,
+unless otherwise overridden by a command line invocation, no substitutes will be
+used.
+
+If you start `guix-daemon` using an init script, you can edit said script to
+supply this flag.
+
+[b17e]: https://bootstrappable.org/
+[r12e/source-date-epoch]: https://reproducible-builds.org/docs/source-date-epoch/
+[env-vars-list]: #recognized-environment-variables
diff --git a/bench-ci/guix/guix-attest b/bench-ci/guix/guix-attest
new file mode 100755
index 0000000000..b0ef28dc3f
--- /dev/null
+++ b/bench-ci/guix/guix-attest
@@ -0,0 +1,263 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+# 1. Checks if we're at the top directory of the Bitcoin Core repository
+# 2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## Sanity Checks ##
+###################
+
+################
+# Required non-builtin commands should be invokable
+################
+
+check_tools cat env basename mkdir diff sort
+
+if [ -z "$NO_SIGN" ]; then
+ # make it possible to override the gpg binary
+ GPG=${GPG:-gpg}
+
+ # $GPG can contain extra arguments passed to the binary
+ # so let's check only the existence of arg[0]
+ # shellcheck disable=SC2206
+ GPG_ARRAY=($GPG)
+ check_tools "${GPG_ARRAY[0]}"
+fi
+
+################
+# Required env vars should be non-empty
+################
+
+cmd_usage() {
+cat < \\
+ SIGNER=GPG_KEY_NAME[=SIGNER_NAME] \\
+ [ NO_SIGN=1 ]
+ ./contrib/guix/guix-attest
+
+Example w/o overriding signing name:
+
+ env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
+ SIGNER=achow101 \\
+ ./contrib/guix/guix-attest
+
+Example overriding signing name:
+
+ env GUIX_SIGS_REPO=/home/dongcarl/guix.sigs \\
+ SIGNER=0x96AB007F1A7ED999=dongcarl \\
+ ./contrib/guix/guix-attest
+
+Example w/o signing, just creating SHA256SUMS:
+
+ env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
+ SIGNER=achow101 \\
+ NO_SIGN=1 \\
+ ./contrib/guix/guix-attest
+
+EOF
+}
+
+if [ -z "$GUIX_SIGS_REPO" ] || [ -z "$SIGNER" ]; then
+ cmd_usage
+ exit 1
+fi
+
+################
+# GUIX_SIGS_REPO should exist as a directory
+################
+
+if [ ! -d "$GUIX_SIGS_REPO" ]; then
+cat << EOF
+ERR: The specified GUIX_SIGS_REPO is not an existent directory:
+
+ '$GUIX_SIGS_REPO'
+
+Hint: Please clone the guix.sigs repository and point to it with the
+ GUIX_SIGS_REPO environment variable.
+
+EOF
+cmd_usage
+exit 1
+fi
+
+################
+# The key specified in SIGNER should be usable
+################
+
+IFS='=' read -r gpg_key_name signer_name <<< "$SIGNER"
+if [ -z "${signer_name}" ]; then
+ signer_name="$gpg_key_name"
+fi
+
+if [ -z "$NO_SIGN" ] && ! ${GPG} --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
+ echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'"
+ exit 1
+fi
+
+################
+# We should be able to find at least one output
+################
+
+echo "Looking for build output SHA256SUMS fragments in ${OUTDIR_BASE}"
+
+shopt -s nullglob
+sha256sum_fragments=( "$OUTDIR_BASE"/*/SHA256SUMS.part ) # This expands to an array of directories...
+shopt -u nullglob
+
+noncodesigned_fragments=()
+codesigned_fragments=()
+
+if (( ${#sha256sum_fragments[@]} )); then
+ echo "Found build output SHA256SUMS fragments:"
+ for outdir in "${sha256sum_fragments[@]}"; do
+ echo " '$outdir'"
+ case "$outdir" in
+ "$OUTDIR_BASE"/*-codesigned/SHA256SUMS.part)
+ codesigned_fragments+=("$outdir")
+ ;;
+ *)
+ noncodesigned_fragments+=("$outdir")
+ ;;
+ esac
+ done
+ echo
+else
+ echo "ERR: Could not find any build output SHA256SUMS fragments in ${OUTDIR_BASE}"
+ exit 1
+fi
+
+##############
+## Attest ##
+##############
+
+# Usage: out_name $outdir
+#
+# HOST: The output directory being attested
+#
+out_name() {
+ basename "$(dirname "$1")"
+}
+
+shasum_already_exists() {
+cat < "$temp_noncodesigned"
+ if [ -e noncodesigned.SHA256SUMS ]; then
+ # The SHA256SUMS already exists, make sure it's exactly what we
+ # expect, error out if not
+ if diff -u noncodesigned.SHA256SUMS "$temp_noncodesigned"; then
+ echo "A noncodesigned.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
+ else
+ shasum_already_exists noncodesigned.SHA256SUMS
+ exit 1
+ fi
+ else
+ mv "$temp_noncodesigned" noncodesigned.SHA256SUMS
+ fi
+ else
+ echo "ERR: No noncodesigned outputs found for '${VERSION}', exiting..."
+ exit 1
+ fi
+
+ temp_all="$(mktemp)"
+ trap 'rm -rf -- "$temp_all"' EXIT
+
+ if (( ${#codesigned_fragments[@]} )); then
+ # Note: all.SHA256SUMS attests to all of $sha256sum_fragments, but is
+ # not needed if there are no $codesigned_fragments
+ cat "${sha256sum_fragments[@]}" \
+ | sort -u \
+ | sort -k2 \
+ | basenameify_SHA256SUMS \
+ > "$temp_all"
+ if [ -e all.SHA256SUMS ]; then
+ # The SHA256SUMS already exists, make sure it's exactly what we
+ # expect, error out if not
+ if diff -u all.SHA256SUMS "$temp_all"; then
+ echo "An all.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
+ else
+ shasum_already_exists all.SHA256SUMS
+ exit 1
+ fi
+ else
+ mv "$temp_all" all.SHA256SUMS
+ fi
+ else
+ # It is fine to have the codesigned outputs be missing (perhaps the
+ # detached codesigs have not been published yet), just print a log
+ # message instead of erroring out
+ echo "INFO: No codesigned outputs found for '${VERSION}', skipping..."
+ fi
+
+ if [ -z "$NO_SIGN" ]; then
+ echo "Signing SHA256SUMS to produce SHA256SUMS.asc"
+ for i in *.SHA256SUMS; do
+ if [ ! -e "$i".asc ]; then
+ ${GPG} --detach-sign \
+ --digest-algo sha256 \
+ --local-user "$gpg_key_name" \
+ --armor \
+ --output "$i".asc "$i"
+ else
+ echo "Signature already there"
+ fi
+ done
+ else
+ echo "Not signing SHA256SUMS as \$NO_SIGN is not empty"
+ fi
+ echo ""
+)
diff --git a/bench-ci/guix/guix-build b/bench-ci/guix/guix-build
new file mode 100755
index 0000000000..efe031162d
--- /dev/null
+++ b/bench-ci/guix/guix-build
@@ -0,0 +1,468 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+# 1. Checks if we're at the top directory of the Bitcoin Core repository
+# 2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## SANITY CHECKS ##
+###################
+
+################
+# Required non-builtin commands should be invocable
+################
+
+check_tools cat mkdir make getent curl git guix
+
+################
+# GUIX_BUILD_OPTIONS should be empty
+################
+#
+# GUIX_BUILD_OPTIONS is an environment variable recognized by guix commands that
+# can perform builds. This seems like what we want instead of
+# ADDITIONAL_GUIX_COMMON_FLAGS, but the value of GUIX_BUILD_OPTIONS is actually
+# _appended_ to normal command-line options. Meaning that they will take
+# precedence over the command-specific ADDITIONAL_GUIX__FLAGS.
+#
+# This seems like a poor user experience. Thus we check for GUIX_BUILD_OPTIONS's
+# existence here and direct users of this script to use our (more flexible)
+# custom environment variables.
+if [ -n "$GUIX_BUILD_OPTIONS" ]; then
+cat << EOF
+Error: Environment variable GUIX_BUILD_OPTIONS is not empty:
+ '$GUIX_BUILD_OPTIONS'
+
+Unfortunately this script is incompatible with GUIX_BUILD_OPTIONS, please unset
+GUIX_BUILD_OPTIONS and use ADDITIONAL_GUIX_COMMON_FLAGS to set build options
+across guix commands or ADDITIONAL_GUIX__FLAGS to set build options for a
+specific guix command.
+
+See contrib/guix/README.md for more details.
+EOF
+exit 1
+fi
+
+################
+# The git worktree should not be dirty
+################
+
+if ! git diff-index --quiet HEAD -- && [ -z "$FORCE_DIRTY_WORKTREE" ]; then
+cat << EOF
+ERR: The current git worktree is dirty, which may lead to broken builds.
+
+ Aborting...
+
+Hint: To make your git worktree clean, You may want to:
+ 1. Commit your changes,
+ 2. Stash your changes, or
+ 3. Set the 'FORCE_DIRTY_WORKTREE' environment variable if you insist on
+ using a dirty worktree
+EOF
+exit 1
+fi
+
+mkdir -p "$VERSION_BASE"
+
+################
+# Build directories should not exist
+################
+
+# Default to building for all supported HOSTs (overridable by environment)
+# powerpc64le-linux-gnu currently disabled due non-determinism issues across build arches.
+export HOSTS="${HOSTS:-x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu powerpc64-linux-gnu
+ x86_64-w64-mingw32
+ x86_64-apple-darwin arm64-apple-darwin}"
+
+# Usage: distsrc_for_host HOST
+#
+# HOST: The current platform triple we're building for
+#
+distsrc_for_host() {
+ echo "${DISTSRC_BASE}/distsrc-${VERSION}-${1}"
+}
+
+# Accumulate a list of build directories that already exist...
+hosts_distsrc_exists=""
+for host in $HOSTS; do
+ if [ -e "$(distsrc_for_host "$host")" ]; then
+ hosts_distsrc_exists+=" ${host}"
+ fi
+done
+
+if [ -n "$hosts_distsrc_exists" ]; then
+# ...so that we can print them out nicely in an error message
+cat << EOF
+ERR: Build directories for this commit already exist for the following platform
+ triples you're attempting to build, probably because of previous builds.
+ Please remove, or otherwise deal with them prior to starting another build.
+
+ Aborting...
+
+Hint: To blow everything away, you may want to use:
+
+ $ ./contrib/guix/guix-clean
+
+Specifically, this will remove all files without an entry in the index,
+excluding the SDK directory, the depends download cache, the depends built
+packages cache, the garbage collector roots for Guix environments, and the
+output directory.
+EOF
+for host in $hosts_distsrc_exists; do
+ echo " ${host} '$(distsrc_for_host "$host")'"
+done
+exit 1
+else
+ mkdir -p "$DISTSRC_BASE"
+fi
+
+################
+# When building for darwin, the macOS SDK should exist
+################
+
+for host in $HOSTS; do
+ case "$host" in
+ *darwin*)
+ OSX_SDK="$(make -C "${PWD}/depends" --no-print-directory HOST="$host" print-OSX_SDK | sed 's@^[^=]\+=@@g')"
+ if [ -e "$OSX_SDK" ]; then
+ echo "Found macOS SDK at '${OSX_SDK}', using..."
+ break
+ else
+ echo "macOS SDK does not exist at '${OSX_SDK}', please place the extracted, untarred SDK there to perform darwin builds, or define SDK_PATH environment variable. Exiting..."
+ exit 1
+ fi
+ ;;
+ esac
+done
+
+################
+# VERSION_BASE should have enough space
+################
+
+avail_KiB="$(df -Pk "$VERSION_BASE" | sed 1d | tr -s ' ' | cut -d' ' -f4)"
+total_required_KiB=0
+for host in $HOSTS; do
+ case "$host" in
+ *darwin*) required_KiB=440000 ;;
+ *mingw*) required_KiB=7600000 ;;
+ *) required_KiB=6400000 ;;
+ esac
+ total_required_KiB=$((total_required_KiB+required_KiB))
+done
+
+if (( total_required_KiB > avail_KiB )); then
+ total_required_GiB=$((total_required_KiB / 1048576))
+ avail_GiB=$((avail_KiB / 1048576))
+ echo "Performing a Bitcoin Core Guix build for the selected HOSTS requires ${total_required_GiB} GiB, however, only ${avail_GiB} GiB is available. Please free up some disk space before performing the build."
+ exit 1
+fi
+
+################
+# Check that we can connect to the guix-daemon
+################
+
+cat << EOF
+Checking that we can connect to the guix-daemon...
+
+Hint: If this hangs, you may want to try turning your guix-daemon off and on
+ again.
+
+EOF
+if ! guix gc --list-failures > /dev/null; then
+cat << EOF
+
+ERR: Failed to connect to the guix-daemon, please ensure that one is running and
+ reachable.
+EOF
+exit 1
+fi
+
+# Developer note: we could use `guix repl` for this check and run:
+#
+# (import (guix store)) (close-connection (open-connection))
+#
+# However, the internal API is likely to change more than the CLI invocation
+
+################
+# Services database must have basic entries
+################
+
+if ! getent services http https ftp > /dev/null 2>&1; then
+cat << EOF
+ERR: Your system's C library cannot find service database entries for at least
+ one of the following services: http, https, ftp.
+
+Hint: Most likely, /etc/services does not exist yet (common for docker images
+ and minimal distros), or you don't have permissions to access it.
+
+ If /etc/services does not exist yet, you may want to install the
+ appropriate package for your distro which provides it.
+
+ On Debian/Ubuntu: netbase
+ On Arch Linux: iana-etc
+
+ For more information, see: getent(1), services(5)
+
+EOF
+
+fi
+
+#########
+# SETUP #
+#########
+
+# Determine the maximum number of jobs to run simultaneously (overridable by
+# environment)
+JOBS="${JOBS:-$(nproc)}"
+
+# Usage: host_to_commonname HOST
+#
+# HOST: The current platform triple we're building for
+#
+host_to_commonname() {
+ case "$1" in
+ *darwin*) echo osx ;;
+ *mingw*) echo win ;;
+ *linux*) echo linux ;;
+ *) exit 1 ;;
+ esac
+}
+
+# Determine the reference time used for determinism (overridable by environment)
+SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -c log.showSignature=false log --format=%at -1)}"
+
+# Precious directories are those which should not be cleaned between successive
+# guix builds
+depends_precious_dir_names='SOURCES_PATH BASE_CACHE SDK_PATH'
+precious_dir_names="${depends_precious_dir_names} OUTDIR_BASE PROFILES_BASE"
+
+# Usage: contains IFS-SEPARATED-LIST ITEM
+contains() {
+ for i in ${1}; do
+ if [ "$i" = "${2}" ]; then
+ return 0 # Found!
+ fi
+ done
+ return 1
+}
+
+# If the user explicitly specified a precious directory, create it so we
+# can map it into the container
+for precious_dir_name in $precious_dir_names; do
+ precious_dir_path="${!precious_dir_name}"
+ if [ -n "$precious_dir_path" ]; then
+ if [ ! -e "$precious_dir_path" ]; then
+ mkdir -p "$precious_dir_path"
+ elif [ -L "$precious_dir_path" ]; then
+ echo "ERR: ${precious_dir_name} cannot be a symbolic link"
+ exit 1
+ elif [ ! -d "$precious_dir_path" ]; then
+ echo "ERR: ${precious_dir_name} must be a directory"
+ exit 1
+ fi
+ fi
+done
+
+mkdir -p "$VAR_BASE"
+
+# Record the _effective_ values of precious directories such that guix-clean can
+# avoid clobbering them if appropriate.
+#
+# shellcheck disable=SC2046,SC2086
+{
+ # Get depends precious dir definitions from depends
+ make -C "${PWD}/depends" \
+ --no-print-directory \
+ -- $(printf "print-%s\n" $depends_precious_dir_names)
+
+ # Get remaining precious dir definitions from the environment
+ for precious_dir_name in $precious_dir_names; do
+ precious_dir_path="${!precious_dir_name}"
+ if ! contains "$depends_precious_dir_names" "$precious_dir_name"; then
+ echo "${precious_dir_name}=${precious_dir_path}"
+ fi
+ done
+} > "${VAR_BASE}/precious_dirs"
+
+# Make sure an output directory exists for our builds
+OUTDIR_BASE="${OUTDIR_BASE:-${VERSION_BASE}/output}"
+mkdir -p "$OUTDIR_BASE"
+
+# Download the depends sources now as we won't have internet access in the build
+# container
+for host in $HOSTS; do
+ make -C "${PWD}/depends" -j"$JOBS" download-"$(host_to_commonname "$host")" ${V:+V=1} ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"}
+done
+
+# Usage: outdir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+outdir_for_host() {
+ echo "${OUTDIR_BASE}/${1}${2:+-${2}}"
+}
+
+# Usage: profiledir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+profiledir_for_host() {
+ echo "${PROFILES_BASE}/${1}${2:+-${2}}"
+}
+
+
+#########
+# BUILD #
+#########
+
+# Function to be called when building for host ${1} and the user interrupts the
+# build
+int_trap() {
+cat << EOF
+** INT received while building ${1}, you may want to clean up the relevant
+ work directories (e.g. distsrc-*) before rebuilding
+
+Hint: To blow everything away, you may want to use:
+
+ $ ./contrib/guix/guix-clean
+
+Specifically, this will remove all files without an entry in the index,
+excluding the SDK directory, the depends download cache, the depends built
+packages cache, the garbage collector roots for Guix environments, and the
+output directory.
+EOF
+}
+
+# Deterministically build Bitcoin Core
+# shellcheck disable=SC2153
+for host in $HOSTS; do
+
+ # Display proper warning when the user interrupts the build
+ trap 'int_trap ${host}' INT
+
+ (
+ # Required for 'contrib/guix/manifest.scm' to output the right manifest
+ # for the particular $HOST we're building for
+ export HOST="$host"
+
+ # shellcheck disable=SC2030
+cat << EOF
+INFO: Building ${VERSION:?not set} for platform triple ${HOST:?not set}:
+ ...using reference timestamp: ${SOURCE_DATE_EPOCH:?not set}
+ ...running at most ${JOBS:?not set} jobs
+ ...from worktree directory: '${PWD}'
+ ...bind-mounted in container to: '/bitcoin'
+ ...in build directory: '$(distsrc_for_host "$HOST")'
+ ...bind-mounted in container to: '$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")'
+ ...outputting in: '$(outdir_for_host "$HOST")'
+ ...bind-mounted in container to: '$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST")'
+ ADDITIONAL FLAGS (if set)
+ ADDITIONAL_GUIX_COMMON_FLAGS: ${ADDITIONAL_GUIX_COMMON_FLAGS}
+ ADDITIONAL_GUIX_ENVIRONMENT_FLAGS: ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS}
+ ADDITIONAL_GUIX_TIMEMACHINE_FLAGS: ${ADDITIONAL_GUIX_TIMEMACHINE_FLAGS}
+EOF
+
+ # Run the build script 'contrib/guix/libexec/build.sh' in the build
+ # container specified by 'contrib/guix/manifest.scm'.
+ #
+ # Explanation of `guix shell` flags:
+ #
+ # --container run command within an isolated container
+ #
+ # Running in an isolated container minimizes build-time differences
+ # between machines and improves reproducibility
+ #
+ # --pure unset existing environment variables
+ #
+ # Same rationale as --container
+ #
+ # --no-cwd do not share current working directory with an
+ # isolated container
+ #
+ # When --container is specified, the default behavior is to share
+ # the current working directory with the isolated container at the
+ # same exact path (e.g. mapping '/home/satoshi/bitcoin/' to
+ # '/home/satoshi/bitcoin/'). This means that the $PWD inside the
+ # container becomes a source of irreproducibility. --no-cwd disables
+ # this behaviour.
+ #
+ # --share=SPEC for containers, share writable host file system
+ # according to SPEC
+ #
+ # --share="$PWD"=/bitcoin
+ #
+ # maps our current working directory to /bitcoin
+ # inside the isolated container, which we later cd
+ # into.
+ #
+ # While we don't want to map our current working directory to the
+ # same exact path (as this introduces irreproducibility), we do want
+ # it to be at a _fixed_ path _somewhere_ inside the isolated
+ # container so that we have something to build. '/bitcoin' was
+ # chosen arbitrarily.
+ #
+ # ${SOURCES_PATH:+--share="$SOURCES_PATH"}
+ #
+ # make the downloaded depends sources path available
+ # inside the isolated container
+ #
+ # The isolated container has no network access as it's in a
+ # different network namespace from the main machine, so we have to
+ # make the downloaded depends sources available to it. The sources
+ # should have been downloaded prior to this invocation.
+ #
+ # --keep-failed keep build tree of failed builds
+ #
+ # When builds of the Guix environment itself (not Bitcoin Core)
+ # fail, it is useful for the build tree to be kept for debugging
+ # purposes.
+ #
+ # ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"}
+ #
+ # fetch substitute from SUBSTITUTE_URLS if they are
+ # authorized
+ #
+ # Depending on the user's security model, it may be desirable to use
+ # substitutes (pre-built packages) from servers that the user trusts.
+ # Please read the README.md in the same directory as this file for
+ # more information.
+ #
+ # shellcheck disable=SC2086,SC2031
+ time-machine shell --manifest="${PWD}/bench-ci/guix/manifest.scm" \
+ --container \
+ --pure \
+ --no-cwd \
+ --share="$PWD"=/bitcoin \
+ --share="$DISTSRC_BASE"=/distsrc-base \
+ --share="$OUTDIR_BASE"=/outdir-base \
+ --expose="$(git rev-parse --git-common-dir)" \
+ ${SOURCES_PATH:+--share="$SOURCES_PATH"} \
+ ${BASE_CACHE:+--share="$BASE_CACHE"} \
+ ${SDK_PATH:+--share="$SDK_PATH"} \
+ --cores="$JOBS" \
+ --keep-failed \
+ --fallback \
+ --link-profile \
+ --root="$(profiledir_for_host "${HOST}")" \
+ ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
+ ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \
+ -- env HOST="$host" \
+ DISTNAME="$DISTNAME" \
+ JOBS="$JOBS" \
+ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \
+ ${V:+V=1} \
+ ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} \
+ ${BASE_CACHE:+BASE_CACHE="$BASE_CACHE"} \
+ ${SDK_PATH:+SDK_PATH="$SDK_PATH"} \
+ DISTSRC="$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")" \
+ OUTDIR="$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST")" \
+ DIST_ARCHIVE_BASE=/outdir-base/dist-archive \
+ bash -c "cd /bitcoin && bash bench-ci/guix/libexec/build.sh"
+ )
+
+done
diff --git a/bench-ci/guix/guix-clean b/bench-ci/guix/guix-clean
new file mode 100755
index 0000000000..9af0a793cf
--- /dev/null
+++ b/bench-ci/guix/guix-clean
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+# 1. Checks if we're at the top directory of the Bitcoin Core repository
+# 2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## Sanity Checks ##
+###################
+
+################
+# Required non-builtin commands should be invokable
+################
+
+check_tools cat mkdir make git guix
+
+
+#############
+## Clean ##
+#############
+
+# Usage: under_dir MAYBE_PARENT MAYBE_CHILD
+#
+# If MAYBE_CHILD is a subdirectory of MAYBE_PARENT, print the relative path
+# from MAYBE_PARENT to MAYBE_CHILD. Otherwise, return 1 as the error code.
+#
+# NOTE: This does not perform any symlink-resolving or path canonicalization.
+#
+under_dir() {
+ local path_residue
+ path_residue="${2##"${1}"}"
+ if [ -z "$path_residue" ] || [ "$path_residue" = "$2" ]; then
+ return 1
+ else
+ echo "$path_residue"
+ fi
+}
+
+# Usage: dir_under_git_root MAYBE_CHILD
+#
+# If MAYBE_CHILD is under the current git repository and exists, print the
+# relative path from the git repository's top-level directory to MAYBE_CHILD,
+# otherwise, exit with an error code.
+#
+dir_under_git_root() {
+ local rv
+ rv="$(under_dir "$(git_root)" "$1")"
+ [ -n "$rv" ] && echo "$rv"
+}
+
+shopt -s nullglob
+found_precious_dirs_files=( "${version_base_prefix}"*/"${var_base_basename}/precious_dirs" ) # This expands to an array of directories...
+shopt -u nullglob
+
+exclude_flags=()
+
+for precious_dirs_file in "${found_precious_dirs_files[@]}"; do
+ # Make sure the precious directories (e.g. SOURCES_PATH, BASE_CACHE, SDK_PATH)
+ # are excluded from git-clean
+ echo "Found precious_dirs file: '${precious_dirs_file}'"
+
+ # Exclude the precious_dirs file itself
+ if dirs_file_exclude_fragment=$(dir_under_git_root "$(dirname "$precious_dirs_file")"); then
+ exclude_flags+=( --exclude="${dirs_file_exclude_fragment}/precious_dirs" )
+ fi
+
+ # Read each 'name=dir' pair from the precious_dirs file
+ while IFS='=' read -r name dir; do
+ # Add an exclusion flag if the precious directory is under the git root.
+ if under=$(dir_under_git_root "$dir"); then
+ echo "Avoiding ${name}: ${under}"
+ exclude_flags+=( --exclude="$under" )
+ fi
+ done < "$precious_dirs_file"
+done
+
+git clean -xdff "${exclude_flags[@]}"
diff --git a/bench-ci/guix/guix-codesign b/bench-ci/guix/guix-codesign
new file mode 100755
index 0000000000..4694209e00
--- /dev/null
+++ b/bench-ci/guix/guix-codesign
@@ -0,0 +1,378 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+# 1. Checks if we're at the top directory of the Bitcoin Core repository
+# 2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## SANITY CHECKS ##
+###################
+
+################
+# Required non-builtin commands should be invocable
+################
+
+check_tools cat mkdir git guix
+
+################
+# Required env vars should be non-empty
+################
+
+cmd_usage() {
+ cat < \\
+ ./contrib/guix/guix-codesign
+
+EOF
+}
+
+if [ -z "$DETACHED_SIGS_REPO" ]; then
+ cmd_usage
+ exit 1
+fi
+
+################
+# GUIX_BUILD_OPTIONS should be empty
+################
+#
+# GUIX_BUILD_OPTIONS is an environment variable recognized by guix commands that
+# can perform builds. This seems like what we want instead of
+# ADDITIONAL_GUIX_COMMON_FLAGS, but the value of GUIX_BUILD_OPTIONS is actually
+# _appended_ to normal command-line options. Meaning that they will take
+# precedence over the command-specific ADDITIONAL_GUIX__FLAGS.
+#
+# This seems like a poor user experience. Thus we check for GUIX_BUILD_OPTIONS's
+# existence here and direct users of this script to use our (more flexible)
+# custom environment variables.
+if [ -n "$GUIX_BUILD_OPTIONS" ]; then
+cat << EOF
+Error: Environment variable GUIX_BUILD_OPTIONS is not empty:
+ '$GUIX_BUILD_OPTIONS'
+
+Unfortunately this script is incompatible with GUIX_BUILD_OPTIONS, please unset
+GUIX_BUILD_OPTIONS and use ADDITIONAL_GUIX_COMMON_FLAGS to set build options
+across guix commands or ADDITIONAL_GUIX__FLAGS to set build options for a
+specific guix command.
+
+See contrib/guix/README.md for more details.
+EOF
+exit 1
+fi
+
+################
+# The codesignature git worktree should not be dirty
+################
+
+if ! git -C "$DETACHED_SIGS_REPO" diff-index --quiet HEAD -- && [ -z "$FORCE_DIRTY_WORKTREE" ]; then
+ cat << EOF
+ERR: The DETACHED CODESIGNATURE git worktree is dirty, which may lead to broken builds.
+
+ Aborting...
+
+Hint: To make your git worktree clean, You may want to:
+ 1. Commit your changes,
+ 2. Stash your changes, or
+ 3. Set the 'FORCE_DIRTY_WORKTREE' environment variable if you insist on
+ using a dirty worktree
+EOF
+ exit 1
+fi
+
+################
+# Build directories should not exist
+################
+
+# Default to building for all supported HOSTs (overridable by environment)
+export HOSTS="${HOSTS:-x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin}"
+
+# Usage: distsrc_for_host HOST
+#
+# HOST: The current platform triple we're building for
+#
+distsrc_for_host() {
+ echo "${DISTSRC_BASE}/distsrc-${VERSION}-${1}-codesigned"
+}
+
+# Accumulate a list of build directories that already exist...
+hosts_distsrc_exists=""
+for host in $HOSTS; do
+ if [ -e "$(distsrc_for_host "$host")" ]; then
+ hosts_distsrc_exists+=" ${host}"
+ fi
+done
+
+if [ -n "$hosts_distsrc_exists" ]; then
+# ...so that we can print them out nicely in an error message
+cat << EOF
+ERR: Build directories for this commit already exist for the following platform
+ triples you're attempting to build, probably because of previous builds.
+ Please remove, or otherwise deal with them prior to starting another build.
+
+ Aborting...
+
+Hint: To blow everything away, you may want to use:
+
+ $ ./contrib/guix/guix-clean
+
+Specifically, this will remove all files without an entry in the index,
+excluding the SDK directory, the depends download cache, the depends built
+packages cache, the garbage collector roots for Guix environments, and the
+output directory.
+EOF
+for host in $hosts_distsrc_exists; do
+ echo " ${host} '$(distsrc_for_host "$host")'"
+done
+exit 1
+else
+ mkdir -p "$DISTSRC_BASE"
+fi
+
+
+################
+# Unsigned tarballs SHOULD exist
+################
+
+# Usage: outdir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+outdir_for_host() {
+ echo "${OUTDIR_BASE}/${1}${2:+-${2}}"
+}
+
+
+unsigned_tarball_for_host() {
+ case "$1" in
+ *mingw*)
+ echo "$(outdir_for_host "$1")/${DISTNAME}-win64-unsigned.tar.gz"
+ ;;
+ *darwin*)
+ echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-unsigned.tar.gz"
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+}
+
+# Accumulate a list of build directories that already exist...
+hosts_unsigned_tarball_missing=""
+for host in $HOSTS; do
+ if [ ! -e "$(unsigned_tarball_for_host "$host")" ]; then
+ hosts_unsigned_tarball_missing+=" ${host}"
+ fi
+done
+
+if [ -n "$hosts_unsigned_tarball_missing" ]; then
+ # ...so that we can print them out nicely in an error message
+ cat << EOF
+ERR: Unsigned tarballs do not exist
+...
+
+EOF
+for host in $hosts_unsigned_tarball_missing; do
+ echo " ${host} '$(unsigned_tarball_for_host "$host")'"
+done
+exit 1
+fi
+
+################
+# Check that we can connect to the guix-daemon
+################
+
+cat << EOF
+Checking that we can connect to the guix-daemon...
+
+Hint: If this hangs, you may want to try turning your guix-daemon off and on
+ again.
+
+EOF
+if ! guix gc --list-failures > /dev/null; then
+ cat << EOF
+
+ERR: Failed to connect to the guix-daemon, please ensure that one is running and
+ reachable.
+EOF
+ exit 1
+fi
+
+# Developer note: we could use `guix repl` for this check and run:
+#
+# (import (guix store)) (close-connection (open-connection))
+#
+# However, the internal API is likely to change more than the CLI invocation
+
+
+#########
+# SETUP #
+#########
+
+# Determine the maximum number of jobs to run simultaneously (overridable by
+# environment)
+JOBS="${JOBS:-$(nproc)}"
+
+# Determine the reference time used for determinism (overridable by environment)
+SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -c log.showSignature=false log --format=%at -1)}"
+
+# Make sure an output directory exists for our builds
+OUTDIR_BASE="${OUTDIR_BASE:-${VERSION_BASE}/output}"
+mkdir -p "$OUTDIR_BASE"
+
+# Usage: profiledir_for_host HOST SUFFIX
+#
+# HOST: The current platform triple we're building for
+#
+profiledir_for_host() {
+ echo "${PROFILES_BASE}/${1}${2:+-${2}}"
+}
+
+#########
+# BUILD #
+#########
+
+# Function to be called when codesigning for host ${1} and the user interrupts
+# the codesign
+int_trap() {
+cat << EOF
+** INT received while codesigning ${1}, you may want to clean up the relevant
+ work directories (e.g. distsrc-*) before recodesigning
+
+Hint: To blow everything away, you may want to use:
+
+ $ ./contrib/guix/guix-clean
+
+Specifically, this will remove all files without an entry in the index,
+excluding the SDK directory, the depends download cache, the depends built
+packages cache, the garbage collector roots for Guix environments, and the
+output directory.
+EOF
+}
+
+# Deterministically build Bitcoin Core
+# shellcheck disable=SC2153
+for host in $HOSTS; do
+
+ # Display proper warning when the user interrupts the build
+ trap 'int_trap ${host}' INT
+
+ (
+ # Required for 'contrib/guix/manifest.scm' to output the right manifest
+ # for the particular $HOST we're building for
+ export HOST="$host"
+
+ # shellcheck disable=SC2030
+cat << EOF
+INFO: Codesigning ${VERSION:?not set} for platform triple ${HOST:?not set}:
+ ...using reference timestamp: ${SOURCE_DATE_EPOCH:?not set}
+ ...from worktree directory: '${PWD}'
+ ...bind-mounted in container to: '/bitcoin'
+ ...in build directory: '$(distsrc_for_host "$HOST")'
+ ...bind-mounted in container to: '$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")'
+ ...outputting in: '$(outdir_for_host "$HOST" codesigned)'
+ ...bind-mounted in container to: '$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST" codesigned)'
+ ...using detached signatures in: '${DETACHED_SIGS_REPO:?not set}'
+ ...bind-mounted in container to: '/detached-sigs'
+EOF
+
+
+ # Run the build script 'contrib/guix/libexec/build.sh' in the build
+ # container specified by 'contrib/guix/manifest.scm'.
+ #
+ # Explanation of `guix shell` flags:
+ #
+ # --container run command within an isolated container
+ #
+ # Running in an isolated container minimizes build-time differences
+ # between machines and improves reproducibility
+ #
+ # --pure unset existing environment variables
+ #
+ # Same rationale as --container
+ #
+ # --no-cwd do not share current working directory with an
+ # isolated container
+ #
+ # When --container is specified, the default behavior is to share
+ # the current working directory with the isolated container at the
+ # same exact path (e.g. mapping '/home/satoshi/bitcoin/' to
+ # '/home/satoshi/bitcoin/'). This means that the $PWD inside the
+ # container becomes a source of irreproducibility. --no-cwd disables
+ # this behaviour.
+ #
+ # --share=SPEC for containers, share writable host file system
+ # according to SPEC
+ #
+ # --share="$PWD"=/bitcoin
+ #
+ # maps our current working directory to /bitcoin
+ # inside the isolated container, which we later cd
+ # into.
+ #
+ # While we don't want to map our current working directory to the
+ # same exact path (as this introduces irreproducibility), we do want
+ # it to be at a _fixed_ path _somewhere_ inside the isolated
+ # container so that we have something to build. '/bitcoin' was
+ # chosen arbitrarily.
+ #
+ # ${SOURCES_PATH:+--share="$SOURCES_PATH"}
+ #
+ # make the downloaded depends sources path available
+ # inside the isolated container
+ #
+ # The isolated container has no network access as it's in a
+ # different network namespace from the main machine, so we have to
+ # make the downloaded depends sources available to it. The sources
+ # should have been downloaded prior to this invocation.
+ #
+ # ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"}
+ #
+ # fetch substitute from SUBSTITUTE_URLS if they are
+ # authorized
+ #
+ # Depending on the user's security model, it may be desirable to use
+ # substitutes (pre-built packages) from servers that the user trusts.
+ # Please read the README.md in the same directory as this file for
+ # more information.
+ #
+ # shellcheck disable=SC2086,SC2031
+ time-machine shell --manifest="${PWD}/contrib/guix/manifest.scm" \
+ --container \
+ --pure \
+ --no-cwd \
+ --share="$PWD"=/bitcoin \
+ --share="$DISTSRC_BASE"=/distsrc-base \
+ --share="$OUTDIR_BASE"=/outdir-base \
+ --share="$DETACHED_SIGS_REPO"=/detached-sigs \
+ --expose="$(git rev-parse --git-common-dir)" \
+ --expose="$(git -C "$DETACHED_SIGS_REPO" rev-parse --git-common-dir)" \
+ ${SOURCES_PATH:+--share="$SOURCES_PATH"} \
+ --cores="$JOBS" \
+ --keep-failed \
+ --fallback \
+ --link-profile \
+ --root="$(profiledir_for_host "${HOST}" codesigned)" \
+ ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
+ ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \
+ -- env HOST="$host" \
+ DISTNAME="$DISTNAME" \
+ JOBS="$JOBS" \
+ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \
+ ${V:+V=1} \
+ ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} \
+ DISTSRC="$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")" \
+ OUTDIR="$(OUTDIR_BASE=/outdir-base && outdir_for_host "$HOST" codesigned)" \
+ DIST_ARCHIVE_BASE=/outdir-base/dist-archive \
+ DETACHED_SIGS_REPO=/detached-sigs \
+ UNSIGNED_TARBALL="$(OUTDIR_BASE=/outdir-base && unsigned_tarball_for_host "$HOST")" \
+ bash -c "cd /bitcoin && bash contrib/guix/libexec/codesign.sh"
+ )
+
+done
diff --git a/bench-ci/guix/guix-verify b/bench-ci/guix/guix-verify
new file mode 100755
index 0000000000..02ae022741
--- /dev/null
+++ b/bench-ci/guix/guix-verify
@@ -0,0 +1,174 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# Source the common prelude, which:
+# 1. Checks if we're at the top directory of the Bitcoin Core repository
+# 2. Defines a few common functions and variables
+#
+# shellcheck source=libexec/prelude.bash
+source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
+
+
+###################
+## Sanity Checks ##
+###################
+
+################
+# Required non-builtin commands should be invokable
+################
+
+check_tools cat diff gpg
+
+################
+# Required env vars should be non-empty
+################
+
+cmd_usage() {
+cat < [ SIGNER= ] ./contrib/guix/guix-verify
+
+Example overriding signer's manifest to use as base
+
+ env GUIX_SIGS_REPO=/home/dongcarl/guix.sigs SIGNER=achow101 ./contrib/guix/guix-verify
+
+EOF
+}
+
+if [ -z "$GUIX_SIGS_REPO" ]; then
+ cmd_usage
+ exit 1
+fi
+
+################
+# GUIX_SIGS_REPO should exist as a directory
+################
+
+if [ ! -d "$GUIX_SIGS_REPO" ]; then
+cat << EOF
+ERR: The specified GUIX_SIGS_REPO is not an existent directory:
+
+ '$GUIX_SIGS_REPO'
+
+Hint: Please clone the guix.sigs repository and point to it with the
+ GUIX_SIGS_REPO environment variable.
+
+EOF
+cmd_usage
+exit 1
+fi
+
+##############
+## Verify ##
+##############
+
+OUTSIGDIR_BASE="${GUIX_SIGS_REPO}/${VERSION}"
+echo "Looking for signature directories in '${OUTSIGDIR_BASE}'"
+echo ""
+
+# Usage: verify compare_manifest current_manifest
+verify() {
+ local compare_manifest="$1"
+ local current_manifest="$2"
+ if ! gpg --quiet --batch --verify "$current_manifest".asc "$current_manifest" 1>&2; then
+ echo "ERR: Failed to verify GPG signature in '${current_manifest}'"
+ echo ""
+ echo "Hint: Either the signature is invalid or the public key is missing"
+ echo ""
+ failure=1
+ elif ! diff --report-identical "$compare_manifest" "$current_manifest" 1>&2; then
+ echo "ERR: The SHA256SUMS attestation in these two directories differ:"
+ echo " '${compare_manifest}'"
+ echo " '${current_manifest}'"
+ echo ""
+ failure=1
+ else
+ echo "Verified: '${current_manifest}'"
+ echo ""
+ fi
+}
+
+shopt -s nullglob
+all_noncodesigned=( "$OUTSIGDIR_BASE"/*/noncodesigned.SHA256SUMS )
+shopt -u nullglob
+
+echo "--------------------"
+echo ""
+if (( ${#all_noncodesigned[@]} )); then
+ compare_noncodesigned="${all_noncodesigned[0]}"
+ if [[ -n "$SIGNER" ]]; then
+ signer_noncodesigned="$OUTSIGDIR_BASE/$SIGNER/noncodesigned.SHA256SUMS"
+ if [[ -f "$signer_noncodesigned" ]]; then
+ echo "Using $SIGNER's manifest as the base to compare against"
+ compare_noncodesigned="$signer_noncodesigned"
+ else
+ echo "Unable to find $SIGNER's manifest, using the first one found"
+ fi
+ else
+ echo "No SIGNER provided, using the first manifest found"
+ fi
+
+ for current_manifest in "${all_noncodesigned[@]}"; do
+ verify "$compare_noncodesigned" "$current_manifest"
+ done
+
+ echo "DONE: Checking output signatures for noncodesigned.SHA256SUMS"
+ echo ""
+else
+ echo "WARN: No signature directories with noncodesigned.SHA256SUMS found"
+ echo ""
+fi
+
+shopt -s nullglob
+all_all=( "$OUTSIGDIR_BASE"/*/all.SHA256SUMS )
+shopt -u nullglob
+
+echo "--------------------"
+echo ""
+if (( ${#all_all[@]} )); then
+ compare_all="${all_all[0]}"
+ if [[ -n "$SIGNER" ]]; then
+ signer_all="$OUTSIGDIR_BASE/$SIGNER/all.SHA256SUMS"
+ if [[ -f "$signer_all" ]]; then
+ echo "Using $SIGNER's manifest as the base to compare against"
+ compare_all="$signer_all"
+ else
+ echo "Unable to find $SIGNER's manifest, using the first one found"
+ fi
+ else
+ echo "No SIGNER provided, using the first manifest found"
+ fi
+
+ for current_manifest in "${all_all[@]}"; do
+ verify "$compare_all" "$current_manifest"
+ done
+
+ # Sanity check: there should be no entries that exist in
+ # noncodesigned.SHA256SUMS that doesn't exist in all.SHA256SUMS
+ if [[ "$(comm -23 <(sort "$compare_noncodesigned") <(sort "$compare_all") | wc -c)" -ne 0 ]]; then
+ echo "ERR: There are unique lines in noncodesigned.SHA256SUMS which"
+ echo " do not exist in all.SHA256SUMS, something went very wrong."
+ exit 1
+ fi
+
+ echo "DONE: Checking output signatures for all.SHA256SUMS"
+ echo ""
+else
+ echo "WARN: No signature directories with all.SHA256SUMS found"
+ echo ""
+fi
+
+echo "===================="
+echo ""
+if (( ${#all_noncodesigned[@]} + ${#all_all[@]} == 0 )); then
+ echo "ERR: Unable to perform any verifications as no signature directories"
+ echo " were found"
+ echo ""
+ exit 1
+fi
+
+if [ -n "$failure" ]; then
+ exit 1
+fi
diff --git a/bench-ci/guix/libexec/build.sh b/bench-ci/guix/libexec/build.sh
new file mode 100755
index 0000000000..1a2683e886
--- /dev/null
+++ b/bench-ci/guix/libexec/build.sh
@@ -0,0 +1,395 @@
+#!/usr/bin/env bash
+# Copyright (c) 2019-2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+export LC_ALL=C
+set -e -o pipefail
+export TZ=UTC
+
+# Although Guix _does_ set umask when building its own packages (in our case,
+# this is all packages in manifest.scm), it does not set it for `guix
+# shell`. It does make sense for at least `guix shell --container`
+# to set umask, so if that change gets merged upstream and we bump the
+# time-machine to a commit which includes the aforementioned change, we can
+# remove this line.
+#
+# This line should be placed before any commands which creates files.
+umask 0022
+
+if [ -n "$V" ]; then
+ # Print both unexpanded (-v) and expanded (-x) forms of commands as they are
+ # read from this file.
+ set -vx
+ # Set VERBOSE for CMake-based builds
+ export VERBOSE="$V"
+fi
+
+# Check that required environment variables are set
+cat << EOF
+Required environment variables as seen inside the container:
+ DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set}
+ DISTNAME: ${DISTNAME:?not set}
+ HOST: ${HOST:?not set}
+ SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set}
+ JOBS: ${JOBS:?not set}
+ DISTSRC: ${DISTSRC:?not set}
+ OUTDIR: ${OUTDIR:?not set}
+EOF
+
+ACTUAL_OUTDIR="${OUTDIR}"
+OUTDIR="${DISTSRC}/output"
+
+#####################
+# Environment Setup #
+#####################
+
+# The depends folder also serves as a base-prefix for depends packages for
+# $HOSTs after successfully building.
+BASEPREFIX="${PWD}/depends"
+
+# Given a package name and an output name, return the path of that output in our
+# current guix environment
+store_path() {
+ grep --extended-regexp "/[^-]{32}-${1}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \
+ | head --lines=1 \
+ | sed --expression='s|\x29*$||' \
+ --expression='s|^[[:space:]]*"||' \
+ --expression='s|"[[:space:]]*$||'
+}
+
+
+# Set environment variables to point the NATIVE toolchain to the right
+# includes/libs
+NATIVE_GCC="$(store_path gcc-toolchain)"
+
+unset LIBRARY_PATH
+unset CPATH
+unset C_INCLUDE_PATH
+unset CPLUS_INCLUDE_PATH
+unset OBJC_INCLUDE_PATH
+unset OBJCPLUS_INCLUDE_PATH
+
+export C_INCLUDE_PATH="${NATIVE_GCC}/include"
+export CPLUS_INCLUDE_PATH="${NATIVE_GCC}/include/c++:${NATIVE_GCC}/include"
+
+case "$HOST" in
+ *darwin*) export LIBRARY_PATH="${NATIVE_GCC}/lib" ;; # Required for qt/qmake
+ *mingw*) export LIBRARY_PATH="${NATIVE_GCC}/lib" ;;
+ *)
+ NATIVE_GCC_STATIC="$(store_path gcc-toolchain static)"
+ export LIBRARY_PATH="${NATIVE_GCC}/lib:${NATIVE_GCC_STATIC}/lib"
+ ;;
+esac
+
+# Set environment variables to point the CROSS toolchain to the right
+# includes/libs for $HOST
+case "$HOST" in
+ *mingw*)
+ # Determine output paths to use in CROSS_* environment variables
+ CROSS_GLIBC="$(store_path "mingw-w64-x86_64-winpthreads")"
+ CROSS_GCC="$(store_path "gcc-cross-${HOST}")"
+ CROSS_GCC_LIB_STORE="$(store_path "gcc-cross-${HOST}" lib)"
+ CROSS_GCC_LIBS=( "${CROSS_GCC_LIB_STORE}/lib/gcc/${HOST}"/* ) # This expands to an array of directories...
+ CROSS_GCC_LIB="${CROSS_GCC_LIBS[0]}" # ...we just want the first one (there should only be one)
+
+ # The search path ordering is generally:
+ # 1. gcc-related search paths
+ # 2. libc-related search paths
+ # 2. kernel-header-related search paths (not applicable to mingw-w64 hosts)
+ export CROSS_C_INCLUDE_PATH="${CROSS_GCC_LIB}/include:${CROSS_GCC_LIB}/include-fixed:${CROSS_GLIBC}/include"
+ export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GCC}/include/c++/${HOST}:${CROSS_GCC}/include/c++/backward:${CROSS_C_INCLUDE_PATH}"
+ export CROSS_LIBRARY_PATH="${CROSS_GCC_LIB_STORE}/lib:${CROSS_GCC_LIB}:${CROSS_GLIBC}/lib"
+ ;;
+ *darwin*)
+ # The CROSS toolchain for darwin uses the SDK and ignores environment variables.
+ # See depends/hosts/darwin.mk for more details.
+ ;;
+ *linux*)
+ CROSS_GLIBC="$(store_path "glibc-cross-${HOST}")"
+ CROSS_GLIBC_STATIC="$(store_path "glibc-cross-${HOST}" static)"
+ CROSS_KERNEL="$(store_path "linux-libre-headers-cross-${HOST}")"
+ CROSS_GCC="$(store_path "gcc-cross-${HOST}")"
+ CROSS_GCC_LIB_STORE="$(store_path "gcc-cross-${HOST}" lib)"
+ CROSS_GCC_LIBS=( "${CROSS_GCC_LIB_STORE}/lib/gcc/${HOST}"/* ) # This expands to an array of directories...
+ CROSS_GCC_LIB="${CROSS_GCC_LIBS[0]}" # ...we just want the first one (there should only be one)
+
+ export CROSS_C_INCLUDE_PATH="${CROSS_GCC_LIB}/include:${CROSS_GCC_LIB}/include-fixed:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include"
+ export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GCC}/include/c++/${HOST}:${CROSS_GCC}/include/c++/backward:${CROSS_C_INCLUDE_PATH}"
+ export CROSS_LIBRARY_PATH="${CROSS_GCC_LIB_STORE}/lib:${CROSS_GCC_LIB}:${CROSS_GLIBC}/lib:${CROSS_GLIBC_STATIC}/lib"
+ ;;
+ *)
+ exit 1 ;;
+esac
+
+# Sanity check CROSS_*_PATH directories
+IFS=':' read -ra PATHS <<< "${CROSS_C_INCLUDE_PATH}:${CROSS_CPLUS_INCLUDE_PATH}:${CROSS_LIBRARY_PATH}"
+for p in "${PATHS[@]}"; do
+ if [ -n "$p" ] && [ ! -d "$p" ]; then
+ echo "'$p' doesn't exist or isn't a directory... Aborting..."
+ exit 1
+ fi
+done
+
+# Disable Guix ld auto-rpath behavior
+export GUIX_LD_WRAPPER_DISABLE_RPATH=yes
+
+# Make /usr/bin if it doesn't exist
+[ -e /usr/bin ] || mkdir -p /usr/bin
+
+# Symlink file and env to a conventional path
+[ -e /usr/bin/file ] || ln -s --no-dereference "$(command -v file)" /usr/bin/file
+[ -e /usr/bin/env ] || ln -s --no-dereference "$(command -v env)" /usr/bin/env
+
+# Determine the correct value for -Wl,--dynamic-linker for the current $HOST
+case "$HOST" in
+ x86_64-linux-gnu) ;;
+ *linux*)
+ glibc_dynamic_linker=$(
+ case "$HOST" in
+ arm-linux-gnueabihf) echo /lib/ld-linux-armhf.so.3 ;;
+ aarch64-linux-gnu) echo /lib/ld-linux-aarch64.so.1 ;;
+ riscv64-linux-gnu) echo /lib/ld-linux-riscv64-lp64d.so.1 ;;
+ powerpc64-linux-gnu) echo /lib64/ld64.so.1;;
+ powerpc64le-linux-gnu) echo /lib64/ld64.so.2;;
+ *) exit 1 ;;
+ esac
+ )
+ ;;
+esac
+
+# Environment variables for determinism
+export TAR_OPTIONS="--owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name"
+export TZ="UTC"
+
+####################
+# Depends Building #
+####################
+
+# Build the depends tree, overriding variables that assume multilib gcc
+make -C depends --jobs="$JOBS" HOST="$HOST" \
+ ${V:+V=1} \
+ ${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \
+ ${BASE_CACHE+BASE_CACHE="$BASE_CACHE"} \
+ ${SDK_PATH+SDK_PATH="$SDK_PATH"} \
+ x86_64_linux_CC=x86_64-linux-gnu-gcc \
+ x86_64_linux_CXX=x86_64-linux-gnu-g++ \
+ x86_64_linux_AR=x86_64-linux-gnu-gcc-ar \
+ x86_64_linux_RANLIB=x86_64-linux-gnu-gcc-ranlib \
+ x86_64_linux_NM=x86_64-linux-gnu-gcc-nm \
+ x86_64_linux_STRIP=x86_64-linux-gnu-strip \
+ NO_QT=1 \
+ NO_QR=1 \
+ NO_ZMQ=1 \
+ NO_WALLET=1 \
+ NO_BDB=1 \
+ NO_USDT=1
+
+case "$HOST" in
+ *darwin*)
+ # Unset now that Qt is built
+ unset C_INCLUDE_PATH
+ unset CPLUS_INCLUDE_PATH
+ unset LIBRARY_PATH
+ ;;
+esac
+
+###########################
+# Source Tarball Building #
+###########################
+
+GIT_ARCHIVE="${DIST_ARCHIVE_BASE}/${DISTNAME}.tar.gz"
+
+# Create the source tarball if not already there
+if [ ! -e "$GIT_ARCHIVE" ]; then
+ mkdir -p "$(dirname "$GIT_ARCHIVE")"
+ git archive --prefix="${DISTNAME}/" --output="$GIT_ARCHIVE" HEAD
+fi
+
+mkdir -p "$OUTDIR"
+
+###########################
+# Binary Tarball Building #
+###########################
+
+# CONFIGFLAGS
+CONFIGFLAGS="-DREDUCE_EXPORTS=ON -DBUILD_BENCH=OFF -DBUILD_GUI_TESTS=OFF -DBUILD_FUZZ_BINARY=OFF"
+
+# BENCHCOINFLAGS
+BENCHCOINFLAGS="-DBUILD_CLI=OFF -DBUILD_TESTS=OFF -DCMAKE_CXX_FLAGS=-fno-omit-frame-pointer"
+
+# CFLAGS
+HOST_CFLAGS="-O2 -g"
+HOST_CFLAGS+=$(find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;)
+case "$HOST" in
+ *mingw*) HOST_CFLAGS+=" -fno-ident" ;;
+ *darwin*) unset HOST_CFLAGS ;;
+esac
+
+# CXXFLAGS
+HOST_CXXFLAGS="$HOST_CFLAGS"
+
+case "$HOST" in
+ arm-linux-gnueabihf) HOST_CXXFLAGS="${HOST_CXXFLAGS} -Wno-psabi" ;;
+esac
+
+# LDFLAGS
+case "$HOST" in
+ x86_64-linux-gnu) HOST_LDFLAGS=" -static-pie -static-libgcc -Wl,-O2" ;;
+ *linux*) HOST_LDFLAGS="-Wl,--as-needed -Wl,--dynamic-linker=$glibc_dynamic_linker -static-libstdc++ -Wl,-O2" ;;
+ *mingw*) HOST_LDFLAGS="-Wl,--no-insert-timestamp" ;;
+esac
+
+mkdir -p "$DISTSRC"
+(
+ cd "$DISTSRC"
+
+ # Extract the source tarball
+ tar --strip-components=1 -xf "${GIT_ARCHIVE}"
+
+ # Configure this DISTSRC for $HOST
+ # shellcheck disable=SC2086
+ env CFLAGS="${HOST_CFLAGS}" CXXFLAGS="${HOST_CXXFLAGS}" LDFLAGS="${HOST_LDFLAGS}" \
+ cmake -S . -B build \
+ --toolchain "${BASEPREFIX}/${HOST}/toolchain.cmake" \
+ -DWITH_CCACHE=OFF \
+ ${CONFIGFLAGS} \
+ ${BENCHCOINFLAGS}
+
+ # Build Bitcoin Core
+ cmake --build build -j "$JOBS" ${V:+--verbose}
+
+ mkdir -p "$OUTDIR"
+
+ # Make the os-specific installers
+ case "$HOST" in
+ *mingw*)
+ cmake --build build -j "$JOBS" -t deploy ${V:+--verbose}
+ mv build/bitcoin-win64-setup.exe "${OUTDIR}/${DISTNAME}-win64-setup-unsigned.exe"
+ ;;
+ esac
+
+ # Setup the directory where our Bitcoin Core build for HOST will be
+ # installed. This directory will also later serve as the input for our
+ # binary tarballs.
+ INSTALLPATH="${PWD}/installed/${DISTNAME}"
+ mkdir -p "${INSTALLPATH}"
+ # Install built Bitcoin Core to $INSTALLPATH
+ case "$HOST" in
+ *darwin*)
+ # This workaround can be dropped for CMake >= 3.27.
+ # See the upstream commit 689616785f76acd844fd448c51c5b2a0711aafa2.
+ find build -name 'cmake_install.cmake' -exec sed -i 's| -u -r | |g' {} +
+
+ cmake --install build --strip --prefix "${INSTALLPATH}" ${V:+--verbose}
+ ;;
+ *)
+ cmake --install build --prefix "${INSTALLPATH}" ${V:+--verbose}
+ ;;
+ esac
+
+ case "$HOST" in
+ *darwin*)
+ cmake --build build --target deploy ${V:+--verbose}
+ mv build/dist/Bitcoin-Core.zip "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.zip"
+ mkdir -p "unsigned-app-${HOST}"
+ cp --target-directory="unsigned-app-${HOST}" \
+ contrib/macdeploy/detached-sig-create.sh
+ mv --target-directory="unsigned-app-${HOST}" build/dist
+ (
+ cd "unsigned-app-${HOST}"
+ find . -print0 \
+ | sort --zero-terminated \
+ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
+ )
+ ;;
+ esac
+ (
+ cd installed
+
+ case "$HOST" in
+ *mingw*)
+ cp "${DISTSRC}/doc/README_windows.txt" "${DISTNAME}/readme.txt"
+ ;;
+ *linux*)
+ cp "${DISTSRC}/README.md" "${DISTNAME}/"
+ ;;
+ esac
+
+ # copy over the example bitcoin.conf file. if contrib/devtools/gen-bitcoin-conf.sh
+ # has not been run before buildling, this file will be a stub
+ cp "${DISTSRC}/share/examples/bitcoin.conf" "${DISTNAME}/"
+
+ cp -r "${DISTSRC}/share/rpcauth" "${DISTNAME}/share/"
+
+ # Finally, deterministically produce {non-,}debug binary tarballs ready
+ # for release
+ case "$HOST" in
+ *mingw*)
+ find "${DISTNAME}" -not -name "*.dbg" -print0 \
+ | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+ find "${DISTNAME}" -not -name "*.dbg" \
+ | sort \
+ | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}.zip" && exit 1 )
+ find "${DISTNAME}" -name "*.dbg" -print0 \
+ | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+ find "${DISTNAME}" -name "*.dbg" \
+ | sort \
+ | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-debug.zip" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-debug.zip" && exit 1 )
+ ;;
+ *linux*)
+ find "${DISTNAME}" -not -name "*.dbg" -print0 \
+ | sort --zero-terminated \
+ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
+ find "${DISTNAME}" -name "*.dbg" -print0 \
+ | sort --zero-terminated \
+ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-debug.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-debug.tar.gz" && exit 1 )
+ ;;
+ *darwin*)
+ find "${DISTNAME}" -print0 \
+ | sort --zero-terminated \
+ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
+ ;;
+ esac
+ ) # $DISTSRC/installed
+
+ case "$HOST" in
+ *mingw*)
+ cp -rf --target-directory=. contrib/windeploy
+ (
+ cd ./windeploy
+ mkdir -p unsigned
+ cp --target-directory=unsigned/ "${OUTDIR}/${DISTNAME}-win64-setup-unsigned.exe"
+ find . -print0 \
+ | sort --zero-terminated \
+ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" && exit 1 )
+ )
+ ;;
+ esac
+) # $DISTSRC
+
+rm -rf "$ACTUAL_OUTDIR"
+mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
+ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 )
+
+(
+ cd /outdir-base
+ {
+ echo "$GIT_ARCHIVE"
+ find "$ACTUAL_OUTDIR" -type f
+ } | xargs realpath --relative-base="$PWD" \
+ | xargs sha256sum \
+ | sort -k2 \
+ | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part
+)
diff --git a/bench-ci/guix/libexec/codesign.sh b/bench-ci/guix/libexec/codesign.sh
new file mode 100755
index 0000000000..b56d2a2309
--- /dev/null
+++ b/bench-ci/guix/libexec/codesign.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+# Copyright (c) 2021-2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+export LC_ALL=C
+set -e -o pipefail
+export TZ=UTC
+
+# Although Guix _does_ set umask when building its own packages (in our case,
+# this is all packages in manifest.scm), it does not set it for `guix
+# shell`. It does make sense for at least `guix shell --container`
+# to set umask, so if that change gets merged upstream and we bump the
+# time-machine to a commit which includes the aforementioned change, we can
+# remove this line.
+#
+# This line should be placed before any commands which creates files.
+umask 0022
+
+if [ -n "$V" ]; then
+ # Print both unexpanded (-v) and expanded (-x) forms of commands as they are
+ # read from this file.
+ set -vx
+ # Set VERBOSE for CMake-based builds
+ export VERBOSE="$V"
+fi
+
+# Check that required environment variables are set
+cat << EOF
+Required environment variables as seen inside the container:
+ UNSIGNED_TARBALL: ${UNSIGNED_TARBALL:?not set}
+ DETACHED_SIGS_REPO: ${DETACHED_SIGS_REPO:?not set}
+ DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set}
+ DISTNAME: ${DISTNAME:?not set}
+ HOST: ${HOST:?not set}
+ SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set}
+ DISTSRC: ${DISTSRC:?not set}
+ OUTDIR: ${OUTDIR:?not set}
+EOF
+
+ACTUAL_OUTDIR="${OUTDIR}"
+OUTDIR="${DISTSRC}/output"
+
+git_head_version() {
+ local recent_tag
+ if recent_tag="$(git -C "$1" describe --exact-match HEAD 2> /dev/null)"; then
+ echo "${recent_tag#v}"
+ else
+ git -C "$1" rev-parse --short=12 HEAD
+ fi
+}
+
+CODESIGNATURE_GIT_ARCHIVE="${DIST_ARCHIVE_BASE}/${DISTNAME}-codesignatures-$(git_head_version "$DETACHED_SIGS_REPO").tar.gz"
+
+# Create the codesignature tarball if not already there
+if [ ! -e "$CODESIGNATURE_GIT_ARCHIVE" ]; then
+ mkdir -p "$(dirname "$CODESIGNATURE_GIT_ARCHIVE")"
+ git -C "$DETACHED_SIGS_REPO" archive --output="$CODESIGNATURE_GIT_ARCHIVE" HEAD
+fi
+
+mkdir -p "$OUTDIR"
+
+mkdir -p "$DISTSRC"
+(
+ cd "$DISTSRC"
+
+ tar -xf "$UNSIGNED_TARBALL"
+
+ mkdir -p codesignatures
+ tar -C codesignatures -xf "$CODESIGNATURE_GIT_ARCHIVE"
+
+ case "$HOST" in
+ *mingw*)
+ find "$PWD" -name "*-unsigned.exe" | while read -r infile; do
+ infile_base="$(basename "$infile")"
+
+ # Codesigned *-unsigned.exe and output to OUTDIR
+ osslsigncode attach-signature \
+ -in "$infile" \
+ -out "${OUTDIR}/${infile_base/-unsigned}" \
+ -CAfile "$GUIX_ENVIRONMENT/etc/ssl/certs/ca-certificates.crt" \
+ -sigin codesignatures/win/"$infile_base".pem
+ done
+ ;;
+ *darwin*)
+ # Apply detached codesignatures to dist/ (in-place)
+ signapple apply dist/Bitcoin-Qt.app codesignatures/osx/dist
+
+ # Make a .zip from dist/
+ cd dist/
+ find . -print0 \
+ | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+ find . | sort \
+ | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST}.zip"
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+) # $DISTSRC
+
+rm -rf "$ACTUAL_OUTDIR"
+mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
+ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 )
+
+(
+ cd /outdir-base
+ {
+ echo "$UNSIGNED_TARBALL"
+ echo "$CODESIGNATURE_GIT_ARCHIVE"
+ find "$ACTUAL_OUTDIR" -type f
+ } | xargs realpath --relative-base="$PWD" \
+ | xargs sha256sum \
+ | sort -k2 \
+ | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part
+)
diff --git a/bench-ci/guix/libexec/prelude.bash b/bench-ci/guix/libexec/prelude.bash
new file mode 100644
index 0000000000..813b8e53c0
--- /dev/null
+++ b/bench-ci/guix/libexec/prelude.bash
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+export LC_ALL=C
+set -e -o pipefail
+
+# shellcheck source=contrib/shell/realpath.bash
+source contrib/shell/realpath.bash
+
+# shellcheck source=contrib/shell/git-utils.bash
+source contrib/shell/git-utils.bash
+
+# Source guix profile from the runner home directory
+GUIX_PROFILE=/home/github-runner/.config/guix/current
+. "$GUIX_PROFILE/etc/profile"
+echo "Using the following guix command:"
+command -v guix
+echo "Guix command symlink points to:"
+readlink -f "$(command -v guix)"
+echo "Current Guix profile:"
+echo "$GUIX_PROFILE"
+echo "Profile generation info:"
+guix describe
+
+################
+# Required non-builtin commands should be invocable
+################
+
+check_tools() {
+ for cmd in "$@"; do
+ if ! command -v "$cmd" > /dev/null 2>&1; then
+ echo "ERR: This script requires that '$cmd' is installed and available in your \$PATH"
+ exit 1
+ fi
+ done
+}
+
+check_tools cat env readlink dirname basename git
+
+################
+# We should be at the top directory of the repository
+################
+
+same_dir() {
+ local resolved1 resolved2
+ resolved1="$(bash_realpath "${1}")"
+ resolved2="$(bash_realpath "${2}")"
+ [ "$resolved1" = "$resolved2" ]
+}
+
+if ! same_dir "${PWD}" "$(git_root)"; then
+cat << EOF
+ERR: This script must be invoked from the top level of the git repository
+
+Hint: This may look something like:
+ env FOO=BAR ./contrib/guix/guix-
+
+EOF
+exit 1
+fi
+
+################
+# Execute "$@" in a pinned, possibly older version of Guix, for reproducibility
+# across time.
+time-machine() {
+ # shellcheck disable=SC2086
+ guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \
+ --commit=4fa41a04802c43dc4c064b7ac4c2e6a4e92f63b6 \
+ --cores="$JOBS" \
+ --keep-failed \
+ --fallback \
+ ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
+ ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_TIMEMACHINE_FLAGS} \
+ -- "$@"
+}
+
+
+################
+# Set common variables
+################
+
+VERSION="${FORCE_VERSION:-$(git_head_version)}"
+DISTNAME="${DISTNAME:-bitcoin-${VERSION}}"
+
+version_base_prefix="${PWD}/guix-build-"
+VERSION_BASE="${version_base_prefix}${VERSION}" # TOP
+
+DISTSRC_BASE="${DISTSRC_BASE:-${VERSION_BASE}}"
+
+OUTDIR_BASE="${OUTDIR_BASE:-${VERSION_BASE}/output}"
+
+var_base_basename="var"
+VAR_BASE="${VAR_BASE:-${VERSION_BASE}/${var_base_basename}}"
+
+profiles_base_basename="profiles"
+PROFILES_BASE="${PROFILES_BASE:-${VAR_BASE}/${profiles_base_basename}}"
diff --git a/bench-ci/guix/manifest.scm b/bench-ci/guix/manifest.scm
new file mode 100644
index 0000000000..29ac9cab7b
--- /dev/null
+++ b/bench-ci/guix/manifest.scm
@@ -0,0 +1,610 @@
+(use-modules (gnu packages)
+ ((gnu packages bash) #:select (bash-minimal))
+ (gnu packages bison)
+ ((gnu packages certs) #:select (nss-certs))
+ ((gnu packages cmake) #:select (cmake-minimal))
+ (gnu packages commencement)
+ (gnu packages compression)
+ (gnu packages cross-base)
+ (gnu packages file)
+ (gnu packages gawk)
+ (gnu packages gcc)
+ ((gnu packages installers) #:select (nsis-x86_64))
+ ((gnu packages linux) #:select (linux-libre-headers-6.1))
+ (gnu packages llvm)
+ (gnu packages mingw)
+ (gnu packages pkg-config)
+ ((gnu packages python) #:select (python-minimal))
+ ((gnu packages python-build) #:select (python-tomli))
+ ((gnu packages python-crypto) #:select (python-asn1crypto))
+ ((gnu packages tls) #:select (openssl))
+ ((gnu packages version-control) #:select (git-minimal))
+ (guix build-system cmake)
+ (guix build-system gnu)
+ (guix build-system python)
+ (guix build-system trivial)
+ (guix download)
+ (guix gexp)
+ (guix git-download)
+ ((guix licenses) #:prefix license:)
+ (guix packages)
+ ((guix utils) #:select (cc-for-target substitute-keyword-arguments)))
+
+(define-syntax-rule (search-our-patches file-name ...)
+ "Return the list of absolute file names corresponding to each
+FILE-NAME found in ./patches relative to the current file."
+ (parameterize
+ ((%patch-path (list (string-append (dirname (current-filename)) "/patches"))))
+ (list (search-patch file-name) ...)))
+
+(define building-on (string-append "--build=" (list-ref (string-split (%current-system) #\-) 0) "-guix-linux-gnu"))
+
+(define (make-cross-toolchain target
+ base-gcc-for-libc
+ base-kernel-headers
+ base-libc
+ base-gcc)
+ "Create a cross-compilation toolchain package for TARGET"
+ (let* ((xbinutils (cross-binutils target))
+ ;; 1. Build a cross-compiling gcc without targeting any libc, derived
+ ;; from BASE-GCC-FOR-LIBC
+ (xgcc-sans-libc (cross-gcc target
+ #:xgcc base-gcc-for-libc
+ #:xbinutils xbinutils))
+ ;; 2. Build cross-compiled kernel headers with XGCC-SANS-LIBC, derived
+ ;; from BASE-KERNEL-HEADERS
+ (xkernel (cross-kernel-headers target
+ #:linux-headers base-kernel-headers
+ #:xgcc xgcc-sans-libc
+ #:xbinutils xbinutils))
+ ;; 3. Build a cross-compiled libc with XGCC-SANS-LIBC and XKERNEL,
+ ;; derived from BASE-LIBC
+ (xlibc (cross-libc target
+ #:libc base-libc
+ #:xgcc xgcc-sans-libc
+ #:xbinutils xbinutils
+ #:xheaders xkernel))
+ ;; 4. Build a cross-compiling gcc targeting XLIBC, derived from
+ ;; BASE-GCC
+ (xgcc (cross-gcc target
+ #:xgcc base-gcc
+ #:xbinutils xbinutils
+ #:libc xlibc)))
+ ;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and
+ ;; XGCC
+ (package
+ (name (string-append target "-toolchain"))
+ (version (package-version xgcc))
+ (source #f)
+ (build-system trivial-build-system)
+ (arguments '(#:builder (begin (mkdir %output) #t)))
+ (propagated-inputs
+ (list xbinutils
+ xlibc
+ xgcc
+ `(,xlibc "static")
+ `(,xgcc "lib")))
+ (synopsis (string-append "Complete GCC tool chain for " target))
+ (description (string-append "This package provides a complete GCC tool
+chain for " target " development."))
+ (home-page (package-home-page xgcc))
+ (license (package-license xgcc)))))
+
+(define base-gcc gcc-12) ;; 12.4.0
+
+(define base-linux-kernel-headers linux-libre-headers-6.1)
+
+(define* (make-bitcoin-cross-toolchain target
+ #:key
+ (base-gcc-for-libc linux-base-gcc)
+ (base-kernel-headers base-linux-kernel-headers)
+ (base-libc glibc-2.31)
+ (base-gcc linux-base-gcc))
+ "Convenience wrapper around MAKE-CROSS-TOOLCHAIN with default values
+desirable for building Bitcoin Core release binaries."
+ (make-cross-toolchain target
+ base-gcc-for-libc
+ base-kernel-headers
+ base-libc
+ base-gcc))
+
+(define (gcc-mingw-patches gcc)
+ (package-with-extra-patches gcc
+ (search-our-patches "gcc-remap-guix-store.patch")))
+
+(define (binutils-mingw-patches binutils)
+ (package-with-extra-patches binutils
+ (search-our-patches "binutils-unaligned-default.patch")))
+
+(define (winpthreads-patches mingw-w64-x86_64-winpthreads)
+ (package-with-extra-patches mingw-w64-x86_64-winpthreads
+ (search-our-patches "winpthreads-remap-guix-store.patch")))
+
+(define (make-mingw-pthreads-cross-toolchain target)
+ "Create a cross-compilation toolchain package for TARGET"
+ (let* ((xbinutils (binutils-mingw-patches (cross-binutils target)))
+ (machine (substring target 0 (string-index target #\-)))
+ (pthreads-xlibc (winpthreads-patches (make-mingw-w64 machine
+ #:xgcc (cross-gcc target #:xgcc (gcc-mingw-patches base-gcc))
+ #:with-winpthreads? #t)))
+ (pthreads-xgcc (cross-gcc target
+ #:xgcc (gcc-mingw-patches mingw-w64-base-gcc)
+ #:xbinutils xbinutils
+ #:libc pthreads-xlibc)))
+ ;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and
+ ;; XGCC
+ (package
+ (name (string-append target "-posix-toolchain"))
+ (version (package-version pthreads-xgcc))
+ (source #f)
+ (build-system trivial-build-system)
+ (arguments '(#:builder (begin (mkdir %output) #t)))
+ (propagated-inputs
+ (list xbinutils
+ pthreads-xlibc
+ pthreads-xgcc
+ `(,pthreads-xgcc "lib")))
+ (synopsis (string-append "Complete GCC tool chain for " target))
+ (description (string-append "This package provides a complete GCC tool
+chain for " target " development."))
+ (home-page (package-home-page pthreads-xgcc))
+ (license (package-license pthreads-xgcc)))))
+
+;; While LIEF is packaged in Guix, we maintain our own package,
+;; to simplify building, and more easily apply updates.
+;; Moreover, the Guix's package uses cmake, which caused build
+;; failure; see https://github.com/bitcoin/bitcoin/pull/27296.
+(define-public python-lief
+ (package
+ (name "python-lief")
+ (version "0.13.2")
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/lief-project/LIEF")
+ (commit version)))
+ (file-name (git-file-name name version))
+ (modules '((guix build utils)))
+ (snippet
+ '(begin
+ ;; Configure build for Python bindings.
+ (substitute* "api/python/config-default.toml"
+ (("(ninja = )true" all m)
+ (string-append m "false"))
+ (("(parallel-jobs = )0" all m)
+ (string-append m (number->string (parallel-job-count)))))))
+ (sha256
+ (base32
+ "0y48x358ppig5xp97ahcphfipx7cg9chldj2q5zrmn610fmi4zll"))))
+ (build-system python-build-system)
+ (native-inputs (list cmake-minimal python-tomli))
+ (arguments
+ (list
+ #:tests? #f ;needs network
+ #:phases #~(modify-phases %standard-phases
+ (add-before 'build 'change-directory
+ (lambda _
+ (chdir "api/python")))
+ (replace 'build
+ (lambda _
+ (invoke "python" "setup.py" "build"))))))
+ (home-page "https://github.com/lief-project/LIEF")
+ (synopsis "Library to instrument executable formats")
+ (description
+ "@code{python-lief} is a cross platform library which can parse, modify
+and abstract ELF, PE and MachO formats.")
+ (license license:asl2.0)))
+
+(define osslsigncode
+ (package
+ (name "osslsigncode")
+ (version "2.5")
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/mtrojnar/osslsigncode")
+ (commit version)))
+ (sha256
+ (base32
+ "1j47vwq4caxfv0xw68kw5yh00qcpbd56d7rq6c483ma3y7s96yyz"))))
+ (build-system cmake-build-system)
+ (inputs (list openssl))
+ (home-page "https://github.com/mtrojnar/osslsigncode")
+ (synopsis "Authenticode signing and timestamping tool")
+ (description "osslsigncode is a small tool that implements part of the
+functionality of the Microsoft tool signtool.exe - more exactly the Authenticode
+signing and timestamping. But osslsigncode is based on OpenSSL and cURL, and
+thus should be able to compile on most platforms where these exist.")
+ (license license:gpl3+))) ; license is with openssl exception
+
+(define-public python-elfesteem
+ (let ((commit "2eb1e5384ff7a220fd1afacd4a0170acff54fe56"))
+ (package
+ (name "python-elfesteem")
+ (version (git-version "0.1" "1" commit))
+ (source
+ (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/LRGH/elfesteem")
+ (commit commit)))
+ (file-name (git-file-name name commit))
+ (sha256
+ (base32
+ "07x6p8clh11z8s1n2kdxrqwqm2almgc5qpkcr9ckb6y5ivjdr5r6"))))
+ (build-system python-build-system)
+ ;; There are no tests, but attempting to run python setup.py test leads to
+ ;; PYTHONPATH problems, just disable the test
+ (arguments '(#:tests? #f))
+ (home-page "https://github.com/LRGH/elfesteem")
+ (synopsis "ELF/PE/Mach-O parsing library")
+ (description "elfesteem parses ELF, PE and Mach-O files.")
+ (license license:lgpl2.1))))
+
+(define-public python-oscrypto
+ (package
+ (name "python-oscrypto")
+ (version "1.3.0")
+ (source
+ (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/wbond/oscrypto")
+ (commit version)))
+ (file-name (git-file-name name version))
+ (sha256
+ (base32
+ "1v5wkmzcyiqy39db8j2dvkdrv2nlsc48556h73x4dzjwd6kg4q0a"))
+ (patches (search-our-patches "oscrypto-hard-code-openssl.patch"))))
+ (build-system python-build-system)
+ (native-search-paths
+ (list (search-path-specification
+ (variable "SSL_CERT_FILE")
+ (file-type 'regular)
+ (separator #f) ;single entry
+ (files '("etc/ssl/certs/ca-certificates.crt")))))
+
+ (propagated-inputs
+ (list python-asn1crypto openssl))
+ (arguments
+ `(#:phases
+ (modify-phases %standard-phases
+ (add-after 'unpack 'hard-code-path-to-libscrypt
+ (lambda* (#:key inputs #:allow-other-keys)
+ (let ((openssl (assoc-ref inputs "openssl")))
+ (substitute* "oscrypto/__init__.py"
+ (("@GUIX_OSCRYPTO_USE_OPENSSL@")
+ (string-append openssl "/lib/libcrypto.so" "," openssl "/lib/libssl.so")))
+ #t)))
+ (add-after 'unpack 'disable-broken-tests
+ (lambda _
+ ;; This test is broken as there is no keyboard interrupt.
+ (substitute* "tests/test_trust_list.py"
+ (("^(.*)class TrustListTests" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ (substitute* "tests/test_tls.py"
+ (("^(.*)class TLSTests" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ #t))
+ (replace 'check
+ (lambda _
+ (invoke "python" "run.py" "tests")
+ #t)))))
+ (home-page "https://github.com/wbond/oscrypto")
+ (synopsis "Compiler-free Python crypto library backed by the OS")
+ (description "oscrypto is a compilation-free, always up-to-date encryption library for Python.")
+ (license license:expat)))
+
+(define-public python-oscryptotests
+ (package (inherit python-oscrypto)
+ (name "python-oscryptotests")
+ (propagated-inputs
+ (list python-oscrypto))
+ (arguments
+ `(#:tests? #f
+ #:phases
+ (modify-phases %standard-phases
+ (add-after 'unpack 'hard-code-path-to-libscrypt
+ (lambda* (#:key inputs #:allow-other-keys)
+ (chdir "tests")
+ #t)))))))
+
+(define-public python-certvalidator
+ (let ((commit "a145bf25eb75a9f014b3e7678826132efbba6213"))
+ (package
+ (name "python-certvalidator")
+ (version (git-version "0.1" "1" commit))
+ (source
+ (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/achow101/certvalidator")
+ (commit commit)))
+ (file-name (git-file-name name commit))
+ (sha256
+ (base32
+ "1qw2k7xis53179lpqdqyylbcmp76lj7sagp883wmxg5i7chhc96k"))))
+ (build-system python-build-system)
+ (propagated-inputs
+ (list python-asn1crypto
+ python-oscrypto
+ python-oscryptotests)) ;; certvalidator tests import oscryptotests
+ (arguments
+ `(#:phases
+ (modify-phases %standard-phases
+ (add-after 'unpack 'disable-broken-tests
+ (lambda _
+ (substitute* "tests/test_certificate_validator.py"
+ (("^(.*)class CertificateValidatorTests" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ (substitute* "tests/test_crl_client.py"
+ (("^(.*)def test_fetch_crl" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ (substitute* "tests/test_ocsp_client.py"
+ (("^(.*)def test_fetch_ocsp" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ (substitute* "tests/test_registry.py"
+ (("^(.*)def test_build_paths" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ (substitute* "tests/test_validate.py"
+ (("^(.*)def test_revocation_mode_hard" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ (substitute* "tests/test_validate.py"
+ (("^(.*)def test_revocation_mode_soft" line indent)
+ (string-append indent
+ "@unittest.skip(\"Disabled by Guix\")\n"
+ line)))
+ #t))
+ (replace 'check
+ (lambda _
+ (invoke "python" "run.py" "tests")
+ #t)))))
+ (home-page "https://github.com/wbond/certvalidator")
+ (synopsis "Python library for validating X.509 certificates and paths")
+ (description "certvalidator is a Python library for validating X.509
+certificates or paths. Supports various options, including: validation at a
+specific moment in time, whitelisting and revocation checks.")
+ (license license:expat))))
+
+(define-public python-signapple
+ (let ((commit "62155712e7417aba07565c9780a80e452823ae6a"))
+ (package
+ (name "python-signapple")
+ (version (git-version "0.1" "1" commit))
+ (source
+ (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/achow101/signapple")
+ (commit commit)))
+ (file-name (git-file-name name commit))
+ (sha256
+ (base32
+ "1nm6rm4h4m7kbq729si4cm8rzild62mk4ni8xr5zja7l33fhv3gb"))))
+ (build-system python-build-system)
+ (propagated-inputs
+ (list python-asn1crypto
+ python-oscrypto
+ python-certvalidator
+ python-elfesteem))
+ ;; There are no tests, but attempting to run python setup.py test leads to
+ ;; problems, just disable the test
+ (arguments '(#:tests? #f))
+ (home-page "https://github.com/achow101/signapple")
+ (synopsis "Mach-O binary signature tool")
+ (description "signapple is a Python tool for creating, verifying, and
+inspecting signatures in Mach-O binaries.")
+ (license license:expat))))
+
+(define-public mingw-w64-base-gcc
+ (package
+ (inherit base-gcc)
+ (arguments
+ (substitute-keyword-arguments (package-arguments base-gcc)
+ ((#:configure-flags flags)
+ `(append ,flags
+ ;; https://gcc.gnu.org/install/configure.html
+ (list "--enable-threads=posix",
+ "--enable-default-ssp=yes",
+ "--disable-gcov",
+ building-on)))))))
+
+(define-public linux-base-gcc
+ (package
+ (inherit base-gcc)
+ (arguments
+ (substitute-keyword-arguments (package-arguments base-gcc)
+ ((#:configure-flags flags)
+ `(append ,flags
+ ;; https://gcc.gnu.org/install/configure.html
+ (list "--enable-initfini-array=yes",
+ "--enable-default-ssp=yes",
+ "--enable-default-pie=yes",
+ "--enable-standard-branch-protection=yes",
+ "--enable-cet=yes",
+ "--disable-gcov",
+ building-on)))
+ ((#:phases phases)
+ `(modify-phases ,phases
+ ;; Given a XGCC package, return a modified package that replace each instance of
+ ;; -rpath in the default system spec that's inserted by Guix with -rpath-link
+ (add-after 'pre-configure 'replace-rpath-with-rpath-link
+ (lambda _
+ (substitute* (cons "gcc/config/rs6000/sysv4.h"
+ (find-files "gcc/config"
+ "^gnu-user.*\\.h$"))
+ (("-rpath=") "-rpath-link="))
+ #t))))))))
+
+(define-public glibc-2.31
+ (let ((commit "7b27c450c34563a28e634cccb399cd415e71ebfe"))
+ (package
+ (inherit glibc) ;; 2.39
+ (version "2.31")
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://sourceware.org/git/glibc.git")
+ (commit commit)))
+ (file-name (git-file-name "glibc" commit))
+ (sha256
+ (base32
+ "017qdpr5id7ddb4lpkzj2li1abvw916m3fc6n7nw28z4h5qbv2n0"))
+ (patches (search-our-patches "glibc-guix-prefix.patch"))))
+ (arguments
+ (substitute-keyword-arguments (package-arguments glibc)
+ ((#:configure-flags flags)
+ `(append ,flags
+ ;; https://www.gnu.org/software/libc/manual/html_node/Configuring-and-compiling.html
+ (list "--enable-stack-protector=all",
+ "--enable-cet",
+ "--enable-bind-now",
+ "--disable-werror",
+ "--disable-timezone-tools",
+ "--disable-profile",
+ building-on)))
+ ((#:phases phases)
+ `(modify-phases ,phases
+ (add-before 'configure 'set-etc-rpc-installation-directory
+ (lambda* (#:key outputs #:allow-other-keys)
+ ;; Install the rpc data base file under `$out/etc/rpc'.
+ ;; Otherwise build will fail with "Permission denied."
+ ;; Can be removed when we are building 2.32 or later.
+ (let ((out (assoc-ref outputs "out")))
+ (substitute* "sunrpc/Makefile"
+ (("^\\$\\(inst_sysconfdir\\)/rpc(.*)$" _ suffix)
+ (string-append out "/etc/rpc" suffix "\n"))
+ (("^install-others =.*$")
+ (string-append "install-others = " out "/etc/rpc\n")))))))))))))
+
+;; The sponge tool from moreutils.
+(define-public sponge
+ (package
+ (name "sponge")
+ (version "0.69")
+ (source (origin
+ (method url-fetch)
+ (uri (string-append
+ "https://git.joeyh.name/index.cgi/moreutils.git/snapshot/
+ moreutils-" version ".tar.gz"))
+ (file-name (string-append "moreutils-" version ".tar.gz"))
+ (sha256
+ (base32
+ "1l859qnzccslvxlh5ghn863bkq2vgmqgnik6jr21b9kc6ljmsy8g"))))
+ (build-system gnu-build-system)
+ (arguments
+ (list #:phases
+ #~(modify-phases %standard-phases
+ (delete 'configure)
+ (replace 'install
+ (lambda* (#:key outputs #:allow-other-keys)
+ (let ((bin (string-append (assoc-ref outputs "out") "/bin")))
+ (install-file "sponge" bin)))))
+ #:make-flags
+ #~(list "sponge" (string-append "CC=" #$(cc-for-target)))))
+ (home-page "https://joeyh.name/code/moreutils/")
+ (synopsis "Miscellaneous general-purpose command-line tools")
+ (description "Just sponge")
+ (license license:gpl2+)))
+
+(define-public glibc-2.40
+ (let ((commit "e899ca3651f8c5e01bf3420cfb34aad97d093f74"))
+ (package
+ (inherit glibc) ;; 2.35
+ (version "2.40")
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://sourceware.org/git/glibc.git")
+ (commit commit)))
+ (file-name (git-file-name "glibc" commit))
+ (sha256
+ (base32
+ "0r34cgz171x10nhkiw7llfqnhz5jihhh7d6gay6bjz7208yjjvx7"))
+ (patches (search-our-patches "glibc-2.40-guix-prefix.patch"))))
+ (arguments
+ (substitute-keyword-arguments (package-arguments glibc)
+ ((#:configure-flags flags)
+ `(append ,flags
+ ;; https://www.gnu.org/software/libc/manual/html_node/Configuring-and-compiling.html
+ (list "--enable-stack-protector=all",
+ "--enable-bind-now",
+ "--disable-werror",
+ "--enable-fortify-source",
+ "--enable-cet=yes",
+ "--enable-nscd=no",
+ "--enable-static-nss=yes",
+ "--enable-static-pie=yes",
+ "--disable-timezone-tools",
+ "--disable-profile",
+ building-on))))))))
+
+(packages->manifest
+ (append
+ (list ;; The Basics
+ bash-minimal
+ which
+ coreutils-minimal
+ ;; File(system) inspection
+ file
+ grep
+ diffutils
+ findutils
+ ;; File transformation
+ patch
+ gawk
+ sed
+ sponge
+ ;; Compression and archiving
+ tar
+ gzip
+ xz
+ ;; Build tools
+ gcc-toolchain-12
+ cmake-minimal
+ gnu-make
+ ;; Scripting
+ python-minimal ;; (3.10)
+ ;; Git
+ git-minimal
+ ;; Tests
+ python-lief)
+ (let ((target (getenv "HOST")))
+ (cond ((string-suffix? "-mingw32" target)
+ (list zip
+ (make-mingw-pthreads-cross-toolchain "x86_64-w64-mingw32")
+ nsis-x86_64
+ nss-certs
+ osslsigncode))
+ ((string-contains target "x86_64-linux-")
+ (list bison
+ pkg-config
+ (list gcc-toolchain-12 "static")
+ (make-bitcoin-cross-toolchain target
+ #:base-libc glibc-2.40)))
+ ((string-contains target "-linux-")
+ (list bison
+ pkg-config
+ (list gcc-toolchain-12 "static")
+ (make-bitcoin-cross-toolchain target)))
+ ((string-contains target "darwin")
+ (list clang-toolchain-18
+ lld-18
+ (make-lld-wrapper lld-18 #:lld-as-ld? #t)
+ python-signapple
+ zip))
+ (else '())))))
diff --git a/bench-ci/guix/patches/binutils-unaligned-default.patch b/bench-ci/guix/patches/binutils-unaligned-default.patch
new file mode 100644
index 0000000000..d1bc71aee1
--- /dev/null
+++ b/bench-ci/guix/patches/binutils-unaligned-default.patch
@@ -0,0 +1,22 @@
+commit 6537181f59ed186a341db621812a6bc35e22eaf6
+Author: fanquake
+Date: Wed Apr 10 12:15:52 2024 +0200
+
+ build: turn on -muse-unaligned-vector-move by default
+
+ This allows us to avoid (more invasively) patching GCC, to avoid
+ unaligned instruction use.
+
+diff --git a/gas/config/tc-i386.c b/gas/config/tc-i386.c
+index e0632681477..14a9653abdf 100644
+--- a/gas/config/tc-i386.c
++++ b/gas/config/tc-i386.c
+@@ -801,7 +801,7 @@ static unsigned int no_cond_jump_promotion = 0;
+ static unsigned int sse2avx;
+
+ /* Encode aligned vector move as unaligned vector move. */
+-static unsigned int use_unaligned_vector_move;
++static unsigned int use_unaligned_vector_move = 1;
+
+ /* Encode scalar AVX instructions with specific vector length. */
+ static enum
diff --git a/bench-ci/guix/patches/gcc-remap-guix-store.patch b/bench-ci/guix/patches/gcc-remap-guix-store.patch
new file mode 100644
index 0000000000..a8b41d485b
--- /dev/null
+++ b/bench-ci/guix/patches/gcc-remap-guix-store.patch
@@ -0,0 +1,20 @@
+Without ffile-prefix-map, the debug symbols will contain paths for the
+guix store which will include the hashes of each package. However, the
+hash for the same package will differ when on different architectures.
+In order to be reproducible regardless of the architecture used to build
+the package, map all guix store prefixes to something fixed, e.g. /usr.
+
+--- a/libgcc/Makefile.in
++++ b/libgcc/Makefile.in
+@@ -854,7 +854,7 @@ endif
+ # libgcc_eh.a, only LIB2ADDEH matters. If we do, only LIB2ADDEHSTATIC and
+ # LIB2ADDEHSHARED matter. (Usually all three are identical.)
+
+-c_flags := -fexceptions
++c_flags := -fexceptions $(shell find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;)
+
+ ifeq ($(enable_shared),yes)
+
+--
+2.37.0
+
diff --git a/bench-ci/guix/patches/glibc-2.40-guix-prefix.patch b/bench-ci/guix/patches/glibc-2.40-guix-prefix.patch
new file mode 100644
index 0000000000..d1783bcf45
--- /dev/null
+++ b/bench-ci/guix/patches/glibc-2.40-guix-prefix.patch
@@ -0,0 +1,47 @@
+Without ffile-prefix-map, the debug symbols will contain paths for the
+guix store which will include the hashes of each package. However, the
+hash for the same package will differ when on different architectures.
+In order to be reproducible regardless of the architecture used to build
+the package, map all guix store prefixes to something fixed, e.g. /usr.
+
+--- a/Makeconfig
++++ b/Makeconfig
+@@ -1074,6 +1074,10 @@ CPPFLAGS-.o = $(pic-default)
+ CFLAGS-.o = $(filter %frame-pointer,$(+cflags)) $(pie-default)
+ CFLAGS-.o += $(call elide-fortify-source,.o,$(routines_no_fortify))
+ CFLAGS-.o += $(call elide-fortify-source,_chk.o,$(routines_no_fortify))
++
++# Map Guix store paths to /usr
++CFLAGS-.o += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;`
++
+ libtype.o := lib%.a
+ object-suffixes += .o
+ ifeq (yes,$(build-shared))
+diff --git a/iconv/Makefile b/iconv/Makefile
+index afb3fb7bdb..5acee345e0 100644
+--- a/iconv/Makefile
++++ b/iconv/Makefile
+@@ -65,6 +65,9 @@ CFLAGS-gconv_cache.c += -DGCONV_DIR='"$(gconvdir)"'
+ CFLAGS-gconv_conf.c += -DGCONV_PATH='"$(gconvdir)"'
+ CFLAGS-iconvconfig.c += -DGCONV_PATH='"$(gconvdir)"' -DGCONV_DIR='"$(gconvdir)"'
+
++# Map Guix store paths to /usr
++CFLAGS-.c += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;`
++
+ # Set libof-* for each routine.
+ cpp-srcs-left := $(iconv_prog-modules) $(iconvconfig-modules)
+ lib := iconvprogs
+diff --git a/posix/Makefile b/posix/Makefile
+index 3d368b91f6..d79d8fb648 100644
+--- a/posix/Makefile
++++ b/posix/Makefile
+@@ -590,6 +590,9 @@ CFLAGS-execlp.os = -fomit-frame-pointer
+ CFLAGS-nanosleep.c += -fexceptions -fasynchronous-unwind-tables
+ CFLAGS-fork.c = $(libio-mtsafe) $(config-cflags-wno-ignored-attributes)
+
++# Map Guix store paths to /usr
++CFLAGS-.c += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;`
++
+ tstgetopt-ARGS = -a -b -cfoobar --required foobar --optional=bazbug \
+ --none random --col --color --colour
+
diff --git a/bench-ci/guix/patches/glibc-guix-prefix.patch b/bench-ci/guix/patches/glibc-guix-prefix.patch
new file mode 100644
index 0000000000..60e12ca525
--- /dev/null
+++ b/bench-ci/guix/patches/glibc-guix-prefix.patch
@@ -0,0 +1,16 @@
+Without ffile-prefix-map, the debug symbols will contain paths for the
+guix store which will include the hashes of each package. However, the
+hash for the same package will differ when on different architectures.
+In order to be reproducible regardless of the architecture used to build
+the package, map all guix store prefixes to something fixed, e.g. /usr.
+
+--- a/Makeconfig
++++ b/Makeconfig
+@@ -1007,6 +1007,7 @@ object-suffixes :=
+ CPPFLAGS-.o = $(pic-default)
+ # libc.a must be compiled with -fPIE/-fpie for static PIE.
+ CFLAGS-.o = $(filter %frame-pointer,$(+cflags)) $(pie-default)
++CFLAGS-.o += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;`
+ libtype.o := lib%.a
+ object-suffixes += .o
+ ifeq (yes,$(build-shared))
diff --git a/bench-ci/guix/patches/oscrypto-hard-code-openssl.patch b/bench-ci/guix/patches/oscrypto-hard-code-openssl.patch
new file mode 100644
index 0000000000..32027f2d09
--- /dev/null
+++ b/bench-ci/guix/patches/oscrypto-hard-code-openssl.patch
@@ -0,0 +1,13 @@
+diff --git a/oscrypto/__init__.py b/oscrypto/__init__.py
+index eb27313..371ab24 100644
+--- a/oscrypto/__init__.py
++++ b/oscrypto/__init__.py
+@@ -302,3 +302,8 @@ def load_order():
+ 'oscrypto._win.tls',
+ 'oscrypto.tls',
+ ]
++
++
++paths = '@GUIX_OSCRYPTO_USE_OPENSSL@'.split(',')
++assert len(paths) == 2, 'Value for OSCRYPTO_USE_OPENSSL env var must be two paths separated by a comma'
++use_openssl(*paths)
diff --git a/bench-ci/guix/patches/winpthreads-remap-guix-store.patch b/bench-ci/guix/patches/winpthreads-remap-guix-store.patch
new file mode 100644
index 0000000000..e1f1a6eba5
--- /dev/null
+++ b/bench-ci/guix/patches/winpthreads-remap-guix-store.patch
@@ -0,0 +1,17 @@
+Without ffile-prefix-map, the debug symbols will contain paths for the
+guix store which will include the hashes of each package. However, the
+hash for the same package will differ when on different architectures.
+In order to be reproducible regardless of the architecture used to build
+the package, map all guix store prefixes to something fixed, e.g. /usr.
+
+--- a/mingw-w64-libraries/winpthreads/Makefile.in
++++ b/mingw-w64-libraries/winpthreads/Makefile.in
+@@ -478,7 +478,7 @@ top_build_prefix = @top_build_prefix@
+ top_builddir = @top_builddir@
+ top_srcdir = @top_srcdir@
+ SUBDIRS = . tests
+-AM_CFLAGS = -Wall -DWIN32_LEAN_AND_MEAN $(am__append_1)
++AM_CFLAGS = -Wall -DWIN32_LEAN_AND_MEAN $(am__append_1) $(shell find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;)
+ ACLOCAL_AMFLAGS = -I m4
+ lib_LTLIBRARIES = libwinpthread.la
+ include_HEADERS = include/pthread.h include/sched.h include/semaphore.h include/pthread_unistd.h include/pthread_time.h include/pthread_compat.h include/pthread_signal.h
diff --git a/bench-ci/parse_and_plot.py b/bench-ci/parse_and_plot.py
new file mode 100755
index 0000000000..db577417b2
--- /dev/null
+++ b/bench-ci/parse_and_plot.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python3
+import sys
+import os
+import re
+import datetime
+import matplotlib.pyplot as plt
+
+
+def parse_updatetip_line(line):
+ match = re.match(
+ r'^([\d\-:TZ]+) UpdateTip: new best.+height=(\d+).+tx=(\d+).+cache=([\d.]+)MiB\((\d+)txo\)',
+ line
+ )
+ if not match:
+ return None
+ iso_str, height_str, tx_str, cache_size_mb_str, cache_coins_count_str = match.groups()
+ parsed_datetime = datetime.datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
+ return parsed_datetime, int(height_str), int(tx_str), float(cache_size_mb_str), int(cache_coins_count_str)
+
+
+def parse_leveldb_compact_line(line):
+ match = re.match(r'^([\d\-:TZ]+) \[leveldb] Compacting.*files', line)
+ if not match:
+ return None
+ iso_str = match.groups()[0]
+ parsed_datetime = datetime.datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
+ return parsed_datetime
+
+
+def parse_leveldb_generated_table_line(line):
+ match = re.match(r'^([\d\-:TZ]+) \[leveldb] Generated table.*: (\d+) keys, (\d+) bytes', line)
+ if not match:
+ return None
+ iso_str, keys_count_str, bytes_count_str = match.groups()
+ parsed_datetime = datetime.datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
+ return parsed_datetime, int(keys_count_str), int(bytes_count_str)
+
+def parse_validation_txadd_line(line):
+ match = re.match(r'^([\d\-:TZ]+) \[validation] TransactionAddedToMempool: txid=.+wtxid=.+', line)
+ if not match:
+ return None
+ iso_str = match.groups()[0]
+ parsed_datetime = datetime.datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
+ return parsed_datetime
+
+
+def parse_coindb_write_batch_line(line):
+ match = re.match(r'^([\d\-:TZ]+) \[coindb] Writing (partial|final) batch of ([\d.]+) MiB', line)
+ if not match:
+ return None
+ iso_str, is_partial_str, size_mb_str = match.groups()
+ parsed_datetime = datetime.datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
+ return parsed_datetime, is_partial_str, float(size_mb_str)
+
+
+def parse_coindb_commit_line(line):
+ match = re.match(r'^([\d\-:TZ]+) \[coindb] Committed (\d+) changed transaction outputs', line)
+ if not match:
+ return None
+ iso_str, txout_count_str = match.groups()
+ parsed_datetime = datetime.datetime.strptime(iso_str, "%Y-%m-%dT%H:%M:%SZ")
+ return parsed_datetime, int(txout_count_str)
+
+def parse_log_file(log_file):
+ with open(log_file, 'r', encoding='utf-8') as f:
+ update_tip_data = []
+ leveldb_compact_data = []
+ leveldb_gen_table_data = []
+ validation_txadd_data = []
+ coindb_write_batch_data = []
+ coindb_commit_data = []
+
+ for line in f:
+ if result := parse_updatetip_line(line):
+ update_tip_data.append(result)
+ elif result := parse_leveldb_compact_line(line):
+ leveldb_compact_data.append(result)
+ elif result := parse_leveldb_generated_table_line(line):
+ leveldb_gen_table_data.append(result)
+ elif result := parse_validation_txadd_line(line):
+ validation_txadd_data.append(result)
+ elif result := parse_coindb_write_batch_line(line):
+ coindb_write_batch_data.append(result)
+ elif result := parse_coindb_commit_line(line):
+ coindb_commit_data.append(result)
+
+ if not update_tip_data:
+ print("No UpdateTip entries found.")
+ sys.exit(0)
+
+ assert all(update_tip_data[i][0] <= update_tip_data[i + 1][0] for i in
+ range(len(update_tip_data) - 1)), "UpdateTip entries are not sorted by time"
+
+ return update_tip_data, leveldb_compact_data, leveldb_gen_table_data, validation_txadd_data, coindb_write_batch_data, coindb_commit_data
+
+
+def generate_plot(x, y, x_label, y_label, title, output_file):
+ if not x or not y:
+ print(f"Skipping plot '{title}' as there is no data.")
+ return
+
+ plt.figure(figsize=(30, 10))
+ plt.plot(x, y)
+ plt.title(title, fontsize=20)
+ plt.xlabel(x_label, fontsize=16)
+ plt.ylabel(y_label, fontsize=16)
+ plt.grid(True)
+ plt.xticks(rotation=90, fontsize=12)
+ plt.yticks(fontsize=12)
+ plt.tight_layout()
+ plt.savefig(output_file)
+ plt.close()
+ print(f"Saved plot to {output_file}")
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print(f"Usage: {sys.argv[0]} ")
+ sys.exit(1)
+
+ log_file = sys.argv[1]
+ if not os.path.isfile(log_file):
+ print(f"File not found: {log_file}")
+ sys.exit(1)
+
+ png_dir = sys.argv[2]
+ os.makedirs(png_dir, exist_ok=True)
+
+ update_tip_data, leveldb_compact_data, leveldb_gen_table_data, validation_txadd_data, coindb_write_batch_data, coindb_commit_data = parse_log_file(log_file)
+ times, heights, tx_counts, cache_size, cache_count = zip(*update_tip_data)
+ float_minutes = [(t - times[0]).total_seconds() / 60 for t in times]
+
+ generate_plot(float_minutes, heights, "Elapsed minutes", "Block Height", "Block Height vs Time", os.path.join(png_dir, "height_vs_time.png"))
+ generate_plot(heights, cache_size, "Block Height", "Cache Size (MiB)", "Cache Size vs Block Height", os.path.join(png_dir, "cache_vs_height.png"))
+ generate_plot(float_minutes, cache_size, "Elapsed minutes", "Cache Size (MiB)", "Cache Size vs Time", os.path.join(png_dir, "cache_vs_time.png"))
+ generate_plot(heights, tx_counts, "Block Height", "Transaction Count", "Transactions vs Block Height", os.path.join(png_dir, "tx_vs_height.png"))
+ generate_plot(times, cache_count, "Block Height", "Coins Cache Size", "Coins Cache Size vs Time", os.path.join(png_dir, "coins_cache_vs_time.png"))
+
+ # LevelDB Compaction and Generated Tables
+ if leveldb_compact_data:
+ leveldb_compact_times = [(t - times[0]).total_seconds() / 60 for t in leveldb_compact_data]
+ leveldb_compact_y = [1 for _ in leveldb_compact_times] # dummy y axis to mark compactions
+ generate_plot(leveldb_compact_times, leveldb_compact_y, "Elapsed minutes", "LevelDB Compaction", "LevelDB Compaction Events vs Time", os.path.join(png_dir, "leveldb_compact_vs_time.png"))
+ if leveldb_gen_table_data:
+ leveldb_gen_table_times, leveldb_gen_table_keys, leveldb_gen_table_bytes = zip(*leveldb_gen_table_data)
+ leveldb_gen_table_float_minutes = [(t - times[0]).total_seconds() / 60 for t in leveldb_gen_table_times]
+ generate_plot(leveldb_gen_table_float_minutes, leveldb_gen_table_keys, "Elapsed minutes", "Number of keys", "LevelDB Keys Generated vs Time", os.path.join(png_dir, "leveldb_gen_keys_vs_time.png"))
+ generate_plot(leveldb_gen_table_float_minutes, leveldb_gen_table_bytes, "Elapsed minutes", "Number of bytes", "LevelDB Bytes Generated vs Time", os.path.join(png_dir, "leveldb_gen_bytes_vs_time.png"))
+
+ # validation mempool add transaction lines
+ if validation_txadd_data:
+ validation_txadd_times = [(t - times[0]).total_seconds() / 60 for t in validation_txadd_data]
+ validation_txadd_y = [1 for _ in validation_txadd_times] # dummy y axis to mark transaction additions
+ generate_plot(validation_txadd_times, validation_txadd_y, "Elapsed minutes", "Transaction Additions", "Transaction Additions to Mempool vs Time", os.path.join(png_dir, "validation_txadd_vs_time.png"))
+
+ # coindb write batch lines
+ if coindb_write_batch_data:
+ coindb_write_batch_times, is_partial_strs, sizes_mb = zip(*coindb_write_batch_data)
+ coindb_write_batch_float_minutes = [(t - times[0]).total_seconds() / 60 for t in coindb_write_batch_times]
+ generate_plot(coindb_write_batch_float_minutes, sizes_mb, "Elapsed minutes", "Batch Size MiB", "Coin Database Partial/Final Write Batch Size vs Time", os.path.join(png_dir, "coindb_write_batch_size_vs_time.png"))
+ if coindb_commit_data:
+ coindb_commit_times, txout_counts = zip(*coindb_commit_data)
+ coindb_commit_float_minutes = [(t - times[0]).total_seconds() / 60 for t in coindb_commit_times]
+ generate_plot(coindb_commit_float_minutes, txout_counts, "Elapsed minutes", "Transaction Output Count", "Coin Database Transaction Output Committed vs Time", os.path.join(png_dir, "coindb_commit_txout_vs_time.png"))
+
+
+ print("Plots saved!")
\ No newline at end of file
diff --git a/bench-ci/run-assumeutxo-bench.sh b/bench-ci/run-assumeutxo-bench.sh
new file mode 100755
index 0000000000..a1ee910ed4
--- /dev/null
+++ b/bench-ci/run-assumeutxo-bench.sh
@@ -0,0 +1,154 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+# Helper function to check and clean datadir
+clean_datadir() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+
+ # Create the directory if it doesn't exist
+ mkdir -p "${TMP_DATADIR}"
+
+ # If we're in CI, clean without confirmation
+ if [ -n "${CI:-}" ]; then
+ rm -Rf "${TMP_DATADIR:?}"/*
+ else
+ read -rp "Are you sure you want to delete everything in ${TMP_DATADIR}? [y/N] " response
+ if [[ "$response" =~ ^[Yy]$ ]]; then
+ rm -Rf "${TMP_DATADIR:?}"/*
+ else
+ echo "Aborting..."
+ exit 1
+ fi
+ fi
+}
+
+# Helper function to clear logs
+clean_logs() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+ local logfile="${TMP_DATADIR}/debug.log"
+
+ echo "Checking for ${logfile}"
+ if [ -e "${logfile}" ]; then
+ echo "Removing ${logfile}"
+ rm "${logfile}"
+ fi
+}
+
+# Execute CMD before each set of timing runs.
+setup_assumeutxo_snapshot_run() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+ local commit="$2"
+ clean_datadir "${TMP_DATADIR}"
+}
+
+# Execute CMD before each timing run.
+prepare_assumeutxo_snapshot_run() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+ local UTXO_PATH="$2"
+ local CONNECT_ADDRESS="$3"
+ local CHAIN="$4"
+ local DBCACHE="$5"
+ local commit="$6"
+ local BINARIES_DIR="$7"
+
+ # Run the actual preparation steps
+ clean_datadir "${TMP_DATADIR}"
+ # Use the pre-built binaries from BINARIES_DIR
+ "${BINARIES_DIR}/${commit}/bitcoind" --help
+ taskset -c 0-15 "${BINARIES_DIR}/${commit}/bitcoind" -datadir="${TMP_DATADIR}" -connect="${CONNECT_ADDRESS}" -daemon=0 -chain="${CHAIN}" -stopatheight=1 -printtoconsole=0
+ taskset -c 0-15 "${BINARIES_DIR}/${commit}/bitcoind" -datadir="${TMP_DATADIR}" -connect="${CONNECT_ADDRESS}" -daemon=0 -chain="${CHAIN}" -dbcache="${DBCACHE}" -pausebackgroundsync=1 -loadutxosnapshot="${UTXO_PATH}" -printtoconsole=0 || true
+ clean_logs "${TMP_DATADIR}"
+}
+
+# Executed after each timing run
+conclude_assumeutxo_snapshot_run() {
+ set -euxo pipefail
+
+ local commit="$1"
+ local TMP_DATADIR="$2"
+ local PNG_DIR="$3"
+
+ # Search in subdirs e.g. $datadir/signet
+ debug_log=$(find "${TMP_DATADIR}" -name debug.log -print -quit)
+ if [ -n "${debug_log}" ]; then
+ echo "Generating plots from ${debug_log}"
+ if [ -x "bench-ci/parse_and_plot.py" ]; then
+ bench-ci/parse_and_plot.py "${debug_log}" "${PNG_DIR}"
+ else
+ ls -al "bench-ci/"
+ echo "parse_and_plot.py not found or not executable, skipping plot generation"
+ fi
+ else
+ ls -al "${TMP_DATADIR}/"
+ echo "debug.log not found, skipping plot generation"
+ fi
+
+ # Move flamegraph if exists
+ if [ -e flamegraph.svg ]; then
+ mv flamegraph.svg "${commit}"-flamegraph.svg
+ fi
+}
+
+# Execute CMD after the completion of all benchmarking runs for each individual
+# command to be benchmarked.
+cleanup_assumeutxo_snapshot_run() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+
+ # Clean up the datadir
+ clean_datadir "${TMP_DATADIR}"
+}
+
+run_benchmark() {
+ local base_commit="$1"
+ local head_commit="$2"
+ local TMP_DATADIR="$3"
+ local UTXO_PATH="$4"
+ local results_file="$5"
+ local png_dir="$6"
+ local chain="$7"
+ local stop_at_height="$8"
+ local connect_address="$9"
+ local dbcache="${10}"
+ local BINARIES_DIR="${11}"
+
+ # Export functions so they can be used by hyperfine
+ export -f setup_assumeutxo_snapshot_run
+ export -f prepare_assumeutxo_snapshot_run
+ export -f conclude_assumeutxo_snapshot_run
+ export -f cleanup_assumeutxo_snapshot_run
+ export -f clean_datadir
+ export -f clean_logs
+
+ # Run hyperfine
+ hyperfine \
+ --shell=bash \
+ --setup "setup_assumeutxo_snapshot_run ${TMP_DATADIR} {commit}" \
+ --prepare "prepare_assumeutxo_snapshot_run ${TMP_DATADIR} ${UTXO_PATH} ${connect_address} ${chain} ${dbcache} {commit} ${BINARIES_DIR}" \
+ --conclude "conclude_assumeutxo_snapshot_run {commit} ${TMP_DATADIR} ${png_dir}" \
+ --cleanup "cleanup_assumeutxo_snapshot_run ${TMP_DATADIR}" \
+ --runs 1 \
+ --export-json "${results_file}" \
+ --command-name "base (${base_commit})" \
+ --command-name "head (${head_commit})" \
+ "taskset -c 1 flamegraph --palette bitcoin --title 'bitcoind assumeutxo IBD@{commit}' -c 'record -F 101 --call-graph fp' -- taskset -c 2-15 ${BINARIES_DIR}/{commit}/bitcoind -datadir=${TMP_DATADIR} -connect=${connect_address} -daemon=0 -chain=${chain} -stopatheight=${stop_at_height} -dbcache=${dbcache} -printtoconsole=0 -debug=coindb -debug=leveldb -debug=bench -debug=validation" \
+ -L commit "base,head"
+}
+
+# Main execution
+if [ "$#" -ne 11 ]; then
+ echo "Usage: $0 base_commit head_commit TMP_DATADIR UTXO_PATH results_dir png_dir chain stop_at_height connect_address dbcache BINARIES_DIR"
+ exit 1
+fi
+
+run_benchmark "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}"
diff --git a/bench-ci/run-benchmark.sh b/bench-ci/run-benchmark.sh
new file mode 100755
index 0000000000..759cf8ae27
--- /dev/null
+++ b/bench-ci/run-benchmark.sh
@@ -0,0 +1,163 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+# Helper function to check and clean datadir
+clean_datadir() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+
+ # Create the directory if it doesn't exist
+ mkdir -p "${TMP_DATADIR}"
+
+ # If we're in CI, clean without confirmation
+ if [ -n "${CI:-}" ]; then
+ rm -Rf "${TMP_DATADIR:?}"/*
+ else
+ read -rp "Are you sure you want to delete everything in ${TMP_DATADIR}? [y/N] " response
+ if [[ "$response" =~ ^[Yy]$ ]]; then
+ rm -Rf "${TMP_DATADIR:?}"/*
+ else
+ echo "Aborting..."
+ exit 1
+ fi
+ fi
+}
+
+# Helper function to clear logs
+clean_logs() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+ local logfile="${TMP_DATADIR}/debug.log"
+
+ echo "Checking for ${logfile}"
+ if [ -e "${logfile}" ]; then
+ echo "Removing ${logfile}"
+ rm "${logfile}"
+ fi
+}
+
+# Execute CMD before each set of timing runs.
+setup_run() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+ local commit="$2"
+ clean_datadir "${TMP_DATADIR}"
+}
+
+# Execute CMD before each timing run.
+prepare_run() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+ local ORIGINAL_DATADIR="$2"
+
+ # Run the actual preparation steps
+ clean_datadir "${TMP_DATADIR}"
+ # Don't copy hidden files so use *
+ taskset -c 0-15 cp -r "$ORIGINAL_DATADIR"/* "$TMP_DATADIR"
+ clean_logs "${TMP_DATADIR}"
+}
+
+# Executed after each timing run
+conclude_run() {
+ set -euxo pipefail
+
+ local commit="$1"
+ local TMP_DATADIR="$2"
+ local PNG_DIR="$3"
+
+ # Search in subdirs e.g. $datadir/signet
+ debug_log=$(find "${TMP_DATADIR}" -name debug.log -print -quit)
+ if [ -n "${debug_log}" ]; then
+ echo "Generating plots from ${debug_log}"
+ if [ -x "bench-ci/parse_and_plot.py" ]; then
+ bench-ci/parse_and_plot.py "${debug_log}" "${PNG_DIR}"
+ else
+ ls -al "bench-ci/"
+ echo "parse_and_plot.py not found or not executable, skipping plot generation"
+ fi
+ else
+ ls -al "${TMP_DATADIR}/"
+ echo "debug.log not found, skipping plot generation"
+ fi
+
+ # Move flamegraph if exists
+ if [ -e flamegraph.svg ]; then
+ mv flamegraph.svg "${commit}"-flamegraph.svg
+ fi
+}
+
+# Execute CMD after the completion of all benchmarking runs for each individual
+# command to be benchmarked.
+cleanup_run() {
+ set -euxo pipefail
+
+ local TMP_DATADIR="$1"
+
+ # Clean up the datadir
+ clean_datadir "${TMP_DATADIR}"
+}
+
+run_benchmark() {
+ local base_commit="$1"
+ local head_commit="$2"
+ local TMP_DATADIR="$3"
+ local ORIGINAL_DATADIR="$4"
+ local results_file="$5"
+ local png_dir="$6"
+ local chain="$7"
+ local stop_at_height="$8"
+ local connect_address="$9"
+ local dbcache="${10}"
+ local BINARIES_DIR="${11}"
+
+ # Export functions so they can be used by hyperfine
+ export -f setup_run
+ export -f prepare_run
+ export -f conclude_run
+ export -f cleanup_run
+ export -f clean_datadir
+ export -f clean_logs
+
+ # Debug: Print all variables being used
+ echo "=== Debug Information ==="
+ echo "TMP_DATADIR: ${TMP_DATADIR}"
+ echo "ORIGINAL_DATADIR: ${ORIGINAL_DATADIR}"
+ echo "BINARIES_DIR: ${BINARIES_DIR}"
+ echo "base_commit: ${base_commit}"
+ echo "head_commit: ${head_commit}"
+ echo "results_file: ${results_file}"
+ echo "png_dir: ${png_dir}"
+ echo "chain: ${chain}"
+ echo "stop_at_height: ${stop_at_height}"
+ echo "connect_address: ${connect_address}"
+ echo "dbcache: ${dbcache}"
+ echo "\n"
+
+ # Run hyperfine
+ hyperfine \
+ --shell=bash \
+ --setup "setup_run ${TMP_DATADIR} {commit}" \
+ --prepare "prepare_run ${TMP_DATADIR} ${ORIGINAL_DATADIR}" \
+ --conclude "conclude_run {commit} ${TMP_DATADIR} ${png_dir}" \
+ --cleanup "cleanup_run ${TMP_DATADIR}" \
+ --runs 5 \
+ --export-json "${results_file}" \
+ --show-output \
+ --command-name "base (${base_commit})" \
+ --command-name "head (${head_commit})" \
+ "taskset -c 1 flamegraph --palette bitcoin --title 'bitcoind IBD@{commit}' -c 'record -F 101 --call-graph fp' -- taskset -c 2-15 chrt -r 1 ${BINARIES_DIR}/{commit}/bitcoind -datadir=${TMP_DATADIR} -connect=${connect_address} -daemon=0 -prune=10000 -chain=${chain} -stopatheight=${stop_at_height} -dbcache=${dbcache} -printtoconsole=0 -debug=coindb -debug=leveldb -debug=bench -debug=validation" \
+ -L commit "base,head"
+}
+
+# Main execution
+if [ "$#" -ne 11 ]; then
+ echo "Usage: $0 base_commit head_commit TMP_DATADIR ORIGINAL_DATADIR results_dir png_dir chain stop_at_height connect_address dbcache BINARIES_DIR"
+ exit 1
+fi
+
+run_benchmark "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}"
diff --git a/ci/test/00_setup_env_i686_multiprocess.sh b/ci/test/00_setup_env_i686_multiprocess.sh
index e659486407..1aa143a7f0 100755
--- a/ci/test/00_setup_env_i686_multiprocess.sh
+++ b/ci/test/00_setup_env_i686_multiprocess.sh
@@ -18,7 +18,6 @@ export BITCOIN_CONFIG="\
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER='clang;-m32' \
-DCMAKE_CXX_COMPILER='clang++;-m32' \
- -DCMAKE_CXX_FLAGS='-Wno-error=documentation' \
-DAPPEND_CPPFLAGS='-DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE' \
"
export BITCOIND=bitcoin-node # Used in functional tests
diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh
index d7ff7c2c13..c59a506a71 100755
--- a/ci/test/00_setup_env_native_asan.sh
+++ b/ci/test/00_setup_env_native_asan.sh
@@ -19,7 +19,7 @@ else
fi
export CONTAINER_NAME=ci_native_asan
-export APT_LLVM_V="19"
+export APT_LLVM_V="20"
export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev python3-zmq qtbase5-dev qttools5-dev qttools5-dev-tools libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE}"
export NO_DEPENDS=1
export GOAL="install"
diff --git a/ci/test/00_setup_env_native_fuzz.sh b/ci/test/00_setup_env_native_fuzz.sh
index 597e2401a2..1b5a27bb6c 100755
--- a/ci/test/00_setup_env_native_fuzz.sh
+++ b/ci/test/00_setup_env_native_fuzz.sh
@@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
export CONTAINER_NAME=ci_native_fuzz
-export APT_LLVM_V="19"
+export APT_LLVM_V="20"
export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev libevent-dev libboost-dev libsqlite3-dev"
export NO_DEPENDS=1
export RUN_UNIT_TESTS=false
diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh
index d82dda344f..988107e7fb 100755
--- a/ci/test/00_setup_env_native_tsan.sh
+++ b/ci/test/00_setup_env_native_tsan.sh
@@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8
export CONTAINER_NAME=ci_native_tsan
export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
-export APT_LLVM_V="19"
+export APT_LLVM_V="20"
export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev libc++abi-${APT_LLVM_V}-dev libc++-${APT_LLVM_V}-dev python3-zmq"
export DEP_OPTS="CC=clang-${APT_LLVM_V} CXX='clang++-${APT_LLVM_V} -stdlib=libc++'"
export GOAL="install"
diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh
index a46f9ffabb..e1acc40708 100755
--- a/ci/test/01_base_install.sh
+++ b/ci/test/01_base_install.sh
@@ -49,7 +49,7 @@ if [ -n "$PIP_PACKAGES" ]; then
fi
if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then
- ${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-19.1.6" /msan/llvm-project
+ ${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-20.1.0-rc1" /msan/llvm-project
cmake -G Ninja -B /msan/clang_build/ \
-DLLVM_ENABLE_PROJECTS="clang" \
diff --git a/cmake/module/Maintenance.cmake b/cmake/module/Maintenance.cmake
index 61251d2439..d53e7d331e 100644
--- a/cmake/module/Maintenance.cmake
+++ b/cmake/module/Maintenance.cmake
@@ -23,25 +23,6 @@ function(add_maintenance_targets)
return()
endif()
- if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- set(exe_format MACHO)
- elseif(WIN32)
- set(exe_format PE)
- else()
- set(exe_format ELF)
- endif()
-
- # In CMake, the components of the compiler invocation are separated into distinct variables:
- # - CMAKE_CXX_COMPILER: the full path to the compiler binary itself (e.g., /usr/bin/clang++).
- # - CMAKE_CXX_COMPILER_ARG1: a string containing initial compiler options (e.g., --target=x86_64-apple-darwin -nostdlibinc).
- # By concatenating these variables, we form the complete command line to be passed to a Python script via the CXX environment variable.
- string(STRIP "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" cxx_compiler_command)
- add_custom_target(test-security-check
- COMMAND ${CMAKE_COMMAND} -E env CXX=${cxx_compiler_command} CXXFLAGS=${CMAKE_CXX_FLAGS} LDFLAGS=${CMAKE_EXE_LINKER_FLAGS} ${PYTHON_COMMAND} ${PROJECT_SOURCE_DIR}/contrib/devtools/test-security-check.py TestSecurityChecks.test_${exe_format}
- COMMAND ${CMAKE_COMMAND} -E env CXX=${cxx_compiler_command} CXXFLAGS=${CMAKE_CXX_FLAGS} LDFLAGS=${CMAKE_EXE_LINKER_FLAGS} ${PYTHON_COMMAND} ${PROJECT_SOURCE_DIR}/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_${exe_format}
- VERBATIM
- )
-
foreach(target IN ITEMS bitcoind bitcoin-qt bitcoin-cli bitcoin-tx bitcoin-util bitcoin-wallet test_bitcoin bench_bitcoin)
if(TARGET ${target})
list(APPEND executables $)
diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md
index 56eaeef815..bf0070f2f5 100644
--- a/contrib/devtools/README.md
+++ b/contrib/devtools/README.md
@@ -115,8 +115,8 @@ example:
BUILDDIR=$PWD/build contrib/devtools/gen-bitcoin-conf.sh
```
-security-check.py and test-security-check.py
-============================================
+security-check.py
+=================
Perform basic security checks on a series of executables.
diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py
deleted file mode 100755
index 99f171608d..0000000000
--- a/contrib/devtools/test-security-check.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2015-2022 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-'''
-Test script for security-check.py
-'''
-import lief
-import os
-import subprocess
-import unittest
-
-from utils import determine_wellknown_cmd
-
-def write_testcode(filename):
- with open(filename, 'w', encoding="utf8") as f:
- f.write('''
- #include
- int main()
- {
- std::printf("the quick brown fox jumps over the lazy god\\n");
- return 0;
- }
- ''')
-
-def clean_files(source, executable):
- os.remove(source)
- os.remove(executable)
-
-def env_flags() -> list[str]:
- # This should behave the same as AC_TRY_LINK, so arrange well-known flags
- # in the same order as autoconf would.
- #
- # See the definitions for ac_link in autoconf's lib/autoconf/c.m4 file for
- # reference.
- flags: list[str] = []
- for var in ['CXXFLAGS', 'CPPFLAGS', 'LDFLAGS']:
- flags += filter(None, os.environ.get(var, '').split(' '))
- return flags
-
-def call_security_check(cxx: str, source: str, executable: str, options) -> tuple:
- subprocess.run([*cxx,source,'-o',executable] + env_flags() + options, check=True)
- p = subprocess.run([os.path.join(os.path.dirname(__file__), 'security-check.py'), executable], stdout=subprocess.PIPE, text=True)
- return (p.returncode, p.stdout.rstrip())
-
-def get_arch(cxx, source, executable):
- subprocess.run([*cxx, source, '-o', executable] + env_flags(), check=True)
- binary = lief.parse(executable)
- arch = binary.abstract.header.architecture
- os.remove(executable)
- return arch
-
-class TestSecurityChecks(unittest.TestCase):
- def test_ELF(self):
- source = 'test1.cpp'
- executable = 'test1'
- cxx = determine_wellknown_cmd('CXX', 'g++')
- write_testcode(source)
- arch = get_arch(cxx, source, executable)
-
- if arch == lief.ARCHITECTURES.X86:
- pass_flags = ['-D_FORTIFY_SOURCE=3', '-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code', '-fcf-protection=full']
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-zexecstack']), (1, executable + ': failed NX'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-znorelro']), (1, executable + ': failed RELRO'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-z,noseparate-code']), (1, executable + ': failed SEPARATE_CODE'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-U_FORTIFY_SOURCE']), (1, executable + ': failed FORTIFY'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
- else:
- pass_flags = ['-D_FORTIFY_SOURCE=3', '-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code']
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-zexecstack']), (1, executable + ': failed NX'))
- # LIEF fails to parse RISC-V with no PIE correctly, and doesn't detect the fortified function,
- # so skip this test for RISC-V (for now). See https://github.com/lief-project/LIEF/issues/1082.
- if arch != lief.ARCHITECTURES.RISCV:
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-U_FORTIFY_SOURCE']), (1, executable + ': failed FORTIFY'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-znorelro']), (1, executable + ': failed RELRO'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-z,noseparate-code']), (1, executable + ': failed SEPARATE_CODE'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
-
- clean_files(source, executable)
-
- def test_PE(self):
- source = 'test1.cpp'
- executable = 'test1.exe'
- cxx = determine_wellknown_cmd('CXX', 'x86_64-w64-mingw32-g++')
- write_testcode(source)
-
- pass_flags = ['-Wl,--nxcompat', '-Wl,--enable-reloc-section', '-Wl,--dynamicbase', '-Wl,--high-entropy-va', '-pie', '-fPIE', '-fcf-protection=full', '-fstack-protector-all', '-lssp']
-
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fno-stack-protector']), (1, executable + ': failed CANARY'))
- # https://github.com/lief-project/LIEF/issues/1076 - in future, we could test this individually.
- # self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-reloc-section']), (1, executable + ': failed RELOC_SECTION'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-nxcompat']), (1, executable + ': failed NX'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-dynamicbase']), (1, executable + ': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA')) # -pie -fPIE does nothing without --dynamicbase
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-high-entropy-va']), (1, executable + ': failed HIGH_ENTROPY_VA'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
-
- clean_files(source, executable)
-
- def test_MACHO(self):
- source = 'test1.cpp'
- executable = 'test1'
- cxx = determine_wellknown_cmd('CXX', 'clang++')
- write_testcode(source)
- arch = get_arch(cxx, source, executable)
-
- if arch == lief.ARCHITECTURES.X86:
- pass_flags = ['-Wl,-pie', '-fstack-protector-all', '-fcf-protection=full', '-Wl,-fixup_chains']
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-no_pie', '-Wl,-no_fixup_chains']), (1, executable+': failed FIXUP_CHAINS PIE')) # -fixup_chains is incompatible with -no_pie
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-no_fixup_chains']), (1, executable + ': failed FIXUP_CHAINS'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fno-stack-protector']), (1, executable + ': failed CANARY'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-flat_namespace']), (1, executable + ': failed NOUNDEFS'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
- else:
- # arm64 darwin doesn't support non-PIE binaries or executable stacks
- pass_flags = ['-fstack-protector-all', '-Wl,-fixup_chains', '-mbranch-protection=bti']
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-mbranch-protection=none']), (1, executable + ': failed BRANCH_PROTECTION'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-no_fixup_chains']), (1, executable + ': failed FIXUP_CHAINS'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fno-stack-protector']), (1, executable + ': failed CANARY'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-flat_namespace']), (1, executable + ': failed NOUNDEFS'))
- self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
-
- clean_files(source, executable)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py
deleted file mode 100755
index b4b524dac0..0000000000
--- a/contrib/devtools/test-symbol-check.py
+++ /dev/null
@@ -1,174 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2020-2022 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-'''
-Test script for symbol-check.py
-'''
-import os
-import subprocess
-import unittest
-
-from utils import determine_wellknown_cmd
-
-def call_symbol_check(cxx: list[str], source, executable, options):
- # This should behave the same as AC_TRY_LINK, so arrange well-known flags
- # in the same order as autoconf would.
- #
- # See the definitions for ac_link in autoconf's lib/autoconf/c.m4 file for
- # reference.
- env_flags: list[str] = []
- for var in ['CXXFLAGS', 'CPPFLAGS', 'LDFLAGS']:
- env_flags += filter(None, os.environ.get(var, '').split(' '))
-
- subprocess.run([*cxx,source,'-o',executable] + env_flags + options, check=True)
- p = subprocess.run([os.path.join(os.path.dirname(__file__), 'symbol-check.py'), executable], stdout=subprocess.PIPE, text=True)
- os.remove(source)
- os.remove(executable)
- return (p.returncode, p.stdout.rstrip())
-
-class TestSymbolChecks(unittest.TestCase):
- def test_ELF(self):
- source = 'test1.cpp'
- executable = 'test1'
- cxx = determine_wellknown_cmd('CXX', 'g++')
-
- # -lutil is part of the libc6 package so a safe bet that it's installed
- # it's also out of context enough that it's unlikely to ever become a real dependency
- source = 'test2.cpp'
- executable = 'test2'
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- #include
-
- int main()
- {
- login(0);
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-lutil']),
- (1, executable + ': libutil.so.1 is not in ALLOWED_LIBRARIES!\n' +
- executable + ': failed LIBRARY_DEPENDENCIES'))
-
- # finally, check a simple conforming binary
- source = 'test3.cpp'
- executable = 'test3'
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- #include
-
- int main()
- {
- std::printf("42");
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, []),
- (0, ''))
-
- def test_MACHO(self):
- source = 'test1.cpp'
- executable = 'test1'
- cxx = determine_wellknown_cmd('CXX', 'clang++')
-
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- #include
-
- int main()
- {
- XML_ExpatVersion();
- return 0;
- }
-
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-lexpat', '-Wl,-platform_version','-Wl,macos', '-Wl,11.4', '-Wl,11.4']),
- (1, 'libexpat.1.dylib is not in ALLOWED_LIBRARIES!\n' +
- f'{executable}: failed DYNAMIC_LIBRARIES MIN_OS SDK'))
-
- source = 'test2.cpp'
- executable = 'test2'
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- #include
-
- int main()
- {
- CGMainDisplayID();
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-framework', 'CoreGraphics', '-Wl,-platform_version','-Wl,macos', '-Wl,11.4', '-Wl,11.4']),
- (1, f'{executable}: failed MIN_OS SDK'))
-
- source = 'test3.cpp'
- executable = 'test3'
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- int main()
- {
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-Wl,-platform_version','-Wl,macos', '-Wl,13.0', '-Wl,11.4']),
- (1, f'{executable}: failed SDK'))
-
- def test_PE(self):
- source = 'test1.cpp'
- executable = 'test1.exe'
- cxx = determine_wellknown_cmd('CXX', 'x86_64-w64-mingw32-g++')
-
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- #include
-
- int main()
- {
- PdhConnectMachineA(NULL);
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-lpdh', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,2']),
- (1, 'pdh.dll is not in ALLOWED_LIBRARIES!\n' +
- executable + ': failed DYNAMIC_LIBRARIES'))
-
- source = 'test2.cpp'
- executable = 'test2.exe'
-
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- int main()
- {
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-Wl,--major-subsystem-version', '-Wl,9', '-Wl,--minor-subsystem-version', '-Wl,9']),
- (1, executable + ': failed SUBSYSTEM_VERSION'))
-
- source = 'test3.cpp'
- executable = 'test3.exe'
- with open(source, 'w', encoding="utf8") as f:
- f.write('''
- #include
-
- int main()
- {
- CoFreeUnusedLibrariesEx(0,0);
- return 0;
- }
- ''')
-
- self.assertEqual(call_symbol_check(cxx, source, executable, ['-lole32', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,2']),
- (0, ''))
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh
index 6c252e7870..ddb8297d9e 100755
--- a/contrib/guix/libexec/build.sh
+++ b/contrib/guix/libexec/build.sh
@@ -247,8 +247,6 @@ mkdir -p "$DISTSRC"
# Build Bitcoin Core
cmake --build build -j "$JOBS" ${V:+--verbose}
- # Check that symbol/security checks tools are sane.
- cmake --build build --target test-security-check ${V:+--verbose}
# Perform basic security checks on a series of executables.
cmake --build build -j 1 --target check-security ${V:+--verbose}
# Check that executables only contain allowed version symbols.
diff --git a/contrib/tracing/README.md b/contrib/tracing/README.md
index aa48b3e3f3..0757ab9c76 100644
--- a/contrib/tracing/README.md
+++ b/contrib/tracing/README.md
@@ -335,4 +335,25 @@ $ python3 contrib/tracing/mempool_monitor.py $(pidof bitcoind)
โ 13:10:32Z added c78e87be86c828137a6e7e00a177c03b52202ce4c39029b99904c2a094b9da87 with feerate 11.00 sat/vB (1562 sat, 142 vbytes) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+### log_p2p_connections.bt
+
+A `bpftrace` script to log information about opened, closed, misbehaving, and
+evicted P2P connections. Uses the `net:*_connection` tracepoints.
+
+```bash
+$ bpftrace contrib/tracing/log_p2p_connections.bt
+```
+
+This should produce an output similar to the following.
+
+```bash
+Attaching 6 probes...
+Logging opened, closed, misbehaving, and evicted P2P connections
+OUTBOUND conn to 127.0.0.1:15287: id=0, type=block-relay-only, network=0, total_out=1
+INBOUND conn from 127.0.0.1:45324: id=1, type=inbound, network=0, total_in=1
+MISBEHAVING conn id=1, score_before=0, score_increase=20, message='getdata message size = 50001', threshold_exceeded=false
+CLOSED conn to 127.0.0.1:15287: id=0, type=block-relay-only, network=0, established=1231006505
+EVICTED conn to 127.0.0.1:45324: id=1, type=inbound, network=0, established=1612312312
+...
```
diff --git a/contrib/tracing/log_p2p_connections.bt b/contrib/tracing/log_p2p_connections.bt
new file mode 100755
index 0000000000..fd2cc934d7
--- /dev/null
+++ b/contrib/tracing/log_p2p_connections.bt
@@ -0,0 +1,51 @@
+#!/usr/bin/env bpftrace
+
+BEGIN
+{
+ printf("Logging opened, closed, misbehaving, and evicted P2P connections\n")
+}
+
+usdt:./build/src/bitcoind:net:inbound_connection
+{
+ $id = (int64) arg0;
+ $addr = str(arg1);
+ $conn_type = str(arg2);
+ $network = (int32) arg3;
+ $existing = (uint64) arg4;
+ printf("INBOUND conn from %s: id=%ld, type=%s, network=%d, total=%d\n", $addr, $id, $conn_type, $network, $existing);
+}
+
+usdt:./build/src/bitcoind:net:outbound_connection
+{
+ $id = (int64) arg0;
+ $addr = str(arg1);
+ $conn_type = str(arg2);
+ $network = (int32) arg3;
+ $existing = (uint64) arg4;
+ printf("OUTBOUND conn to %s: id=%ld, type=%s, network=%d, total=%d\n", $addr, $id, $conn_type, $network, $existing);
+}
+
+usdt:./build/src/bitcoind:net:closed_connection
+{
+ $id = (int64) arg0;
+ $addr = str(arg1);
+ $conn_type = str(arg2);
+ $network = (int32) arg3;
+ printf("CLOSED conn to %s: id=%ld, type=%s, network=%d, established=%ld\n", $addr, $id, $conn_type, $network, arg4);
+}
+
+usdt:./build/src/bitcoind:net:evicted_inbound_connection
+{
+ $id = (int64) arg0;
+ $addr = str(arg1);
+ $conn_type = str(arg2);
+ $network = (int32) arg3;
+ printf("EVICTED conn to %s: id=%ld, type=%s, network=%d, established=%ld\n", $addr, $id, $conn_type, $network, arg4);
+}
+
+usdt:./build/src/bitcoind:net:misbehaving_connection
+{
+ $id = (int64) arg0;
+ $message = str(arg1);
+ printf("MISBEHAVING conn id=%ld, message='%s'\n", $id, $message);
+}
diff --git a/depends/Makefile b/depends/Makefile
index 1053c8a249..7eb0988d63 100644
--- a/depends/Makefile
+++ b/depends/Makefile
@@ -217,7 +217,6 @@ $(host_prefix)/toolchain.cmake : toolchain.cmake.in $(host_prefix)/.stamp_$(fina
-e 's|@STRIP@|$(host_STRIP)|' \
-e 's|@OBJCOPY@|$(host_OBJCOPY)|' \
-e 's|@OBJDUMP@|$(host_OBJDUMP)|' \
- -e 's|@depends_prefix@|$(host_prefix)|' \
-e 's|@CFLAGS@|$(strip $(host_CFLAGS))|' \
-e 's|@CFLAGS_RELEASE@|$(strip $(host_release_CFLAGS))|' \
-e 's|@CFLAGS_DEBUG@|$(strip $(host_debug_CFLAGS))|' \
diff --git a/depends/packages/capnp.mk b/depends/packages/capnp.mk
index 0c211cbc45..7f41d3b5a4 100644
--- a/depends/packages/capnp.mk
+++ b/depends/packages/capnp.mk
@@ -9,7 +9,7 @@ define $(package)_set_vars :=
$(package)_config_opts := -DBUILD_TESTING=OFF
$(package)_config_opts += -DWITH_OPENSSL=OFF
$(package)_config_opts += -DWITH_ZLIB=OFF
- $(package)_cxxflags += -ffile-prefix-map=$$($(package)_extract_dir)=/usr
+ $(package)_cxxflags += -fdebug-prefix-map=$($(package)_extract_dir)=/usr -fmacro-prefix-map=$($(package)_extract_dir)=/usr
endef
define $(package)_config_cmds
diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk
index 14ad24a1de..a55684ae11 100644
--- a/depends/packages/libevent.mk
+++ b/depends/packages/libevent.mk
@@ -13,7 +13,7 @@ define $(package)_set_vars
$(package)_config_opts=-DCMAKE_BUILD_TYPE=None -DEVENT__DISABLE_BENCHMARK=ON -DEVENT__DISABLE_OPENSSL=ON
$(package)_config_opts+=-DEVENT__DISABLE_SAMPLES=ON -DEVENT__DISABLE_REGRESS=ON
$(package)_config_opts+=-DEVENT__DISABLE_TESTS=ON -DEVENT__LIBRARY_TYPE=STATIC
- $(package)_cflags += -ffile-prefix-map=$($(package)_extract_dir)=/usr
+ $(package)_cflags += -fdebug-prefix-map=$($(package)_extract_dir)=/usr -fmacro-prefix-map=$($(package)_extract_dir)=/usr
$(package)_cppflags += -D_GNU_SOURCE
$(package)_cppflags_mingw32=-D_WIN32_WINNT=0x0A00
diff --git a/depends/packages/libmultiprocess.mk b/depends/packages/libmultiprocess.mk
index a181e05100..afbd315e38 100644
--- a/depends/packages/libmultiprocess.mk
+++ b/depends/packages/libmultiprocess.mk
@@ -13,7 +13,7 @@ ifneq ($(host),$(build))
$(package)_config_opts := -DCAPNP_EXECUTABLE="$$(native_capnp_prefixbin)/capnp"
$(package)_config_opts += -DCAPNPC_CXX_EXECUTABLE="$$(native_capnp_prefixbin)/capnpc-c++"
endif
-$(package)_cxxflags += -ffile-prefix-map=$$($(package)_extract_dir)=/usr
+$(package)_cxxflags += -fdebug-prefix-map=$($(package)_extract_dir)=/usr -fmacro-prefix-map=$($(package)_extract_dir)=/usr
endef
define $(package)_config_cmds
diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk
index 89e10d15ef..8bf84b1f1c 100644
--- a/depends/packages/zeromq.mk
+++ b/depends/packages/zeromq.mk
@@ -17,7 +17,7 @@ define $(package)_set_vars
$(package)_config_opts += -DWITH_LIBBSD=OFF -DENABLE_CURVE=OFF -DENABLE_CPACK=OFF
$(package)_config_opts += -DBUILD_SHARED=OFF -DBUILD_TESTS=OFF -DZMQ_BUILD_TESTS=OFF
$(package)_config_opts += -DENABLE_DRAFTS=OFF -DZMQ_BUILD_TESTS=OFF
- $(package)_cxxflags += -ffile-prefix-map=$($(package)_extract_dir)=/usr
+ $(package)_cxxflags += -fdebug-prefix-map=$($(package)_extract_dir)=/usr -fmacro-prefix-map=$($(package)_extract_dir)=/usr
$(package)_config_opts_mingw32 += -DZMQ_WIN32_WINNT=0x0A00 -DZMQ_HAVE_IPC=OFF
endef
diff --git a/depends/toolchain.cmake.in b/depends/toolchain.cmake.in
index e72e6e29e2..89a6e36969 100644
--- a/depends/toolchain.cmake.in
+++ b/depends/toolchain.cmake.in
@@ -81,12 +81,12 @@ set(CMAKE_OBJDUMP "@OBJDUMP@")
# affected by a potentially random environment.
set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF)
-set(CMAKE_FIND_ROOT_PATH "@depends_prefix@")
+set(CMAKE_FIND_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
-set(QT_TRANSLATIONS_DIR "@depends_prefix@/translations")
+set(QT_TRANSLATIONS_DIR "${CMAKE_CURRENT_LIST_DIR}/translations")
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT CMAKE_HOST_APPLE)
# The find_package(Qt ...) function internally uses find_library()
diff --git a/doc/benchcoin.md b/doc/benchcoin.md
new file mode 100644
index 0000000000..832c8f9842
--- /dev/null
+++ b/doc/benchcoin.md
@@ -0,0 +1,140 @@
+# benchcoin
+
+A Bitcoin Core benchmarking fork
+
+This repository is a fork of Bitcoin Core that performs automated assumeutxo-based IBD benchmarking.
+It allows you to measure and compare the performance impact of certain types of changes to Bitcoin Core's codebase on a longer-running IBD benchmark, in a (pretty) reproducible fashion.
+
+## Features
+
+- Automated IBD benchmarking on pull requests
+- Multiple configurations:
+ - Signet (fast fail test)
+ - Mainnet with default cache
+ - Mainnet with large cache
+- Performance visualizations including:
+ - Flamegraphs for CPU profiling
+ - Time series plots of various metrics
+ - Compare `base` (bitcoin/bitcoin:master) and `head` (PR)
+
+## Example Flamegraph
+
+Below is an example flamegraph showing CPU utilization during IBD:
+
+
+
+## How to use it
+
+1. Rebase/cherry-pick your changes on top of benchcoin/master
+2. Open a Pull Request against **this repo**
+3. Wait for the bot to comment on your PR after it's finished.
+
+See the [Contributing](#contributing) section for more details.
+
+## How it works
+
+When you open a pull request against this repository:
+
+1. The CI workflow automatically builds both the base and PR versions of bitcoind
+2. Runs IBD benchmarks using assumeutxo snapshots
+3. Records performance metrics and creates various visualizations
+4. Posts results as a comment on your PR
+
+The benchmarks test three configurations:
+- Signet
+ - From snapshot @ height 160,000 to height 220,000
+- Mainnet-default: with default (450 MB) dbcache
+ - From snapshot @ height 840,000 to height 855,000
+- Mainnet-large: with 32000 MB dbcache
+ - From snapshot @ height 840,000 to height 855,000
+
+## Benchmark Outputs
+
+For each benchmark run, you'll get a github pages page with:
+
+- Timing comparisons between base and PR versions
+- CPU flamegraphs showing where time is spent
+- Time series plots showing:
+ - Block height vs time
+ - Cache size vs block height
+ - Cache size vs time
+ - Transaction count vs block height
+ - Coins cache size vs time
+ - LevelDB metrics
+ - Memory pool metrics
+
+## Local Development
+
+To run benchmarks locally (WIP, and Linux-only due to [shell.nix](../shell.nix) limitations):
+
+1. Make sure you have [Nix package manager](https://nixos.org/download/) installed
+
+2. Setup the Nix development environment:
+```bash
+nix-shell
+```
+
+3. Run a local benchmark:
+```bash
+just run-signet
+```
+
+This will:
+- Create a temporary directory for testing
+- Build both base and PR versions
+- Download the required UTXO snapshot if needed
+- Run the benchmark
+- Generate performance visualizations
+
+## Technical Details
+
+The benchmarking system uses:
+- [Hyperfine](https://github.com/sharkdp/hyperfine) for benchmark timing
+- [Flamegraph](https://github.com/willcl-ark/flamegraph) for CPU profiling
+- [matplotlib](https://matplotlib.org/) for metric visualization
+- [GitHub Actions](https://github.com/features/actions) for CI automation
+
+The system leverages assumeutxo to speed up IBD (to a more interesting height) by loading a snapshot.
+
+We use a custom assumeutxo patch which does introduces two commandline options for assumeutxo, specifically for
+benchmarking. these commands are:
+
+```
+-pausebackgroundsync - pauses background verification of historical blocks in the background.
+-loadutxosnapshot= - load an assumeutxo snapshot on startup, instead of needing to go through the rpc command.
+ The node will shutdown immediately after the snapshot has been loaded.
+```
+
+### Runner & seed
+
+The CI runner is self-hosted on a Hetzner AX52 running at the bitcoin-dev-tools organsation level.
+It is running NixOS using configuration found in this repo: [nix-github-runner](https://github.com/bitcoin-dev-tools/nix-github-runner) for easier deployment and reproducibility.
+
+The runner host has 16 cores, with one used for system, one for `flamegraph` (i.e. `perf record`) and 14 dedicated to the Bitcoin Core node under test.
+
+The benchmarking peer on the runner is served blocks over the (real) "internet" (it may be LAN as it's within a single Hetzner region) via a single peer to exercise full IBD codepaths. This naturally may introduce some variance, but it was deemed preferable to running another bitcoin core on the same machine.
+
+This seed peer is another Hetzner VPS in the same region, and its configuration can be found here: [nix-seed-node](https://github.com/bitcoin-dev-tools/nix-seed-node)
+
+## Contributing
+
+### Benchmark an existing bitcoin/bitcoin PR
+
+This requires `just` be installed. If you don't have `just` installed you can run the commands in the [justfile](../justfile) manually.
+
+1. Fork this repository (or bitcoin/bitcoin and add this as a remote)
+2. Create a new branch from benchcoin/master
+3. Run: `just pick-pr ` to cherry-pick commits from the PR
+4. Push the branch
+5. Open a pull request **against this repo. NOT bitcoin/bitcoin**
+
+### Benchmark standalone/new changes
+
+1. Fork this repository (or bitcoin/bitcoin and add this as a remote)
+2. Make your changes to Bitcoin Core
+3. Open a pull request **against this repo. NOT bitcoin/bitcoin**
+4. Wait for benchmark results to be posted on your PR here
+
+## License
+
+This project is licensed under the same terms as Bitcoin Core - see the [COPYING](../COPYING) file for details.
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index 37e594e762..eb5c191790 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -432,8 +432,8 @@ RPC that, when enabled, logs the location and duration of each lock contention
to the `debug.log` file.
The `-DCMAKE_BUILD_TYPE=Debug` build option adds `-DDEBUG_LOCKCONTENTION` to the
-compiler flags. You may also enable it manually by building with `-DDEBUG_LOCKCONTENTION` added to your CPPFLAGS,
-i.e. `CPPFLAGS="-DDEBUG_LOCKCONTENTION"`, then build and run bitcoind.
+compiler flags. You may also enable it manually by building with `-DDEBUG_LOCKCONTENTION`
+added to your CPPFLAGS, i.e. `-DAPPEND_CPPFLAGS="-DDEBUG_LOCKCONTENTION"`.
You can then use the `-debug=lock` configuration option at bitcoind startup or
`bitcoin-cli logging '["lock"]'` at runtime to turn on lock contention logging.
diff --git a/doc/flamegraph.svg b/doc/flamegraph.svg
new file mode 100644
index 0000000000..77f05068ed
--- /dev/null
+++ b/doc/flamegraph.svg
@@ -0,0 +1,491 @@
+
diff --git a/doc/release-notes-31384.md b/doc/release-notes-31384.md
new file mode 100644
index 0000000000..9256ec16f4
--- /dev/null
+++ b/doc/release-notes-31384.md
@@ -0,0 +1,34 @@
+- Node and Mining
+
+---
+
+- **PR #31384** fixed an issue where block reserved weight for fixed-size block header, transactions count,
+ and coinbase transaction was done in two separate places.
+ Before this pull request, the policy default for the maximum block weight was `3,996,000` WU, calculated by
+ subtracting `4,000 WU` from the `4,000,000 WU` consensus limit to account for the fixed-size block header,
+ transactions count, and coinbase transaction. During block assembly, Bitcoin Core clamped custom `-blockmaxweight`
+ value to not be more than the policy default.
+
+ Additionally, the mining code added another `4,000 WU` to the initial reservation, reducing the effective block template
+ size to `3,992,000 WU`.
+
+ Due to this issue, the total reserved weight was always `8,000 WU`, meaning that even when specifying a `-blockmaxweight`
+ higher than the policy default, the actual block size never exceeded `3,992,000 WU`.
+
+ The fix consolidates the reservation into a single place and introduces a new startup option,
+ `-blockreservedweight` (default: `8,000 WU`). This startup option specifies the reserved weight for
+ the fixed-size block header, transactions count, and coinbase transaction.
+ The default value of `-blockreservedweight` was chosen to preserve the previous behavior.
+
+ **Upgrade Note:** The default `-blockreservedweight` ensures backward compatibility for users who relied on the previous behavior.
+
+ Users who manually set `-blockmaxweight` to its maximum value of `4,000,000 WU` should be aware that this
+ value previously had no effect since it was clamped to `3,996,000 WU`.
+
+ Users lowering `-blockreservedweight` should ensure that the total weight (for the block header, transaction count, and coinbase transaction)
+ does not exceed the reduced value.
+
+ As a safety check, Bitcoin core will **fail to start** when `-blockreservedweight` init parameter value is lower than `2000` weight units.
+
+ Bitcoin Core will also **fail to start** if the `-blockmaxweight` or `-blockreservedweight` init parameter exceeds
+ consensus limit of `4,000,000` weight units.
diff --git a/doc/tracing.md b/doc/tracing.md
index 184f6b0100..53577438fa 100644
--- a/doc/tracing.md
+++ b/doc/tracing.md
@@ -55,6 +55,9 @@ The currently available tracepoints are listed here.
### Context `net`
+[^address-length]: An Onion v3 address with a `:` and a five digit port has 68
+ chars. However, addresses of peers added with host names might be longer.
+
#### Tracepoint `net:inbound_message`
Is called when a message is received from a peer over the P2P network. Passes
@@ -62,7 +65,7 @@ information about our peer, the connection and the message as arguments.
Arguments passed:
1. Peer ID as `int64`
-2. Peer Address and Port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (max. length 68 characters)
+2. Peer Address and Port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (normally up to 68 characters[^address-length])
3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
4. Message Type (inv, ping, getdata, addrv2, ...) as `pointer to C-style String` (max. length 20 characters)
5. Message Size in bytes as `uint64`
@@ -81,7 +84,7 @@ information about our peer, the connection and the message as arguments.
Arguments passed:
1. Peer ID as `int64`
-2. Peer Address and Port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (max. length 68 characters)
+2. Peer Address and Port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (normally up to 68 characters[^address-length])
3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
4. Message Type (inv, ping, getdata, addrv2, ...) as `pointer to C-style String` (max. length 20 characters)
5. Message Size in bytes as `uint64`
@@ -93,6 +96,62 @@ to user-space in full. Messages longer than a 32kb might be cut off. This can
be detected in tracing scripts by comparing the message size to the length of
the passed message.
+#### Tracepoint `net:inbound_connection`
+
+Is called when a new inbound connection is opened to us. Passes information about
+the peer and the number of inbound connections including the newly opened connection.
+
+Arguments passed:
+1. Peer ID as `int64`
+2. Peer address and port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (normally up to 68 characters[^address-length])
+3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
+4. Network the peer connects from as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`.
+5. Number of existing inbound connections as `uint64` including the newly opened inbound connection.
+
+#### Tracepoint `net:outbound_connection`
+
+Is called when a new outbound connection is opened by us. Passes information about
+the peer and the number of outbound connections including the newly opened connection.
+
+Arguments passed:
+1. Peer ID as `int64`
+2. Peer address and port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (normally up to 68 characters[^address-length])
+3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
+4. Network of the peer as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`.
+5. Number of existing outbound connections as `uint64` including the newly opened outbound connection.
+
+#### Tracepoint `net:evicted_inbound_connection`
+
+Is called when a inbound connection is evicted by us. Passes information about the evicted peer and the time at connection establishment.
+
+Arguments passed:
+1. Peer ID as `int64`
+2. Peer address and port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (normally up to 68 characters[^address-length])
+3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
+4. Network the peer connects from as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`.
+5. Connection established UNIX epoch timestamp in seconds as `uint64`.
+
+#### Tracepoint `net:misbehaving_connection`
+
+Is called when a connection is misbehaving. Passes the peer id and a
+reason for the peers misbehavior.
+
+Arguments passed:
+1. Peer ID as `int64`.
+2. Reason why the peer is misbehaving as `pointer to C-style String` (max. length 128 characters).
+
+#### Tracepoint `net:closed_connection`
+
+Is called when a connection is closed. Passes information about the closed peer
+and the time at connection establishment.
+
+Arguments passed:
+1. Peer ID as `int64`
+2. Peer address and port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (normally up to 68 characters[^address-length])
+3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
+4. Network the peer connects from as `uint32` (1 = IPv4, 2 = IPv6, 3 = Onion, 4 = I2P, 5 = CJDNS). See `Network` enum in `netaddress.h`.
+5. Connection established UNIX epoch timestamp in seconds as `uint64`.
+
### Context `validation`
#### Tracepoint `validation:block_connected`
diff --git a/justfile b/justfile
new file mode 100644
index 0000000000..a8f99b2ba6
--- /dev/null
+++ b/justfile
@@ -0,0 +1,45 @@
+set shell := ["bash", "-uc"]
+
+os := os()
+
+default:
+ just --list
+
+# Build base and head binaries for CI
+[group('ci')]
+build-assumeutxo-binaries-guix base_commit head_commit:
+ ./bench-ci/build_binaries.sh {{ base_commit }} {{ head_commit }}
+
+# Run signet assumeutxo CI workflow
+[group('ci')]
+run-assumeutxo-signet-ci base_commit head_commit TMP_DATADIR UTXO_PATH results_file dbcache png_dir binaries_dir:
+ ./bench-ci/run-assumeutxo-bench.sh {{ base_commit }} {{ head_commit }} {{ TMP_DATADIR }} {{ UTXO_PATH }} {{ results_file }} {{ png_dir }} signet 220000 "148.251.128.115:55555" {{ dbcache }} {{ binaries_dir }}
+
+# Run mainnet assumeutxo CI workflow for default cache
+[group('ci')]
+run-assumeutxo-mainnet-default-ci base_commit head_commit TMP_DATADIR UTXO_PATH results_file dbcache png_dir binaries_dir:
+ ./bench-ci/run-assumeutxo-bench.sh {{ base_commit }} {{ head_commit }} {{ TMP_DATADIR }} {{ UTXO_PATH }} {{ results_file }} {{ png_dir }} main 855000 "148.251.128.115:33333" {{ dbcache }} {{ binaries_dir }}
+
+# Run mainnet assumeutxo CI workflow for large cache
+[group('ci')]
+run-assumeutxo-mainnet-large-ci base_commit head_commit TMP_DATADIR UTXO_PATH results_file dbcache png_dir binaries_dir:
+ ./bench-ci/run-assumeutxo-bench.sh {{ base_commit }} {{ head_commit }} {{ TMP_DATADIR }} {{ UTXO_PATH }} {{ results_file }} {{ png_dir }} main 855000 "148.251.128.115:33333" {{ dbcache }} {{ binaries_dir }}
+
+# Run mainnet benchmark workflow for large cache
+[group('ci')]
+run-mainnet-large-ci base_commit head_commit TMP_DATADIR ORIGINAL_DATADIR results_file dbcache png_dir binaries_dir:
+ ./bench-ci/run-benchmark.sh {{ base_commit }} {{ head_commit }} {{ TMP_DATADIR }} {{ ORIGINAL_DATADIR }} {{ results_file }} {{ png_dir }} main 855000 "148.251.128.115:33333" {{ dbcache }} {{ binaries_dir }}
+
+# Cherry-pick commits from a bitcoin core PR onto this branch
+[group('git')]
+pick-pr pr_number:
+ #!/usr/bin/env bash
+ set -euxo pipefail
+
+ if ! git remote get-url upstream 2>/dev/null | grep -q "bitcoin/bitcoin"; then
+ echo "Error: 'upstream' remote not found or doesn't point to bitcoin/bitcoin"
+ echo "Please add it with: `git remote add upstream https://github.com/bitcoin/bitcoin.git`"
+ exit 1
+ fi
+
+ git fetch upstream pull/{{ pr_number }}/head:bench-{{ pr_number }} && git cherry-pick $(git rev-list --reverse bench-{{ pr_number }} --not upstream/master)
diff --git a/libbitcoinkernel.pc.in b/libbitcoinkernel.pc.in
index b8f9331587..6b622c926b 100644
--- a/libbitcoinkernel.pc.in
+++ b/libbitcoinkernel.pc.in
@@ -4,7 +4,7 @@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @CLIENT_NAME@ kernel library
-Description: Experimental library for the Bitcoin Core validation engine.
+Description: Experimental library for the @CLIENT_NAME@ validation engine.
Version: @CLIENT_VERSION_STRING@
Libs: -L${libdir} -lbitcoinkernel
Libs.private: -L${libdir} @LIBS_PRIVATE@
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000..26605fc849
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,15 @@
+[project]
+name = "bitcoin-core-deps"
+version = "0.1.0"
+dependencies = [
+ "codespell==2.2.6",
+ "lief==0.13.2",
+ "mypy==1.4.1",
+ "pyzmq==25.1.0",
+ # Removing in favour of packaged nixpkgs bin which is not dynamically linked
+ # "ruff==0.5.5",
+ "vulture==2.6",
+ "pyperf==2.8.0",
+ "matplotlib==3.8.0",
+ "numpy==1.26.0"
+]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000..c9b220b6fe
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,28 @@
+# This file was autogenerated by uv via the following command:
+# uv pip compile pyproject.toml -o requirements.txt
+codespell==2.2.6
+ # via bitcoin-core-deps (pyproject.toml)
+lief==0.13.2
+ # via bitcoin-core-deps (pyproject.toml)
+matplotlib==3.8.0
+ # via bitcoin-core-deps (pyproject.toml)
+mypy==1.4.1
+ # via bitcoin-core-deps (pyproject.toml)
+mypy-extensions==1.0.0
+ # via mypy
+numpy==1.26.0
+ # via bitcoin-core-deps (pyproject.toml)
+psutil==6.1.0
+ # via pyperf
+pyperf==2.8.0
+ # via bitcoin-core-deps (pyproject.toml)
+pyzmq==25.1.0
+ # via bitcoin-core-deps (pyproject.toml)
+toml==0.10.2
+ # via vulture
+tomli==2.0.2
+ # via mypy
+typing-extensions==4.12.2
+ # via mypy
+vulture==2.6
+ # via bitcoin-core-deps (pyproject.toml)
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000000..b1914a75d9
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,101 @@
+# Copyright 0xB10C, willcl-ark
+{ pkgs ? import
+ (fetchTarball "https://github.com/nixos/nixpkgs/archive/nixos-24.11.tar.gz")
+ { }, }:
+let
+ inherit (pkgs.lib) optionals strings;
+ inherit (pkgs) stdenv;
+
+ # Override the default cargo-flamegraph with a custom fork
+ cargo-flamegraph = pkgs.rustPlatform.buildRustPackage rec {
+ pname =
+ "flamegraph"; # Match the name in Cargo.toml, doesn't seem to work otherwise
+ version = "bitcoin-core";
+
+ src = pkgs.fetchFromGitHub {
+ owner = "willcl-ark";
+ repo = "flamegraph";
+ rev = "bitcoin-core";
+ sha256 = "sha256-tQbr3MYfAiOxeT12V9au5KQK5X5JeGuV6p8GR/Sgen4=";
+ };
+
+ doCheck = false;
+ cargoHash = "sha256-QWPqTyTFSZNJNayNqLmsQSu0rX26XBKfdLROZ9tRjrg=";
+
+ useFetchCargoVendor = true;
+
+ nativeBuildInputs =
+ pkgs.lib.optionals stdenv.hostPlatform.isLinux [ pkgs.makeWrapper ];
+ buildInputs = pkgs.lib.optionals stdenv.hostPlatform.isDarwin
+ [ pkgs.darwin.apple_sdk.frameworks.Security ];
+
+ postFixup = pkgs.lib.optionalString stdenv.hostPlatform.isLinux ''
+ wrapProgram $out/bin/cargo-flamegraph \
+ --set-default PERF ${pkgs.linuxPackages.perf}/bin/perf
+ wrapProgram $out/bin/flamegraph \
+ --set-default PERF ${pkgs.linuxPackages.perf}/bin/perf
+ '';
+ };
+
+in pkgs.mkShell {
+ nativeBuildInputs = with pkgs; [
+ autoconf
+ automake
+ boost
+ ccache
+ clang_18
+ cmake
+ libevent
+ libtool
+ pkg-config
+ sqlite
+ zeromq
+ ];
+ buildInputs = with pkgs; [
+ just
+ bash
+ git
+ shellcheck
+ python310
+ uv
+
+ # Benchmarking
+ cargo-flamegraph
+ flamegraph
+ hyperfine
+ jq
+ linuxKernel.packages.linux_6_6.perf
+ perf-tools
+ util-linux
+
+ # Binary patching
+ patchelf
+
+ # Guix
+ curl
+ getent
+ ];
+
+ shellHook = ''
+ echo "Bitcoin Core build nix-shell"
+ echo ""
+ echo "Setting up python venv"
+
+ # fixes libstdc++ issues and libgl.so issues
+ export LD_LIBRARY_PATH=${stdenv.cc.cc.lib}/lib/:$LD_LIBRARY_PATH
+
+ uv venv --python 3.10
+ source .venv/bin/activate
+ uv pip install -r pyproject.toml
+
+ patch-binary() {
+ if [ -z "$1" ]; then
+ echo "Usage: patch-binary "
+ return 1
+ fi
+ patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$1"
+ }
+ echo "Added patch-binary command"
+ echo " Usage: 'patch-binary '"
+ '';
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 89fdd855a4..8c42359d2d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -320,7 +320,7 @@ if(BUILD_DAEMON)
)
list(APPEND installable_targets bitcoind)
endif()
-if(WITH_MULTIPROCESS)
+if(WITH_MULTIPROCESS AND BUILD_DAEMON)
add_executable(bitcoin-node
bitcoind.cpp
init/bitcoin-node.cpp
@@ -332,8 +332,9 @@ if(WITH_MULTIPROCESS)
$
)
list(APPEND installable_targets bitcoin-node)
+endif()
- if(BUILD_TESTS)
+if(WITH_MULTIPROCESS AND BUILD_TESTS)
# bitcoin_ipc_test library target is defined here in src/CMakeLists.txt
# instead of src/test/CMakeLists.txt so capnp files in src/test/ are able to
# reference capnp files in src/ipc/capnp/ by relative path. The Cap'n Proto
@@ -347,7 +348,6 @@ if(WITH_MULTIPROCESS)
test/ipc_test.capnp
)
add_dependencies(bitcoin_ipc_test bitcoin_ipc_headers)
- endif()
endif()
diff --git a/src/bech32.cpp b/src/bech32.cpp
index 5694ad54c8..81695aceba 100644
--- a/src/bech32.cpp
+++ b/src/bech32.cpp
@@ -364,7 +364,7 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values
std::string ret;
ret.reserve(hrp.size() + 1 + values.size() + CHECKSUM_SIZE);
ret += hrp;
- ret += '1';
+ ret += SEPARATOR;
for (const uint8_t& i : values) ret += CHARSET[i];
for (const uint8_t& i : CreateChecksum(encoding, hrp, values)) ret += CHARSET[i];
return ret;
@@ -374,7 +374,7 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values
DecodeResult Decode(const std::string& str, CharLimit limit) {
std::vector errors;
if (!CheckCharacters(str, errors)) return {};
- size_t pos = str.rfind('1');
+ size_t pos = str.rfind(SEPARATOR);
if (str.size() > limit) return {};
if (pos == str.npos || pos == 0 || pos + CHECKSUM_SIZE >= str.size()) {
return {};
@@ -413,7 +413,7 @@ std::pair> LocateErrors(const std::string& str, Ch
return std::make_pair("Invalid character or mixed case", std::move(error_locations));
}
- size_t pos = str.rfind('1');
+ size_t pos = str.rfind(SEPARATOR);
if (pos == str.npos) {
return std::make_pair("Missing separator", std::vector{});
}
diff --git a/src/bech32.h b/src/bech32.h
index 33d1ca1935..6d5a68ec5a 100644
--- a/src/bech32.h
+++ b/src/bech32.h
@@ -21,8 +21,8 @@
namespace bech32
{
-/** The Bech32 and Bech32m checksum size */
-constexpr size_t CHECKSUM_SIZE = 6;
+static constexpr size_t CHECKSUM_SIZE = 6;
+static constexpr char SEPARATOR = '1';
enum class Encoding {
INVALID, //!< Failed decoding
diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp
index e657f01871..eec3362d4e 100644
--- a/src/bitcoin-chainstate.cpp
+++ b/src/bitcoin-chainstate.cpp
@@ -251,9 +251,6 @@ int main(int argc, char* argv[])
case BlockValidationResult::BLOCK_TIME_FUTURE:
std::cerr << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl;
break;
- case BlockValidationResult::BLOCK_CHECKPOINT:
- std::cerr << "the block failed to meet one of our checkpoints" << std::endl;
- break;
}
}
diff --git a/src/chain.h b/src/chain.h
index 13f7582385..c6d1640768 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -92,7 +92,7 @@ enum BlockStatus : uint32_t {
//! Reserved (was BLOCK_VALID_HEADER).
BLOCK_VALID_RESERVED = 1,
- //! All parent headers found, difficulty matches, timestamp >= median previous, checkpoint. Implies all parents
+ //! All parent headers found, difficulty matches, timestamp >= median previous. Implies all parents
//! are also at least TREE.
BLOCK_VALID_TREE = 2,
diff --git a/src/common/netif.cpp b/src/common/netif.cpp
index 08f034a412..7424f977c7 100644
--- a/src/common/netif.cpp
+++ b/src/common/netif.cpp
@@ -169,16 +169,9 @@ std::optional QueryDefaultGatewayImpl(sa_family_t family)
std::optional FromSockAddr(const struct sockaddr* addr)
{
- // Check valid length. Note that sa_len is not part of POSIX, and exists on MacOS and some BSDs only, so we can't
- // do this check in SetSockAddr.
- if (!(addr->sa_family == AF_INET && addr->sa_len == sizeof(struct sockaddr_in)) &&
- !(addr->sa_family == AF_INET6 && addr->sa_len == sizeof(struct sockaddr_in6))) {
- return std::nullopt;
- }
-
// Fill in a CService from the sockaddr, then drop the port part.
CService service;
- if (service.SetSockAddr(addr)) {
+ if (service.SetSockAddr(addr, addr->sa_len)) {
return (CNetAddr)service;
}
return std::nullopt;
diff --git a/src/common/pcp.cpp b/src/common/pcp.cpp
index 3cc1cba924..111a8e69d4 100644
--- a/src/common/pcp.cpp
+++ b/src/common/pcp.cpp
@@ -235,9 +235,9 @@ std::optional> PCPSendRecv(Sock &sock, const std::string &p
}
// Wait for response(s) until we get a valid response, a network error, or time out.
- auto cur_time = time_point_cast(steady_clock::now());
+ auto cur_time = time_point_cast(MockableSteadyClock::now());
auto deadline = cur_time + timeout_per_try;
- while ((cur_time = time_point_cast(steady_clock::now())) < deadline) {
+ while ((cur_time = time_point_cast(MockableSteadyClock::now())) < deadline) {
Sock::Event occurred = 0;
if (!sock.Wait(deadline - cur_time, Sock::RECV, &occurred)) {
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "%s: Could not wait on socket: %s\n", protocol, NetworkErrorString(WSAGetLastError()));
@@ -426,7 +426,7 @@ std::variant PCPRequestPortMap(const PCPMappingNonc
return MappingError::NETWORK_ERROR;
}
CService internal;
- if (!internal.SetSockAddr((struct sockaddr*)&internal_addr)) return MappingError::NETWORK_ERROR;
+ if (!internal.SetSockAddr((struct sockaddr*)&internal_addr, internal_addrlen)) return MappingError::NETWORK_ERROR;
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "pcp: Internal address after connect: %s\n", internal.ToStringAddr());
// Build request packet. Make sure the packet is zeroed so that reserved fields are zero
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index dffa9f2dd6..2b2dd4bb68 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -63,7 +63,6 @@ enum class BlockValidationResult {
BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on
BLOCK_INVALID_PREV, //!< A block this one builds on is invalid
BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad)
- BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints
BLOCK_HEADER_LOW_WORK //!< the block header may be on a too-little-work chain
};
diff --git a/src/init.cpp b/src/init.cpp
index 10abd503fc..cbe2ec62b4 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -607,7 +608,8 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, chainstate, and other validation data structures every operations. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checkaddrman=", strprintf("Run addrman consistency checks every operations. Use 0 to disable. (default: %u)", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checkmempool=", strprintf("Run mempool consistency checks every transactions. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
- argsman.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block %s (default: %u)", defaultChainParams->Checkpoints().GetHeight(), DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
+ // Checkpoints were removed. We keep `-checkpoints` as a hidden arg to display a more user friendly error when set.
+ argsman.AddArg("-checkpoints", "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
argsman.AddArg("-deprecatedrpc=", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
@@ -648,6 +650,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-blockmaxweight=", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
+ argsman.AddArg("-blockreservedweight=", strprintf("Reserve space for the fixed-size block header plus the largest coinbase transaction the mining software may add to the block. (default: %d).", DEFAULT_BLOCK_RESERVED_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
argsman.AddArg("-blockmintxfee=", strprintf("Set lowest fee rate (in %s/kvB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
argsman.AddArg("-blockversion=", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION);
@@ -877,6 +880,11 @@ bool AppInitParameterInteraction(const ArgsManager& args)
InitWarning(_("Option '-upnp' is set but UPnP support was dropped in version 29.0. Consider using '-natpmp' instead."));
}
+ // We removed checkpoints but keep the option to warn users who still have it in their config.
+ if (args.IsArgSet("-checkpoints")) {
+ InitWarning(_("Option '-checkpoints' is set but checkpoints were removed. This option has no effect."));
+ }
+
// Error if network-specific options (-addnode, -connect, etc) are
// specified in default section of config file, but not overridden
// on the command line or in this chain's section of the config file.
@@ -1015,6 +1023,23 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
}
+ if (args.IsArgSet("-blockmaxweight")) {
+ const auto max_block_weight = args.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
+ if (max_block_weight > MAX_BLOCK_WEIGHT) {
+ return InitError(strprintf(_("Specified -blockmaxweight (%d) exceeds consensus maximum block weight (%d)"), max_block_weight, MAX_BLOCK_WEIGHT));
+ }
+ }
+
+ if (args.IsArgSet("-blockreservedweight")) {
+ const auto block_reserved_weight = args.GetIntArg("-blockreservedweight", DEFAULT_BLOCK_RESERVED_WEIGHT);
+ if (block_reserved_weight > MAX_BLOCK_WEIGHT) {
+ return InitError(strprintf(_("Specified -blockreservedweight (%d) exceeds consensus maximum block weight (%d)"), block_reserved_weight, MAX_BLOCK_WEIGHT));
+ }
+ if (block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
+ return InitError(strprintf(_("Specified -blockreservedweight (%d) is lower than minimum safety value of (%d)"), block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT));
+ }
+ }
+
nBytesPerSigOp = args.GetIntArg("-bytespersigop", nBytesPerSigOp);
if (!g_wallet_init_interface.ParameterInteraction()) return false;
diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp
index 50b07bda70..e57d6679e1 100644
--- a/src/ipc/capnp/mining.capnp
+++ b/src/ipc/capnp/mining.capnp
@@ -35,7 +35,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
useMempool @0 :Bool $Proxy.name("use_mempool");
- coinbaseMaxAdditionalWeight @1 :UInt64 $Proxy.name("coinbase_max_additional_weight");
+ blockReservedWeight @1 :UInt64 $Proxy.name("block_reserved_weight");
coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops");
}
diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp
index 349e14a5d6..2d0780ec21 100644
--- a/src/kernel/chainparams.cpp
+++ b/src/kernel/chainparams.cpp
@@ -167,24 +167,6 @@ class CMainParams : public CChainParams {
fDefaultConsistencyChecks = false;
m_is_mockable_chain = false;
- checkpointData = {
- {
- { 11111, uint256{"0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"}},
- { 33333, uint256{"000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6"}},
- { 74000, uint256{"0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20"}},
- {105000, uint256{"00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97"}},
- {134444, uint256{"00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe"}},
- {168000, uint256{"000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763"}},
- {193000, uint256{"000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317"}},
- {210000, uint256{"000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e"}},
- {216116, uint256{"00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e"}},
- {225430, uint256{"00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932"}},
- {250000, uint256{"000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214"}},
- {279000, uint256{"0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40"}},
- {295000, uint256{"00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983"}},
- }
- };
-
m_assumeutxo_data = {
{
.height = 840'000,
@@ -280,12 +262,6 @@ class CTestNetParams : public CChainParams {
fDefaultConsistencyChecks = false;
m_is_mockable_chain = false;
- checkpointData = {
- {
- {546, uint256{"000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70"}},
- }
- };
-
m_assumeutxo_data = {
{
.height = 2'500'000,
@@ -384,12 +360,6 @@ class CTestNet4Params : public CChainParams {
fDefaultConsistencyChecks = false;
m_is_mockable_chain = false;
- checkpointData = {
- {
- {},
- }
- };
-
m_assumeutxo_data = {
{}
};
@@ -607,12 +577,6 @@ class CRegTestParams : public CChainParams
fDefaultConsistencyChecks = true;
m_is_mockable_chain = true;
- checkpointData = {
- {
- {0, uint256{"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"}},
- }
- };
-
m_assumeutxo_data = {
{ // For use by unit tests
.height = 110,
diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h
index f7ea141c37..77991d497d 100644
--- a/src/kernel/chainparams.h
+++ b/src/kernel/chainparams.h
@@ -16,7 +16,6 @@
#include
#include
-#include