diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh
new file mode 100755
index 0000000000..a22f7cf0ae
--- /dev/null
+++ b/.claude/hooks/session-start.sh
@@ -0,0 +1,386 @@
+#!/bin/bash
+set -euo pipefail
+
+# Only run in remote (Cloud) environments
+if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
+ exit 0
+fi
+
+PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/user/firo}"
+BUILD_DIR="${CLAUDE_BUILD_DIR:-${PROJECT_DIR}/build}"
+BLS_PREFIX="${CLAUDE_PREFIX_DIR:-/usr/local}"
+HOOK_STATE_DIR="${CLAUDE_HOOK_STATE_DIR:-${BLS_PREFIX}/share/firo-cloud-session-start}"
+BUILD_STAMP_FILE="${BUILD_DIR}/.build_stamp"
+BLS_STAMP_FILE="${HOOK_STATE_DIR}/bls-dash.stamp"
+LIBBACKTRACE_STAMP_FILE="${HOOK_STATE_DIR}/libbacktrace.stamp"
+LIBTOR_STAMP_FILE="${HOOK_STATE_DIR}/libtor.stamp"
+PATH_EXPORT_LINE="export PATH=\"${BUILD_DIR}/bin:\$PATH\""
+
+REQUIRED_BUILD_BINS=(
+ firod
+ firo-cli
+ firo-tx
+ test_firo
+)
+
+BLS_VERSION="1.1.0"
+BLS_SHA256="276c8573104e5f18bb5b9fd3ffd49585dda5ba5f6de2de74759dda8ca5a9deac"
+RELIC_COMMIT="3a23142be0a5510a3aa93cd6c76fc59d3fc732a5"
+RELIC_SHA256="ddad83b1406985a1e4703bd03bdbab89453aa700c0c99567cf8de51c205e5dde"
+
+# Pin to the same commit and hash used by depends/packages/backtrace.mk
+LIBBACKTRACE_COMMIT="b9e40069c0b47a722286b94eb5231f7f05c08713"
+LIBBACKTRACE_SHA256="81b37e762965c676b3316e90564c89f6480606add446651c785862571a1fdbca"
+LIBTOR_STUB_VERSION="1"
+
+ensure_path_export()
+{
+ if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
+ if ! grep -qF "${PATH_EXPORT_LINE}" "$CLAUDE_ENV_FILE" 2>/dev/null; then
+ echo "${PATH_EXPORT_LINE}" >> "$CLAUDE_ENV_FILE"
+ fi
+ fi
+}
+
+stamp_matches()
+{
+ local stamp_path="$1"
+ local expected="$2"
+ local current
+
+ [ -f "${stamp_path}" ] || return 1
+ current=$(<"${stamp_path}")
+ [ "${current}" = "${expected}" ]
+}
+
+write_stamp()
+{
+ local stamp_path="$1"
+ local content="$2"
+ local stamp_dir tmp_file
+
+ stamp_dir="$(dirname "${stamp_path}")"
+ tmp_file="$(mktemp)"
+ printf '%s\n' "${content}" > "${tmp_file}"
+
+ if mkdir -p "${stamp_dir}" 2>/dev/null && install -m 644 "${tmp_file}" "${stamp_path}" 2>/dev/null; then
+ rm -f "${tmp_file}"
+ return 0
+ fi
+
+ sudo install -d "${stamp_dir}"
+ sudo install -m 644 "${tmp_file}" "${stamp_path}"
+ rm -f "${tmp_file}"
+}
+
+build_outputs_present()
+{
+ local bin_name
+
+ for bin_name in "${REQUIRED_BUILD_BINS[@]}"; do
+ [ -f "${BUILD_DIR}/bin/${bin_name}" ] || return 1
+ done
+}
+
+build_stamp_content()
+{
+ local git_head git_status_hash
+
+ git_head="$(git -C "${PROJECT_DIR}" rev-parse HEAD)"
+ git_status_hash="$(git -C "${PROJECT_DIR}" status --porcelain --untracked-files=no | sha256sum | cut -d' ' -f1)"
+
+ cat </dev/null
+ echo "Acquire::https::Proxy \"${HTTP_PROXY}\";" | sudo tee -a /etc/apt/apt.conf.d/99proxy >/dev/null
+fi
+
+########################################
+# 2. Install system packages
+########################################
+PACKAGES=(
+ # Build tools
+ ccache
+ ninja-build
+ pkg-config
+ autoconf
+ automake
+ libtool
+ bsdmainutils
+
+ # Core libraries
+ libboost-system-dev
+ libboost-filesystem-dev
+ libboost-program-options-dev
+ libboost-thread-dev
+ libboost-chrono-dev
+ libboost-atomic-dev
+ libboost-test-dev
+ libssl-dev
+ libevent-dev
+ libgmp-dev
+ libzmq3-dev
+ libsqlite3-dev
+ libdb5.3++-dev
+ libminiupnpc-dev
+ zlib1g-dev
+
+ # Linting
+ clang-format
+
+ # Python test dependencies
+ python3-zmq
+)
+
+MISSING=()
+for pkg in "${PACKAGES[@]}"; do
+ if ! dpkg -s "$pkg" &>/dev/null; then
+ MISSING+=("$pkg")
+ fi
+done
+
+if [ ${#MISSING[@]} -gt 0 ]; then
+ sudo apt-get update -qq
+ sudo apt-get install -y --no-install-recommends "${MISSING[@]}"
+fi
+
+########################################
+# 3. Build bls-dash from source
+# (github.com is accessible via proxy)
+########################################
+if [ ! -f "${BLS_PREFIX}/lib/libbls-dash.a" ] \
+ || ! stamp_matches "${BLS_STAMP_FILE}" "${BLS_STAMP_CONTENT}"; then
+ WORK_DIR=$(mktemp -d)
+
+ curl -sL "https://github.com/dashpay/bls-signatures/archive/${BLS_VERSION}.tar.gz" \
+ -o "${WORK_DIR}/bls.tar.gz"
+ curl -sL "https://github.com/relic-toolkit/relic/archive/${RELIC_COMMIT}.tar.gz" \
+ -o "${WORK_DIR}/relic.tar.gz"
+
+ # Verify downloaded tarballs against known hashes
+ echo "${BLS_SHA256} ${WORK_DIR}/bls.tar.gz" | sha256sum -c - || \
+ { echo "ERROR: bls-signatures tarball SHA256 mismatch"; rm -rf "${WORK_DIR}"; exit 1; }
+ echo "${RELIC_SHA256} ${WORK_DIR}/relic.tar.gz" | sha256sum -c - || \
+ { echo "ERROR: relic tarball SHA256 mismatch"; rm -rf "${WORK_DIR}"; exit 1; }
+
+ mkdir -p "${WORK_DIR}/bls-signatures"
+ tar -xzf "${WORK_DIR}/bls.tar.gz" -C "${WORK_DIR}/bls-signatures" --strip-components=1
+
+ # Apply Firo's patches first (before URL substitution)
+ cd "${WORK_DIR}/bls-signatures"
+ if [ -f "${PROJECT_DIR}/depends/patches/bls-dash/bls-signatures.patch" ]; then
+ patch -p1 < "${PROJECT_DIR}/depends/patches/bls-dash/bls-signatures.patch"
+ fi
+
+ # Point CMake at local relic tarball instead of git
+ sed -i "s|GIT_REPOSITORY https://github.com/relic-toolkit/relic.git|URL \"${WORK_DIR}/relic.tar.gz\"|" \
+ src/CMakeLists.txt
+ sed -i "s|GIT_TAG.*RELIC_GIT_TAG.*|URL_HASH SHA256=${RELIC_SHA256}|" \
+ src/CMakeLists.txt
+
+ # Build with Unix Makefiles (Ninja has globbing issues with the combined archive step)
+ cmake -G "Unix Makefiles" \
+ -DCMAKE_INSTALL_PREFIX="${BLS_PREFIX}" \
+ -DCMAKE_PREFIX_PATH="${BLS_PREFIX}" \
+ -DSTLIB=ON -DSHLIB=OFF -DSTBIN=ON \
+ -DBUILD_BLS_PYTHON_BINDINGS=0 \
+ -DBUILD_BLS_TESTS=0 \
+ -DBUILD_BLS_BENCHMARKS=0 \
+ -DOPSYS=LINUX -DCMAKE_SYSTEM_NAME=Linux \
+ -DWSIZE=64 \
+ "-DCMAKE_C_FLAGS=-UBLSALLOC_SODIUM" \
+ "-DCMAKE_CXX_FLAGS=-UBLSALLOC_SODIUM" \
+ -S"${WORK_DIR}/bls-signatures" -B"${WORK_DIR}/bls-build"
+
+ make -C "${WORK_DIR}/bls-build" -j"$(nproc)"
+ sudo cmake --install "${WORK_DIR}/bls-build"
+ sudo ldconfig
+ write_stamp "${BLS_STAMP_FILE}" "${BLS_STAMP_CONTENT}"
+
+ rm -rf "${WORK_DIR}"
+ cd "${PROJECT_DIR}"
+fi
+
+########################################
+# 4. Build libbacktrace from source
+# (needed for ENABLE_CRASH_HOOKS)
+########################################
+if [ ! -f "${BLS_PREFIX}/lib/libbacktrace.a" ] \
+ || ! stamp_matches "${LIBBACKTRACE_STAMP_FILE}" "${LIBBACKTRACE_STAMP_CONTENT}"; then
+ WORK_DIR=$(mktemp -d)
+ curl -sL "https://github.com/ianlancetaylor/libbacktrace/archive/${LIBBACKTRACE_COMMIT}.tar.gz" \
+ -o "${WORK_DIR}/libbacktrace.tar.gz"
+ echo "${LIBBACKTRACE_SHA256} ${WORK_DIR}/libbacktrace.tar.gz" | sha256sum -c - || \
+ { echo "ERROR: libbacktrace tarball SHA256 mismatch"; rm -rf "${WORK_DIR}"; exit 1; }
+ mkdir -p "${WORK_DIR}/libbacktrace"
+ tar -xzf "${WORK_DIR}/libbacktrace.tar.gz" -C "${WORK_DIR}/libbacktrace" --strip-components=1
+
+ cd "${WORK_DIR}/libbacktrace"
+ ./configure --prefix="${BLS_PREFIX}" --enable-static --disable-shared CFLAGS="-fPIC"
+ make -j"$(nproc)"
+ sudo make install
+ write_stamp "${LIBBACKTRACE_STAMP_FILE}" "${LIBBACKTRACE_STAMP_CONTENT}"
+
+ rm -rf "${WORK_DIR}"
+ cd "${PROJECT_DIR}"
+fi
+
+########################################
+# 5. Create stub libtor.a
+# archive.torproject.org is blocked
+# by the cloud egress proxy, so we
+# create a stub with the symbols
+# referenced by src/init.cpp.
+# The embedded Tor is not needed for
+# development/testing workflows.
+########################################
+if [ ! -f "${BLS_PREFIX}/lib/libtor.a" ] \
+ || ! stamp_matches "${LIBTOR_STAMP_FILE}" "${LIBTOR_STAMP_CONTENT}"; then
+ WORK_DIR=$(mktemp -d)
+
+ cat > "${WORK_DIR}/tor_stub.c" << 'STUBEOF'
+#include
+int tor_main(int argc, char *argv[]) {
+ (void)argc; (void)argv;
+ fprintf(stderr, "tor: stub library - embedded Tor not available in this build\n");
+ return 1;
+}
+void tor_cleanup(void) {}
+STUBEOF
+
+ gcc -c -fPIC "${WORK_DIR}/tor_stub.c" -o "${WORK_DIR}/tor_stub.o"
+ ar rcs "${WORK_DIR}/libtor.a" "${WORK_DIR}/tor_stub.o"
+ sudo install -m 644 "${WORK_DIR}/libtor.a" "${BLS_PREFIX}/lib/libtor.a"
+ write_stamp "${LIBTOR_STAMP_FILE}" "${LIBTOR_STAMP_CONTENT}"
+
+ rm -rf "${WORK_DIR}"
+fi
+
+########################################
+# 6. Configure CMake
+# The upstream AddBoostIfNeeded.cmake
+# hardcodes Boost_USE_STATIC_RUNTIME=ON.
+# Cloud uses system shared Boost, so we
+# patch the local copy and hide the
+# change from git with assume-unchanged.
+########################################
+if [ ! -f "${BUILD_DIR}/build.ninja" ] \
+ || ! stamp_matches "${BUILD_STAMP_FILE}" "${BUILD_STAMP_CONTENT}"; then
+ ensure_boost_runtime_override
+ cmake -G Ninja \
+ -DBUILD_DAEMON=ON \
+ -DBUILD_CLI=ON \
+ -DBUILD_TX=ON \
+ -DBUILD_GUI=OFF \
+ -DBUILD_TESTS=ON \
+ -DENABLE_WALLET=ON \
+ -DWITH_BDB=ON \
+ -DWITH_ZMQ=ON \
+ -DENABLE_CRASH_HOOKS=ON \
+ -DWARN_INCOMPATIBLE_BDB=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
+ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
+ -S"${PROJECT_DIR}" -B"${BUILD_DIR}"
+fi
+
+########################################
+# 7. Build
+########################################
+if ! build_outputs_present \
+ || ! stamp_matches "${BUILD_STAMP_FILE}" "${BUILD_STAMP_CONTENT}"; then
+ cmake --build "${BUILD_DIR}" -j"$(nproc)"
+fi
+
+if build_outputs_present; then
+ write_stamp "${BUILD_STAMP_FILE}" "${BUILD_STAMP_CONTENT}"
+fi
+
+########################################
+# 8. Set environment variables
+########################################
+ensure_path_export
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000000..3d10a7757b
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,15 @@
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh",
+ "timeout": 1800
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml
index 832a46236d..bed3ce726e 100644
--- a/.github/workflows/ci-master.yml
+++ b/.github/workflows/ci-master.yml
@@ -19,7 +19,7 @@ env:
jobs:
get-commit-hash:
name: Get Commit Hash String
- runs-on: ubuntu-latest
+ runs-on: ubuntu-slim
outputs:
short_sha: ${{ steps.short-sha.outputs.sha }}
steps:
@@ -78,7 +78,7 @@ jobs:
steps:
- name: Set up Python 3.8
if: matrix.is_linux
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v6
with:
python-version: '3.8'
- name: Checkout
@@ -101,19 +101,19 @@ jobs:
run: |
set -exo pipefail
sudo apt-get update
- sudo apt-get install -y python3-zmq cmake build-essential ninja-build ccache
+ sudo apt-get install -y python3-zmq cmake build-essential ninja-build meson ccache
- name: Install Required Packages (Windows)
if: matrix.is_windows
run: |
set -exo pipefail
sudo apt-get update
- sudo apt-get install -y g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 cmake mingw-w64-common mingw-w64-i686-dev mingw-w64-x86-64-dev wget unzip ninja-build ccache
+ sudo apt-get install -y g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 cmake mingw-w64-common mingw-w64-i686-dev mingw-w64-x86-64-dev wget unzip ninja-build meson ccache
- name: Install Required Packages (Mac)
if: matrix.is_mac
run: |
set -exo pipefail
brew uninstall --force cmake
- brew install --force automake coreutils python-setuptools cmake make pkg-config ninja ccache
+ brew install --force automake coreutils python-setuptools cmake make pkg-config ninja meson ccache
- name: Install setuptools
if: matrix.is_mac
run: |
@@ -175,9 +175,11 @@ jobs:
depends-${{ matrix.platform }}-${{ steps.depends-host-triplet.outputs.value }}-
depends-${{ matrix.platform }}-
- name: Download Dependencies
- if: steps.cache-depends.outputs.cache-hit != 'true'
run: |
set -exo pipefail
+ # Clear download stamps to ensure new/changed extra sources are fetched
+ # even when restoring from a stale cache (partial restore-key match).
+ rm -rf depends/sources/download-stamps
attempt=1
max=5
until make -C depends download; do
@@ -301,7 +303,7 @@ jobs:
mkdir -p $ARTIFACT_DIR
mv build/bin/firo-cli${{ matrix.bin_ext }} build/bin/firo-tx${{ matrix.bin_ext }} build/bin/firod${{ matrix.bin_ext }} build/bin/firo-qt${{ matrix.bin_ext }} $ARTIFACT_DIR
- name: Upload Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.platform }}-cmake-${{ matrix.build_type }}-binaries-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}
@@ -323,7 +325,7 @@ jobs:
fi
- name: Upload Test Logs Artifact
if: failure() && matrix.is_linux
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: test-${{ matrix.platform }}-cmake-${{ matrix.build_type }}-logs-${{ env.COMMIT_HASH }}
path: ${{ env.TEST_LOG_ARTIFACT_DIR }}
@@ -367,10 +369,6 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0 # Optional: Ensures git describe finds tags correctly
- - name: Set up Python 3.8
- uses: actions/setup-python@v4
- with:
- python-version: '3.8'
- name: Free Disk Space
run: |
set -exo pipefail
@@ -430,26 +428,90 @@ jobs:
sudo aa-complain unprivileged_userns || true
# Remove apparmor_parser so Guix installer skips AppArmor setup
sudo apt-get remove -y apparmor || true
- # Pre-import Guix signing keys into root's keyring (installer runs as sudo), sometimes guix script fail to fetch keys from ftpmirror.gnu.org
+
export VER=1.5.0
export ARCH=x86_64-linux
export BASE=https://ftp.gnu.org/gnu/guix
export TAR="guix-binary-${VER}.${ARCH}.tar.xz"
- # --- verify the signature, but never abort the job ---
- if gpg --verify "${TAR}.sig" "${TAR}" &> /dev/null
- then
- echo "GPG verification succeeded."
- else
- echo "GPG verification failed – continuing anyway."
- fi
+ export GUIX_INSTALLER_SHA256=7f544e48c9082baf489207ac506acd78bf251355bbafffd744498e658880879d
+ export GUIX_TAR_SHA256=aa41025489c5061543e9c48873eaa829b900b2da75d40f9648913622f5f47817
+
+ # Keep these trust anchors explicit and reviewed. This set matches
+ # the release-signing keys trusted by the Guix 1.5.0 installer, so
+ # the release can be signed by a different Guix maintainer without
+ # letting a downloaded script decide our trust policy.
+ GUIX_KEY_FPRS=(
+ 3CE464558A84FDC69DB40CFB090B11993D9AEBB5
+ 27D586A4F8900854329FF09F1260E46482E63562
+ A28BF40C3E551372662D14F741AAE7DCCA3D8351
+ )
+ GUIX_FPR_REGEX="$(IFS='|'; echo "${GUIX_KEY_FPRS[*]}")"
+
cd /tmp
- wget -q "${BASE}/${TAR}" -O "${TAR}"
- wget -q "${BASE}/${TAR}.sig" -O "${TAR}.sig"
- wget -q https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh -O guix-install.sh
+
+ # 1. Download and pin-check all unsigned inputs before use.
+ echo "::group::Download Guix installer and binary tarball"
+ wget --tries=5 --timeout=30 --waitretry=10 \
+ "https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh?h=v${VER}" \
+ -O guix-install.sh
+ echo "${GUIX_INSTALLER_SHA256} guix-install.sh" | sha256sum -c -
chmod +x guix-install.sh
+
+ wget --tries=5 --timeout=30 --waitretry=10 "${BASE}/${TAR}" -O "${TAR}"
+ echo "${GUIX_TAR_SHA256} ${TAR}" | sha256sum -c -
+ wget --tries=5 --timeout=30 --waitretry=10 "${BASE}/${TAR}.sig" -O "${TAR}.sig"
+ echo "::endgroup::"
+
+ # 2. Best-effort import each trusted key into an isolated keyring.
+ # Some maintainers may not have published their key to every
+ # keyserver (or to any keyserver at all), so we don't fail on
+ # individual misses. We only fail later if NONE of the imported
+ # keys can verify the actual tarball signature. The trust set
+ # is still bounded by the pinned list: VALIDSIG must
+ # name a fingerprint from GUIX_FPR_REGEX, regardless of which
+ # keys were imported.
+ echo "::group::Import trusted Guix maintainer keys"
+ export GNUPGHOME=$(mktemp -d)
+ chmod 700 "${GNUPGHOME}"
+ imported_count=0
+ for fpr in "${GUIX_KEY_FPRS[@]}"; do
+ for ks in hkps://keys.openpgp.org hkps://keyserver.ubuntu.com hkps://pgp.mit.edu; do
+ if gpg --batch --keyserver "${ks}" --recv-keys "${fpr}"; then
+ # Confirm the imported key actually has the expected fingerprint.
+ if gpg --list-keys --with-colons "${fpr}" \
+ | awk -F: '$1=="fpr"{print $10}' | grep -qx "${fpr}"; then
+ imported_count=$((imported_count + 1))
+ break
+ fi
+ fi
+ echo "Keyserver ${ks} failed for ${fpr}, trying next..."
+ done
+ done
+ if [ "${imported_count}" -eq 0 ]; then
+ echo "Could not import any trusted Guix signing key from any keyserver"
+ exit 1
+ fi
+ echo "Imported ${imported_count}/${#GUIX_KEY_FPRS[@]} trusted keys"
+ echo "::endgroup::"
+
+ # 3. Verify the tarball signature against the trusted keys (blocking).
+ # The keyring contains ONLY the pinned trust anchors, so a valid
+ # signature must come from one of them; the explicit VALIDSIG
+ # fingerprint match also documents which key signed this release.
+ echo "::group::Verify tarball signature"
+ gpg --status-fd 1 --verify "${TAR}.sig" "${TAR}" > gpg-status.txt
+ cat gpg-status.txt
+ awk -v allowed="${GUIX_FPR_REGEX}" '
+ $1 == "[GNUPG:]" && $2 == "VALIDSIG" &&
+ ($3 ~ "^(" allowed ")$" || $NF ~ "^(" allowed ")$") { found=1 }
+ END { exit found ? 0 : 1 }
+ ' gpg-status.txt
+ echo "::endgroup::"
+
+ # 4. Run the pinned installer with the cryptographically verified tarball.
# Disable pipefail strictly for this command so 'yes' crashing doesn't fail the build
set +o pipefail
- yes '' 2>/dev/null | sudo env GUIX_BINARY_FILE_NAME="/tmp/${TAR}" ./guix-install.sh # Tell installer to use your already-verified tarball
+ yes '' 2>/dev/null | sudo env GUIX_BINARY_FILE_NAME="/tmp/${TAR}" ./guix-install.sh
set -o pipefail
echo "Guix install script completed"
source /etc/profile.d/zzz-guix.sh
@@ -543,14 +605,14 @@ jobs:
mv share/man . || true
- name: Upload Release Binaries (Linux)
if: ${{ !matrix.is_darwin && !matrix.is_windows }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-release-binaries-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-release-binaries.tar.gz
compression-level: 0
- name: Upload Debug Binaries (Linux)
if: ${{ !matrix.is_darwin && !matrix.is_windows }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-debug-binaries-${{ env.COMMIT_HASH }}
path: |
@@ -562,56 +624,56 @@ jobs:
compression-level: 6
- name: Upload Debug Symbols (Linux)
if: ${{ !matrix.is_darwin && !matrix.is_windows }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-debug-symbols-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-debug-symbols.tar.gz
compression-level: 0
- name: Upload Check Sums (Linux)
if: ${{ !matrix.is_darwin && !matrix.is_windows }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-checksums-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-checksums-sha256
compression-level: 0
- name: Upload Windows Release Binaries
if: matrix.is_windows
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: x86_64-win64-guix-release-binaries-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-release-binaries.zip
compression-level: 0
- name: Upload Windows Debug Symbols
if: matrix.is_windows
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: x86_64-win64-guix-debug-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-debug-symbols.zip
compression-level: 0
- name: Upload Windows Installer (Unsigned)
if: matrix.is_windows
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: x86_64-win64-guix-installer-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-installer-unsigned.exe
compression-level: 0
- name: Upload Windows Setup Sign Bundle
if: matrix.is_windows
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: x86_64-win64-guix-setup-sign-bundle-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-setup-sign-bundle.tar.gz
compression-level: 0
- name: Upload Windows Checksums
if: matrix.is_windows
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: x86_64-win64-guix-checksums-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-checksums-sha256
compression-level: 0
- name: Upload MacOS Release Binaries
if: matrix.is_darwin
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-release-binaries-${{ env.COMMIT_HASH }}
path: |
@@ -624,22 +686,22 @@ jobs:
compression-level: 6
- name: Upload MacOS Sign Bundle
if: matrix.is_darwin
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-sign-bundle-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-sign-bundle.tar.gz
compression-level: 0
- name: Upload MacOS App Image
if: matrix.is_darwin
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-app-image-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-app-image.tar.gz
compression-level: 0
- name: Upload Check Sums (Darwin)
if: matrix.is_darwin
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact_prefix }}-checksums-${{ env.COMMIT_HASH }}
path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-checksums-sha256
- compression-level: 0
\ No newline at end of file
+ compression-level: 0
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..3e35a0bcdf
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,297 @@
+# AGENTS.md - Firo repository guide
+
+This file is a concise guide for coding agents working in the Firo repository. It is intentionally practical: use it to orient yourself quickly, choose the right build and test commands, and avoid risky changes in consensus- or wallet-sensitive code.
+
+## Project snapshot
+
+- Privacy-focused cryptocurrency forked from Bitcoin Core
+- Current version in `CMakeLists.txt`: `0.14.15.3`
+- Languages: C++20 and C11
+- Build system: CMake 3.22+ with out-of-tree builds only
+- Main executables:
+ - `firod`
+ - `firo-cli`
+ - `firo-tx`
+ - `firo-qt`
+
+Core features include Spark and Lelantus privacy protocols, deterministic masternodes, LLMQ-based ChainLocks and InstantSend, BIP47 payment codes, and FiroPOW mining.
+
+## Repository map
+
+Top-level directories you will touch most often:
+
+- `src/` - production C++ code
+- `src/test/` - Boost unit tests
+- `src/wallet/test/`, `src/libspark/test/`, `src/liblelantus/test/`, `src/hdmint/test/` - domain-specific unit tests
+- `qa/rpc-tests/` - Python functional tests
+- `qa/pull-tester/` - functional test runner and framework
+- `depends/` - deterministic dependency build system and cross-compilation support
+- `cmake/` - CMake modules and helper logic
+- `doc/` - developer notes and build documentation
+- `.github/workflows/ci-master.yml` - main CI pipeline
+
+Important source areas:
+
+- `src/validation.cpp` - block and transaction validation, consensus-critical
+- `src/net_processing.cpp` / `src/net.cpp` - peer-to-peer networking
+- `src/init.cpp` - startup and shutdown wiring
+- `src/miner.cpp` / `src/pow.cpp` - mining and proof-of-work logic
+- `src/chainparams.cpp` - network configuration
+- `src/txmempool.cpp` - mempool behavior
+- `src/wallet/` - wallet storage, RPC, coin selection, privacy spends
+- `src/libspark/`, `src/spark/` - current privacy protocol
+- `src/liblelantus/` - legacy privacy protocol
+- `src/evo/`, `src/llmq/` - masternodes, quorums, ChainLocks, InstantSend
+
+## Build quickstart
+
+Firo uses a two-stage build:
+
+1. Build dependencies with `depends/`
+2. Configure and build with CMake
+
+Preferred Linux build matching CI:
+
+```bash
+make -C depends -j$(nproc)
+
+export HOST_TRIPLET=$(depends/config.guess)
+env PKG_CONFIG_PATH="$(realpath depends/$HOST_TRIPLET/lib/pkgconfig):$PKG_CONFIG_PATH" \
+cmake -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE="$(realpath depends/$HOST_TRIPLET/toolchain.cmake)" \
+ -DBUILD_CLI=ON \
+ -DBUILD_DAEMON=ON \
+ -DBUILD_GUI=ON \
+ -DBUILD_TESTS=ON \
+ -DENABLE_CRASH_HOOKS=ON \
+ -DCMAKE_BUILD_TYPE=Release \
+ -S "$(pwd)" \
+ -B "$(pwd)/build"
+
+cmake --build build
+```
+
+Headless build:
+
+```bash
+cmake -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE="$(realpath depends/$HOST_TRIPLET/toolchain.cmake)" \
+ -DBUILD_GUI=OFF \
+ -DBUILD_CLI=ON \
+ -DBUILD_DAEMON=ON \
+ -DBUILD_TESTS=ON \
+ -S "$(pwd)" \
+ -B "$(pwd)/build"
+cmake --build build
+```
+
+Useful CMake options:
+
+| Option | Default | Notes |
+|--------|---------|-------|
+| `BUILD_DAEMON` | `ON` | Builds `firod` |
+| `BUILD_GUI` | `ON` | Builds `firo-qt`; requires Qt `6.7.3` |
+| `BUILD_CLI` | `ON` | Builds `firo-cli` |
+| `BUILD_TX` | `${BUILD_CLI}` | Builds `firo-tx` |
+| `BUILD_TESTS` | `OFF` | Builds `test_firo` and test targets |
+| `BUILD_BENCH` | `OFF` | Builds `bench_firo` |
+| `ENABLE_WALLET` | `ON` | Wallet support |
+| `WITH_BDB` | `OFF` | Legacy Berkeley DB wallet support |
+| `WITH_ZMQ` | `ON` | ZMQ notification support |
+| `ENABLE_CRASH_HOOKS` | config-dependent | Enabled by default for release-like single-config builds |
+| `CLIENT_VERSION_IS_RELEASE` | `false` | Set in CI per build type |
+
+Build outputs are placed under `build/bin/` and libraries under `build/lib/`.
+
+### Cross-compilation
+
+`depends/` supports multiple host triplets. Typical examples:
+
+- Native Linux: `$(depends/config.guess)`
+- Windows: `x86_64-w64-mingw32`
+- Linux ARM64: `aarch64-linux-gnu`
+- macOS cross-builds require an SDK in `depends/SDKs/`
+
+Example Windows dependency build:
+
+```bash
+make -C depends HOST=x86_64-w64-mingw32 -j$(nproc)
+```
+
+## Testing
+
+Always run the most targeted tests that cover your change. For high-risk areas such as consensus, wallet accounting, Spark/Lelantus logic, or networking, broaden coverage before finishing.
+
+### Unit tests
+
+Build with `-DBUILD_TESTS=ON`, then run:
+
+```bash
+cd build
+ctest --output-on-failure
+```
+
+Run a specific Boost test suite:
+
+```bash
+./bin/test_firo --run_test= --catch_system_error=no --log_level=test_suite -- DEBUG_LOG_OUT
+```
+
+Examples:
+
+- `./bin/test_firo --run_test=lelantus_tests`
+- `./bin/test_firo --run_test=spark_wallet_tests`
+
+Test registration is driven from `src/test/CMakeLists.txt`.
+
+### Functional / RPC tests
+
+Functional tests live under `qa/rpc-tests/` and launch real `firod` nodes in regtest mode.
+
+Before running the harness, copy binaries where the test framework expects them:
+
+```bash
+cp -rf build/bin/* build/src/
+qa/pull-tester/rpc-tests.py -extended
+```
+
+Run a single script directly:
+
+```bash
+qa/rpc-tests/.py
+```
+
+Useful environment variables:
+
+- `FIROD` - defaults to `build/src/firod`
+- `FIROCLI` - defaults to `build/src/firo-cli`
+
+### Benchmarks and fuzzing
+
+Benchmarks:
+
+```bash
+cmake -B build -DBUILD_BENCH=ON ...
+cmake --build build
+./build/bin/bench_firo
+```
+
+Fuzz targets live in `src/fuzz/`. See `doc/fuzzing.md` for setup details.
+
+## CI reference
+
+The main workflow is `.github/workflows/ci-master.yml`.
+
+Current CI behavior:
+
+- Runs on pushes to all branches and PRs targeting `master`
+- Ignores `doc/**` and `**/README.md`
+- Linux matrix: Release and Debug, with unit tests and RPC tests
+- Windows matrix: Release and Debug cross-builds on Ubuntu
+- macOS matrix: Release and Debug builds
+- Uses `ccache`
+- Caches `depends/` artifacts
+
+If you want to reproduce CI most closely, use Ninja, the `depends/` toolchain, and the same build flags shown above.
+
+## Coding conventions
+
+Source of truth:
+
+- `src/.clang-format`
+- `doc/developer-notes.md`
+
+Important rules:
+
+- 4-space indentation, no tabs
+- Linux brace style: new line for namespaces, classes, and function definitions
+- No column limit in formatting rules
+- Prefer `++i` over `i++`
+- Avoid `using namespace`
+- Use RAII and smart pointers instead of manual ownership
+- Use `std::string` instead of C string APIs where practical
+- Use `.find()` on maps for reads; avoid `operator[]` when it would insert
+- Use `ParseInt32`, `ParseInt64`, `ParseUInt32`, `ParseUInt64`, and `ParseDouble` from `utilstrencodings.h`
+- Avoid variable shadowing; `-Wshadow` is enabled
+- Use `uint8_t` or `int8_t` instead of plain `char` when signedness matters
+- Assertions must not have side effects
+
+Threading and locking:
+
+- Use `LOCK` / `TRY_LOCK` macros
+- Scope lock regions carefully with braces
+- Debug builds can help surface lock-order issues
+
+Documentation style:
+
+- Prefer Doxygen-compatible comments for non-obvious public interfaces
+- Use succinct comments for tricky logic, not commentary on obvious assignments
+
+## Risk areas and change guidance
+
+Be especially careful in these parts of the tree:
+
+- Consensus and validation: `src/validation.cpp`, `src/consensus/`, `src/pow.cpp`
+- Chain parameters and network selection: `src/chainparams.cpp`
+- Mempool and relay behavior: `src/txmempool.cpp`, `src/net_processing.cpp`
+- Wallet correctness and privacy spends: `src/wallet/`, `src/spark/`, `src/liblelantus/`, `src/libspark/`
+- LLMQ / masternodes: `src/llmq/`, `src/evo/`
+
+Guidelines for agents:
+
+- Keep changes minimal and localized
+- Do not mix pure formatting changes with behavior changes
+- Add or update tests when behavior changes
+- Treat privacy, consensus, wallet balance accounting, serialization, and networking as high-regression areas
+- Prefer exposing new behavior through RPC before adding GUI surfaces
+- When touching a subtree directory such as `src/secp256k1`, `src/leveldb`, `src/univalue`, or `src/crypto/ctaes`, verify whether the change belongs upstream
+
+## Network ports
+
+| Network | P2P | RPC |
+|---------|-----|-----|
+| Mainnet | 8168 | 8888 |
+| Testnet | 18168 | 18888 |
+| Devnet | 38168 | 38888 |
+| Regtest | 18444 | 28888 |
+
+Use `-regtest` for local multi-node functional testing and `-testnet` for public test network behavior.
+
+## PR and commit conventions
+
+Preferred PR title prefixes:
+
+- `Consensus:`
+- `Net:` or `P2P:`
+- `RPC/REST/ZMQ:`
+- `Wallet:`
+- `Qt:`
+- `Mining:`
+- `Tests:`
+- `Docs:`
+- `Utils and libraries:`
+- `Scripts and tools:`
+- `Trivial:`
+
+PR template: `.github/pull_request_template.md`
+
+It expects:
+
+1. `## PR intention`
+2. `## Code changes brief`
+
+Commit guidance:
+
+- Keep commits atomic
+- Do not mix formatting-only changes into logic commits
+- Use a short subject line, ideally 50 characters or less
+- Add a blank line before the body when a body is needed
+- Reference issues with `refs #1234`, `fixes #1234`, or similar when applicable
+
+## Related docs worth reading
+
+- `CLAUDE.md`
+- `doc/developer-notes.md`
+- `.github/workflows/ci-master.yml`
+- `src/.clang-format`
+- `doc/fuzzing.md`
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..0a4d851b76
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,239 @@
+# CLAUDE.md - Firo Development Guide
+
+## Project Overview
+
+Firo is a privacy-focused cryptocurrency forked from Bitcoin Core, featuring zero-knowledge proof protocols (Spark, Lelantus), masternode infrastructure (LLMQ), and FiroPOW mining. Current version: **0.14.15.3**. Licensed under MIT.
+
+## Build System
+
+**CMake 3.22+** with **C++20** standard. No autotools.
+
+### Quick Build (Linux)
+
+```bash
+# 1. Build dependencies
+make -C depends -j$(nproc)
+
+# 2. Configure and build
+export HOST_TRIPLET=$(depends/config.guess)
+cmake -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE=$(pwd)/depends/$HOST_TRIPLET/toolchain.cmake \
+ -DBUILD_TESTS=ON -DBUILD_GUI=ON -DENABLE_CRASH_HOOKS=ON \
+ -DCMAKE_BUILD_TYPE=Release \
+ -S. -Bbuild
+cd build && ninja
+```
+
+### Key CMake Options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `BUILD_DAEMON` | ON | Build `firod` |
+| `BUILD_GUI` | ON | Build `firo-qt` (requires Qt 6.7.3+) |
+| `BUILD_CLI` | ON | Build `firo-cli` |
+| `BUILD_TX` | `${BUILD_CLI}` | Build `firo-tx` (defaults to same as `BUILD_CLI`) |
+| `BUILD_TESTS` | OFF | Build unit test suite |
+| `ENABLE_WALLET` | ON | Wallet functionality |
+| `WITH_ZMQ` | ON | ZeroMQ notifications |
+| `ENABLE_CRASH_HOOKS` | OFF | Stack trace generation (auto-enabled for Release/RelWithDebInfo/MinSizeRel) |
+| `CLIENT_VERSION_IS_RELEASE` | false | Release build flag |
+
+### Build Outputs
+
+Binaries go to `build/bin/`: `firod`, `firo-cli`, `firo-qt`, `firo-tx`
+
+### Cross-Compilation
+
+Use the `depends/` system with host triplets:
+- Linux: `x86_64-pc-linux-gnu` (default), `aarch64-linux-gnu`
+- Windows: `make -C depends HOST=x86_64-w64-mingw32`
+- macOS: requires SDK in `depends/SDKs/`
+
+### Docker Build
+
+```bash
+docker build . -t firo-local
+docker run -d --name firod -v "${HOME}/.firo:/home/firod/.firo" firo-local
+```
+
+## Testing
+
+### Unit Tests (Boost.Test)
+
+```bash
+cmake -Bbuild -DBUILD_TESTS=ON ...
+cd build && ctest --output-on-failure
+```
+
+Test source: `src/test/` (~80 test files). Framework setup in `src/test/test_bitcoin.h`.
+
+### Integration Tests (Python)
+
+```bash
+# Copy binaries where the test harness expects them
+cp -rf build/bin/* build/src/
+qa/pull-tester/rpc-tests.py -extended
+```
+
+Test scripts: `qa/rpc-tests/` (100+ Python scripts covering wallet, privacy protocols, masternodes, consensus).
+
+### Test Networks
+
+- `-testnet` for multi-node testing over the network
+- `-regtest` for local single-node testing with on-demand block creation
+
+## Repository Structure
+
+```
+src/ # Main source code
+├── libspark/ # Spark protocol (current privacy protocol) - ZK proofs, crypto primitives
+├── spark/ # Spark wallet integration and state management
+├── liblelantus/ # Lelantus protocol (legacy privacy protocol)
+├── wallet/ # Full wallet implementation
+├── rpc/ # JSON-RPC API endpoints
+├── qt/ # Qt GUI wallet
+├── evo/ # Deterministic masternode lists, special transactions
+├── llmq/ # Long Living Masternode Quorums (chainlocks, instant send)
+├── bls/ # BLS signatures for quorum signing
+├── bip47/ # BIP47 payment codes
+├── hdmint/ # Hierarchical deterministic minting
+├── crypto/ # Cryptographic functions (SHA, HMAC, ChaCha20, Lyra2Z, ProgPoW)
+├── consensus/ # Consensus rules and validation parameters
+├── primitives/ # Block and transaction primitives
+├── script/ # Bitcoin script interpreter
+├── policy/ # Transaction policy (fees)
+├── secp256k1/ # ECDSA library (subtree)
+├── leveldb/ # Key-value storage (subtree)
+├── univalue/ # JSON parsing (subtree)
+├── test/ # Unit tests
+├── fuzz/ # Fuzz testing
+├── bench/ # Benchmarks
+├── config/ # Build configuration headers
+├── validation.cpp/h # Core block/transaction validation (~6100 lines)
+├── net.cpp/h # Network layer
+├── net_processing.cpp/h # Peer protocol handling
+├── init.cpp/h # Application initialization
+├── miner.cpp/h # Block mining (FiroPOW)
+├── pow.cpp/h # Proof-of-Work consensus
+├── chainparams.cpp/h # Network parameters (mainnet/testnet/regtest)
+└── firo_params.h # Protocol parameters
+depends/ # Deterministic dependency build system
+cmake/ # CMake modules
+contrib/ # Auxiliary tools (gitian, guix, packaging, devtools)
+qa/ # Integration test suite
+doc/ # Documentation (build guides, developer notes, API docs)
+share/ # UI resources, scripts
+```
+
+## Coding Conventions
+
+### Style Rules (from `src/.clang-format` and `doc/developer-notes.md`)
+
+- **Indentation**: 4 spaces, no tabs
+- **Braces**: Linux style - new line for namespaces, classes, functions; same line for control flow
+- **No column limit** (ColumnLimit: 0)
+- **Pointer alignment**: Left (`int* p`)
+- **Prefer `++i`** over `i++`
+- **Single-statement `if`** may omit braces on same line; otherwise braces required
+
+```cpp
+namespace foo
+{
+class Class
+{
+ bool Function(const std::string& s, int n)
+ {
+ for (int i = 0; i < n; ++i) {
+ if (!Something()) return false;
+ if (SomethingElse()) {
+ DoMore();
+ } else {
+ DoLess();
+ }
+ }
+ return true;
+ }
+}
+}
+```
+
+### Naming Conventions
+
+- **Classes**: `C` prefix (e.g., `CBlock`, `CTransaction`, `CKey`, `CWallet`)
+- **Member variables**: `m_` prefix or no prefix with context
+- **Boolean flags**: `f` prefix (e.g., `fCompressed`, `fValid`)
+- **Constants**: `UPPER_CASE`
+- **Functions**: `CamelCase` or `camelCase`
+- **Header guards**: `#ifndef BITCOIN___H` (legacy Bitcoin naming preserved)
+
+### Documentation
+
+Use Doxygen-compatible comments:
+```cpp
+/**
+ * Description of function.
+ * @param[in] arg1 Description
+ * @param[in] arg2 Description
+ * @return Description
+ * @pre Precondition
+ */
+bool Function(int arg1, const char* arg2)
+
+int var; //!< Inline member description
+```
+
+### C++ Guidelines
+
+- Use `std::string` over C string functions
+- Use `.find()` on maps for reading, never `[]` (it inserts defaults)
+- Use RAII (`unique_ptr`) for resource management
+- Use `ParseInt32`, `ParseInt64`, `ParseDouble` from `utilstrencodings.h`
+- Use explicitly signed/unsigned `char`, prefer `uint8_t`/`int8_t`
+- Assertions must not have side effects
+- Watch for out-of-bounds vector access; use `.data()` instead of `&v[0]`
+- `-Wshadow` is enabled; avoid variable name shadowing
+- New features should be exposed via RPC first, then GUI
+
+### Threading
+
+The codebase is multi-threaded using `LOCK`/`TRY_LOCK` macros. Compile with `-DDEBUG_LOCKORDER` to detect lock ordering issues. Key threads: `ThreadScriptCheck`, `ThreadImport`, `ThreadSocketHandler`, `ThreadMessageHandler`, `ThreadRPCServer`.
+
+## CI/CD
+
+GitHub Actions (`.github/workflows/ci-master.yml`):
+- Triggers on pushes to all branches and PRs to master (ignores `doc/**` and `README.md`)
+- **Linux** (ubuntu-22.04): Release + Debug builds, unit tests, RPC integration tests
+- **Windows** (cross-compile via MinGW): Release + Debug builds
+- **macOS** (macos-latest): Release + Debug builds
+- **Guix** reproducible builds for all platforms
+- Uses ccache for build acceleration
+- Dependencies cached via `actions/cache`
+
+## Network Ports
+
+| Network | P2P | RPC |
+|---------|-----|-----|
+| Mainnet | 8168 | 8888 |
+| Testnet | 18168 | 18888 |
+| Devnet | 38168 | 38888 |
+| Regtest | 18444 | 28888 |
+
+## Key Privacy Protocols
+
+- **Spark** (`libspark/`, `spark/`): Current privacy protocol using Bulletproofs++, Grootle proofs, Chaum proofs, AEAD encryption, Bech32 addresses
+- **Lelantus** (`liblelantus/`): Legacy privacy protocol with joinsplit, range proofs, Schnorr proofs
+- **BIP47** (`bip47/`): Reusable payment codes to prevent address reuse
+- **Dandelion++**: Transaction propagation privacy (IP obscuring)
+
+## Consensus
+
+- **FiroPOW**: ProgPoW variant - GPU-friendly, ASIC-resistant proof of work
+- **LLMQ Chainlocks**: Deterministic masternode quorums for chain finality
+- **Instant Send**: Quorum-based instant transaction confirmation
+
+## PR and Commit Conventions
+
+- PR titles prefixed by area: `Consensus:`, `Net:` or `P2P:`, `Qt:`, `Wallet:`, `RPC/REST/ZMQ:`, `Mining:`, `Scripts and tools:`, `Tests:`, `Trivial:`, `Utils and libraries:`, `Docs:`
+- Commits should be atomic; don't mix formatting with logic changes
+- Commit messages: short subject (50 chars max), blank line, detailed body
+- Reference issues with `refs #1234`, `fixes #4321`, or `closes #1234`
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e485c9baeb..d5ef11ea1d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,8 +19,8 @@ endif()
set(CLIENT_NAME "Firo Core")
set(CLIENT_VERSION_MAJOR 0)
set(CLIENT_VERSION_MINOR 14)
-set(CLIENT_VERSION_REVISION 15)
-set(CLIENT_VERSION_BUILD 3)
+set(CLIENT_VERSION_REVISION 16)
+set(CLIENT_VERSION_BUILD 1)
set(CLIENT_VERSION_RC 0)
set(CLIENT_VERSION_IS_RELEASE "false" CACHE STRING "Is this a release build?")
set(COPYRIGHT_YEAR "2026")
@@ -234,9 +234,30 @@ endif()
cmake_dependent_option(WITH_DBUS "Enable DBus support." ON "CMAKE_SYSTEM_NAME STREQUAL \"Linux\" AND BUILD_GUI" OFF)
+function(clear_stale_depends_shared_cache_entries)
+ get_cmake_property(cache_vars CACHE_VARIABLES)
+ foreach(cache_var IN LISTS cache_vars)
+ get_property(cache_value CACHE "${cache_var}" PROPERTY VALUE)
+ string(FIND "${cache_value}" "${CMAKE_SOURCE_DIR}/depends/" depends_prefix_pos)
+ if(NOT depends_prefix_pos EQUAL 0)
+ continue()
+ endif()
+ if(NOT cache_value MATCHES "/lib/.*\\.so(\\.[0-9]+)*$")
+ continue()
+ endif()
+ if(EXISTS "${cache_value}")
+ continue()
+ endif()
+
+ message(STATUS "Clearing stale cached library path ${cache_var}: ${cache_value}")
+ unset(${cache_var} CACHE)
+ endforeach()
+endfunction()
+
cmake_dependent_option(BUILD_GUI_TESTS "Build test_firo-qt executable." ON "BUILD_GUI;BUILD_TESTS" OFF)
if(BUILD_GUI)
+ clear_stale_depends_shared_cache_entries()
set(qt_components Core Gui Widgets LinguistTools)
if(ENABLE_WALLET)
list(APPEND qt_components Network)
diff --git a/Dockerfile b/Dockerfile
index b789491b3d..5b4559907d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,7 +16,9 @@ RUN apt-get update && apt-get install -y \
make \
pkg-config \
rsync \
- patch
+ patch \
+ ninja-build \
+ meson
# Build Firo
COPY . /tmp/firo/
diff --git a/README.md b/README.md
index 2f4d6b185b..b70803a0cb 100644
--- a/README.md
+++ b/README.md
@@ -7,10 +7,9 @@
[](https://github.com/firoorg/firo/graphs/code-frequency)
[](https://github.com/firoorg/firo/commits/master)

+[](https://deepwiki.com/firoorg/firo)
-[Firo](https://firo.org) formerly known as Zcoin, is a privacy focused cryptocurrency that utilizes the [Lelantus Spark protocol](https://eprint.iacr.org/2021/1173) which supports high anonymity sets without requiring trusted setup and relying on standard cryptographic assumptions.
-
-The Lelantus Spark cryptographic library and implementation was audited by [HashCloak](https://firo.org/about/research/papers/lelantus_spark_code_audit_report.pdf). The Lelantus Spark cryptography paper has undergone two separate audits by [HashCloak](https://firo.org/about/research/papers/Lelantus_Spark_Audit_Report.pdf) and [Daniel (Linfeng) Zhao](https://firo.org/about/research/papers/LinfengSparkAudit.pdf).
+[Firo](https://firo.org) formerly known as Zcoin, is a privacy-focused cryptocurrency that utilizes the [Spark protocol](https://eprint.iacr.org/2021/1173) which supports high anonymity sets without requiring trusted setup and relying on standard cryptographic assumptions.
Firo also utilises [Dandelion++](https://arxiv.org/abs/1805.11060) to obscure the originating IP of transactions without relying on any external services such as Tor/i2P.
@@ -108,9 +107,9 @@ Bootstrappable builds can [be achieved with Guix.](contrib/guix/README.md)
```sh
sudo apt-get update
-sudo apt-get install python git curl build-essential cmake pkg-config
+sudo apt-get install python git curl build-essential cmake pkg-config ccache
# Also needed for GUI wallet only:
-sudo apt-get install qttools5-dev qttools5-dev-tools libxcb-xkb-dev bison
+sudo apt-get install qttools5-dev qttools5-dev-tools libxcb-xkb-dev bison ninja-build meson libx11-dev
```
If you use a later version of Ubuntu, you may need to replace `python` with `python3`.
diff --git a/contrib/debian/firo-qt.desktop b/contrib/debian/firo-qt.desktop
index 0d610ac4c3..1033a2b756 100644
--- a/contrib/debian/firo-qt.desktop
+++ b/contrib/debian/firo-qt.desktop
@@ -5,5 +5,5 @@ Comment=Connect to Firo
Exec=firo-qt %u
Terminal=false
Type=Application
-Icon=/app/share/icons/hicolor/scalable/apps/org.firo.firo-qt.svg
+Icon=firo-qt
Categories=Office;Finance;
diff --git a/contrib/debian/qt-screenshot.jpg b/contrib/debian/qt-screenshot.jpg
deleted file mode 100644
index 135ff4acba..0000000000
Binary files a/contrib/debian/qt-screenshot.jpg and /dev/null differ
diff --git a/contrib/debian/qt-screenshot.png b/contrib/debian/qt-screenshot.png
new file mode 100644
index 0000000000..bdf3f05ef2
Binary files /dev/null and b/contrib/debian/qt-screenshot.png differ
diff --git a/contrib/docker/builder/Dockerfile b/contrib/docker/builder/Dockerfile
index 223f84fd79..115840c47d 100644
--- a/contrib/docker/builder/Dockerfile
+++ b/contrib/docker/builder/Dockerfile
@@ -1,17 +1,18 @@
-FROM ubuntu:20.04
+FROM ubuntu:24.04
# install required packages
RUN apt-get update && apt-get install -y software-properties-common
RUN apt-get update && apt-get install -y \
autoconf automake bsdmainutils ccache cmake curl g++ g++-mingw-w64-x86-64 gcc gcc-mingw-w64-x86-64 git \
- libbz2-dev libtool make pkg-config python3-pip python3-zmq build-essential minizip lcov default-jre bison
+ libbz2-dev libtool make pkg-config python3-pip python3-zmq build-essential minizip lcov default-jre bison flex python3-pyasyncore \
+ ninja-build meson libx11-dev
# update mingw alternatives
RUN update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix
RUN update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
-RUN pip3 install ez_setup
+RUN pip3 install ez_setup --break-system-packages
# create user to use
RUN useradd -m -U firo-builder
diff --git a/contrib/flathub/org.firo.firo-qt.json b/contrib/flathub/org.firo.firo-qt.json
new file mode 100644
index 0000000000..0d95a747cf
--- /dev/null
+++ b/contrib/flathub/org.firo.firo-qt.json
@@ -0,0 +1,61 @@
+{
+ "id": "org.firo.firo-qt",
+ "runtime": "org.freedesktop.Platform",
+ "sdk": "org.freedesktop.Sdk",
+ "runtime-version": "25.08",
+ "command": "firo-qt",
+ "finish-args": [
+ "--socket=x11",
+ "--socket=pulseaudio",
+ "--device=dri",
+ "--share=ipc",
+ "--share=network",
+ "--filesystem=/run/tor/control.authcookie:ro"
+ ],
+ "modules": [
+ {
+ "name": "firo",
+ "buildsystem": "simple",
+ "build-commands": [
+ "install -m 0755 -D -t /app/bin/internal bin/firod",
+ "install -m 0755 -D -t /app/bin/internal bin/firo-qt",
+ "install -m 0755 -D -t /app/bin/internal bin/firo-cli",
+ "desktop-file-edit --set-key=\"Icon\" --set-value=${FLATPAK_ID} firo-qt.desktop",
+ "install -Dp -m 644 firo-qt.desktop /app/share/applications/${FLATPAK_ID}.desktop",
+ "install -Dp -m 644 firo.svg /app/share/icons/hicolor/scalable/apps/org.firo.firo-qt.svg",
+ "install -Dp -m 644 org.firo.firo-qt.metainfo.xml /app/share/metainfo/org.firo.firo-qt.metainfo.xml",
+ "for b in firo{d,-qt,-cli}; do echo '#!/usr/bin/bash' > /app/bin/$b && echo \"exec /app/bin/internal/$b\" '-datadir=\"${XDG_DATA_HOME}\" \"$@\"' >> /app/bin/$b && chmod 744 /app/bin/$b; done"
+ ],
+ "sources": [
+ {
+ "type": "archive",
+ "only-arches": [
+ "x86_64"
+ ],
+ "url": "https://github.com/firoorg/firo/releases/download/v0.14.16.0/firo-0.14.16.0-linux64.tar.gz",
+ "sha256": "b8f6ff5d7481e5a9dd7c5ad71caa62658b7747a8f3512999786229458397c59d",
+ "x-checker-data": {
+ "type": "html",
+ "url": "https://github.com/firoorg/firo/releases.atom",
+ "version-pattern": "Firo ([\\d\\.]+)",
+ "url-template": "https://github.com/firoorg/firo/releases/download/$version/firo-$version-linux64.tar.gz"
+ }
+ },
+ {
+ "type": "file",
+ "url": "https://raw.githubusercontent.com/firoorg/firo/0f9d27dfef6f27f5c4d97a060c0954846d3ea54/contrib/debian/firo-qt.desktop",
+ "sha256": "79070bd3af4334f4239585630d7d7d6b8d417a15ca7c1ba1a4ef6e4bc276bed4"
+ },
+ {
+ "type": "file",
+ "path": "org.firo.firo-qt.metainfo.xml"
+ },
+ {
+ "type": "file",
+ "url": "https://raw.githubusercontent.com/firoorg/firo/0f9d27dfef6f27f5c4d97a060c0954846d3ea547/src/qt/res/icons/firo.svg",
+ "sha256": "3474d6c9248735896ea2ef27047625f5b114429b9b23e54e852d7ce4f68a2371"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/contrib/flathub/org.firo.firo-qt.metainfo.xml b/contrib/flathub/org.firo.firo-qt.metainfo.xml
index d277784c0a..abe9671f38 100644
--- a/contrib/flathub/org.firo.firo-qt.metainfo.xml
+++ b/contrib/flathub/org.firo.firo-qt.metainfo.xml
@@ -24,16 +24,20 @@
Bitcoin was originally created as an answer to this by ensuring you can be self sovereign over your money and to serve as uncensorable and unseizable money that isn't controlled by any one entity.
Bitcoin's lack of privacy however has now made it much easier to seize or blacklist funds and due to ossification of the protocol, is unlikely to take serious steps to address this.
- Firo has dedicated itself to being a privacy preserving cryptocurrency and have designed and built trustless privacy protocols such as Lelantus and Lelantus Spark that have inspired and shaped the designs of other privacy protocols (for e.g. Triptych, Seraphis, Lelantus-MW).
+ Firo has dedicated itself to being a privacy preserving cryptocurrency and have designed and built trustless privacy protocols such as Spark that have inspired and shaped the designs of other privacy protocols (for e.g. Triptych, Seraphis, Lelantus-MW).
- https://raw.githubusercontent.com/firoorg/firo/0f9d27dfef6f27f5c4d97a060c0954846d3ea547/contrib/debian/qt-screenshot.jpg
+ https://raw.githubusercontent.com/firoorg/firo/837983954d1bc5b1e70474b14b1376747f42c692/contrib/debian/qt-screenshot.png
+
+
+
+
diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh
index 124881166f..f9af6c4a9d 100755
--- a/contrib/guix/libexec/build.sh
+++ b/contrib/guix/libexec/build.sh
@@ -95,6 +95,11 @@ prepend_to_search_env_var() {
export "${1}=${2}${!1:+:}${!1}"
}
+# Native tools built in depends may need the GCC runtime when executed.
+if [ -n "${LIBRARY_PATH}" ]; then
+ prepend_to_search_env_var LD_LIBRARY_PATH "${LIBRARY_PATH}"
+fi
+
# Set environment variables to point the CROSS toolchain to the right
# includes/libs for $HOST
case "$HOST" in
diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm
index 34e849deea..dce1edd19b 100644
--- a/contrib/guix/manifest.scm
+++ b/contrib/guix/manifest.scm
@@ -15,12 +15,14 @@
(gnu packages llvm)
(gnu packages mingw)
(gnu packages ninja)
+ ((gnu packages build-tools) #:select (meson))
(gnu packages pkg-config)
((gnu packages python) #:select (python-minimal))
((gnu packages python-build) #:select (python-tomli python-poetry-core))
((gnu packages python-crypto) #:select (python-asn1crypto))
((gnu packages tls) #:select (openssl))
(gnu packages perl)
+ (gnu packages xml)
((gnu packages version-control) #:select (git-minimal))
(guix build-system cmake)
(guix build-system gnu)
@@ -264,10 +266,12 @@ chain for " target " development."))
cmake-minimal
gnu-make
ninja
+ meson
libtool
automake
autoconf-2.71
pkg-config
+ expat
;; Scripting
python-minimal ;; (3.10)
perl
diff --git a/depends/Makefile b/depends/Makefile
index 8dc0409a3c..afc7ff53a0 100644
--- a/depends/Makefile
+++ b/depends/Makefile
@@ -159,7 +159,7 @@ libevent_packages_$(NO_LIBEVENT) = $(libevent_packages)
qrencode_packages_$(NO_QR) = $(qrencode_packages)
qt_packages_$(NO_QT) = $(qt_packages) $(qt_$(host_os)_packages) $(qt_$(host_arch)_$(host_os)_packages) $(qrencode_packages_)
-qt_native_packages_$(NO_QT) = $(qt_native_packages)
+qt_native_packages_$(NO_QT) = $(qt_native_packages) $(qt_$(host_os)_native_packages) $(qt_$(host_arch)_$(host_os)_native_packages)
bdb_packages_$(NO_BDB) = $(bdb_packages)
sqlite_packages_$(NO_SQLITE) = $(sqlite_packages)
@@ -240,6 +240,7 @@ $(host_prefix)/toolchain.cmake : toolchain.cmake.in $(host_prefix)/.stamp_$(fina
-e 's|@usdt_packages@|$(usdt_packages_)|' \
-e 's|@no_harden@|$(NO_HARDEN)|' \
-e 's|@multiprocess@|$(MULTIPROCESS)|' \
+ -e 's|@host_os@|$(host_os)|' \
$< > $@
touch $@
diff --git a/depends/packages/libXdmcp.mk b/depends/packages/libXdmcp.mk
new file mode 100644
index 0000000000..f50ac117ef
--- /dev/null
+++ b/depends/packages/libXdmcp.mk
@@ -0,0 +1,27 @@
+package=libXdmcp
+$(package)_version=1.1.5
+$(package)_download_path=https://www.x.org/releases/individual/lib/
+$(package)_file_name=$(package)-$($(package)_version).tar.xz
+$(package)_sha256_hash=d8a5222828c3adab70adf69a5583f1d32eb5ece04304f7f8392b6a353aa2228c
+$(package)_dependencies=xproto
+
+define $(package)_set_vars
+ $(package)_config_opts=--disable-shared --disable-docs
+ $(package)_config_opts_linux=--with-pic
+endef
+
+define $(package)_config_cmds
+ $($(package)_autoconf)
+endef
+
+define $(package)_build_cmds
+ $(MAKE)
+endef
+
+define $(package)_stage_cmds
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install
+endef
+
+define $(package)_postprocess_cmds
+ rm -f lib/*.la
+endef
diff --git a/depends/packages/libffi.mk b/depends/packages/libffi.mk
new file mode 100644
index 0000000000..aa4b223b31
--- /dev/null
+++ b/depends/packages/libffi.mk
@@ -0,0 +1,28 @@
+package := libffi
+$(package)_version := 3.5.2
+$(package)_download_path := https://github.com/libffi/$(package)/releases/download/v$($(package)_version)
+$(package)_file_name := $(package)-$($(package)_version).tar.gz
+$(package)_sha256_hash := f3a3082a23b37c293a4fcd1053147b371f2ff91fa7ea1b2a52e335676bac82dc
+
+define $(package)_set_vars
+ $(package)_config_opts := --enable-option-checking --disable-dependency-tracking
+ $(package)_config_opts += --disable-shared --enable-static --disable-docs
+ $(package)_config_opts += --disable-multi-os-directory
+ $(package)_cflags += -D_GNU_SOURCE -std=gnu11
+endef
+
+define $(package)_config_cmds
+ $($(package)_autoconf)
+endef
+
+define $(package)_build_cmds
+ $(MAKE)
+endef
+
+define $(package)_stage_cmds
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install
+endef
+
+define $(package)_postprocess_cmds
+ rm -rf share
+endef
diff --git a/depends/packages/libglvnd.mk b/depends/packages/libglvnd.mk
new file mode 100644
index 0000000000..92b6a0bea4
--- /dev/null
+++ b/depends/packages/libglvnd.mk
@@ -0,0 +1,77 @@
+package := libglvnd
+$(package)_version := 1.4.0
+$(package)_download_path := https://gitlab.freedesktop.org/glvnd/$(package)/-/archive/v$($(package)_version)
+$(package)_file_name := $(package)-v$($(package)_version).tar.bz2
+$(package)_sha256_hash := fdf395391d95f270528dbff6ce2ee54c186753d286ad62e0da5f62c6f67ba915
+$(package)_patches := fix-typeof-gcc14.patch
+
+define $(package)_set_vars
+endef
+
+define $(package)_preprocess_cmds
+ patch -p1 < $($(package)_patch_dir)/fix-typeof-gcc14.patch
+endef
+
+define $(package)_config_cmds
+ cross_arg="" ; \
+ if [ "$(host)" != "$(build)" ]; then \
+ CC="$$($(package)_cc)" ; \
+ CXX="$$($(package)_cxx)" ; \
+ cc_first=$$$${CC%% *} ; \
+ cc_rest=$$$${CC#* } ; \
+ cxx_first=$$$${CXX%% *} ; \
+ cxx_rest=$$$${CXX#* } ; \
+ if [ "$$$$cc_first" = "$$$$CC" ]; then \
+ cc_line="c = ['$$$$CC']" ; \
+ else \
+ cc_line="c = ['$$$$cc_first', '$$$$cc_rest']" ; \
+ fi ; \
+ if [ "$$$$cxx_first" = "$$$$CXX" ]; then \
+ cxx_line="cpp = ['$$$$CXX']" ; \
+ else \
+ cxx_line="cpp = ['$$$$cxx_first', '$$$$cxx_rest']" ; \
+ fi ; \
+ printf '%s\n' "[binaries]" "$$$$cc_line" "$$$$cxx_line" \
+ "ar = '$$($(package)_ar)'" \
+ "pkg-config = 'pkg-config'" \
+ "strip = '$(host_STRIP)'" \
+ "" \
+ "[built-in options]" \
+ "pkg_config_path = ['$(host_prefix)/lib/pkgconfig', '$(host_prefix)/share/pkgconfig']" \
+ "" \
+ "[properties]" \
+ "needs_exe_wrapper = true" \
+ "" \
+ "[host_machine]" \
+ "system = 'linux'" \
+ "cpu_family = '$(host_arch)'" \
+ "cpu = '$(host_arch)'" \
+ "endian = 'little'" \
+ > cross.ini ; \
+ cross_arg="--cross-file cross.ini" ; \
+ fi && \
+ PKG_CONFIG_LIBDIR=$(host_prefix)/lib/pkgconfig \
+ PKG_CONFIG_PATH=$(host_prefix)/share/pkgconfig \
+ CC="$$($(package)_cc)" CXX="$$($(package)_cxx)" \
+ CFLAGS="$$($(package)_cppflags) $$($(package)_cflags) -D_GNU_SOURCE" \
+ CXXFLAGS="$$($(package)_cppflags) $$($(package)_cxxflags)" \
+ LDFLAGS="$$($(package)_ldflags)" \
+ meson setup build --prefix=$(host_prefix) \
+ $$$$cross_arg \
+ --libdir=lib \
+ --default-library=shared \
+ -Dx11=disabled \
+ -Dgles1=false
+endef
+
+define $(package)_build_cmds
+ ninja -C build
+endef
+
+define $(package)_stage_cmds
+ DESTDIR=$($(package)_staging_dir) ninja -C build install
+endef
+
+define $(package)_postprocess_cmds
+ rm -f lib/*.la
+endef
diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk
index 5bbcab8437..e1dea20e46 100644
--- a/depends/packages/libxcb.mk
+++ b/depends/packages/libxcb.mk
@@ -3,12 +3,13 @@ $(package)_version=1.14
$(package)_download_path=https://xcb.freedesktop.org/dist
$(package)_file_name=$(package)-$($(package)_version).tar.xz
$(package)_sha256_hash=a55ed6db98d43469801262d81dc2572ed124edc3db31059d4e9916eb9f844c34
-$(package)_dependencies=xcb_proto libXau
+$(package)_dependencies=xcb_proto libXau libXdmcp
$(package)_patches = remove_pthread_stubs.patch
define $(package)_set_vars
$(package)_config_opts=--disable-devel-docs --without-doxygen --without-launchd
$(package)_config_opts += --disable-dependency-tracking --enable-option-checking
+$(package)_config_opts += --disable-shared
# Disable unneeded extensions.
# More info is available from: https://doc.qt.io/qt-5.15/linux-requirements.html
$(package)_config_opts += --disable-composite --disable-damage --disable-dpms
@@ -37,5 +38,5 @@ define $(package)_stage_cmds
endef
define $(package)_postprocess_cmds
- rm -rf share lib/*.la
+ rm -rf share lib/*.la && mkdir -p _xau _xdmcp && (cd _xau && ar x $(host_prefix)/lib/libXau.a) && (cd _xdmcp && ar x $(host_prefix)/lib/libXdmcp.a) && ar rc lib/libxcb.a _xau/*.o _xdmcp/*.o && ranlib lib/libxcb.a && rm -rf _xau _xdmcp && python3 -c "c=open('lib/pkgconfig/xcb.pc').read();n=c.replace(' -lxcb\n',' -lxcb -lXau -lXdmcp\n');assert n!=c,'xcb.pc patch failed: -lxcb not found at end of Libs line';open('lib/pkgconfig/xcb.pc','w').write(n)"
endef
diff --git a/depends/packages/libxcb_util_image.mk b/depends/packages/libxcb_util_image.mk
index fe8891ccab..ec058997b7 100644
--- a/depends/packages/libxcb_util_image.mk
+++ b/depends/packages/libxcb_util_image.mk
@@ -8,6 +8,7 @@ $(package)_dependencies=libxcb libxcb_util
define $(package)_set_vars
$(package)_config_opts=--disable-devel-docs --without-doxygen
$(package)_config_opts+= --disable-dependency-tracking --enable-option-checking
+$(package)_config_opts+= --disable-shared
endef
define $(package)_preprocess_cmds
@@ -27,5 +28,5 @@ define $(package)_stage_cmds
endef
define $(package)_postprocess_cmds
- rm -rf share/man share/doc lib/*.la
+ rm -rf share/man share/doc lib/*.la && rm -rf _util && mkdir _util && (cd _util && ar x $(host_prefix)/lib/libxcb-util.a) && ar rc lib/libxcb-image.a _util/*.o && ranlib lib/libxcb-image.a && rm -rf _util
endef
diff --git a/depends/packages/libxcb_util_keysyms.mk b/depends/packages/libxcb_util_keysyms.mk
index b114fbed7e..0ccdc1c5e0 100644
--- a/depends/packages/libxcb_util_keysyms.mk
+++ b/depends/packages/libxcb_util_keysyms.mk
@@ -8,6 +8,7 @@ $(package)_dependencies=libxcb xproto
define $(package)_set_vars
$(package)_config_opts=--disable-devel-docs --without-doxygen
$(package)_config_opts += --disable-dependency-tracking --enable-option-checking
+$(package)_config_opts += --disable-shared
endef
define $(package)_preprocess_cmds
diff --git a/depends/packages/libxcb_util_render.mk b/depends/packages/libxcb_util_render.mk
index 905f932239..dfc557e483 100644
--- a/depends/packages/libxcb_util_render.mk
+++ b/depends/packages/libxcb_util_render.mk
@@ -8,6 +8,7 @@ $(package)_dependencies=libxcb
define $(package)_set_vars
$(package)_config_opts=--disable-devel-docs --without-doxygen
$(package)_config_opts += --disable-dependency-tracking --enable-option-checking
+$(package)_config_opts += --disable-shared
endef
define $(package)_preprocess_cmds
diff --git a/depends/packages/libxcb_util_wm.mk b/depends/packages/libxcb_util_wm.mk
index f80f0eb27d..10e1fe34fa 100644
--- a/depends/packages/libxcb_util_wm.mk
+++ b/depends/packages/libxcb_util_wm.mk
@@ -8,6 +8,7 @@ $(package)_dependencies=libxcb
define $(package)_set_vars
$(package)_config_opts=--disable-devel-docs --without-doxygen
$(package)_config_opts += --disable-dependency-tracking --enable-option-checking
+$(package)_config_opts += --disable-shared
endef
define $(package)_preprocess_cmds
diff --git a/depends/packages/libxkbcommon.mk b/depends/packages/libxkbcommon.mk
index b8634e9aa5..9881322bbf 100644
--- a/depends/packages/libxkbcommon.mk
+++ b/depends/packages/libxkbcommon.mk
@@ -12,6 +12,8 @@ $(package)_dependencies=libxcb
define $(package)_set_vars
$(package)_config_opts = --enable-option-checking --disable-dependency-tracking
$(package)_config_opts += --disable-docs
+$(package)_config_opts += --disable-shared
+$(package)_config_opts += --with-xkb-config-root=/usr/share/X11/xkb
$(package)_cflags += -Wno-error=array-bounds
endef
diff --git a/depends/packages/native_expat.mk b/depends/packages/native_expat.mk
new file mode 100644
index 0000000000..695dce32cd
--- /dev/null
+++ b/depends/packages/native_expat.mk
@@ -0,0 +1,21 @@
+package := native_expat
+$(package)_version := $(expat_version)
+$(package)_download_path := https://github.com/libexpat/libexpat/releases/download/R_$(subst .,_,$($(package)_version))/
+$(package)_file_name := expat-$($(package)_version).tar.bz2
+$(package)_sha256_hash := $(expat_sha256_hash)
+
+define $(package)_set_vars
+$(package)_config_opts := --disable-shared --enable-static
+endef
+
+define $(package)_config_cmds
+ $($(package)_autoconf)
+endef
+
+define $(package)_build_cmds
+ $(MAKE)
+endef
+
+define $(package)_stage_cmds
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install
+endef
\ No newline at end of file
diff --git a/depends/packages/native_qt.mk b/depends/packages/native_qt.mk
index ffb65bb7bc..e67a5fff04 100644
--- a/depends/packages/native_qt.mk
+++ b/depends/packages/native_qt.mk
@@ -11,6 +11,7 @@ $(package)_patches += qtbase_avoid_native_float16.patch
$(package)_patches += qtbase_skip_tools.patch
$(package)_patches += rcc_hardcode_timestamp.patch
$(package)_patches += qttools_skip_dependencies.patch
+$(package)_patches += qtwayland_build_scanner_without_gui.patch
$(package)_qttranslations_file_name=$(qt_details_qttranslations_file_name)
$(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash)
@@ -37,6 +38,14 @@ $(package)_extra_sources += $($(package)_top_cmakelists_file_name)-$($(package)_
$(package)_extra_sources += $($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version)
$(package)_extra_sources += $($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version)
+$(package)_qtwayland_file_name=$(qt_details_qtwayland_file_name)
+$(package)_qtwayland_sha256_hash=$(qt_details_qtwayland_sha256_hash)
+
+ifneq ($(filter linux freebsd,$(host_os)),)
+$(package)_dependencies := native_wayland_scanner
+$(package)_extra_sources += $($(package)_qtwayland_file_name)
+endif
+
define $(package)_set_vars
# Build options.
$(package)_config_opts := -release
@@ -95,6 +104,9 @@ $(package)_config_env += OBJCXX="$$(build_CXX)"
endif
$(package)_cmake_opts := -DCMAKE_EXE_LINKER_FLAGS="$$(build_LDFLAGS)"
+ifneq ($(filter linux freebsd,$(host_os)),)
+$(package)_cmake_opts += -DWaylandScanner_EXECUTABLE=$(build_prefix)/bin/wayland-scanner
+endif
ifneq ($(V),)
$(package)_cmake_opts += --log-level=STATUS
endif
@@ -108,6 +120,10 @@ $(call fetch_file,$(package),$($(package)_top_download_path),$($(package)_top_cm
$(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_ecmoptionaladdsubdirectory_download_file),$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version),$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_qttoplevelhelpers_download_file),$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version),$($(package)_top_cmake_qttoplevelhelpers_sha256_hash))
endef
+ifneq ($(filter linux freebsd,$(host_os)),)
+$(package)_fetch_cmds += && \
+$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtwayland_file_name),$($(package)_qtwayland_file_name),$($(package)_qtwayland_sha256_hash))
+endif
define $(package)_extract_cmds
mkdir -p $($(package)_extract_dir) && \
@@ -129,6 +145,12 @@ define $(package)_extract_cmds
cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \
cp $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_qttoplevelhelpers_file_name)
endef
+ifneq ($(filter linux freebsd,$(host_os)),)
+$(package)_extract_cmds += && \
+ echo "$($(package)_qtwayland_sha256_hash) $($(package)_source_dir)/$($(package)_qtwayland_file_name)" | $(build_SHA256SUM) -c - && \
+ mkdir qtwayland && \
+ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtwayland_file_name) -C qtwayland
+endif
define $(package)_preprocess_cmds
patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \
@@ -138,6 +160,10 @@ define $(package)_preprocess_cmds
patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \
patch -p1 -i $($(package)_patch_dir)/qttools_skip_dependencies.patch
endef
+ifneq ($(filter linux freebsd,$(host_os)),)
+$(package)_preprocess_cmds += && \
+ patch -p1 -i $($(package)_patch_dir)/qtwayland_build_scanner_without_gui.patch
+endif
define $(package)_config_cmds
cd qtbase && \
diff --git a/depends/packages/native_wayland_scanner.mk b/depends/packages/native_wayland_scanner.mk
new file mode 100644
index 0000000000..1304961d91
--- /dev/null
+++ b/depends/packages/native_wayland_scanner.mk
@@ -0,0 +1,28 @@
+package := native_wayland_scanner
+$(package)_version := 1.20.0
+$(package)_download_path := https://wayland.freedesktop.org/releases
+$(package)_file_name := wayland-$($(package)_version).tar.xz
+$(package)_sha256_hash := b8a034154c7059772e0fdbd27dbfcda6c732df29cae56a82274f6ec5d7cd8725
+$(package)_dependencies := native_expat
+
+define $(package)_config_cmds
+ PKG_CONFIG_LIBDIR=$(build_prefix)/lib/pkgconfig \
+ PKG_CONFIG_PATH=$(build_prefix)/share/pkgconfig \
+ CC="$$($(package)_cc)" CXX="$$($(package)_cxx)" \
+ meson setup build --prefix=$(build_prefix) \
+ --libdir=lib \
+ -Dc_link_args="['-static-libgcc', '-Wl,-rpath,$(build_prefix)/lib']" \
+ -Dlibraries=false \
+ -Dscanner=true \
+ -Ddocumentation=false \
+ -Dtests=false \
+ -Ddtd_validation=false
+endef
+
+define $(package)_build_cmds
+ ninja -C build
+endef
+
+define $(package)_stage_cmds
+ DESTDIR=$($(package)_staging_dir) ninja -C build install
+endef
diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk
index 3f69b12bf2..c428258856 100644
--- a/depends/packages/packages.mk
+++ b/depends/packages/packages.mk
@@ -3,8 +3,10 @@ darwin_packages:=zeromq
linux_packages:=zeromq
native_packages :=
-qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm
+qt_linux_packages:=qt expat libxcb xcb_proto libXau libXdmcp xproto freetype fontconfig libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm libffi wayland libglvnd
+qt_linux_native_packages := native_expat native_wayland_scanner
qt_freebsd_packages:=$(qt_linux_packages)
+qt_freebsd_native_packages := $(qt_linux_native_packages)
qt_darwin_packages=qt
qt_mingw32_packages=qt
ifneq ($(host),$(build))
diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk
index bb2cc9e3a7..de3003fedf 100644
--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -7,7 +7,7 @@ $(package)_sha256_hash=$(qt_details_qtbase_sha256_hash)
ifneq ($(host),$(build))
$(package)_dependencies := native_$(package)
endif
-$(package)_linux_dependencies := freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm
+$(package)_linux_dependencies := freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm wayland libglvnd
$(package)_freebsd_dependencies := $($(package)_linux_dependencies)
$(package)_patches_path := $(qt_details_patches_path)
$(package)_patches := dont_hardcode_pwd.patch
@@ -17,8 +17,11 @@ $(package)_patches += qtbase_avoid_qmain.patch
$(package)_patches += qtbase_platformsupport.patch
$(package)_patches += qtbase_plugins_cocoa.patch
$(package)_patches += qtbase_skip_tools.patch
+$(package)_patches += qtbase_findxcb_image_aux.patch
$(package)_patches += rcc_hardcode_timestamp.patch
$(package)_patches += qttools_skip_dependencies.patch
+$(package)_patches += qtwayland_decoration_no_egl_gate.patch
+$(package)_patches += qtbase_fix_qyieldcpu_apple_arm.patch
$(package)_qttranslations_file_name=$(qt_details_qttranslations_file_name)
$(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash)
@@ -26,8 +29,12 @@ $(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash)
$(package)_qttools_file_name=$(qt_details_qttools_file_name)
$(package)_qttools_sha256_hash=$(qt_details_qttools_sha256_hash)
+$(package)_qtwayland_file_name=$(qt_details_qtwayland_file_name)
+$(package)_qtwayland_sha256_hash=$(qt_details_qtwayland_sha256_hash)
+
$(package)_extra_sources := $($(package)_qttranslations_file_name)
$(package)_extra_sources += $($(package)_qttools_file_name)
+$(package)_extra_sources += $($(package)_qtwayland_file_name)
$(package)_top_download_path=$(qt_details_top_download_path)
$(package)_top_cmakelists_file_name=$(qt_details_top_cmakelists_file_name)
@@ -49,13 +56,11 @@ define $(package)_set_vars
$(package)_config_env = QT_MAC_SDK_NO_VERSION_CHECK=1
$(package)_config_opts_release = -release
$(package)_config_opts_debug = -debug
-$(package)_config_opts = -no-egl
$(package)_config_opts_debug += -optimized-tools
$(package)_config_opts += -bindir $(build_prefix)/bin
$(package)_config_opts += -c++std c++20
$(package)_config_opts += -confirm-license
$(package)_config_opts += -no-cups
-$(package)_config_opts += -no-egl
$(package)_config_opts += -no-eglfs
$(package)_config_opts += -no-evdev
$(package)_config_opts += -no-gif
@@ -69,7 +74,6 @@ $(package)_config_opts += -no-libjpeg
$(package)_config_opts += -no-libproxy
$(package)_config_opts += -no-libudev
$(package)_config_opts += -no-mtdev
-$(package)_config_opts += -no-opengl
$(package)_config_opts += -no-openssl
$(package)_config_opts += -no-openvg
$(package)_config_opts += -no-reduce-relocations
@@ -162,6 +166,7 @@ endif
$(package)_config_opts_darwin := -no-dbus
$(package)_config_opts_darwin += -no-opengl
+$(package)_config_opts_darwin += -no-egl
$(package)_config_opts_darwin += -pch
$(package)_config_opts_darwin += -no-feature-printsupport
$(package)_config_opts_darwin += -no-freetype
@@ -175,16 +180,18 @@ $(package)_config_opts_linux += -no-xcb-xlib
$(package)_config_opts_linux += -pkg-config
$(package)_config_opts_linux += -system-freetype
$(package)_config_opts_linux += -fontconfig
-$(package)_config_opts_linux += -no-opengl
$(package)_config_opts_linux += -no-feature-vulkan
$(package)_config_opts_linux += -dbus-runtime
$(package)_config_opts_linux += -feature-xcb
+
ifneq ($(LTO),)
$(package)_config_opts_linux += -ltcg
endif
$(package)_config_opts_freebsd := $$($(package)_config_opts_linux)
$(package)_config_opts_mingw32 := -no-dbus
+$(package)_config_opts_mingw32 += -no-opengl
+$(package)_config_opts_mingw32 += -no-egl
$(package)_config_opts_mingw32 += -no-freetype
$(package)_config_opts_mingw32 += -no-pkg-config
@@ -235,6 +242,12 @@ endif
ifeq ($(host_os),linux)
$(package)_cmake_opts += -DQT_FEATURE_xcb=ON
+$(package)_cmake_opts += -DWaylandScanner_EXECUTABLE=$(build_prefix)/bin/wayland-scanner
+endif
+
+ifeq ($(host_os),freebsd)
+$(package)_cmake_opts += -DQT_FEATURE_xcb=ON
+$(package)_cmake_opts += -DWaylandScanner_EXECUTABLE=$(build_prefix)/bin/wayland-scanner
endif
ifdef GUIX_ENVIRONMENT
@@ -277,6 +290,7 @@ define $(package)_fetch_cmds
$(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttranslations_file_name),$($(package)_qttranslations_file_name),$($(package)_qttranslations_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttools_file_name),$($(package)_qttools_file_name),$($(package)_qttools_sha256_hash)) && \
+$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtwayland_file_name),$($(package)_qtwayland_file_name),$($(package)_qtwayland_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_top_download_path),$($(package)_top_cmakelists_download_file),$($(package)_top_cmakelists_file_name)-$($(package)_version),$($(package)_top_cmakelists_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_ecmoptionaladdsubdirectory_download_file),$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version),$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_qttoplevelhelpers_download_file),$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version),$($(package)_top_cmake_qttoplevelhelpers_sha256_hash))
@@ -288,6 +302,7 @@ define $(package)_extract_cmds
echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_qttranslations_sha256_hash) $($(package)_source_dir)/$($(package)_qttranslations_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_qttools_sha256_hash) $($(package)_source_dir)/$($(package)_qttools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
+ echo "$($(package)_qtwayland_sha256_hash) $($(package)_source_dir)/$($(package)_qtwayland_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_top_cmake_qttoplevelhelpers_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
@@ -298,6 +313,8 @@ define $(package)_extract_cmds
$(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttranslations_file_name) -C qttranslations && \
mkdir qttools && \
$(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttools_file_name) -C qttools && \
+ mkdir qtwayland && \
+ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtwayland_file_name) -C qtwayland && \
cp $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version) ./$($(package)_top_cmakelists_file_name) && \
mkdir cmake && \
cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \
@@ -310,12 +327,15 @@ else
define $(package)_extract_cmds
mkdir -p $($(package)_extract_dir) && \
echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
+ echo "$($(package)_qtwayland_sha256_hash) $($(package)_source_dir)/$($(package)_qtwayland_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
echo "$($(package)_top_cmake_qttoplevelhelpers_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
$(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \
mkdir qtbase && \
$(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source) -C qtbase && \
+ mkdir qtwayland && \
+ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtwayland_file_name) -C qtwayland && \
cp $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version) ./$($(package)_top_cmakelists_file_name) && \
mkdir cmake && \
cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \
@@ -334,7 +354,10 @@ define $(package)_preprocess_cmds
patch -p1 -i $($(package)_patch_dir)/qtbase_platformsupport.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase_plugins_cocoa.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase_skip_tools.patch && \
- patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch
+ patch -p1 -i $($(package)_patch_dir)/qtbase_findxcb_image_aux.patch && \
+ patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \
+ patch -p1 -i $($(package)_patch_dir)/qtwayland_decoration_no_egl_gate.patch && \
+ patch -p1 -i $($(package)_patch_dir)/qtbase_fix_qyieldcpu_apple_arm.patch
endef
ifeq ($(host),$(build))
$(package)_preprocess_cmds += && patch -p1 -i $($(package)_patch_dir)/qttools_skip_dependencies.patch
diff --git a/depends/packages/qt_details.mk b/depends/packages/qt_details.mk
index 341448149e..4d7ae09532 100644
--- a/depends/packages/qt_details.mk
+++ b/depends/packages/qt_details.mk
@@ -11,6 +11,9 @@ qt_details_qttranslations_sha256_hash := dcc762acac043b9bb5e4d369b6d6f53e0ecfcf7
qt_details_qttools_file_name := qttools-$(qt_details_suffix)
qt_details_qttools_sha256_hash := f03bb7df619cd9ac9dba110e30b7bcab5dd88eb8bdc9cc752563b4367233203f
+qt_details_qtwayland_file_name := qtwayland-$(qt_details_suffix)
+qt_details_qtwayland_sha256_hash := e326c7ceb628f503bfc20577d5d2df9690ee10db08eb940cb80c759a6972b2b5
+
qt_details_patches_path := $(PATCHES_PATH)/qt
qt_details_top_download_path := https://code.qt.io/cgit/qt/qt5.git/plain
diff --git a/depends/packages/wayland.mk b/depends/packages/wayland.mk
new file mode 100644
index 0000000000..43f7b36ccf
--- /dev/null
+++ b/depends/packages/wayland.mk
@@ -0,0 +1,72 @@
+package := wayland
+native_package := native_wayland_scanner
+$(package)_version = $($(native_package)_version)
+$(package)_download_path = $($(native_package)_download_path)
+$(package)_file_name = $($(native_package)_file_name)
+$(package)_sha256_hash = $($(native_package)_sha256_hash)
+$(package)_dependencies = $(native_package) libffi expat
+
+define $(package)_config_cmds
+ cross_arg="" ; \
+ if [ "$(host)" != "$(build)" ]; then \
+ CC="$$($(package)_cc)" ; \
+ CXX="$$($(package)_cxx)" ; \
+ cc_first=$$$${CC%% *} ; \
+ cc_rest=$$$${CC#* } ; \
+ cxx_first=$$$${CXX%% *} ; \
+ cxx_rest=$$$${CXX#* } ; \
+ if [ "$$$$cc_first" = "$$$$CC" ]; then \
+ cc_line="c = ['$$$$CC']" ; \
+ else \
+ cc_line="c = ['$$$$cc_first', '$$$$cc_rest']" ; \
+ fi ; \
+ if [ "$$$$cxx_first" = "$$$$CXX" ]; then \
+ cxx_line="cpp = ['$$$$CXX']" ; \
+ else \
+ cxx_line="cpp = ['$$$$cxx_first', '$$$$cxx_rest']" ; \
+ fi ; \
+ printf '%s\n' "[binaries]" "$$$$cc_line" "$$$$cxx_line" \
+ "ar = '$$($(package)_ar)'" \
+ "strip = '$$($(package)_ranlib)'" \
+ "pkg-config = 'pkg-config'" \
+ "" \
+ "[built-in options]" \
+ "pkg_config_path = ['$(host_prefix)/lib/pkgconfig', '$(build_prefix)/lib/pkgconfig', '$(host_prefix)/share/pkgconfig']" \
+ "" \
+ "[host_machine]" \
+ "system = 'linux'" \
+ "cpu_family = '$(host_arch)'" \
+ "cpu = '$(host_arch)'" \
+ "endian = 'little'" \
+ > cross.ini ; \
+ cross_arg="--cross-file cross.ini" ; \
+ fi && \
+ PKG_CONFIG_LIBDIR=$(host_prefix)/lib/pkgconfig:$(build_prefix)/lib/pkgconfig \
+ PKG_CONFIG_PATH=$(host_prefix)/share/pkgconfig \
+ CC="$$($(package)_cc)" CXX="$$($(package)_cxx)" \
+ CFLAGS="-D_GNU_SOURCE $$($(package)_cppflags) $$($(package)_cflags)" \
+ CXXFLAGS="$$($(package)_cppflags) $$($(package)_cxxflags)" \
+ LDFLAGS="$$($(package)_ldflags)" \
+ meson setup build --prefix=$(host_prefix) \
+ $$$$cross_arg \
+ --libdir=lib \
+ --default-library=static \
+ -Dlibraries=true \
+ -Dscanner=false \
+ -Ddocumentation=false \
+ -Dtests=false \
+ -Ddtd_validation=false
+endef
+
+define $(package)_build_cmds
+ LD_LIBRARY_PATH="$(host_prefix)/lib:$(build_prefix)/lib$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH}" \
+ ninja -C build
+endef
+
+define $(package)_stage_cmds
+ DESTDIR=$($(package)_staging_dir) ninja -C build install
+endef
+
+define $(package)_postprocess_cmds
+ rm -f lib/*.la
+endef
diff --git a/depends/patches/libglvnd/fix-typeof-gcc14.patch b/depends/patches/libglvnd/fix-typeof-gcc14.patch
new file mode 100644
index 0000000000..788d5f043e
--- /dev/null
+++ b/depends/patches/libglvnd/fix-typeof-gcc14.patch
@@ -0,0 +1,27 @@
+Description: Replace typeof with __typeof__ for GCC 14+ compatibility
+ The typeof() GNU extension is not recognized correctly in newer GCC
+ versions (14+). Use __typeof__ which is the portable GCC/Clang built-in
+ that works across all C standard modes.
+
+--- a/include/glvnd_list.h
++++ b/include/glvnd_list.h
+@@ -275,7 +275,7 @@
+
+ #ifdef HAVE_TYPEOF
+ #define __glvnd_container_of(ptr, sample, member) \
+- glvnd_container_of(ptr, typeof(*sample), member)
++ glvnd_container_of(ptr, __typeof__(*sample), member)
+ #else
+ /* This implementation of __glvnd_container_of has undefined behavior according
+ * to the C standard, but it works in many cases. If your compiler doesn't
+--- a/src/util/glvnd_pthread.c
++++ b/src/util/glvnd_pthread.c
+@@ -87,7 +87,7 @@
+
+ #define GET_MT_FUNC(funcs, handle, func) \
+ pthreadRealFuncs.func = \
+- (typeof(pthreadRealFuncs.func)) \
++ (__typeof__(pthreadRealFuncs.func)) \
+ dlsym(handle, "pthread_" #func); \
+
+ static void glvndSetupPthreads(void)
diff --git a/depends/patches/native_qt/qtwayland_build_scanner_without_gui.patch b/depends/patches/native_qt/qtwayland_build_scanner_without_gui.patch
new file mode 100644
index 0000000000..d5fa39cc6c
--- /dev/null
+++ b/depends/patches/native_qt/qtwayland_build_scanner_without_gui.patch
@@ -0,0 +1,14 @@
+--- a/qtwayland/CMakeLists.txt
++++ b/qtwayland/CMakeLists.txt
+@@ -41,7 +41,10 @@
+ #endif()
+ # special case end
+ if(NOT TARGET Qt::Gui)
+- message(NOTICE "Skipping the build as the condition \"TARGET Qt::Gui\" is not met.")
++ message(NOTICE "Qt::Gui not available, building qtwaylandscanner tool only.")
++ qt_build_repo_begin()
++ add_subdirectory(src/qtwaylandscanner)
++ qt_build_repo_end()
+ return()
+ endif()
+ qt_build_repo()
diff --git a/depends/patches/qt/qtbase_findxcb_image_aux.patch b/depends/patches/qt/qtbase_findxcb_image_aux.patch
new file mode 100644
index 0000000000..97c5b417d1
--- /dev/null
+++ b/depends/patches/qt/qtbase_findxcb_image_aux.patch
@@ -0,0 +1,24 @@
+qtbase: model xcb-image's AUX dependency for static XCB builds
+
+Qt's vendored FindXCB module hardcodes XCB_IMAGE as depending only on XCB
+and SHM. That is enough for shared builds, but the static xcb-image archive
+also calls xcb_aux_create_gc() from xcb-util. When Qt serializes XCB target
+dependencies into libQt6XcbQpa.prl, the missing AUX edge drops libxcb-util.a
+from downstream link lines and native tools like qdbusviewer fail to link.
+
+Teach FindXCB that IMAGE also depends on AUX so static consumers inherit
+libxcb-util.a transitively.
+
+diff --git a/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake b/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake
+--- a/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake
++++ b/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake
+@@ -145,7 +145,7 @@ endforeach()
+ # exceptions
+ set(XCB_XCB_component_deps)
+ set(XCB_COMPOSITE_component_deps XCB XFIXES)
+ set(XCB_DAMAGE_component_deps XCB XFIXES)
+-set(XCB_IMAGE_component_deps XCB SHM)
++set(XCB_IMAGE_component_deps XCB SHM AUX)
+ set(XCB_RENDERUTIL_component_deps XCB RENDER)
+ set(XCB_XFIXES_component_deps XCB RENDER SHAPE)
+ set(XCB_XVMC_component_deps XCB XV)
diff --git a/depends/patches/qt/qtbase_fix_qyieldcpu_apple_arm.patch b/depends/patches/qt/qtbase_fix_qyieldcpu_apple_arm.patch
new file mode 100644
index 0000000000..b28abe45b7
--- /dev/null
+++ b/depends/patches/qt/qtbase_fix_qyieldcpu_apple_arm.patch
@@ -0,0 +1,19 @@
+Fix __yield implicit declaration error on Apple ARM (darwin25+)
+
+On newer Apple Clang (Xcode 16+), __has_builtin(__yield) returns true but
+calling __yield() produces an "implicitly declaring library function" error
+because the declaration requires which is not included here.
+Exclude Apple platforms from this path so the code falls through to the
+__builtin_arm_yield() branch, which is properly supported on Apple Silicon.
+
+--- a/qtbase/src/corelib/thread/qyieldcpu.h
++++ b/qtbase/src/corelib/thread/qyieldcpu.h
+@@ -31,7 +31,7 @@ void qYieldCpu(void)
+ noexcept
+ #endif
+ {
+-#if __has_builtin(__yield)
++#if __has_builtin(__yield) && !defined(__APPLE__)
+ __yield(); // Generic
+ #elif defined(_YIELD_PROCESSOR) && defined(Q_CC_MSVC)
+ _YIELD_PROCESSOR(); // Generic; MSVC's
diff --git a/depends/patches/qt/qtwayland_decoration_no_egl_gate.patch b/depends/patches/qt/qtwayland_decoration_no_egl_gate.patch
new file mode 100644
index 0000000000..7dcc4637a8
--- /dev/null
+++ b/depends/patches/qt/qtwayland_decoration_no_egl_gate.patch
@@ -0,0 +1,38 @@
+qtwayland: do not gate window decorations on a working EGL/client buffer integration
+
+QWaylandDisplay::supportsWindowDecoration() refuses to enable client-side
+decorations unless a client buffer integration (typically wayland-egl) is
+present and reports supportsWindowDecoration()==true. On environments
+without a working EGL — most commonly WSLg, where Wayland clients render
+through SHM only — wayland-egl fails to initialize and the function then
+returns false, short-circuiting QWaylandWindow::createDecoration() before
+the decoration plugin factory is ever consulted. The result is that
+statically linked Qt6 widget apps (such as firo-qt) appear without title
+bar or borders even though the bradient plugin is registered and ready.
+
+Software-rendered Wayland apps do not need EGL to draw their own frames,
+so always allow decorations unless the user has explicitly opted out via
+QT_WAYLAND_DISABLE_WINDOWDECORATION.
+
+diff --git a/qtwayland/src/client/qwaylanddisplay.cpp b/qtwayland/src/client/qwaylanddisplay.cpp
+--- a/qtwayland/src/client/qwaylanddisplay.cpp
++++ b/qtwayland/src/client/qwaylanddisplay.cpp
+@@ -859,13 +859,14 @@ void QWaylandDisplay::forceRoundTrip()
+ bool QWaylandDisplay::supportsWindowDecoration() const
+ {
+ static bool disabled = qgetenv("QT_WAYLAND_DISABLE_WINDOWDECORATION").toInt();
+- // Stop early when disabled via the environment. Do not try to load the integration in
+- // order to play nice with SHM-only, buffer integration-less systems.
++ // Stop early when disabled via the environment.
+ if (disabled)
+ return false;
+
+- static bool integrationSupport = clientBufferIntegration() && clientBufferIntegration()->supportsWindowDecoration();
+- return integrationSupport;
++ // Allow client-side decorations even when no client buffer integration is
++ // available. Gating on EGL excluded SHM-only setups (e.g. WSLg) where
++ // software-rendered Wayland windows still need to draw their own frames.
++ return true;
+ }
+
+ QWaylandWindow *QWaylandDisplay::lastInputWindow() const
diff --git a/depends/toolchain.cmake.in b/depends/toolchain.cmake.in
index 781dcad665..274c20fe8b 100644
--- a/depends/toolchain.cmake.in
+++ b/depends/toolchain.cmake.in
@@ -105,6 +105,13 @@ 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)
+
+# Qt6Core depends on WrapRt::WrapRt which uses find_library(LIBRT rt).
+# Since CMAKE_FIND_ROOT_PATH_MODE_LIBRARY is ONLY, librt (a system library)
+# cannot be found. Set it directly so the linker resolves it normally.
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ set(LIBRT "-lrt" CACHE STRING "" FORCE)
+endif()
set(QT_TRANSLATIONS_DIR "${CMAKE_CURRENT_LIST_DIR}/translations")
# Make sure we only append once, as this file may be called repeatedly.
@@ -143,6 +150,9 @@ if("@qt_packages@" MATCHES "^[ ]*$")
else()
set(BUILD_GUI ON CACHE BOOL "")
set(Qt6_ROOT "${CMAKE_CURRENT_LIST_DIR}" CACHE PATH "")
+ if("@host_os@" MATCHES "^(linux|freebsd)$")
+ set(WaylandScanner_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/native/bin/wayland-scanner" CACHE FILEPATH "" FORCE)
+ endif()
endif()
if("@qrencode_packages@" MATCHES "^[ ]*$")
diff --git a/doc/api-documentation.md b/doc/api-documentation.md
new file mode 100644
index 0000000000..575522b68b
--- /dev/null
+++ b/doc/api-documentation.md
@@ -0,0 +1,1935 @@
+# Firo API and Component Documentation
+
+This document provides comprehensive documentation for all public APIs, functions, and components in the Firo codebase.
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [RPC API Reference](#rpc-api-reference)
+ - [Blockchain RPCs](#blockchain-rpcs)
+ - [Wallet RPCs](#wallet-rpcs)
+ - [Utility RPCs](#utility-rpcs)
+ - [Network RPCs](#network-rpcs)
+ - [Mining RPCs](#mining-rpcs)
+ - [Masternode RPCs](#masternode-rpcs)
+ - [Privacy RPCs](#privacy-rpcs)
+ - [Mobile RPCs](#mobile-rpcs)
+ - [Address Index RPCs](#address-index-rpcs)
+ - [Complete RPC Command Index](#complete-rpc-command-index)
+3. [Privacy Protocols](#privacy-protocols)
+ - [Lelantus Protocol](#lelantus-protocol)
+ - [Spark Protocol](#spark-protocol)
+4. [LLMQ and ChainLocks](#llmq-and-chainlocks)
+5. [Masternode System](#masternode-system)
+6. [Core Components](#core-components)
+7. [Examples](#examples)
+
+---
+
+## Overview
+
+Firo is a privacy-focused cryptocurrency that utilizes the **Lelantus Spark protocol** for high anonymity without requiring trusted setup. The codebase includes:
+
+- **Privacy Protocols**: Lelantus and Spark for anonymous transactions
+- **LLMQ ChainLocks**: Protection against 51% attacks with quick finality
+- **FiroPOW**: ProgPOW-based proof-of-work algorithm
+- **Deterministic Masternodes**: For network services and governance
+
+---
+
+## RPC API Reference
+
+The detailed sections below describe selected high-traffic RPCs. For exhaustive coverage of every command registered in the current branch, see [Complete RPC Command Index](#complete-rpc-command-index).
+
+### Blockchain RPCs
+
+#### `getblockchaininfo`
+Returns an object containing various state info regarding blockchain processing.
+
+**Arguments:** None
+
+**Result:**
+```json
+{
+ "chain": "main",
+ "blocks": 123456,
+ "headers": 123456,
+ "bestblockhash": "000000...",
+ "difficulty": 12345.678,
+ "mediantime": 1234567890,
+ "verificationprogress": 0.99,
+ "chainwork": "000000...",
+ "pruned": false,
+ "softforks": [...],
+ "bip9_softforks": {...}
+}
+```
+
+**Example:**
+```bash
+firo-cli getblockchaininfo
+```
+
+---
+
+#### `getblockcount`
+Returns the number of blocks in the longest blockchain.
+
+**Arguments:** None
+
+**Result:** `n` (numeric) - The current block count
+
+**Example:**
+```bash
+firo-cli getblockcount
+```
+
+---
+
+#### `getbestblockhash`
+Returns the hash of the best (tip) block in the longest blockchain.
+
+**Arguments:** None
+
+**Result:** `"hex"` (string) - The block hash hex encoded
+
+**Example:**
+```bash
+firo-cli getbestblockhash
+```
+
+---
+
+#### `getblock "blockhash" [verbose]`
+Returns block data for the given block hash.
+
+**Arguments:**
+1. `blockhash` (string, required) - The block hash
+2. `verbose` (boolean, optional, default=true) - `true` for a JSON object, `false` for hex-encoded block data
+
+**Result (verbose=false):** `"data"` (string) - The serialized block encoded as hex
+
+**Result (verbose=true):**
+```json
+{
+ "hash": "000000...",
+ "confirmations": 123,
+ "strippedsize": 1234,
+ "size": 1234,
+ "weight": 4936,
+ "height": 123456,
+ "version": 4,
+ "versionHex": "00000004",
+ "merkleroot": "abc123...",
+ "tx": ["txid1", "txid2", ...],
+ "cbTx": {
+ "version": 2,
+ "height": 123456,
+ "merkleRootMNList": "abcd..."
+ },
+ "time": 1234567890,
+ "mediantime": 1234567800,
+ "nonce": 12345,
+ "bits": "1d00ffff",
+ "difficulty": 12345.678,
+ "chainwork": "000000...",
+ "previousblockhash": "000000...",
+ "nextblockhash": "000000...",
+ "chainlock": true
+}
+```
+
+**Example:**
+```bash
+firo-cli getblock "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"
+```
+
+---
+
+#### `getblockhash height`
+Returns hash of block at given height.
+
+**Arguments:**
+1. `height` (numeric, required) - The height index
+
+**Result:** `"hash"` (string) - The block hash
+
+**Example:**
+```bash
+firo-cli getblockhash 1000
+```
+
+---
+
+#### `getblockheader "hash" [verbose]`
+Returns block header information.
+
+**Arguments:**
+1. `hash` (string, required) - The block hash
+2. `verbose` (boolean, optional, default=true) - true for JSON, false for hex
+
+**Result:**
+```json
+{
+ "hash": "...",
+ "confirmations": 123,
+ "height": 123456,
+ "version": 4,
+ "merkleroot": "...",
+ "time": 1234567890,
+ "mediantime": 1234567880,
+ "nonce": 12345,
+ "bits": "1d00ffff",
+ "difficulty": 12345.678,
+ "chainwork": "...",
+ "previousblockhash": "...",
+ "nextblockhash": "...",
+ "chainlock": true
+}
+```
+
+---
+
+#### `getdifficulty`
+Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
+
+**Arguments:** None
+
+**Result:** `n.nnn` (numeric) - The difficulty value
+
+---
+
+#### `getrawmempool [verbose]`
+Returns all transaction ids in memory pool.
+
+**Arguments:**
+1. `verbose` (boolean, optional, default=false) - true for JSON object with details
+
+**Result (verbose=false):**
+```json
+["txid1", "txid2", ...]
+```
+
+**Result (verbose=true):**
+```json
+{
+ "txid": {
+ "size": 1234,
+ "fee": 0.0001,
+ "time": 1234567890,
+ "height": 123456,
+ "depends": [...],
+ "instantlock": true
+ }
+}
+```
+
+---
+
+#### `gettxout "txid" n [include_mempool]`
+Returns details about an unspent transaction output.
+
+**Arguments:**
+1. `txid` (string, required) - The transaction id
+2. `n` (numeric, required) - vout number
+3. `include_mempool` (boolean, optional) - Whether to include mempool
+
+**Result:**
+```json
+{
+ "bestblock": "...",
+ "confirmations": 123,
+ "value": 1.0,
+ "scriptPubKey": {...},
+ "coinbase": false
+}
+```
+
+---
+
+#### `gettxoutsetinfo`
+Returns statistics about the unspent transaction output set.
+
+**Arguments:** None
+
+**Result:**
+```json
+{
+ "height": 123456,
+ "bestblock": "...",
+ "transactions": 123456,
+ "txouts": 234567,
+ "hash_serialized_2": "...",
+ "disk_size": 123456789,
+ "total_amount": 12345678.0
+}
+```
+
+---
+
+#### `getchaintips`
+Returns information about all known tips in the block tree.
+
+**Arguments:** None
+
+**Result:**
+```json
+[
+ {
+ "height": 123456,
+ "hash": "...",
+ "branchlen": 0,
+ "status": "active"
+ }
+]
+```
+
+---
+
+#### `getmempoolinfo`
+Returns details on the active state of the TX memory pool.
+
+**Arguments:** None
+
+**Result:**
+```json
+{
+ "size": 123,
+ "bytes": 123456,
+ "usage": 234567,
+ "maxmempool": 300000000,
+ "mempoolminfee": 0.00001,
+ "instantsendlocks": 5
+}
+```
+
+---
+
+#### `verifychain [checklevel] [nblocks]`
+Verifies blockchain database.
+
+**Arguments:**
+1. `checklevel` (numeric, optional, 0-4, default=3) - How thorough
+2. `nblocks` (numeric, optional, default=6) - Number of blocks to check
+
+**Result:** `true|false` (boolean)
+
+---
+
+### Spark Name RPCs
+
+#### `getsparknamedata "sparkname"`
+Returns information about a Spark name.
+
+**Arguments:**
+1. `sparkname` (string, required) - The Spark name
+
+**Result:**
+```json
+{
+ "address": "spark address",
+ "validUntil": 123456,
+ "additionalInfo": "optional info"
+}
+```
+
+---
+
+#### `getsparknames [onlyown]`
+Returns a list of all Spark names.
+
+**Arguments:**
+1. `onlyown` (boolean, optional, default=false) - Only show wallet's names
+
+**Result:**
+```json
+[
+ {
+ "name": "myname",
+ "address": "sm1...",
+ "validUntil": 123456
+ }
+]
+```
+
+---
+
+### Wallet RPCs
+
+#### `getnewaddress ["account"]`
+Returns a new Firo address for receiving payments.
+
+**Arguments:**
+1. `account` (string, optional) - DEPRECATED. The account name
+
+**Result:** `"firoaddress"` (string) - The new Firo address
+
+**Example:**
+```bash
+firo-cli getnewaddress
+firo-cli getnewaddress "myaccount"
+```
+
+---
+
+#### `getbalance ["account"] [minconf] [include_watchonly]`
+Returns the total available balance.
+
+**Arguments:**
+1. `account` (string, optional) - DEPRECATED
+2. `minconf` (numeric, optional, default=1) - Minimum confirmations
+3. `include_watchonly` (boolean, optional, default=false) - Include watch-only
+
+**Result:** `n` (numeric) - Balance in FIRO
+
+**Example:**
+```bash
+firo-cli getbalance
+firo-cli getbalance "*" 6
+```
+
+---
+
+#### `sendtoaddress "address" amount [comment] [comment_to] [subtractfeefromamount]`
+Send an amount to a given address.
+
+**Arguments:**
+1. `address` (string, required) - The Firo address
+2. `amount` (numeric, required) - Amount in FIRO
+3. `comment` (string, optional) - Comment for reference
+4. `comment_to` (string, optional) - Recipient comment
+5. `subtractfeefromamount` (boolean, optional, default=false)
+
+**Result:** `"txid"` (string) - The transaction id
+
+**Example:**
+```bash
+firo-cli sendtoaddress "aXy123..." 0.1
+firo-cli sendtoaddress "aXy123..." 0.1 "donation" "seans outance"
+```
+
+---
+
+#### `listunspent [minconf] [maxconf] [addresses]`
+Returns array of unspent transaction outputs.
+
+**Arguments:**
+1. `minconf` (numeric, optional, default=1) - Minimum confirmations
+2. `maxconf` (numeric, optional, default=9999999) - Maximum confirmations
+3. `addresses` (array, optional) - Filter by addresses
+
+**Result:**
+```json
+[
+ {
+ "txid": "...",
+ "vout": 0,
+ "address": "...",
+ "amount": 1.0,
+ "confirmations": 123,
+ "spendable": true,
+ "solvable": true
+ }
+]
+```
+
+---
+
+#### `gettransaction "txid" [include_watchonly]`
+Get detailed information about an in-wallet transaction.
+
+**Arguments:**
+1. `txid` (string, required) - The transaction id
+2. `include_watchonly` (boolean, optional, default=false)
+
+**Result:**
+```json
+{
+ "amount": 1.0,
+ "fee": -0.0001,
+ "confirmations": 123,
+ "blockhash": "...",
+ "txid": "...",
+ "time": 1234567890,
+ "details": [...],
+ "hex": "..."
+}
+```
+
+---
+
+#### `listreceivedbyaddress [minconf] [include_empty] [include_watchonly]`
+List balances by receiving address.
+
+**Arguments:**
+1. `minconf` (numeric, optional, default=1)
+2. `include_empty` (boolean, optional, default=false)
+3. `include_watchonly` (boolean, optional, default=false)
+
+**Result:**
+```json
+[
+ {
+ "address": "...",
+ "account": "",
+ "amount": 1.0,
+ "confirmations": 123
+ }
+]
+```
+
+---
+
+#### `listtransactions ["account"] [count] [skip] [include_watchonly]`
+Returns recent transactions for the wallet.
+
+**Arguments:**
+1. `account` (string, optional) - DEPRECATED
+2. `count` (numeric, optional, default=10)
+3. `skip` (numeric, optional, default=0)
+4. `include_watchonly` (boolean, optional, default=false)
+
+**Result:**
+```json
+[
+ {
+ "account": "",
+ "address": "...",
+ "category": "send|receive",
+ "amount": 1.0,
+ "confirmations": 123,
+ "txid": "...",
+ "time": 1234567890
+ }
+]
+```
+
+---
+
+#### `walletpassphrase "passphrase" timeout`
+Stores the wallet decryption key in memory for 'timeout' seconds.
+
+**Arguments:**
+1. `passphrase` (string, required) - The wallet passphrase
+2. `timeout` (numeric, required) - Seconds to keep unlocked
+
+**Example:**
+```bash
+firo-cli walletpassphrase "my pass phrase" 60
+```
+
+---
+
+#### `walletlock`
+Removes the wallet encryption key from memory, locking the wallet.
+
+**Arguments:** None
+
+**Example:**
+```bash
+firo-cli walletlock
+```
+
+---
+
+#### `encryptwallet "passphrase"`
+Encrypts the wallet with 'passphrase'.
+
+**Arguments:**
+1. `passphrase` (string, required) - The passphrase
+
+**Example:**
+```bash
+firo-cli encryptwallet "my pass phrase"
+```
+
+---
+
+#### `dumpprivkey "address"`
+Reveals the private key corresponding to 'address'.
+
+**Arguments:**
+1. `address` (string, required) - The Firo address
+
+**Result:** `"key"` (string) - The private key
+
+---
+
+#### `importprivkey "privkey" ["label"] [rescan]`
+Adds a private key to your wallet.
+
+**Arguments:**
+1. `privkey` (string, required) - The private key
+2. `label` (string, optional, default="")
+3. `rescan` (boolean, optional, default=true)
+
+---
+
+#### `backupwallet "destination"`
+Safely copies wallet.dat to destination.
+
+**Arguments:**
+1. `destination` (string, required) - Destination path/filename
+
+---
+
+#### `signmessage "firoaddress" "message"`
+Sign a message with the private key of a transparent Firo address.
+
+**Arguments:**
+1. `firoaddress` (string, required) - The transparent Firo address whose private key will sign the message
+2. `message` (string, required) - The message to sign
+
+**Result:** `"signature"` (string) - Compact signature encoded in base64
+
+**Notes:**
+- Requires the wallet to be unlocked
+- The address must refer to a wallet key, not a script address
+
+**Example:**
+```bash
+firo-cli walletpassphrase "mypassphrase" 30
+firo-cli signmessage "aXy123..." "my message"
+```
+
+---
+
+
+### Utility RPCs
+
+#### `verifymessage "address" "signature" "message"`
+Verify a signed message for a transparent Firo address.
+
+**Arguments:**
+1. `address` (string, required) - The transparent Firo address used for signing
+2. `signature` (string, required) - The base64 signature returned by `signmessage`
+3. `message` (string, required) - The signed message
+
+**Result:** `true|false` (boolean) - Whether the signature is valid
+
+**Example:**
+```bash
+firo-cli verifymessage "aXy123..." "signature" "my message"
+```
+
+---
+
+
+#### `signmessagewithprivkey "privkey" "message"`
+Sign a message with a WIF private key without using the wallet key store.
+
+**Arguments:**
+1. `privkey` (string, required) - The private key in WIF format
+2. `message` (string, required) - The message to sign
+
+**Result:** `"signature"` (string) - Compact signature encoded in base64
+
+**Example:**
+```bash
+firo-cli signmessagewithprivkey "privkey" "my message"
+```
+
+---
+
+### Network RPCs
+
+#### `getnetworkinfo`
+Returns information about P2P networking.
+
+**Arguments:** None
+
+**Result:**
+```json
+{
+ "version": 140400,
+ "subversion": "/Firo:0.14.4/",
+ "protocolversion": 70214,
+ "localservices": "000000000000000d",
+ "localrelay": true,
+ "timeoffset": 0,
+ "networkactive": true,
+ "connections": 8,
+ "networks": [...],
+ "relayfee": 0.00001,
+ "localaddresses": [...]
+}
+```
+
+---
+
+#### `getpeerinfo`
+Returns data about each connected network node.
+
+**Arguments:** None
+
+**Result:**
+```json
+[
+ {
+ "id": 1,
+ "addr": "192.168.1.1:8168",
+ "services": "000000000000000d",
+ "lastsend": 1234567890,
+ "lastrecv": 1234567890,
+ "bytessent": 123456,
+ "bytesrecv": 234567,
+ "conntime": 1234567800,
+ "version": 70214,
+ "subver": "/Firo:0.14.4/",
+ "inbound": false,
+ "synced_headers": 123456,
+ "synced_blocks": 123456
+ }
+]
+```
+
+---
+
+#### `getconnectioncount`
+Returns the number of connections to other nodes.
+
+**Arguments:** None
+
+**Result:** `n` (numeric) - The connection count
+
+---
+
+#### `addnode "node" "command"`
+Attempts to add or remove a node from the addnode list.
+
+**Arguments:**
+1. `node` (string, required) - The node address
+2. `command` (string, required) - "add", "remove", or "onetry"
+
+**Example:**
+```bash
+firo-cli addnode "192.168.0.6:8168" "onetry"
+```
+
+---
+
+#### `disconnectnode "address"`
+Immediately disconnects from the specified node.
+
+**Arguments:**
+1. `address` (string, required) - The IP address/port
+
+---
+
+#### `ping`
+Requests a ping to be sent to all other nodes.
+
+**Arguments:** None
+
+---
+
+#### `setban "subnet" "command" [bantime] [absolute]`
+Adds or removes an IP/Subnet from the banned list.
+
+**Arguments:**
+1. `subnet` (string, required) - IP/Subnet
+2. `command` (string, required) - "add" or "remove"
+3. `bantime` (numeric, optional) - Time in seconds
+4. `absolute` (boolean, optional)
+
+---
+
+#### `listbanned`
+List all banned IPs/Subnets.
+
+**Arguments:** None
+
+---
+
+#### `clearbanned`
+Clear all banned IPs.
+
+**Arguments:** None
+
+---
+
+### Mining RPCs
+
+#### `getmininginfo`
+Returns mining-related information.
+
+**Arguments:** None
+
+**Result:**
+```json
+{
+ "blocks": 123456,
+ "currentblocksize": 1234,
+ "currentblockweight": 4000,
+ "currentblocktx": 5,
+ "difficulty": 12345.678,
+ "networkhashps": 123456789.0,
+ "pooledtx": 10,
+ "chain": "main"
+}
+```
+
+---
+
+#### `getnetworkhashps [nblocks] [height]`
+Returns estimated network hashes per second.
+
+**Arguments:**
+1. `nblocks` (numeric, optional, default=120) - Blocks to average over
+2. `height` (numeric, optional, default=-1) - Estimate at height
+
+**Result:** `n` (numeric) - Hashes per second
+
+---
+
+#### `getblocktemplate [template_request]`
+Returns data needed to construct a block for mining.
+
+**Arguments:**
+1. `template_request` (json object, optional) - BIP 22/23 compliant request
+
+**Result:**
+```json
+{
+ "version": 4,
+ "previousblockhash": "...",
+ "transactions": [...],
+ "coinbaseaux": {...},
+ "coinbasevalue": 625000000,
+ "target": "...",
+ "mintime": 1234567890,
+ "mutable": ["time", "transactions", "prevblock"],
+ "noncerange": "00000000ffffffff",
+ "sigoplimit": 80000,
+ "sizelimit": 1000000,
+ "curtime": 1234567900,
+ "bits": "1d00ffff",
+ "height": 123457,
+ "znode": [...],
+ "znode_payments_started": true,
+ "znode_payments_enforced": true
+}
+```
+
+---
+
+#### `submitblock "hexdata"`
+Attempts to submit a new block to the network.
+
+**Arguments:**
+1. `hexdata` (string, required) - The hex-encoded block data
+
+---
+
+#### `generate nblocks [maxtries]`
+Mine up to nblocks blocks immediately (regtest only).
+
+**Arguments:**
+1. `nblocks` (numeric, required) - Number of blocks
+2. `maxtries` (numeric, optional, default=1000000)
+
+**Result:** `[blockhashes]` (array) - Hashes of generated blocks
+
+---
+
+#### `generatetoaddress nblocks address [maxtries]`
+Mine blocks immediately to a specified address.
+
+**Arguments:**
+1. `nblocks` (numeric, required)
+2. `address` (string, required)
+3. `maxtries` (numeric, optional, default=1000000)
+
+---
+
+#### `pprpcsb "header_hash" "mix_hash" "nonce"`
+Submit a ProgPOW solution via RPC.
+
+**Arguments:**
+1. `header_hash` (string, required) - ProgPOW header hash
+2. `mix_hash` (string, required) - Mix hash from GPU miner
+3. `nonce` (string, required) - Block nonce
+
+---
+
+### Masternode RPCs
+
+#### `evoznode list [mode] [filter]`
+Get a list of masternodes in different modes.
+
+**Arguments:**
+1. `mode` (string, optional, default="json")
+ - `addr` - IP addresses
+ - `full` - Full info: status, payee, lastpaidtime, lastpaidblock, IP
+ - `info` - Info: status, payee, IP
+ - `json` - JSON format
+ - `lastpaidblock` - Last block paid
+ - `lastpaidtime` - Last time paid
+ - `owneraddress` - Owner Firo address
+ - `payee` - Payout address
+ - `pubKeyOperator` - Operator public key
+ - `status` - ENABLED/POSE_BANNED
+ - `votingaddress` - Voting address
+2. `filter` (string, optional) - Filter by outpoint
+
+**Example:**
+```bash
+firo-cli evoznode list
+firo-cli evoznode list json "aXy123"
+```
+
+---
+
+#### `evoznode count [mode]`
+Get information about number of masternodes.
+
+**Arguments:**
+1. `mode` (string, optional) - "total", "enabled", "all"
+
+**Result (no mode):**
+```json
+{
+ "total": 4000,
+ "enabled": 3900
+}
+```
+
+---
+
+#### `evoznode status`
+Print masternode status information (if running as masternode).
+
+**Result:**
+```json
+{
+ "outpoint": "txid:n",
+ "service": "192.168.1.1:8168",
+ "proTxHash": "...",
+ "collateralHash": "...",
+ "collateralIndex": 0,
+ "dmnState": {...},
+ "state": "READY",
+ "status": "Ready"
+}
+```
+
+---
+
+#### `evoznode current`
+Print info on current masternode winner.
+
+**Result:**
+```json
+{
+ "height": 123456,
+ "IP:port": "192.168.1.1:8168",
+ "proTxHash": "...",
+ "outpoint": "txid:n",
+ "payee": "aXy123..."
+}
+```
+
+---
+
+#### `evoznode winner`
+Print info on next masternode winner to vote for.
+
+---
+
+#### `evoznode winners [count] [filter]`
+Print list of masternode winners.
+
+**Arguments:**
+1. `count` (numeric, optional) - Number of winners
+2. `filter` (string, optional) - Filter results
+
+---
+
+#### `evoznsync status`
+Returns the masternode sync status.
+
+**Result:**
+```json
+{
+ "AssetID": 999,
+ "AssetName": "MASTERNODE_SYNC_FINISHED",
+ "AssetStartTime": 1234567890,
+ "Attempt": 0,
+ "IsBlockchainSynced": true,
+ "IsSynced": true,
+ "IsFailed": false
+}
+```
+
+---
+
+### Privacy RPCs - Lelantus
+
+#### `mintlelantus amount`
+Mint Lelantus coins from transparent balance.
+
+**Arguments:**
+1. `amount` (numeric, required) - Amount to mint in FIRO
+
+**Result:** `"txid"` (string) - The transaction id
+
+---
+
+#### `joinsplit [recipients] [subtractfeefrom]`
+Create a Lelantus JoinSplit transaction.
+
+**Arguments:**
+1. `recipients` (array, required) - Recipient addresses and amounts
+2. `subtractfeefrom` (array, optional) - Indices to subtract fee from
+
+**Example:**
+```bash
+firo-cli joinsplit "[{\"address\":\"aXy...\",\"amount\":1.0}]"
+```
+
+---
+
+#### `listlelantusmints [all]`
+List Lelantus mints in wallet.
+
+**Arguments:**
+1. `all` (boolean, optional, default=false) - Include used mints
+
+---
+
+#### `resetlelantusmint`
+Reset Lelantus mint state for wallet recovery.
+
+---
+
+### Privacy RPCs - Spark
+
+#### `mintspark [outputs]`
+Create a Spark mint transaction.
+
+**Arguments:**
+1. `outputs` (array, required) - Array of {address, amount, memo}
+
+**Example:**
+```bash
+firo-cli mintspark "[{\"address\":\"sm1...\",\"amount\":1.0,\"memo\":\"test\"}]"
+```
+
+---
+
+#### `spendspark [recipients] [subtractfeefrom] [coincontrol]`
+Create a Spark spend transaction.
+
+**Arguments:**
+1. `recipients` (array, required) - Recipient addresses and amounts
+2. `subtractfeefrom` (array, optional)
+3. `coincontrol` (object, optional) - Coin selection options
+
+**Example:**
+```bash
+firo-cli spendspark "[{\"address\":\"sm1...\",\"amount\":0.5}]"
+```
+
+---
+
+#### `getnewsparkaddress`
+Generate a new Spark address.
+
+**Result:** `"sparkaddress"` (string) - New Spark address
+
+---
+
+#### `getsparkbalance`
+Get total Spark balance.
+
+**Result:**
+```json
+{
+ "availableBalance": 10.5,
+ "unconfirmedBalance": 0.5,
+ "totalBalance": 11.0
+}
+```
+
+---
+
+#### `listsparkmints [all]`
+List Spark mints in wallet.
+
+**Arguments:**
+1. `all` (boolean, optional, default=false) - Include spent
+
+---
+
+#### `listsparkspends`
+List Spark spends in wallet.
+
+---
+
+### Mobile RPCs
+
+These RPCs are designed for mobile wallet support (requires `-mobile` flag).
+
+#### `getanonymityset coinGroupId startBlockHash`
+Returns Lelantus anonymity set data.
+
+**Arguments:**
+1. `coinGroupId` (int, required) - Coin group ID
+2. `startBlockHash` (string, required) - Starting block hash (empty for full set)
+
+**Result:**
+```json
+{
+ "blockHash": "base64_encoded",
+ "setHash": "base64_encoded",
+ "coins": [[coin_data, txhash, tag_or_amount, txhash], ...]
+}
+```
+
+---
+
+#### `getsparkanonymityset coinGroupId startBlockHash`
+Returns Spark anonymity set data.
+
+**Arguments:**
+1. `coinGroupId` (int, required) - Coin group ID
+2. `startBlockHash` (string, required) - Starting block hash
+
+**Result:**
+```json
+{
+ "blockHash": "base64_encoded",
+ "setHash": "base64_encoded",
+ "coins": [[serialized_coin, txhash, serial_context], ...]
+}
+```
+
+---
+
+#### `getusedcoinserials startNumber`
+Returns used Lelantus coin serials.
+
+**Arguments:**
+1. `startNumber` (int, required) - Starting offset
+
+**Result:**
+```json
+{
+ "serials": ["base64_serial", ...]
+}
+```
+
+---
+
+#### `getusedcoinstags startNumber`
+Returns used Spark coin tags.
+
+**Arguments:**
+1. `startNumber` (int, required) - Starting offset
+
+**Result:**
+```json
+{
+ "tags": ["base64_tag", ...]
+}
+```
+
+---
+
+#### `getusedcoinstagstxhashes startNumber`
+Returns used Spark coin tags paired with the transaction IDs where they were spent.
+
+**Arguments:**
+1. `startNumber` (int, required) - Number of existing elements on the client side; results before this offset are skipped
+
+**Result:**
+```json
+{
+ "tagsandtxids": [
+ ["base64_tag", "base64_txid"],
+ ...
+ ]
+}
+```
+
+**Notes:**
+- Requires the `-mobile` flag
+- Both the tag and transaction ID are base64 encoded in the result
+
+---
+
+#### `getlatestcoinid`
+Returns the latest Lelantus coin group ID.
+
+**Result:** `n` (numeric) - Latest coin group ID
+
+---
+
+#### `getsparklatestcoinid`
+Returns the latest Spark coin group ID.
+
+**Result:** `n` (numeric) - Latest coin group ID
+
+---
+
+#### `getfeerate`
+Returns the current minimum fee rate.
+
+**Result:**
+```json
+{
+ "rate": 1000
+}
+```
+
+---
+
+#### `getmempoolsparktxids`
+Returns Spark transaction IDs in mempool.
+
+**Result:** `["base64_txid", ...]`
+
+---
+
+#### `getmempoolsparktxs [txids]`
+Returns Spark metadata for transactions.
+
+**Arguments:**
+1. `txids` (object, required) - Array of transaction IDs
+
+**Result:**
+```json
+{
+ "base64_txid": {
+ "lTags": [...],
+ "serial_context": [...],
+ "coins": [...],
+ "isLocked": true
+ }
+}
+```
+
+---
+
+### Address Index RPCs
+
+Requires `-addressindex` flag.
+
+#### `getaddressbalance [addresses]`
+Returns balance for addresses.
+
+**Arguments:**
+1. (object, required) - `{"addresses": ["addr1", "addr2"]}`
+
+**Result:**
+```json
+{
+ "balance": 10000000,
+ "received": 20000000
+}
+```
+
+---
+
+#### `getaddressutxos [addresses]`
+Returns UTXOs for addresses.
+
+**Arguments:**
+1. (object, required) - `{"addresses": ["addr1"]}`
+
+**Result:**
+```json
+[
+ {
+ "address": "aXy...",
+ "txid": "...",
+ "outputIndex": 0,
+ "script": "hex...",
+ "satoshis": 10000000,
+ "height": 123456
+ }
+]
+```
+
+---
+
+#### `getaddresstxids [addresses]`
+Returns transaction IDs for addresses.
+
+**Arguments:**
+1. (object, required) - `{"addresses": ["addr1"], "start": 0, "end": 100000}`
+
+**Result:** `["txid1", "txid2", ...]`
+
+---
+
+#### `getaddressdeltas [addresses]`
+Returns balance changes for addresses.
+
+**Arguments:**
+1. (object, required) - `{"addresses": ["addr1"], "start": 0, "end": 100000}`
+
+**Result:**
+```json
+[
+ {
+ "satoshis": 10000000,
+ "txid": "...",
+ "index": 0,
+ "blockindex": 1,
+ "height": 123456,
+ "address": "aXy..."
+ }
+]
+```
+
+---
+
+#### `getaddressmempool [addresses]`
+Returns mempool deltas for addresses.
+
+**Arguments:**
+1. (object, required) - `{"addresses": ["addr1"]}`
+
+---
+
+
+
+### Complete RPC Command Index
+
+This index is derived from the RPC command registration tables in the current branch. It provides full command coverage even where the detailed sections above only expand a selected subset of commands. Categories such as `hidden` and `test` are internal or debug-oriented interfaces rather than standard end-user RPCs.
+
+#### `blockchain`
+
+`clearmempool`, `getbestblockhash`, `getblock`, `getblockchaininfo`, `getblockcount`, `getblockhash`, `getblockhashes`, `getblockheader`, `getchaintips`, `getdifficulty`, `getmempoolancestors`, `getmempooldescendants`, `getmempoolentry`, `getmempoolinfo`, `getrawmempool`, `getsparknamedata`, `getsparknames`, `getsparknametxdetails`, `getspecialtxes`, `gettxout`, `gettxoutproof`, `gettxoutsetinfo`, `preciousblock`, `pruneblockchain`, `verifychain`, `verifytxoutproof`
+
+#### `wallet`
+
+`abandontransaction`, `addmultisigaddress`, `addwitnessaddress`, `autoMintlelantus`, `automintspark`, `backupwallet`, `bumpfee`, `dumpprivkey`, `dumpsparkviewkey`, `dumpwallet`, `encryptwallet`, `getaccount`, `getaccountaddress`, `getaddressesbyaccount`, `getallsparkaddresses`, `getbalance`, `getnewaddress`, `getnewsparkaddress`, `getprivatebalance`, `getrawchangeaddress`, `getreceivedbyaccount`, `getreceivedbyaddress`, `getsparkaddressbalance`, `getsparkbalance`, `getsparkcoinaddr`, `getsparkdefaultaddress`, `gettotalbalance`, `gettransaction`, `getunconfirmedbalance`, `getwalletinfo`, `identifysparkcoins`, `importaddress`, `importmulti`, `importprivkey`, `importprunedfunds`, `importpubkey`, `importwallet`, `joinsplit`, `keypoolrefill`, `lelantustospark`, `listaccounts`, `listaddressbalances`, `listaddressgroupings`, `listlelantusjoinsplits`, `listlelantusmints`, `listlockunspent`, `listreceivedbyaccount`, `listreceivedbyaddress`, `listsinceblock`, `listsparkmints`, `listsparkspends`, `listtransactions`, `listunspent`, `listunspentlelantusmints`, `listunspentsparkmints`, `lockunspent`, `mintlelantus`, `mintspark`, `move`, `proveprivatetxown`, `regeneratemintpool`, `registersparkname`, `removeprunedfunds`, `removetxmempool`, `removetxwallet`, `requestsparknametransfer`, `resetlelantusmint`, `resetsparkmints`, `sendfrom`, `sendmany`, `sendspark`, `sendsparkmany`, `sendtoaddress`, `sendtransparent`, `setaccount`, `setlelantusmintstatus`, `setmininput`, `setsparkmintstatus`, `settxfee`, `signmessage`, `spendspark`, `transfersparkname`, `walletlock`, `walletpassphrase`, `walletpassphrasechange`
+
+#### `util`
+
+`createmultisig`, `estimatefee`, `estimatepriority`, `estimatesmartfee`, `estimatesmartpriority`, `signmessagewithprivkey`, `validateaddress`, `verifymessage`
+
+#### `network`
+
+`addnode`, `clearbanned`, `disconnectnode`, `getaddednodeinfo`, `getconnectioncount`, `getnettotals`, `getnetworkinfo`, `getpeerinfo`, `listbanned`, `ping`, `setban`, `setnetworkactive`
+
+#### `mining`
+
+`getblocktemplate`, `getmininginfo`, `getnetworkhashps`, `pprpcsb`, `prioritisetransaction`, `submitblock`
+
+#### `rawtransactions`
+
+`createrawtransaction`, `decoderawtransaction`, `decodescript`, `fundrawtransaction`, `getrawtransaction`, `sendrawtransaction`, `signrawtransaction`
+
+#### `addressindex`
+
+`getAddressNumWBalance`, `getCVE17144amount`, `getaddressbalance`, `getaddressdeltas`, `getaddressmempool`, `getaddresstxids`, `getaddressutxos`, `gettotalsupply`, `getzerocoinpoolbalance`
+
+#### `mobile`
+
+`checkifmncollateral`, `getanonymityset`, `getfeerate`, `getlatestcoinid`, `getmempoolsparktxids`, `getmempoolsparktxs`, `getmintmetadata`, `getsparkanonymityset`, `getsparkanonymitysetmeta`, `getsparkanonymitysetsector`, `getsparklatestcoinid`, `getsparkmintmetadata`, `getusedcoinserials`, `getusedcoinstags`, `getusedcoinstagstxhashes`
+
+#### `control`
+
+`getinfo`, `getmemoryinfo`, `help`, `stop`
+
+#### `generating`
+
+`generate`, `generatetoaddress`, `setgenerate`
+
+#### `bip47`
+
+`createrapaddress`, `listrapaddresses`, `sendtorapaddress`, `setupchannel`, `setusednumber`
+
+#### `evo`
+
+`bls`, `protx`, `quorum`, `removeislock`, `spork`
+
+#### `firo`
+
+`evoznode`, `evoznodelist`, `evoznsync`, `verifyprivatetxown`, `znode`, `znodelist`, `znsync`
+
+#### `hidden`
+
+`echo`, `echojson`, `getinfoex`, `getnewexchangeaddress`, `invalidateblock`, `reconsiderblock`, `resendwallettransactions`, `setmocktime`, `waitforblock`, `waitforblockheight`, `waitfornewblock`
+
+#### `test`
+
+`rpcNestedTest`
+
+
+
+## Privacy Protocols
+
+### Lelantus Protocol
+
+Lelantus is a privacy protocol that provides anonymous transactions without trusted setup. It uses:
+
+- **One-out-of-many proofs** for anonymity
+- **Range proofs** to ensure valid amounts
+- **Serial numbers** to prevent double-spending
+
+#### Key Components
+
+**PublicCoin** (`src/liblelantus/coin.h`)
+```cpp
+class PublicCoin {
+ GroupElement value; // Pedersen commitment
+ // Getters
+ const GroupElement& getValue() const;
+ bool operator==(const PublicCoin& other) const;
+};
+```
+
+**PrivateCoin** (`src/liblelantus/coin.h`)
+```cpp
+class PrivateCoin {
+ const Params* params;
+ PublicCoin publicCoin;
+ Scalar serialNumber; // Unique identifier
+ Scalar randomness; // Blinding factor
+ uint64_t v; // Value
+ // Methods
+ const Scalar& getSerialNumber() const;
+ const Scalar& getRandomness() const;
+ uint64_t getV() const;
+};
+```
+
+**JoinSplit** (`src/liblelantus/joinsplit.h`)
+- Combines multiple input coins
+- Produces new output coins
+- Proves spend authorization without revealing which coins are spent
+
+```cpp
+class JoinSplit {
+ // Create a JoinSplit transaction
+ JoinSplit(
+ const Params* p,
+ const std::vector>& Cin,
+ const std::map>& anonymity_sets,
+ const Scalar& Vout,
+ const std::vector& Cout,
+ uint64_t fee,
+ const uint256& txHash
+ );
+
+ // Verify the JoinSplit
+ bool Verify(
+ const std::map>& anonymity_sets,
+ const std::vector& Cout,
+ uint64_t Vout,
+ const uint256& txHash
+ ) const;
+};
+```
+
+---
+
+### Spark Protocol
+
+Spark is Firo's latest privacy protocol with improved features:
+
+- **Stealth addresses** for recipient privacy
+- **Better scalability** than Lelantus
+- **Flexible denominations**
+
+#### Key Components
+
+**SpendKey** (`src/libspark/keys.h`)
+```cpp
+class SpendKey {
+ SpendKey(const Params* params);
+ SpendKey(const Params* params, const Scalar& r_);
+
+ const Scalar& get_s1() const;
+ const Scalar& get_s2() const;
+ const Scalar& get_r() const;
+};
+```
+
+**FullViewKey** (`src/libspark/keys.h`)
+```cpp
+class FullViewKey {
+ FullViewKey(const SpendKey& spend_key);
+
+ const Scalar& get_s1() const;
+ const Scalar& get_s2() const;
+ const GroupElement& get_D() const;
+ const GroupElement& get_P2() const;
+};
+```
+
+**IncomingViewKey** (`src/libspark/keys.h`)
+```cpp
+class IncomingViewKey {
+ IncomingViewKey(const FullViewKey& full_view_key);
+
+ const Scalar& get_s1() const;
+ const GroupElement& get_P2() const;
+ uint64_t get_diversifier(const std::vector& d) const;
+};
+```
+
+**Address** (`src/libspark/keys.h`)
+```cpp
+class Address {
+ Address(const IncomingViewKey& incoming_view_key, uint64_t i);
+
+ std::string encode(unsigned char network) const;
+ unsigned char decode(const std::string& str);
+
+ const std::vector& get_d() const;
+ const GroupElement& get_Q1() const;
+ const GroupElement& get_Q2() const;
+};
+```
+
+**Coin** (`src/libspark/coin.h`)
+```cpp
+class Coin {
+ GroupElement S; // Serial commitment
+ GroupElement K; // Recovery key
+ GroupElement C; // Value commitment
+ // Encrypted data
+ std::vector r_;
+
+ // Methods for coin recovery and verification
+};
+```
+
+**SpendTransaction** (`src/libspark/spend_transaction.h`)
+```cpp
+class SpendTransaction {
+ SpendTransaction(
+ const Params* params,
+ const FullViewKey& full_view_key,
+ const SpendKey& spend_key,
+ const std::vector& inputs,
+ const std::unordered_map& cover_set_data,
+ const std::unordered_map>& cover_sets,
+ uint64_t f,
+ uint64_t vout,
+ const std::vector& outputs
+ );
+
+ uint64_t getFee();
+ const std::vector& getUsedLTags() const;
+ const std::vector& getOutCoins();
+
+ static bool verify(
+ const Params* params,
+ const std::vector& transactions,
+ const std::unordered_map>& cover_sets
+ );
+};
+```
+
+#### Spark Address Format
+
+Spark addresses use Bech32m encoding:
+- **Mainnet prefix:** `sm`
+- **Testnet prefix:** `st`
+- **Regtest prefix:** `sr`
+- **Devnet prefix:** `sd`
+
+Example: `sm1qw508d6qejxtdg4y5r3zarvary0c5xw7k...`
+
+#### Address Validation
+
+##### Transparent Addresses
+
+Transparent addresses use Base58Check encoding. On mainnet:
+- **P2PKH version byte:** `0x52` (decimal `82`). Standard wallet addresses from this version can begin with `a` or `Z`, depending on the payload.
+- **P2SH version byte:** `0x07`. These addresses typically begin with `3`.
+- **Exchange address version bytes:** `0x01b9bb`. These encode to the dedicated `EXX...` format.
+
+On testnet:
+- **P2PKH version byte:** `0x41` (decimal `65`), typically beginning with `T`
+- **P2SH version byte:** `0xb2` (decimal `178`)
+
+Useful regexes:
+- Mainnet P2PKH addresses beginning with `a`: `^a[1-9A-HJ-NP-Za-km-z]{33}$`
+- Mainnet P2PKH addresses beginning with `Z`: `^Z[1-9A-HJ-NP-Za-km-z]{33}$`
+- Combined mainnet P2PKH shorthand: `^[aZ][1-9A-HJ-NP-Za-km-z]{33}$`
+
+##### Spark Addresses
+
+Spark addresses use Bech32m with a human-readable part composed of `s` plus the network code:
+- **Mainnet HRP:** `sm`
+- **Testnet HRP:** `st`
+- **Regtest HRP:** `sr`
+- **Devnet HRP:** `sd`
+
+Useful regex:
+- Mainnet Spark addresses: `^sm1[a-z0-9]{100,}$`
+
+Validation notes:
+- The decoded address must use Bech32m, not legacy Bech32
+- The decoded payload must be exactly `2 * GroupElement::serialize_size + AES_BLOCKSIZE` bytes
+- The embedded network byte must match the active chain parameters
+
+---
+
+## LLMQ and ChainLocks
+
+### ChainLocks
+
+ChainLocks provide instant finality and 51% attack protection.
+
+**CChainLockSig** (`src/llmq/quorums_chainlocks.h`)
+```cpp
+class CChainLockSig {
+ int32_t nHeight; // Block height
+ uint256 blockHash; // Block hash
+ CBLSSignature sig; // BLS signature from quorum
+};
+```
+
+**CChainLocksHandler** (`src/llmq/quorums_chainlocks.h`)
+```cpp
+class CChainLocksHandler {
+ // Check if block has ChainLock
+ bool HasChainLock(int nHeight, const uint256& blockHash);
+
+ // Check for conflicting ChainLock
+ bool HasConflictingChainLock(int nHeight, const uint256& blockHash);
+
+ // Check if transaction is safe for mining
+ bool IsTxSafeForMining(const uint256& txid);
+
+ // Process new ChainLock
+ void ProcessNewChainLock(NodeId from, const CChainLockSig& clsig, const uint256& hash);
+};
+```
+
+### InstantSend
+
+Provides instant transaction confirmation.
+
+**CInstantSendLock** (`src/llmq/quorums_instantsend.h`)
+```cpp
+class CInstantSendLock {
+ std::vector inputs;
+ uint256 txid;
+ CBLSSignature sig;
+};
+```
+
+**CInstantSendManager** (`src/llmq/quorums_instantsend.h`)
+```cpp
+class CInstantSendManager {
+ // Check if transaction is locked
+ bool IsLocked(const uint256& txHash);
+
+ // Get InstantSend lock
+ CInstantSendLockPtr GetConflictingLock(const CTransaction& tx);
+};
+```
+
+---
+
+## Masternode System
+
+### Deterministic Masternodes
+
+Masternodes provide network services and participate in governance.
+
+**CDeterministicMN** (`src/evo/deterministicmns.h`)
+```cpp
+class CDeterministicMN {
+ uint256 proTxHash; // ProRegTx hash
+ COutPoint collateralOutpoint; // Collateral outpoint
+ uint16_t nOperatorReward; // Operator reward share
+ CDeterministicMNState* pdmnState; // Current state
+};
+```
+
+**CDeterministicMNState** (`src/evo/deterministicmns.h`)
+```cpp
+class CDeterministicMNState {
+ int nRegisteredHeight;
+ int nLastPaidHeight;
+ int nPoSePenalty;
+ int nPoSeRevivedHeight;
+ int nPoSeBanHeight;
+ uint16_t nRevocationReason;
+ uint256 confirmedHash;
+ CService addr; // IP address and port
+ CKeyID keyIDOwner;
+ CBLSPublicKey pubKeyOperator;
+ CKeyID keyIDVoting;
+ CScript scriptPayout;
+ CScript scriptOperatorPayout;
+};
+```
+
+### ProRegTx (Masternode Registration)
+
+```cpp
+class CProRegTx {
+ uint16_t nVersion;
+ uint16_t nType;
+ uint16_t nMode;
+ COutPoint collateralOutpoint;
+ CService addr;
+ CKeyID keyIDOwner;
+ CBLSPublicKey pubKeyOperator;
+ CKeyID keyIDVoting;
+ uint16_t nOperatorReward;
+ CScript scriptPayout;
+ uint256 inputsHash;
+ std::vector vchSig; // Owner signature
+};
+```
+
+---
+
+## Core Components
+
+### Transaction Types
+
+Firo supports multiple transaction types:
+
+| Type | Value | Description |
+|------|-------|-------------|
+| `TRANSACTION_NORMAL` | 0 | Standard transaction |
+| `TRANSACTION_PROVIDER_REGISTER` | 1 | Masternode registration |
+| `TRANSACTION_PROVIDER_UPDATE_SERVICE` | 2 | Update masternode service |
+| `TRANSACTION_PROVIDER_UPDATE_REGISTRAR` | 3 | Update masternode registrar |
+| `TRANSACTION_PROVIDER_UPDATE_REVOKE` | 4 | Revoke masternode |
+| `TRANSACTION_COINBASE` | 5 | Coinbase special transaction |
+| `TRANSACTION_LELANTUS` | 8 | Lelantus transaction |
+| `TRANSACTION_SPARK` | 9 | Spark transaction |
+
+### CWallet
+
+The wallet provides key management and transaction handling.
+
+**Key Methods:**
+```cpp
+class CWallet {
+ // Generate new key
+ bool GetKeyFromPool(CPubKey& result);
+
+ // Get balance
+ CAmount GetBalance() const;
+ CAmount GetUnconfirmedBalance() const;
+ CAmount GetImmatureBalance() const;
+
+ // Transactions
+ bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew,
+ CReserveKey& reservekey, CAmount& nFeeRet,
+ std::string& strFailReason, const CCoinControl* coinControl);
+ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman);
+
+ // Privacy operations
+ bool MintAndStoreLelantus(CAmount nValue, std::vector& mints);
+ bool JoinSplitLelantus(const std::vector& vecSend, CWalletTx& wtxNew);
+
+ // Spark operations
+ std::pair GetSparkBalance() const;
+ bool CreateSparkMintTransactions(const std::vector& outputs,
+ std::vector>& wtxAndFee);
+};
+```
+
+### Script Types
+
+```cpp
+enum txnouttype {
+ TX_NONSTANDARD, // Unrecognized script
+ TX_PUBKEY, // Pay to public key
+ TX_PUBKEYHASH, // Pay to public key hash (P2PKH)
+ TX_SCRIPTHASH, // Pay to script hash (P2SH)
+ TX_MULTISIG, // Multisignature
+ TX_NULL_DATA, // OP_RETURN
+ TX_WITNESS_V0_SCRIPTHASH, // P2WSH
+ TX_WITNESS_V0_KEYHASH, // P2WPKH
+ TX_ZEROCOINMINT, // Zerocoin mint (deprecated)
+ TX_LELANTUSMINT, // Lelantus mint
+ TX_LELANTUSJMINT, // Lelantus JMint
+ TX_SPARKMINT, // Spark mint
+ TX_SPARKSMINT, // Spark SMint
+};
+```
+
+---
+
+## Examples
+
+### Example 1: Basic Wallet Operations
+
+```bash
+# Create new address
+firo-cli getnewaddress
+
+# Check balance
+firo-cli getbalance
+
+# Send coins
+firo-cli sendtoaddress "aXy123..." 1.0
+
+# List transactions
+firo-cli listtransactions
+```
+
+### Example 2: Spark Privacy Operations
+
+```bash
+# Generate Spark address
+firo-cli getnewsparkaddress
+
+# Mint to Spark (anonymize coins)
+firo-cli mintspark "[{\"address\":\"sm1...\",\"amount\":10.0,\"memo\":\"savings\"}]"
+
+# Check Spark balance
+firo-cli getsparkbalance
+
+# Send from Spark (private transaction)
+firo-cli spendspark "[{\"address\":\"sm1...\",\"amount\":5.0}]"
+```
+
+### Example 3: Masternode Operations
+
+```bash
+# List all masternodes
+firo-cli evoznode list
+
+# Get masternode count
+firo-cli evoznode count
+
+# Check sync status
+firo-cli evoznsync status
+
+# Get current winner
+firo-cli evoznode current
+```
+
+### Example 4: Mobile Wallet Integration
+
+```python
+import requests
+import base64
+
+# Get anonymity set for Spark
+def get_spark_anonymity_set(coin_group_id, start_block_hash=""):
+ result = rpc_call("getsparkanonymityset", [str(coin_group_id), start_block_hash])
+ return {
+ "blockHash": base64.b64decode(result["blockHash"]),
+ "setHash": base64.b64decode(result["setHash"]),
+ "coins": result["coins"]
+ }
+
+# Get used coin tags
+def get_used_tags(start_number):
+ result = rpc_call("getusedcoinstags", [str(start_number)])
+ return [base64.b64decode(tag) for tag in result["tags"]]
+
+# Check if transaction is locked
+def get_mempool_spark_txs(txids):
+ return rpc_call("getmempoolsparktxs", [{"txids": txids}])
+```
+
+### Example 5: Address Index Queries
+
+```bash
+# Get balance for address
+firo-cli getaddressbalance '{"addresses": ["aXy123..."]}'
+
+# Get UTXOs
+firo-cli getaddressutxos '{"addresses": ["aXy123..."]}'
+
+# Get transaction history
+firo-cli getaddresstxids '{"addresses": ["aXy123..."], "start": 0, "end": 500000}'
+```
+
+### Example 6: Mining Pool Integration
+
+```python
+# Get block template
+template = rpc_call("getblocktemplate", [{"rules": ["segwit"]}])
+
+# For ProgPOW mining, use the pprpcheader
+header_hash = template["pprpcheader"]
+epoch = template["pprpcepoch"]
+
+# Submit solution
+result = rpc_call("pprpcsb", [header_hash, mix_hash, nonce])
+```
+
+---
+
+## Error Codes
+
+| Code | Name | Description |
+|------|------|-------------|
+| -1 | `RPC_MISC_ERROR` | General error |
+| -3 | `RPC_TYPE_ERROR` | Invalid type |
+| -4 | `RPC_WALLET_ERROR` | Wallet error |
+| -5 | `RPC_INVALID_ADDRESS_OR_KEY` | Invalid address or key |
+| -6 | `RPC_WALLET_INSUFFICIENT_FUNDS` | Insufficient funds |
+| -8 | `RPC_INVALID_PARAMETER` | Invalid parameter |
+| -12 | `RPC_WALLET_KEYPOOL_RAN_OUT` | Keypool exhausted |
+| -13 | `RPC_WALLET_UNLOCK_NEEDED` | Wallet locked |
+| -14 | `RPC_WALLET_PASSPHRASE_INCORRECT` | Wrong passphrase |
+| -17 | `RPC_WALLET_ALREADY_UNLOCKED` | Already unlocked |
+| -25 | `RPC_VERIFY_ERROR` | Verification failed |
+| -26 | `RPC_VERIFY_REJECTED` | Transaction rejected |
+| -27 | `RPC_VERIFY_ALREADY_IN_CHAIN` | Already in blockchain |
+
+---
+
+## Configuration Options
+
+### Network Options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-testnet` | false | Use testnet |
+| `-regtest` | false | Use regtest |
+| `-port=` | 8168 | P2P port |
+| `-rpcport=` | 8888 | RPC port |
+| `-maxconnections=` | 125 | Maximum connections |
+
+### Wallet Options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-disablewallet` | false | Disable wallet |
+| `-wallet=` | wallet.dat | Wallet filename |
+| `-rescan` | false | Rescan blockchain |
+| `-zapwallettxes` | 0 | Clear wallet transactions |
+
+### Privacy Options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-enablelelantus` | true | Enable Lelantus |
+| `-enablespark` | true | Enable Spark |
+
+### Index Options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-addressindex` | false | Enable address index |
+| `-txindex` | false | Enable transaction index |
+| `-timestampindex` | false | Enable timestamp index |
+| `-spentindex` | false | Enable spent index |
+
+### Mobile Support
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-mobile` | false | Enable mobile RPCs |
+
+---
+
+## See Also
+
+- [Build Instructions](build-unix.md)
+- [Developer Notes](developer-notes.md)
+- [Release Notes](release-notes.md)
+- [ZMQ Documentation](zmq.md)
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index 8269d88710..ccdc2a6880 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -104,7 +104,8 @@ Not OK (used plenty in the current source, but not picked up):
//
```
-A full list of comment syntaxes picked up by doxygen can be found at http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html,
+A full list of comment syntaxes picked up by doxygen can be found at
+[the Doxygen docblocks reference](https://www.doxygen.nl/manual/docblocks.html).
but if possible use one of the above styles.
Development tips and tricks
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py
index abe9a539d5..0d21ec34a8 100755
--- a/qa/pull-tester/rpc-tests.py
+++ b/qa/pull-tester/rpc-tests.py
@@ -105,10 +105,6 @@
'spark_mintspend.py',
'spark_spend_gettransaction.py',
'spark_setmintstatus_validation.py',
- 'lelantus_mint.py',
- 'lelantus_setmintstatus_validation.py',
- 'lelantus_mintspend.py',
- 'lelantus_spend_gettransaction.py',
'mempool_doublesend_oneblock.py',
'mempool_reorg.py',
'mempool_spendcoinbase.py',
@@ -191,7 +187,6 @@
#, 'dip4-coinbasemerkleroots.py'
# bip47
- 'bip47-sendreceive.py',
'bip47-walletrestore.py',
'sendtoaddress.py',
diff --git a/qa/rpc-tests/bip47-sendreceive.py b/qa/rpc-tests/bip47-sendreceive.py
deleted file mode 100755
index f3af936454..0000000000
--- a/qa/rpc-tests/bip47-sendreceive.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2017-2021 The Firo Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""dip47 sending receiving RPCs QA test.
-"""
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
-
-class Bip47SendReceive(BitcoinTestFramework):
-
- def __init__(self):
- super().__init__()
- self.setup_clean_chain = True
- self.num_nodes = 3
-
- def setup_network(self, split=False):
- self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
- connect_nodes_bi(self.nodes,0,1)
- connect_nodes_bi(self.nodes,1,2)
- connect_nodes_bi(self.nodes,0,2)
- self.is_network_split=False
- self.sync_all()
-
- def run_test(self):
-
- self.nodes[1].generate(210)
- node0_pcode = self.nodes[0].createrapaddress("node0-pcode0")
-
- try:
- self.nodes[1].setupchannel(node0_pcode)
- raise AssertionError('Lelantus balance should be zero')
- except JSONRPCException as e:
- assert(e.error['code']==-6)
-
- self.nodes[1].mintlelantus(1)
- self.nodes[1].mintlelantus(1)
- self.nodes[1].generate(10)
- self.nodes[1].setupchannel(node0_pcode)
- self.nodes[1].generate(1)
- sync_blocks(self.nodes)
- self.nodes[1].sendtorapaddress(node0_pcode, 10)
-
- self.nodes[1].generate(1)
- self.sync_all()
-
- assert_equal(self.nodes[0].getbalance(), Decimal("10.0001"))
-
- self.nodes[0].sendtoaddress(self.nodes[2].getaccountaddress(""), 9.99)
-
- self.sync_all()
- self.nodes[1].generate(1)
- sync_blocks(self.nodes)
-
- assert_equal(self.nodes[2].getbalance(), Decimal("9.99"))
-
-
-if __name__ == '__main__':
- Bip47SendReceive().main()
diff --git a/qa/rpc-tests/dip3-deterministicmns.py b/qa/rpc-tests/dip3-deterministicmns.py
index f92946fe37..ae5582c677 100755
--- a/qa/rpc-tests/dip3-deterministicmns.py
+++ b/qa/rpc-tests/dip3-deterministicmns.py
@@ -296,14 +296,23 @@ def assert_mnlists(self, mns):
for node in self.nodes:
self.assert_mnlist(node, mns)
- def assert_mnlist(self, node, mns):
- if not self.compare_mnlist(node, mns):
- expected = []
- for mn in mns:
- expected.append('%s, %d' % (mn.collateral_txid, mn.collateral_vout))
- self.log.error('mnlist: ' + str(node.evoznode('list', 'status')))
- self.log.error('expected: ' + str(expected))
- raise AssertionError("mnlists does not match provided mns")
+ def assert_mnlist(self, node, mns, timeout=10):
+ # Poll briefly so we don't race state propagation (e.g. right after
+ # invalidateblock, where the masternode manager's tip/cache may not
+ # yet reflect the new active tip on a slow debug build).
+ deadline = time.time() + timeout
+ while True:
+ if self.compare_mnlist(node, mns):
+ return
+ if time.time() >= deadline:
+ break
+ time.sleep(0.1)
+ expected = []
+ for mn in mns:
+ expected.append('%s, %d' % (mn.collateral_txid, mn.collateral_vout))
+ self.log.error('mnlist: ' + str(node.evoznode('list', 'status')))
+ self.log.error('expected: ' + str(expected))
+ raise AssertionError("mnlists does not match provided mns")
def wait_for_sporks(self, timeout=30):
st = time.time()
diff --git a/qa/rpc-tests/getblocktemplate_longpoll.py b/qa/rpc-tests/getblocktemplate_longpoll.py
index 3cddf4046a..8dc3ef4a30 100755
--- a/qa/rpc-tests/getblocktemplate_longpoll.py
+++ b/qa/rpc-tests/getblocktemplate_longpoll.py
@@ -34,6 +34,8 @@ def __init__(self):
def run_test(self):
print("Warning: this test will take about 70 seconds in the best case. Be patient.")
self.nodes[0].generate(10)
+ # Ensure all nodes are synced so that node 1 builds on the same tip in Test 2
+ sync_blocks(self.nodes)
templat = self.nodes[0].getblocktemplate()
longpollid = templat['longpollid']
# longpollid should not change between successive invocations if nothing else happens
@@ -45,29 +47,30 @@ def run_test(self):
thr.start()
# check that thread still lives
thr.join(5) # wait 5 seconds or until thread exits
- assert(thr.is_alive())
+ assert thr.is_alive(), "Test 1: longpoll should not have returned yet"
# Test 2: test that longpoll will terminate if another node generates a block
self.nodes[1].generate(1) # generate a block on another node
- # check that thread will exit now that new transaction entered mempool
- thr.join(5) # wait 5 seconds or until thread exits
- assert(not thr.is_alive())
+ # check that thread will exit after the block propagates to node 0
+ thr.join(30) # wait 30 seconds or until thread exits
+ assert not thr.is_alive(), "Test 2: longpoll did not return after block on node 1"
# Test 3: test that longpoll will terminate if we generate a block ourselves
thr = LongpollThread(self.nodes[0])
thr.start()
- self.nodes[0].generate(1) # generate a block on another node
- thr.join(5) # wait 5 seconds or until thread exits
- assert(not thr.is_alive())
+ self.nodes[0].generate(1) # generate a block on this node
+ thr.join(30) # wait 30 seconds or until thread exits
+ assert not thr.is_alive(), "Test 3: longpoll did not return after block on node 0"
# Test 4: test that introducing a new transaction into the mempool will terminate the longpoll
thr = LongpollThread(self.nodes[0])
thr.start()
- # generate a random transaction and submit it
- (txid, txhex, fee) = random_transaction(self.nodes, Decimal("1.1"), Decimal("0.0"), Decimal("0.001"), 20)
- # after one minute, every 10 seconds the mempool is probed, so in 80 seconds it should have returned
- thr.join(60 + 20)
- assert(not thr.is_alive())
+ # Submit a transaction directly to node 0 (where the longpoll is running)
+ # to avoid depending on cross-node mempool relay timing
+ random_transaction([self.nodes[0]], Decimal("1.1"), Decimal("0.0"), Decimal("0.001"), 20)
+ # after one minute, every 10 seconds the mempool is probed, so in 100 seconds it should have returned
+ thr.join(60 + 20 + 20)
+ assert not thr.is_alive(), "Test 4: longpoll did not return after mempool tx"
if __name__ == '__main__':
GetBlockTemplateLPTest().main()
diff --git a/qa/rpc-tests/lelantus_mint.py b/qa/rpc-tests/lelantus_mint.py
deleted file mode 100755
index e52c12f07f..0000000000
--- a/qa/rpc-tests/lelantus_mint.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_message, JSONRPCException
-
-class LelantusMintTest(BitcoinTestFramework):
- def __init__(self):
- super().__init__()
- self.num_nodes = 1
- self.setup_clean_chain = False
-
- def run_test(self):
- # generate coins
- amounts = [1, 1.1, 2, 10]
-
- # 10 confirmations
- self.nodes[0].mintlelantus(amounts[0])
- self.nodes[0].mintlelantus(amounts[1])
- self.nodes[0].generate(5)
-
- # 5 confirmations
- self.nodes[0].mintlelantus(amounts[2])
- self.nodes[0].mintlelantus(amounts[3])
- self.nodes[0].generate(5)
-
- # get all mints and utxos
- mints = self.verify_listlelantusmints(amounts)
- self.verify_listunspentlelantusmints(amounts)
- self.verify_listunspentlelantusmints([], 1000) # [1000, 9999999]
- self.verify_listunspentlelantusmints([2, 10], 1, 5) # [1, 5]
- self.verify_listunspentlelantusmints([1, 1.1], 6, 10)
- self.verify_listunspentlelantusmints([1, 1.1, 2, 10], 5, 10)
- assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints)))
-
- # state modification test
- # mark two coins as used
- self.nodes[0].setlelantusmintstatus(mints[2]["serialNumber"], True)
- self.nodes[0].setlelantusmintstatus(mints[3]["serialNumber"], True)
-
- mints = self.verify_listlelantusmints(amounts)
- self.verify_listunspentlelantusmints([1, 1.1])
- self.verify_listunspentlelantusmints([], 1000)
- self.verify_listunspentlelantusmints([], 1, 5)
- self.verify_listunspentlelantusmints([1, 1.1], 6, 10)
- self.verify_listunspentlelantusmints([1, 1.1], 5, 10)
- assert_equal([False, False, True, True], list(map(lambda m : m["isUsed"], mints)))
-
- # set a coin as unused
- self.nodes[0].setlelantusmintstatus(mints[3]["serialNumber"], False)
- mints = self.verify_listlelantusmints(amounts)
- self.verify_listunspentlelantusmints([1, 1.1, 10])
- assert_equal([False, False, True, False], list(map(lambda m : m["isUsed"], mints)))
-
- # reset coins state
- self.nodes[0].resetlelantusmint()
- mints = self.verify_listlelantusmints(amounts)
- self.verify_listunspentlelantusmints(amounts)
- assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints)))
-
- def verify_listlelantusmints(self, expected_amounts, *args):
- mints = self.nodes[0].listlelantusmints(*args)
- mints = sorted(mints, key = lambda u: u['amount'])
-
- assert_equal(
- sorted(expected_amounts),
- list(map(lambda u: u['amount'] / 1e8, mints)))
-
- return mints
-
- def verify_listunspentlelantusmints(self, expected_amounts, *args):
- utxos = self.nodes[0].listunspentlelantusmints(*args)
- utxos = sorted(utxos, key = lambda u: float(u['amount']))
-
- assert_equal(
- sorted(expected_amounts),
- list(map(lambda u: float(u['amount']), utxos)))
-
- return utxos
-
-if __name__ == '__main__':
- LelantusMintTest().main()
\ No newline at end of file
diff --git a/qa/rpc-tests/lelantus_mintspend.py b/qa/rpc-tests/lelantus_mintspend.py
deleted file mode 100755
index 689c93cdc9..0000000000
--- a/qa/rpc-tests/lelantus_mintspend.py
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/env python3
-from decimal import *
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
-
-
-class LelantusMintSpendTest(BitcoinTestFramework):
- def __init__(self):
- super().__init__()
- self.num_nodes = 4
- self.setup_clean_chain = False
-
- def run_test(self):
- # Decimal formating: 6 digits for balance will be enought 000.000
- getcontext().prec = 6
- self.nodes[0].generate(11)
- self.sync_all()
-
- start_bal = self.nodes[0].getbalance()
-
- mint_trans = list()
- mint_trans.append(self.nodes[0].mintlelantus(1))
- mint_trans.append(self.nodes[0].mintlelantus(2))
-
- # Get fees from both transactions (they may differ)
- info1 = self.nodes[0].gettransaction(mint_trans[0][0])
- info2 = self.nodes[0].gettransaction(mint_trans[1][0])
-
- # fee in transaction is negative, sum both actual fees
- fee = -(info1['fee'] + info2['fee'])
- cur_bal = self.nodes[0].getbalance()
- start_bal = float(start_bal) - float(fee) - 3
- start_bal = Decimal(format(start_bal, '.8f'))
-
- assert start_bal == cur_bal, \
- 'Unexpected current balance: {}, should be minus two mints and two fee, ' \
- 'but start was {}'.format(cur_bal, start_bal)
-
- for tr in mint_trans:
- info = self.nodes[0].gettransaction(tr[0])
- confrms = info['confirmations']
- assert confrms == 0, \
- 'Confirmations should be {}, ' \
- 'due to {} blocks was generated after transaction was created,' \
- 'but was {}'.format(0, 0, confrms)
-
- tr_type = info['details'][0]['category']
- assert tr_type == 'mint', 'Unexpected transaction type: {}'.format(tr_type)
-
- res = False
- args = {'THAYjKnnCsN5xspnEcb1Ztvw4mSPBuwxzU': 1}
- try:
- res = self.nodes[0].joinsplit(args)
- except JSONRPCException as ex:
- assert ex.error['message'] == 'Insufficient funds'
-
- assert not res, 'Did not raise spend exception, but should be.'
-
- self.nodes[0].generate(1)
- self.sync_all()
-
- # generate last confirmation block - now all transactions should be confimed
- self.nodes[0].generate(1)
- self.sync_all()
-
- for tr in mint_trans:
- info = self.nodes[0].gettransaction(tr[0])
- confrms = info['confirmations']
- assert confrms == 2, \
- 'Confirmations should be 2, ' \
- 'due to 2 blocks was generated after transaction was created,' \
- 'but was {}.'.format(confrms)
- tr_type = info['details'][0]['category']
- assert tr_type == 'mint', 'Unexpected transaction type'
-
- spend_trans = list()
- spend_total = Decimal(0)
-
- self.sync_all()
-
- start_bal = self.nodes[0].getbalance()
- print(start_bal)
- total_spend_fee = 0
-
- myaddr = self.nodes[0].listreceivedbyaddress(0, True)[0]['address']
- print(1)
- args = {myaddr: 1}
-
- spend_trans.append(self.nodes[0].joinsplit(args))
-
- info = self.nodes[0].gettransaction(spend_trans[-1])
- confrms = info['confirmations']
- tr_type = info['details'][0]['category']
- total_spend_fee += -info['fee']
- print(info['fee'])
- print(self.nodes[0].getbalance())
- spend_total = float(spend_total) + 1
- assert confrms == 0, \
- 'Confirmations should be 0, ' \
- 'due to 0 blocks was generated after transaction was created,' \
- 'but was {}.'.format(confrms)
- assert tr_type == 'spend', 'Unexpected transaction type'
- print(self.nodes[0].getbalance())
-
- before_new = self.nodes[0].getbalance()
- self.nodes[0].generate(2)
- after_new = self.nodes[0].getbalance()
- delta = after_new - before_new
- self.sync_all()
-
- # # Start balance increase on generated blocks to confirm
- start_bal += delta
- cur_bal = Decimal(format(self.nodes[0].getbalance(), '.1f'))
- spend_total = Decimal(format(spend_total, '.8f'))
-
- #TODO check why currently was not minused from total sum
- start_bal = start_bal + spend_total
-
- assert start_bal == cur_bal, \
- 'Unexpected current balance: {}, should increase on {}, ' \
- 'but start was {}'.format(cur_bal, spend_total, start_bal)
-
- for tr in spend_trans:
- info = self.nodes[0].gettransaction(tr)
-
- confrms = info['confirmations']
- tr_type = info['details'][0]['category']
- assert confrms >= 1, \
- 'Confirmations should be 1 or more, ' \
- 'due to 1 blocks was generated after transaction was created,' \
- 'but was {}.'.format(confrms)
- assert tr_type == 'spend', 'Unexpected transaction type'
-
-
-if __name__ == '__main__':
- LelantusMintSpendTest().main()
diff --git a/qa/rpc-tests/lelantus_setmintstatus_validation.py b/qa/rpc-tests/lelantus_setmintstatus_validation.py
deleted file mode 100755
index 61b4d6257e..0000000000
--- a/qa/rpc-tests/lelantus_setmintstatus_validation.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python3
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
-
-class SetLelantusMintSatusValidationWithFundsTest(BitcoinTestFramework):
- def __init__(self):
- super().__init__()
- self.num_nodes = 4
- self.setup_clean_chain = False
-
- def setup_nodes(self):
- # This test requires mocktime
- enable_mocktime()
- return start_nodes(self.num_nodes, self.options.tmpdir)
-
- def run_test(self):
- self.nodes[0].generate(100)
- self.sync_all()
-
- txid = self.nodes[0].mintlelantus(10)
-
- lelantus_mint = self.nodes[0].listlelantusmints()
-
- assert len(lelantus_mint) == len(txid), 'Should be same number.'
-
- mint_info = lelantus_mint[0]
-
- assert not mint_info['isUsed'], \
- 'This mint with txid: {} should not be Used.'.format(txid)
-
- print('Set mint status from False to True.')
-
- self.nodes[0].setlelantusmintstatus(mint_info['serialNumber'], True)
-
- lelantus_mint = self.nodes[0].listlelantusmints()
-
- assert len(lelantus_mint) == len(txid), 'Should be same number.'
-
- mint_info = lelantus_mint[0]
-
- assert mint_info['isUsed'], \
- 'This mint with txid: {} should be Used.'.format(txid)
-
- print('Set mint status from True to False back.')
-
- self.nodes[0].setlelantusmintstatus(mint_info['serialNumber'], False)
-
- lelantus_mint = self.nodes[0].listlelantusmints()
-
- assert len(lelantus_mint) == len(txid), 'Should be same number.'
-
- mint_info = lelantus_mint[0]
-
- assert not mint_info['isUsed'], \
- 'This mint with txid: {} should not be Used.'.format(txid)
-
-
- assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [(mint_info['serialNumber'], "sometext")])
- assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [mint_info['serialNumber']])
- assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [])
- assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, ["sometext"])
- assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [123])
-
-
-if __name__ == '__main__':
- SetLelantusMintSatusValidationWithFundsTest().main()
\ No newline at end of file
diff --git a/qa/rpc-tests/lelantus_spend_gettransaction.py b/qa/rpc-tests/lelantus_spend_gettransaction.py
deleted file mode 100755
index 73599a2890..0000000000
--- a/qa/rpc-tests/lelantus_spend_gettransaction.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env python3
-from decimal import *
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
-
-class JoinSplitGettransactionTest(BitcoinTestFramework):
- def __init__(self):
- super().__init__()
- self.num_nodes = 4
- self.setup_clean_chain = True
-
- def setup_nodes(self):
- # This test requires mocktime
- enable_mocktime()
- return start_nodes(self.num_nodes, self.options.tmpdir)
-
- def run_test(self):
- self.nodes[0].generate(101)
- self.sync_all()
-
- # get a watch only address
- watchonly_address = self.nodes[3].getnewaddress()
- watchonly_pubkey = self.nodes[3].validateaddress(watchonly_address)["pubkey"]
- self.nodes[0].importpubkey(watchonly_pubkey, "", True)
-
- valid_address = self.nodes[0].getnewaddress()
-
- for _ in range(10):
- self.nodes[0].mintlelantus(1)
-
- self.nodes[0].generate(10)
- self.sync_all()
-
- # case 1: Spend many with watchonly address
- spendto_wo_id = self.nodes[0].joinsplit({watchonly_address: 1})
- spendto_wo_tx = self.nodes[0].gettransaction(spendto_wo_id)
-
- assert spendto_wo_tx['amount'] == Decimal('-1')
- assert spendto_wo_tx['fee'] < Decimal('0')
- assert isinstance(spendto_wo_tx['details'], list)
- assert len(spendto_wo_tx['details']) == 1
- assert spendto_wo_tx['details'][0]['involvesWatchonly']
-
- # case 2: Spend many with watchonly address and valid address
- spendto_wo_and_valid_id = self.nodes[0].joinsplit({watchonly_address: 1, valid_address: 2})
- spendto_wo_and_valid_tx = self.nodes[0].gettransaction(spendto_wo_and_valid_id)
-
- assert spendto_wo_and_valid_tx['amount'] == Decimal('-1')
- assert spendto_wo_and_valid_tx['fee'] < Decimal('0')
- assert isinstance(spendto_wo_and_valid_tx['details'], list)
- assert len(spendto_wo_and_valid_tx['details']) == 3
-
- involves_watch_only_count = 0
- for detial in spendto_wo_and_valid_tx['details']:
- if 'involvesWatchonly' in detial and bool(detial['involvesWatchonly']):
- involves_watch_only_count += 1
-
- assert involves_watch_only_count == 1
-
- # case 3: spend many with watchonly address and invalid address
- assert_raises(JSONRPCException, self.nodes[0].joinsplit, [{watchonly_address: 1, 'invalidaddress': 2}])
-
-if __name__ == '__main__':
- JoinSplitGettransactionTest().main()
-
-
diff --git a/qa/rpc-tests/llmq-cl-evospork.py b/qa/rpc-tests/llmq-cl-evospork.py
index c22f67c73f..45dd291541 100755
--- a/qa/rpc-tests/llmq-cl-evospork.py
+++ b/qa/rpc-tests/llmq-cl-evospork.py
@@ -5,8 +5,9 @@
from test_framework.mininode import *
from test_framework.test_framework import EvoZnodeTestFramework
+from test_framework.authproxy import JSONRPCException
from test_framework.util import *
-from time import *
+from time import time, sleep
'''
llmq-chainlocks.py
@@ -33,15 +34,16 @@ def run_test(self):
# mine single block, wait for chainlock
self.nodes[0].generate(1)
-
- self.wait_for_chainlock_tip_all_nodes()
+ sync_blocks(self.nodes)
+ self.wait_for_chainlock_tip_all_nodes(self.nodes[0].getbestblockhash())
self.payment_address = self.nodes[0].getaccountaddress("")
self.nodes[0].sendtoaddress(self.payment_address, 1)
# mine many blocks, wait for chainlock
while self.nodes[0].getblockcount() < 800:
self.nodes[0].generate(20)
- self.wait_for_chainlock_tip_all_nodes()
+ sync_blocks(self.nodes, timeout=120)
+ self.wait_for_chainlock_tip_all_nodes(self.nodes[0].getbestblockhash())
# assert that all blocks up until the tip are chainlocked
for h in range(1, self.nodes[0].getblockcount()):
@@ -51,13 +53,13 @@ def run_test(self):
# cannot invalidate tip
current_tip = self.nodes[0].getbestblockhash()
self.nodes[0].invalidateblock(current_tip)
- sleep(2)
- assert(current_tip == self.nodes[0].getbestblockhash())
+ self.wait_for_tip(self.nodes[0], current_tip, timeout=15)
##### Disable chainlocks for 10 blocks
self.nodes[0].importprivkey(self.sporkprivkey)
- self.disable_chainlocks(self.nodes[0].getblockcount() + 10)
+ reenable_height = self.nodes[0].getblockcount() + 10
+ self.disable_chainlocks(reenable_height)
self.nodes[0].generate(1)
assert(False == self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"])
@@ -69,56 +71,80 @@ def run_test(self):
##### Enable chainlocks
- self.nodes[0].generate(10)
- self.nodes[0].spork('list')
- self.wait_for_chainlock_tip_all_nodes()
+ connected_nodes = [n for n in self.nodes if n != self.nodes[5]]
+
+ # Mine up to the re-enable height, then use the next block for
+ # chainlock assertions to avoid the reactivation transition edge.
+ self.nodes[0].generate(reenable_height - self.nodes[0].getblockcount())
+ sync_blocks(connected_nodes, timeout=120)
sporks = self.nodes[0].spork("list")
assert(not sporks["blockchain"])
assert(not sporks["mempool"])
+
+ self.nodes[0].generate(1)
+ sync_blocks(connected_nodes, timeout=120)
+ self.wait_for_chainlock_tip(connected_nodes, self.nodes[0].getbestblockhash(), timeout=90)
assert(True == self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"])
+ chainlocked_tip = self.nodes[0].getbestblockhash()
# generate a longer chain on the isolated node then reconnect it back and make sure it picks the chainlocked chain
self.nodes[5].generate(20)
reconnect_isolated_node(self.nodes[5], 1)
self.nodes[0].generate(1)
current_tip = self.nodes[0].getbestblockhash()
- timeout = 15
- while current_tip != self.nodes[5].getbestblockhash():
- if timeout == 0: # retry
- self.nodes[0].generate(1)
- current_tip = self.nodes[0].getbestblockhash()
- timeout = 15
- break
- sleep(1)
- timeout = timeout - 1
- while current_tip != self.nodes[5].getbestblockhash():
- assert timeout > 0, "Timed out when waiting for a chainlocked chain"
- sleep(1)
- timeout = timeout - 1
-
-
-
- def wait_for_chainlock_tip_all_nodes(self):
- for node in self.nodes:
- tip = node.getbestblockhash()
- self.wait_for_chainlock(node, tip)
-
- def wait_for_chainlock_tip(self, node):
- tip = node.getbestblockhash()
- self.wait_for_chainlock(node, tip)
-
- def wait_for_chainlock(self, node, block_hash):
+ assert self.nodes[0].getblock(current_tip)["previousblockhash"] == chainlocked_tip, \
+ "Node 0 did not extend the chainlocked tip"
+ try:
+ self.wait_for_tip(self.nodes[5], current_tip, timeout=15)
+ except AssertionError:
+ self.nodes[0].generate(1)
+ current_tip = self.nodes[0].getbestblockhash()
+ self.wait_for_tip(self.nodes[5], current_tip, timeout=15)
+ assert self.nodes[0].getbestblockhash() == current_tip, \
+ "Node 0 did not keep the chainlocked tip"
+ assert self.nodes[5].getbestblockhash() == current_tip, \
+ "Isolated node did not adopt the chainlocked tip"
+ assert self.nodes[5].getblockcount() == self.nodes[0].getblockcount(), \
+ "Node 5 block count diverges from node 0 after reconnect"
+
+
+
+ def wait_for_chainlock_tip_all_nodes(self, tip_hash=None, timeout=60):
+ self.wait_for_chainlock_tip(self.nodes, tip_hash, timeout)
+
+ def wait_for_chainlock_tip(self, nodes, tip_hash=None, timeout=60):
+ if not isinstance(nodes, list):
+ nodes = [nodes]
+ if tip_hash is None:
+ tip_hash = nodes[0].getbestblockhash()
+ for node in nodes:
+ self.wait_for_chainlock(node, tip_hash, timeout)
+
+ def wait_for_chainlock(self, node, block_hash, timeout=60):
t = time()
- while time() - t < 30:
+ while time() - t < timeout:
try:
block = node.getblock(block_hash)
if block["confirmations"] > 0 and block["chainlock"]:
return
- except:
- # block might not be on the node yet
+ except JSONRPCException:
pass
sleep(0.1)
- raise AssertionError("wait_for_chainlock timed out")
+ raise AssertionError("wait_for_chainlock timed out for block %s" % block_hash)
+
+ def wait_for_tip(self, node, expected_tip, timeout=15):
+ """Wait until node's best block hash equals expected_tip."""
+ last_tip = ""
+ t = time()
+ while time() - t < timeout:
+ try:
+ last_tip = node.getbestblockhash()
+ if last_tip == expected_tip:
+ return
+ except JSONRPCException:
+ pass
+ sleep(0.5)
+ raise AssertionError("wait_for_tip timed out: expected tip %s, got %s" % (expected_tip, last_tip))
def disable_chainlocks(self, till_height):
self.nodes[0].spork(self.sporkprivkey, self.payment_address, {"disable":{"chainlocks": till_height}})
diff --git a/qa/rpc-tests/llmq-is-spark.py b/qa/rpc-tests/llmq-is-spark.py
index 2c70c5e81a..d59ac343c5 100755
--- a/qa/rpc-tests/llmq-is-spark.py
+++ b/qa/rpc-tests/llmq-is-spark.py
@@ -18,7 +18,7 @@
Testing Instantsend for Spark transactions
'''
-class LLMQ_IS_Lelantus(EvoZnodeTestFramework):
+class LLMQ_IS_Spark(EvoZnodeTestFramework):
def __init__(self):
super().__init__(6, 5, extra_args=[['-debug=instantsend']] * 6 )
self.sporkprivkey = "cW2YM2xaeCaebfpKguBahUAgEzLXgSserWRuD29kSyKHq1TTgwRQ"
@@ -55,4 +55,4 @@ def run_test(self):
assert(self.wait_for_instantlock(spendTxid, self.nodes[0]))
if __name__ == '__main__':
- LLMQ_IS_Lelantus().main()
+ LLMQ_IS_Spark().main()
diff --git a/qa/rpc-tests/proxy_test.py b/qa/rpc-tests/proxy_test.py
index 9ccc0ffbb0..37bcbfd30d 100755
--- a/qa/rpc-tests/proxy_test.py
+++ b/qa/rpc-tests/proxy_test.py
@@ -170,14 +170,21 @@ def networks_dict(d):
r[x['name']] = x
return r
+ def assert_network_names(info):
+ assert_equal(sorted(x['name'] for x in info['networks']), ['ipv4', 'ipv6', 'onion'])
+
# test RPC getnetworkinfo
- n0 = networks_dict(self.nodes[0].getnetworkinfo())
+ n0_info = self.nodes[0].getnetworkinfo()
+ assert_network_names(n0_info)
+ n0 = networks_dict(n0_info)
for net in ['ipv4','ipv6','onion']:
assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n0[net]['proxy_randomize_credentials'], True)
assert_equal(n0['onion']['reachable'], True)
- n1 = networks_dict(self.nodes[1].getnetworkinfo())
+ n1_info = self.nodes[1].getnetworkinfo()
+ assert_network_names(n1_info)
+ n1 = networks_dict(n1_info)
for net in ['ipv4','ipv6']:
assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n1[net]['proxy_randomize_credentials'], False)
@@ -185,14 +192,18 @@ def networks_dict(d):
assert_equal(n1['onion']['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['reachable'], True)
- n2 = networks_dict(self.nodes[2].getnetworkinfo())
+ n2_info = self.nodes[2].getnetworkinfo()
+ assert_network_names(n2_info)
+ n2 = networks_dict(n2_info)
for net in ['ipv4','ipv6','onion']:
assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n2[net]['proxy_randomize_credentials'], True)
assert_equal(n2['onion']['reachable'], True)
if self.have_ipv6:
- n3 = networks_dict(self.nodes[3].getnetworkinfo())
+ n3_info = self.nodes[3].getnetworkinfo()
+ assert_network_names(n3_info)
+ n3 = networks_dict(n3_info)
for net in ['ipv4','ipv6']:
assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
assert_equal(n3[net]['proxy_randomize_credentials'], False)
diff --git a/qa/rpc-tests/spark_mint.py b/qa/rpc-tests/spark_mint.py
index e57da982ba..598e0a99f1 100755
--- a/qa/rpc-tests/spark_mint.py
+++ b/qa/rpc-tests/spark_mint.py
@@ -3,17 +3,15 @@
from test_framework.util import assert_equal, assert_raises_message, JSONRPCException
class SparkMintTest(BitcoinTestFramework):
- def __init__(self):
- super().__init__()
+ def set_test_params(self):
self.num_nodes = 1
- self.setup_clean_chain = False
+ self.setup_clean_chain = True
def run_test(self):
assert_raises_message(
JSONRPCException,
"Spark is not activated yet",
self.nodes[0].mintspark, 1)
-
self.nodes[0].generate(501)
# generate coins
diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py
index 3e320b592c..399efa9f9f 100644
--- a/qa/rpc-tests/test_framework/util.py
+++ b/qa/rpc-tests/test_framework/util.py
@@ -448,6 +448,11 @@ def stop_node(node, i, wait=True):
node.stop()
except http.client.CannotSendRequest as e:
logger.exception("Unable to stop node")
+ except (ConnectionError, http.client.HTTPException) as e:
+ # firod may close the RPC socket before sending a response and then
+ # refuse further connections while shutting down. Treat these as a
+ # successful stop request; wait_node() below confirms the process exits.
+ logger.debug("Stop RPC on node %d disconnected (%s); relying on process exit" % (i, e))
if wait:
wait_node(i)
diff --git a/qa/rpc-tests/wallet-dump.py b/qa/rpc-tests/wallet-dump.py
index c1fee55d1c..d495330d8f 100755
--- a/qa/rpc-tests/wallet-dump.py
+++ b/qa/rpc-tests/wallet-dump.py
@@ -81,8 +81,8 @@ def setup_network(self, split=False):
def run_test (self):
tmpdir = self.options.tmpdir
- # 21 hdmint keys generated initially (0-20)
- hdmint_key_count = 21
+ # hdmint/sigma mint keys no longer generated (Lelantus stripped)
+ hdmint_key_count = 0
# generate 20 addresses to compare against the dump
test_addr_count = 20
@@ -132,8 +132,8 @@ def run_test (self):
assert_equal(found_addr, test_addr_count)
assert_equal(found_addr_chg, 50) # 50 block were mined
- # Wallet encryption doesn't change master key anymore, therefore we just verify hdmint_key_count is the same as before.
- assert_equal(found_addr_sigma, hdmint_key_count) # hdmint keys
+ # Wallet encryption doesn't change master key anymore; sigma key count unchanged after Lelantus strip (0).
+ assert_equal(found_addr_sigma, hdmint_key_count)
assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one)
if __name__ == '__main__':
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bf9e78a2d8..aa80161a0d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -200,7 +200,6 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/compressor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core_read.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core_write.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/hdmint/hdmint.cpp
${CMAKE_CURRENT_SOURCE_DIR}/init.cpp
${CMAKE_CURRENT_SOURCE_DIR}/key.cpp
${CMAKE_CURRENT_SOURCE_DIR}/keystore.cpp
@@ -270,22 +269,6 @@ add_library(firo_node STATIC EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/httprpc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/httpserver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/init.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/lelantus.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/coin.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/innerproduct_proof_generator.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/innerproduct_proof_verifier.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/joinsplit.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/lelantus_primitives.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/lelantus_prover.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/lelantus_verifier.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/params.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/range_prover.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/range_verifier.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/schnorr_prover.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/schnorr_verifier.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/sigmaextended_prover.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/sigmaextended_verifier.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/spend_metadata.cpp
${CMAKE_CURRENT_SOURCE_DIR}/libspark/aead.cpp
${CMAKE_CURRENT_SOURCE_DIR}/libspark/bech32.cpp
${CMAKE_CURRENT_SOURCE_DIR}/libspark/bpplus.cpp
diff --git a/src/arith_uint256.h b/src/arith_uint256.h
index b1543a4dea..6f51f2d9bd 100644
--- a/src/arith_uint256.h
+++ b/src/arith_uint256.h
@@ -292,7 +292,7 @@ class arith_uint256 : public base_uint<256> {
* complexities of the sign bit and using base 256 are probably an
* implementation accident.
*/
- arith_uint256& SetCompact(uint32_t nCompact, bool *pfNegative = NULL, bool *pfOverflow = NULL);
+ arith_uint256& SetCompact(uint32_t nCompact, bool* pfNegative = nullptr, bool* pfOverflow = nullptr);
uint32_t GetCompact(bool fNegative = false) const;
friend uint256 ArithToUint256(const arith_uint256 &);
diff --git a/src/batchproof_container.cpp b/src/batchproof_container.cpp
index 98b760d89d..e2a2fe040e 100644
--- a/src/batchproof_container.cpp
+++ b/src/batchproof_container.cpp
@@ -1,8 +1,4 @@
#include "batchproof_container.h"
-#include "liblelantus/sigmaextended_verifier.h"
-#include "liblelantus/threadpool.h"
-#include "liblelantus/range_verifier.h"
-#include "lelantus.h"
#include "ui_interface.h"
#include "spark/state.h"
@@ -18,20 +14,11 @@ BatchProofContainer* BatchProofContainer::get_instance() {
}
void BatchProofContainer::init() {
- tempRangeProofs.clear();
tempSparkTransactions.clear();
}
void BatchProofContainer::finalize() {
if (fCollectProofs) {
- for (const auto& itr : tempLelantusSigmaProofs) {
- lelantusSigmaProofs[itr.first].insert(lelantusSigmaProofs[itr.first].begin(), itr.second.begin(), itr.second.end());
- }
-
- for (const auto& itr : tempRangeProofs) {
- rangeProofs[itr.first].insert(rangeProofs[itr.first].begin(), itr.second.begin(), itr.second.end());
- }
-
sparkTransactions.insert(sparkTransactions.end(), tempSparkTransactions.begin(), tempSparkTransactions.end());
}
fCollectProofs = false;
@@ -39,218 +26,11 @@ void BatchProofContainer::finalize() {
void BatchProofContainer::verify() {
if (!fCollectProofs) {
- batch_lelantus();
- batch_rangeProofs();
batch_spark();
}
fCollectProofs = false;
}
-void BatchProofContainer::add(lelantus::JoinSplit* joinSplit,
- const std::map& setSizes,
- const Scalar& challenge,
- bool fStartLelantusBlacklist) {
- const std::vector& sigma_proofs = joinSplit->getLelantusProof().sigma_proofs;
- const std::vector& serials = joinSplit->getCoinSerialNumbers();
- const std::vector& groupIds = joinSplit->getCoinGroupIds();
- if (joinSplit->isSigmaToLelantus())
- return;
-
- for (size_t i = 0; i < sigma_proofs.size(); i++) {
- // pair(pair(set id, fAfterFixes), isSigmaToLelantus)
- std::pair idAndFlag = std::make_pair(groupIds[i], fStartLelantusBlacklist);
- tempLelantusSigmaProofs[idAndFlag].push_back(LelantusSigmaProofData(sigma_proofs[i], serials[i], challenge, setSizes.at(groupIds[i])));
- }
-}
-
-
-void BatchProofContainer::add(lelantus::JoinSplit* joinSplit, const std::vector& Cout) {
- tempRangeProofs[joinSplit->getVersion()].push_back(std::make_pair(joinSplit->getLelantusProof().bulletproofs, Cout));
-}
-
-void BatchProofContainer::removeLelantus(std::unordered_map spentSerials) {
- for (auto& spendSerial : spentSerials) {
-
- int id = spendSerial.second;
-
- // afterFixes bool with the pair of set id is considered separate set identifiers, so try to find in one set, if not found try also in another
- std::pair key1 = std::make_pair(id, false);
- std::pair key2 = std::make_pair(id, true);
- std::vector* vProofs;
- if (lelantusSigmaProofs.count(key1) > 0) {
- vProofs = &lelantusSigmaProofs[key1];
- erase(vProofs, spendSerial.first);
- }
-
- if (lelantusSigmaProofs.count(key2) > 0) {
- vProofs = &lelantusSigmaProofs[key2];
- erase(vProofs, spendSerial.first);
- }
- }
-}
-
-void BatchProofContainer::remove(const std::vector& rangeProofsToRemove) {
- for (auto& itrRemove : rangeProofsToRemove) {
- for (auto itrVersions = rangeProofs.begin(); itrVersions != rangeProofs.end(); ++itrVersions) {
- bool found = false;
- for (auto itr = itrVersions->second.begin(); itr != itrVersions->second.end(); ++itr) {
- if (itr->first.T_x1 == itrRemove.T_x1 && itr->first.T_x2 == itrRemove.T_x2 && itr->first.u == itrRemove.u) {
- itrVersions->second.erase(itr);
- found = true;
- break;
- }
- }
- if (itrVersions->second.empty()) {
- rangeProofs.erase(itrVersions);
- itrVersions--;
- }
- if (found)
- break;
- }
- }
-}
-
-void BatchProofContainer::erase(std::vector* vProofs, const Scalar& serial) {
- vProofs->erase(std::remove_if(vProofs->begin(),
- vProofs->end(),
- [serial](LelantusSigmaProofData& proof){return proof.serialNumber == serial;}),
- vProofs->end());
-
-}
-
-void BatchProofContainer::batch_lelantus() {
- if (!lelantusSigmaProofs.empty()){
- LogPrintf("Lelantus batch verification started.\n");
- uiInterface.UpdateProgressBarLabel("Batch verifying Lelantus...");
- }
- else
- return;
-
- auto params = lelantus::Params::get_default();
-
- DoNotDisturb dnd;
- std::size_t threadsMaxCount = std::min((unsigned int)lelantusSigmaProofs.size(), boost::thread::hardware_concurrency());
- std::vector> parallelTasks;
- parallelTasks.reserve(threadsMaxCount);
- ParallelOpThreadPool threadPool(threadsMaxCount);
- auto itr = lelantusSigmaProofs.begin();
-
- lelantus::SigmaExtendedVerifier sigmaVerifier(params->get_g(), params->get_sigma_h(), params->get_sigma_n(),
- params->get_sigma_m());
- for (std::size_t j = 0; j < lelantusSigmaProofs.size(); j += threadsMaxCount) {
- for (std::size_t i = j; i < j + threadsMaxCount; ++i) {
- if (i < lelantusSigmaProofs.size()) {
- std::vector anonymity_set;
- lelantus::CLelantusState* state = lelantus::CLelantusState::GetState();
- std::vector coins;
- state->GetAnonymitySet(
- itr->first.first,
- itr->first.second,
- coins);
- anonymity_set.reserve(coins.size());
- for (auto& coin : coins)
- anonymity_set.emplace_back(coin.getValue());
-
- size_t m = itr->second.size();
- std::vector serials;
- serials.reserve(m);
- std::vector setSizes;
- setSizes.reserve(m);
- std::vector proofs;
- proofs.reserve(m);
- std::vector challenges;
- challenges.reserve(m);
-
- for (auto& proofData : itr->second) {
- serials.emplace_back(proofData.serialNumber);
- setSizes.emplace_back(proofData.anonymitySetSize);
- proofs.emplace_back(proofData.lelantusSigmaProof);
- challenges.emplace_back(proofData.challenge);
- }
-
-
-
- parallelTasks.emplace_back(threadPool.PostTask([=]() {
- try {
- if (!sigmaVerifier.batchverify(anonymity_set, challenges, serials, setSizes, proofs))
- return false;
- } catch (const std::exception &) {
- return false;
- }
- return true;
- }));
-
- ++itr;
- }
- }
- bool isFail = false;
- for (auto& th : parallelTasks) {
- if (!th.get())
- isFail = true;
- }
-
- if (isFail) {
- LogPrintf("Lelantus batch verification failed.");
- throw std::invalid_argument("Lelantus batch verification failed, please run Firo with -reindex -batching=0");
- }
-
- parallelTasks.clear();
- }
- if (!lelantusSigmaProofs.empty())
- LogPrintf("Lelantus batch verification finished successfully.\n");
- lelantusSigmaProofs.clear();
-}
-
-void BatchProofContainer::batch_rangeProofs() {
- if (!rangeProofs.empty()){
- LogPrintf("RangeProof batch verification started.\n");
- uiInterface.UpdateProgressBarLabel("Batch verifying Range Proofs...");
- }
-
- auto params = lelantus::Params::get_default();
- for (const auto& itr : rangeProofs) {
- lelantus::RangeVerifier rangeVerifier(params->get_h1(), params->get_h0(), params->get_g(), params->get_bulletproofs_g(), params->get_bulletproofs_h(), params->get_bulletproofs_n(), itr.first);
- std::vector> V;
- std::vector> commitments;
- size_t proofSize = itr.second.size();
- V.resize(proofSize); //size of batch
- commitments.resize(proofSize); // size of batch
- std::vector proofs;
- proofs.reserve(proofSize); // size of batch
- for (size_t i = 0; i < proofSize; ++i) {
- size_t coutSize = itr.second[i].second.size();
- std::size_t m = coutSize * 2;
-
- while (m & (m - 1))
- m++;
- proofs.emplace_back(itr.second[i].first);
- V[i].reserve(m); // aggregation size
- commitments[i].reserve(2 * coutSize);
- commitments[i].resize(coutSize); // prepend zero elements, to match the prover's behavior
- auto& Cout = itr.second[i].second;
- for (std::size_t j = 0; j < coutSize; ++j) {
- V[i].push_back(Cout[j].getValue());
- V[i].push_back(Cout[j].getValue() + params->get_h1_limit_range());
- commitments[i].emplace_back(Cout[j].getValue());
- }
-
- // Pad with zero elements
- for (std::size_t t = coutSize * 2; t < m; ++t)
- V[i].push_back(GroupElement());
- }
-
- if (!rangeVerifier.verify(V, commitments, proofs)) {
- LogPrintf("RangeProof batch verification failed.\n");
- throw std::invalid_argument("RangeProof batch verification failed, please run Firo with -reindex -batching=0");
- }
- }
-
- if (!rangeProofs.empty())
- LogPrintf("RangeProof batch verification finished successfully.\n");
-
- rangeProofs.clear();
-}
-
void BatchProofContainer::add(const spark::SpendTransaction& tx) {
tempSparkTransactions.push_back(tx);
}
diff --git a/src/batchproof_container.h b/src/batchproof_container.h
index 519c84074c..dc87dcfce2 100644
--- a/src/batchproof_container.h
+++ b/src/batchproof_container.h
@@ -3,7 +3,6 @@
#include
#include "chain.h"
-#include "liblelantus/joinsplit.h"
#include "libspark/spend_transaction.h"
extern CChain chainActive;
@@ -12,42 +11,12 @@ class BatchProofContainer {
public:
static BatchProofContainer* get_instance();
- struct LelantusSigmaProofData {
- LelantusSigmaProofData(const lelantus::SigmaExtendedProof& lelantusSigmaProof_,
- const Scalar& serialNumber_,
- const Scalar& challenge_,
- size_t anonymitySetSize_)
- : lelantusSigmaProof(lelantusSigmaProof_),
- serialNumber(serialNumber_),
- challenge(challenge_),
- anonymitySetSize(anonymitySetSize_) {}
-
- lelantus::SigmaExtendedProof lelantusSigmaProof;
- Scalar serialNumber;
- Scalar challenge;
- size_t anonymitySetSize;
- };
-
void init();
void finalize();
void verify();
- void add(lelantus::JoinSplit* joinSplit,
- const std::map& setSizes,
- const Scalar& challenge,
- bool fStartLelantusBlacklist);
-
- void add(lelantus::JoinSplit* joinSplit, const std::vector& Cout);
-
- void removeLelantus(std::unordered_map spentSerials);
- void remove(const std::vector& rangeProofsToRemove);
- void erase(std::vector* vProofs, const Scalar& serial);
-
- void batch_lelantus();
- void batch_rangeProofs();
-
void add(const spark::SpendTransaction& tx);
void remove(const spark::SpendTransaction& tx);
void batch_spark();
@@ -56,17 +25,9 @@ class BatchProofContainer {
private:
static std::unique_ptr instance;
- // temp containers, to forget in case block connection fails
- // map ((id, afterFixes), fIsSigmaToLelantus) to (sigma proof, serial, set size, challenge)
- std::map, std::vector> tempLelantusSigmaProofs;
- // map (version to (Range proof, Pubcoins))
- std::map>>> tempRangeProofs;
// temp spark transaction proofs
std::vector tempSparkTransactions;
- // containers to keep proofs for batching
- std::map, std::vector> lelantusSigmaProofs;
- std::map>>> rangeProofs;
// spark transaction proofs
std::vector sparkTransactions;
};
diff --git a/src/bip47/account.cpp b/src/bip47/account.cpp
index 6a0c1f9538..1d75f63c9b 100644
--- a/src/bip47/account.cpp
+++ b/src/bip47/account.cpp
@@ -6,7 +6,6 @@
#include "util.h"
#include "bip47/bip47utils.h"
#include "wallet/wallet.h"
-#include "lelantus.h"
namespace bip47 {
@@ -251,27 +250,10 @@ bool CAccountReceiver::acceptMaskedPayload(std::vector const & ma
bool CAccountReceiver::acceptMaskedPayload(std::vector const & maskedPayload, CTransaction const & tx)
{
- std::unique_ptr jsplit;
- try {
- jsplit = lelantus::ParseLelantusJoinSplit(tx);
- }catch (const std::exception &) {
- return false;
- }
- if (!jsplit)
- return false;
- std::unique_ptr pcode;
- CExtKey pcodePrivkey = utils::Derive(privkey, {0});
- try {
- CDataStream ds(SER_NETWORK, 0);
- ds << jsplit->getCoinSerialNumbers()[0];
- pcode = bip47::utils::PcodeFromMaskedPayload(maskedPayload, (unsigned char const *)ds.vch.data(), ds.vch.size(), pcodePrivkey.key, jsplit->GetEcdsaPubkeys()[0]);
- if (!pcode)
- return false;
- } catch (std::runtime_error const &) {
- return false;
- }
- acceptPcode(*pcode);
- return true;
+ // Lelantus was removed; BIP47 masked payload from Lelantus JoinSplit is no longer parsed.
+ (void)maskedPayload;
+ (void)tx;
+ return false;
}
CPaymentCode const & CAccountReceiver::lastPcode() const
diff --git a/src/bip47/secretpoint.cpp b/src/bip47/secretpoint.cpp
index 1c7b97ae9d..3f55039400 100644
--- a/src/bip47/secretpoint.cpp
+++ b/src/bip47/secretpoint.cpp
@@ -1,6 +1,4 @@
-#include "liblelantus/coin.h"
#include "bip47/secretpoint.h"
-#include "liblelantus/openssl_context.h"
#include "bip47/bip47utils.h"
#include "utilstrencodings.h"
diff --git a/src/bloom.cpp b/src/bloom.cpp
index 9b18b2468f..e41103db7f 100644
--- a/src/bloom.cpp
+++ b/src/bloom.cpp
@@ -223,8 +223,8 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t
}
case(TRANSACTION_COINBASE):
case(TRANSACTION_QUORUM_COMMITMENT):
+ case(TRANSACTION_LELANTUS):
case (TRANSACTION_SPORK):
- case (TRANSACTION_LELANTUS):
// No aditional checks for this transaction types
return false;
}
diff --git a/src/bls/bls.h b/src/bls/bls.h
index 6330d0f95c..a95e552e0a 100644
--- a/src/bls/bls.h
+++ b/src/bls/bls.h
@@ -13,6 +13,10 @@
// bls-signatures uses relic, which may define DEBUG and ERROR, which leads to many warnings in some build setups
#undef ERROR
#undef DEBUG
+#ifdef __APPLE__
+// macOS defines err_get_code as a macro which conflicts with relic's function declaration
+#undef err_get_code
+#endif
#include
#include
#include
diff --git a/src/chain.h b/src/chain.h
index f4629b72c8..76a04b9a05 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -14,7 +14,6 @@
#include "bitcoin_bignum/bignum.h"
#include
#include
-#include "liblelantus/coin.h"
#include "libspark/coin.h"
#include "evo/spork.h"
#include "firo_params.h"
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index d22e882c0e..653f155ac1 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -485,9 +485,6 @@ class CMainParams : public CChainParams {
// Bip39
consensus.nMnemonicBlock = 222400;
- // moving lelantus data to v3 payload
- consensus.nLelantusV3PayloadStartBlock = 401580;
-
// ProgPow
consensus.nPPSwitchTime = 1635228000; // Tue Oct 26 2021 06:00:00 GMT+0000
consensus.nPPBlockNumber = 419264;
@@ -504,6 +501,7 @@ class CMainParams : public CChainParams {
consensus.nSparkNamesStartBlock = 1104500; // ~ May 28th 2025
consensus.nSparkNamesFee = standardSparkNamesFee;
consensus.nSparkNamesV2StartBlock = SPARK_NAME_TRANSFER_MAINNET_START_BLOCK;
+ consensus.nSparkNamesV21StartBlock = SPARK_NAME_V21_MAINNET_START_BLOCK;
}
virtual bool SkipUndoForBlock(int nHeight) const override
{
@@ -746,7 +744,6 @@ class CTestNetParams : public CChainParams {
consensus.nLelantusStartBlock = ZC_LELANTUS_TESTNET_STARTING_BLOCK;
consensus.nLelantusFixesStartBlock = ZC_LELANTUS_TESTNET_FIXES_START_BLOCK;
-
consensus.nSparkStartBlock = SPARK_TESTNET_START_BLOCK;
consensus.nLelantusGracefulPeriod = LELANTUS_TESTNET_GRACEFUL_PERIOD;
consensus.nSigmaEndBlock = ZC_SIGMA_TESTNET_END_BLOCK;
@@ -802,9 +799,6 @@ class CTestNetParams : public CChainParams {
// Bip39
consensus.nMnemonicBlock = 1;
- // moving lelantus data to v3 payload
- consensus.nLelantusV3PayloadStartBlock = 35000;
-
// ProgPow
consensus.nPPSwitchTime = 1630069200; // August 27 2021, 13:00 UTC
consensus.nPPBlockNumber = 37305;
@@ -820,6 +814,7 @@ class CTestNetParams : public CChainParams {
consensus.nSparkNamesStartBlock = 174000;
consensus.nSparkNamesFee = standardSparkNamesFee;
consensus.nSparkNamesV2StartBlock = SPARK_NAME_TRANSFER_TESTNET_START_BLOCK;
+ consensus.nSparkNamesV21StartBlock = 200000;
}
};
@@ -1060,9 +1055,6 @@ class CDevNetParams : public CChainParams {
// Bip39
consensus.nMnemonicBlock = 1;
- // moving lelantus data to v3 payload
- consensus.nLelantusV3PayloadStartBlock = 1;
-
// ProgPow
consensus.nPPSwitchTime = 1631261566; // immediately after network start
consensus.nPPBlockNumber = 1;
@@ -1078,6 +1070,7 @@ class CDevNetParams : public CChainParams {
consensus.nSparkNamesStartBlock = 3500;
consensus.nSparkNamesFee = standardSparkNamesFee;
consensus.nSparkNamesV2StartBlock = SPARK_NAME_TRANSFER_DEVNET_START_BLOCK;
+ consensus.nSparkNamesV21StartBlock = SPARK_NAME_TRANSFER_DEVNET_START_BLOCK + 200;
}
};
@@ -1271,9 +1264,9 @@ class CRegTestParams : public CChainParams {
consensus.nStartSigmaBlacklist = INT_MAX;
consensus.nRestartSigmaWithBlacklistCheck = INT_MAX;
consensus.nOldSigmaBanBlock = 1;
- consensus.nLelantusStartBlock = 100;
- consensus.nLelantusFixesStartBlock = 100;
- consensus.nSparkStartBlock = 400;
+ consensus.nLelantusStartBlock = 1;
+ consensus.nLelantusFixesStartBlock = 1;
+ consensus.nSparkStartBlock = 100;
consensus.nExchangeAddressStartBlock = 1000;
consensus.nLelantusGracefulPeriod = 600;
consensus.nSigmaEndBlock = 1;
@@ -1317,9 +1310,6 @@ class CRegTestParams : public CChainParams {
// Bip39
consensus.nMnemonicBlock = 0;
- // moving lelantus data to v3 payload
- consensus.nLelantusV3PayloadStartBlock = 800;
-
// ProgPow
// this can be overridden with either -ppswitchtime or -ppswitchtimefromnow flags
consensus.nPPSwitchTime = INT_MAX;
@@ -1333,6 +1323,7 @@ class CRegTestParams : public CChainParams {
consensus.nSparkNamesStartBlock = 2000;
consensus.nSparkNamesFee = standardSparkNamesFee;
consensus.nSparkNamesV2StartBlock = 2500;
+ consensus.nSparkNamesV21StartBlock = 2700;
}
void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout)
diff --git a/src/coin_containers.cpp b/src/coin_containers.cpp
index d755ee5a49..2069ca4f05 100644
--- a/src/coin_containers.cpp
+++ b/src/coin_containers.cpp
@@ -4,7 +4,7 @@
#include
namespace lelantus {
-std::size_t CScalarHash::operator ()(const Scalar& bn) const noexcept {
+std::size_t CScalarHash::operator ()(const secp_primitives::Scalar& bn) const noexcept {
std::vector bnData(bn.memoryRequired());
bn.serialize(&bnData[0]);
@@ -18,7 +18,7 @@ std::size_t CScalarHash::operator ()(const Scalar& bn) const noexcept {
}
std::size_t CPublicCoinHash::operator ()(const lelantus::PublicCoin& coin) const noexcept {
- uint256 hash = coin.getValueHash();
+ ::uint256 hash = coin.getValueHash();
std::size_t result;
std::memcpy(&result, hash.begin(), sizeof(std::size_t));
diff --git a/src/coin_containers.h b/src/coin_containers.h
index 0e162c65b2..4f05d9bb0f 100644
--- a/src/coin_containers.h
+++ b/src/coin_containers.h
@@ -2,9 +2,14 @@
#define COIN_CONTAINERS_H
#include
-#include "liblelantus/coin.h"
+#include "uint256.h"
+#include "serialize.h"
#include
+#include
+
+// lelantus::PublicCoin and secp_primitives types
+#include "libspark/coin.h"
namespace lelantus {
diff --git a/src/consensus/params.h b/src/consensus/params.h
index f38aaf1d7b..f96281abca 100644
--- a/src/consensus/params.h
+++ b/src/consensus/params.h
@@ -278,6 +278,7 @@ struct Params {
int nSparkNamesStartBlock;
int nSparkNamesV2StartBlock; // v2 enables spark name transfer
+ int nSparkNamesV21StartBlock; // v2.1 tweaks rules for renewals and transfers
std::array nSparkNamesFee;
int nLelantusGracefulPeriod;
@@ -430,9 +431,6 @@ struct Params {
/** block to start reorg depth enforcement */
int nMaxReorgDepthEnforcementBlock;
- /** move lelantus data to v3 payload since this block */
- int nLelantusV3PayloadStartBlock;
-
/** whitelisted transactions */
std::set txidWhitelist;
diff --git a/src/crypto/MerkleTreeProof/arith_uint256.h b/src/crypto/MerkleTreeProof/arith_uint256.h
index 3a880ce295..3e70255269 100644
--- a/src/crypto/MerkleTreeProof/arith_uint256.h
+++ b/src/crypto/MerkleTreeProof/arith_uint256.h
@@ -288,7 +288,7 @@ class arith_uint256 : public base_uint<256> {
* complexities of the sign bit and using base 256 are probably an
* implementation accident.
*/
- arith_uint256& SetCompact(uint32_t nCompact, bool *pfNegative = NULL, bool *pfOverflow = NULL);
+ arith_uint256& SetCompact(uint32_t nCompact, bool* pfNegative = nullptr, bool* pfOverflow = nullptr);
uint32_t GetCompact(bool fNegative = false) const;
friend uint256 ArithToUint256(const arith_uint256 &);
diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp
index dc9b261887..949a756d4e 100644
--- a/src/evo/specialtx.cpp
+++ b/src/evo/specialtx.cpp
@@ -42,11 +42,12 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali
return llmq::CheckLLMQCommitment(tx, pindexPrev, state);
case TRANSACTION_SPORK:
return CheckSporkTx(tx, pindexPrev, state);
+ case TRANSACTION_LELANTUS:
+ return true;
case TRANSACTION_SPARK:
// spark transaction checks are done in other places
return true;
- case TRANSACTION_LELANTUS:
- // lelantus transaction checks are done in other places
+ case TRANSACTION_ALIAS:
return true;
}
@@ -75,6 +76,8 @@ bool ProcessSpecialTx(const CTransaction& tx, const CBlockIndex* pindex, CValida
return true;
case TRANSACTION_SPARK:
return true;
+ case TRANSACTION_ALIAS:
+ return true;
}
return state.DoS(100, false, REJECT_INVALID, "bad-tx-type-proc");
@@ -102,6 +105,8 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex)
return true;
case TRANSACTION_SPARK:
return true;
+ case TRANSACTION_ALIAS:
+ return true;
}
return false;
diff --git a/src/evo/spork.cpp b/src/evo/spork.cpp
index 9a0ccfb400..77e5f2080f 100644
--- a/src/evo/spork.cpp
+++ b/src/evo/spork.cpp
@@ -53,19 +53,7 @@ bool CheckSporkTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValida
static bool IsTransactionAllowed(const CTransaction &tx, const ActiveSporkMap &sporkMap, CValidationState &state)
{
- if (tx.IsLelantusTransaction()) {
- if (sporkMap.count(CSporkAction::featureLelantus) > 0)
- return state.DoS(100, false, REJECT_CONFLICT, "txn-lelantus-disabled", false, "Lelantus transactions are disabled at the moment");
-
- if (tx.IsLelantusJoinSplit()) {
- const auto &limitSpork = sporkMap.find(CSporkAction::featureLelantusTransparentLimit);
- if (limitSpork != sporkMap.cend()) {
- if (lelantus::GetSpendTransparentAmount(tx) > (CAmount)limitSpork->second.second)
- return state.DoS(100, false, REJECT_CONFLICT, "txn-lelantus-disabled", false, "Lelantus transaction is over the transparent limit");
- }
- }
- }
- else if (tx.IsSparkTransaction()) {
+ if (tx.IsSparkTransaction()) {
if (sporkMap.count(CSporkAction::featureSpark) > 0)
return state.DoS(100, false, REJECT_CONFLICT, "txn-spark-disabled", false, "Spark transactions are disabled at the moment");
@@ -176,24 +164,7 @@ bool CSporkManager::IsTransactionAllowed(const CTransaction &tx, const ActiveSpo
}
bool CSporkManager::IsBlockAllowed(const CBlock &block, const CBlockIndex *pindex, CValidationState &state) {
- if (pindex->activeDisablingSporks.count(CSporkAction::featureLelantusTransparentLimit) > 0) {
- // limit total transparent output of lelantus joinsplit
- int64_t limit = pindex->activeDisablingSporks.at(CSporkAction::featureLelantusTransparentLimit).second;
- CAmount totalTransparentOutput = 0;
-
- for (const auto &tx: block.vtx) {
- if (!tx->IsLelantusJoinSplit())
- continue;
-
- totalTransparentOutput += lelantus::GetSpendTransparentAmount(*tx);
- }
-
- if (totalTransparentOutput > CAmount(limit))
- return state.DoS(100, false, REJECT_CONFLICT, "txn-lelantus-disabled", false, "Block is over the transparent output limit because of existing spork");
- }
-
if (pindex->activeDisablingSporks.count(CSporkAction::featureSparkTransparentLimit) > 0) {
- // limit total transparent output of lelantus joinsplit
int64_t limit = pindex->activeDisablingSporks.at(CSporkAction::featureSparkTransparentLimit).second;
CAmount totalTransparentOutput = 0;
diff --git a/src/evo/spork.h b/src/evo/spork.h
index 4b4d67fc67..a750c46ebb 100644
--- a/src/evo/spork.h
+++ b/src/evo/spork.h
@@ -13,8 +13,6 @@ typedef std::map> ActiveSporkMap;
// one action to perform. Spork transaction can have multiple actions
struct CSporkAction
{
- static constexpr const char *featureLelantus = "lelantus";
- static constexpr const char *featureLelantusTransparentLimit = "lelantustransparentlimit";
static constexpr const char *featureChainlocks = "chainlocks";
static constexpr const char *featureInstantSend = "instantsend";
static constexpr const char *featureSpark = "spark";
diff --git a/src/firo_params.h b/src/firo_params.h
index 5158008f58..9a8d57c0c3 100644
--- a/src/firo_params.h
+++ b/src/firo_params.h
@@ -193,6 +193,8 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput
#define SPARK_NAME_TRANSFER_TESTNET_START_BLOCK 189800
#define SPARK_NAME_TRANSFER_DEVNET_START_BLOCK 3900
+#define SPARK_NAME_V21_MAINNET_START_BLOCK 1329000 // Approx June 22 2026
+
// Versions of zerocoin mint/spend transactions
#define ZEROCOIN_TX_VERSION_3 30
#define ZEROCOIN_TX_VERSION_3_1 31
diff --git a/src/hdmint/hdmint.cpp b/src/hdmint/hdmint.cpp
deleted file mode 100644
index 1a43351a28..0000000000
--- a/src/hdmint/hdmint.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2019 The Firo Core Developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include
-#include "hdmint.h"
-
-/**
- * CHDMint empty constructor
- */
-CHDMint::CHDMint()
-{
- SetNull();
-}
-
-/**
- * CHDMint constructor from given values
- */
-CHDMint::CHDMint(const int32_t& nCount, const CKeyID& seedId, const uint256& hashSerial, const GroupElement& pubCoinValue)
-{
- SetNull();
- this->nCount = nCount;
- this->seedId = seedId;
- this->hashSerial = hashSerial;
- this->pubCoinValue = pubCoinValue;
-}
-
-/**
- * Set HDMint object null
- */
-void CHDMint::SetNull()
-{
- nCount = 0;
- seedId.SetNull();
- hashSerial.SetNull();
- txid.SetNull();
- nHeight = -1;
- nId = -1;
- amount = 0;
- isUsed = false;
-}
-
-/**
- * Convert CHDMint object to string
- *
- * @return CHDMint object as string
- */
-std::string CHDMint::ToString() const
-{
- return strprintf(" HDMint:\n count=%d\n seedId=%s\n hashSerial=%s\n hashPubCoinValue=%s\n txid=%s\n height=%d\n id=%d\n amount=%d\n isUsed=%d\n",
- nCount, seedId.ToString(), hashSerial.GetHex(), GetPubCoinHash().GetHex(), txid.GetHex(), nHeight, nId, amount, isUsed);
-}
diff --git a/src/hdmint/hdmint.h b/src/hdmint/hdmint.h
deleted file mode 100644
index 1831f198c9..0000000000
--- a/src/hdmint/hdmint.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2019 The Firo Core Developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#ifndef FIRO_HDMINT_H
-#define FIRO_HDMINT_H
-
-#include "primitives/mint_spend.h"
-
-/**
- * CHDMint object
- *
- * struct that is safe to store essential mint data, without holding any information that allows for actual spending
- * (ie. serial, randomness, private key)
- */
-class CHDMint
-{
-private:
- int32_t nCount;
- CKeyID seedId;
- uint256 hashSerial;
- GroupElement pubCoinValue;
- uint256 txid;
- int nHeight;
- int nId;
- int64_t amount;
- bool isUsed;
-
-public:
- CHDMint();
- CHDMint(const int32_t& nCount, const CKeyID& seedId, const uint256& hashSerial, const GroupElement& pubCoinValue);
-
- int64_t GetAmount() const {
- return amount;
- }
- int32_t GetCount() const { return nCount; }
- int GetHeight() const { return nHeight; }
- int GetId() const { return nId; }
- CKeyID GetSeedId() const { return seedId; }
- uint256 GetSerialHash() const { return hashSerial; }
- GroupElement GetPubcoinValue() const { return pubCoinValue; }
- uint256 GetPubCoinHash() const { return primitives::GetPubCoinValueHash(pubCoinValue); }
- uint256 GetTxHash() const { return txid; }
- bool IsUsed() const { return isUsed; }
- void SetAmount(int64_t amount) { this->amount = amount; }
- void SetHeight(int nHeight) { this->nHeight = nHeight; }
- void SetId(int nId) { this->nId = nId; }
- void SetNull();
- void SetTxHash(const uint256& txid) { this->txid = txid; }
- void SetUsed(const bool isUsed) { this->isUsed = isUsed; }
- void SetPubcoinValue(const GroupElement pubCoinValue) { this->pubCoinValue = pubCoinValue; }
- std::string ToString() const;
-
- ADD_SERIALIZE_METHODS;
-
- template
- inline void SerializationOp(Stream& s, Operation ser_action)
- {
- READWRITE(nCount);
- READWRITE(seedId);
- READWRITE(hashSerial);
- READWRITE(pubCoinValue);
- READWRITE(txid);
- READWRITE(nHeight);
- READWRITE(nId);
- READWRITE(amount);
- READWRITE(isUsed);
- };
-};
-
-#endif //FIRO_HDMINT_H
-
diff --git a/src/hdmint/mintpool.cpp b/src/hdmint/mintpool.cpp
deleted file mode 100644
index 1debd918a1..0000000000
--- a/src/hdmint/mintpool.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2019 The Firo Core Developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include "hdmint/mintpool.h"
-#include "lelantus.h"
-CMintPool::CMintPool(){}
-
-/**
- * Add a mintpool entry
- */
-void CMintPool::Add(std::pair pMint, bool fVerbose)
-{
- insert(pMint);
-
- if (fVerbose)
- LogPrintf("%s : add %s count %d to mint pool\n", __func__, pMint.first.GetHex().substr(0, 6), std::get<2>(pMint.second));
-}
-
-/**
- * Sort mintpool entries in terms of the mint count.
- *
- * @return success
- */
-bool SortSmallest(const std::pair& a, const std::pair& b)
-{
- return std::get<2>(a.second) < std::get<2>(b.second);
-}
-
-/**
- * place the mintpool in listMints.
- */
-void CMintPool::List(std::list>& listMints)
-{
- for (auto pMint : *(this)) {
- listMints.emplace_back(pMint);
- }
-
- listMints.sort(SortSmallest);
-}
-
-/**
- * clear the current mintpool
- */
-void CMintPool::Reset()
-{
- clear();
-}
-
-bool CMintPool::Get(int32_t nCount, uint160 hashSeedMaster, std::pair& result){
- for (auto pMint : *(this)) {
- if(std::get<0>(pMint.second)==hashSeedMaster && std::get<2>(pMint.second)==nCount){
- result = pMint;
- return true;
- }
- }
-
- return false;
-
-}
-
-
diff --git a/src/hdmint/mintpool.h b/src/hdmint/mintpool.h
deleted file mode 100644
index ca11b546ce..0000000000
--- a/src/hdmint/mintpool.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2019 The Firo Core Developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#ifndef FIRO_MINTPOOL_H
-#define FIRO_MINTPOOL_H
-
-#include