Merge branch 'main' into retain_chiabip

This commit is contained in:
Kyle Altendorf
2025-10-06 13:52:51 -04:00
274 changed files with 13420 additions and 5437 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ updates:
schedule:
interval: "weekly"
day: "tuesday"
open-pull-requests-limit: 10
open-pull-requests-limit: 30
rebase-strategy: auto
labels:
- dependencies
+3 -3
View File
@@ -47,7 +47,7 @@ jobs:
build:
name: Benchmarks
runs-on: benchmark
runs-on: ${{ github.repository_owner == 'Chia-Network' && 'benchmark' || 'ubuntu-latest' }}
needs:
- setup
container:
@@ -69,7 +69,7 @@ jobs:
uses: Chia-Network/actions/git-mark-workspace-safe@main
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -78,7 +78,7 @@ jobs:
mode: poetry
- name: Checkout test blocks and plots
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: "Chia-Network/test-cache"
path: ".chia"
@@ -59,7 +59,7 @@ jobs:
madmax-suffix: "x86-64"
bladebit-suffix: "ubuntu-x86-64.tar.gz"
arch-artifact-name: intel
- runs-on: [Linux, ARM64]
- runs-on: ubuntu-24.04-arm
arch: arm64
madmax-suffix: "arm64"
bladebit-suffix: "ubuntu-arm64.tar.gz"
@@ -77,7 +77,7 @@ jobs:
uses: Chia-Network/actions/git-mark-workspace-safe@main
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
@@ -92,6 +92,11 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node per .nvmrc in GUI
uses: actions/setup-node@v5
with:
node-version-file: chia-blockchain-gui/.nvmrc
- name: Get latest madmax plotter
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -254,7 +259,7 @@ jobs:
matrix:
os:
- runs-on:
arm: [linux, arm64]
arm: [ubuntu-24.04-arm]
intel: [ubuntu-latest]
distribution:
- name: debian:bookworm
@@ -291,7 +296,7 @@ jobs:
- uses: Chia-Network/actions/clean-workspace@main
- name: Download packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
id: download
with:
name: chia-installers-linux-deb-${{ matrix.arch.artifact-name }}
@@ -67,7 +67,7 @@ jobs:
uses: Chia-Network/actions/git-mark-workspace-safe@main
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
@@ -82,6 +82,11 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node per .nvmrc in GUI
uses: actions/setup-node@v5
with:
node-version-file: chia-blockchain-gui/.nvmrc
- name: Get latest madmax plotter
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -302,7 +307,7 @@ jobs:
- uses: Chia-Network/actions/clean-workspace@main
- name: Download packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
id: download
with:
name: chia-installers-linux-rpm-intel
+8 -8
View File
@@ -53,11 +53,11 @@ jobs:
matrix:
python-version: ["3.10"]
os:
- runs-on: macos-13
- runs-on: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-intel' || 'macos-13' }}
name: intel
bladebit-suffix: macos-x86-64.tar.gz
arch-artifact-name: intel
- runs-on: macos-13-arm64
- runs-on: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-arm64' || 'macos-15' }}
name: m1
bladebit-suffix: macos-arm64.tar.gz
arch-artifact-name: arm
@@ -71,7 +71,7 @@ jobs:
- uses: Chia-Network/actions/clean-workspace@main
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
@@ -176,10 +176,10 @@ jobs:
- uses: chia-network/actions/activate-venv@main
- name: Setup Node 18.x
uses: actions/setup-node@v4
- name: Setup Node per .nvmrc in GUI
uses: actions/setup-node@v5
with:
node-version: "18.x"
node-version-file: chia-blockchain-gui/.nvmrc
- name: Prepare GUI cache
id: gui-ref
@@ -296,7 +296,7 @@ jobs:
- name: 13
matrix: 13
runs-on:
intel: macos-13
intel: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-intel' || 'macos-13' }}
- name: 14
matrix: 14
runs-on:
@@ -322,7 +322,7 @@ jobs:
- uses: Chia-Network/actions/clean-workspace@main
- name: Download packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
id: download
with:
name: chia-installers-macos-dmg-${{ matrix.arch.artifact-name }}
@@ -60,7 +60,7 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
@@ -95,10 +95,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Setup Node 18.x
uses: actions/setup-node@v4
- name: Setup Node per .nvmrc in GUI
uses: actions/setup-node@v5
with:
node-version: "18.x"
node-version-file: chia-blockchain-gui/.nvmrc
- name: Test for secrets access
id: check_secrets
@@ -336,7 +336,7 @@ jobs:
- uses: Chia-Network/actions/clean-workspace@main
- name: Download packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: chia-installers-windows-exe-intel
path: packages
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -28,12 +28,12 @@ jobs:
matrix: linux
runs-on:
intel: ubuntu-latest
arm: [linux, arm64]
arm: ubuntu-24.04-arm
- name: macOS
matrix: macos
runs-on:
intel: macos-13
arm: macos-13-arm64
intel: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-intel' || 'macos-13' }}
arm: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-arm64' || 'macos-15' }}
- name: Windows
matrix: windows
runs-on:
@@ -54,7 +54,7 @@ jobs:
- uses: Chia-Network/actions/clean-workspace@main
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
- uses: Chia-Network/actions/setup-python@main
with:
+2 -1
View File
@@ -29,6 +29,7 @@ concurrency:
jobs:
analyze:
if: github.repository_owner == 'Chia-Network'
name: Analyze
runs-on: ubuntu-latest
@@ -42,7 +43,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
+2 -1
View File
@@ -13,10 +13,11 @@ permissions:
jobs:
dependency-review:
if: github.repository_owner == 'Chia-Network'
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
-40
View File
@@ -1,40 +0,0 @@
name: "Update Mozilla CA sub module"
on:
workflow_dispatch:
jobs:
update_ca_module:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
repository: chia-network/chia-blockchain
submodules: recursive
token: "${{ secrets.GITHUB_TOKEN }}"
- name: Set up commit signing
uses: Chia-Network/actions/commit-sign/gpg@main
with:
gpg_private_key: ${{ secrets.CHIA_AUTOMATION_PRIVATE_GPG_KEY }}
passphrase: ${{ secrets.CHIA_AUTOMATION_PRIVATE_GPG_PASSPHRASE }}
- name: "Add changes to new branch"
run: |
cd ./mozilla-ca
git pull origin main
- name: "Create Pull Request"
uses: peter-evans/create-pull-request@v7
with:
base: main
body: "Newest Mozilla CA cert"
branch: mozilla-ca-updates
commit-message: "adding ca updates"
delete-branch: true
reviewers: "wjblanke,emlowe"
assignees: "wallentx"
title: "CA Cert updates"
token: "${{ secrets.GITHUB_TOKEN }}"
committer: "ChiaAutomation <automation@chia.net>"
author: "ChiaAutomation <automation@chia.net>"
+9 -4
View File
@@ -25,12 +25,12 @@ jobs:
matrix: linux
runs-on:
intel: ubuntu-latest
arm: [linux, arm64]
arm: ubuntu-24.04-arm
- name: macOS
matrix: macos
runs-on:
intel: macos-13
arm: macos-13-arm64
intel: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-intel' || 'macos-13' }}
arm: macos-latest
- name: Windows
matrix: windows
runs-on:
@@ -50,6 +50,11 @@ jobs:
matrix: windows
arch:
matrix: arm
# Back to self hosted mac intel runners, which we have very few of
- os:
matrix: macos
arch:
matrix: intel
steps:
- name: Clean workspace
@@ -61,7 +66,7 @@ jobs:
run: |
git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -27,6 +27,7 @@ permissions:
jobs:
publish:
if: github.repository_owner == 'Chia-Network'
name: Publish ${{ matrix.arch.name }} ${{ matrix.mode.name }} ${{ matrix.os.file-type.name }}
runs-on: ubuntu-latest
timeout-minutes: 5
@@ -59,7 +60,7 @@ jobs:
directories: ${{ steps.create-venv.outputs.activate-venv-directories }}
- name: Download constraints file
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: constraints-file-${{ matrix.arch.artifact-name }}
path: venv
@@ -69,7 +70,7 @@ jobs:
pip install --constraint venv/constraints.txt py3createtorrent
- name: Download packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: chia-installers-${{ matrix.os.artifact-platform-name }}-${{ matrix.os.file-type.extension }}-${{ matrix.arch.artifact-name }}
path: artifacts/
@@ -96,7 +97,7 @@ jobs:
- name: Configure AWS credentials
if: steps.check_secrets.outputs.HAS_AWS_SECRET
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload
aws-region: us-west-2
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
+1
View File
@@ -4,6 +4,7 @@ on:
types: [opened, labeled, unlabeled, synchronize]
jobs:
check-labels:
if: github.repository_owner == 'Chia-Network'
runs-on: ubuntu-latest
permissions:
checks: write
+2 -1
View File
@@ -5,9 +5,10 @@ on:
jobs:
stale:
if: github.repository_owner == 'Chia-Network'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
operations-per-run: 10000
ascending: true
+1
View File
@@ -11,6 +11,7 @@ permissions:
jobs:
start_release:
if: github.repository_owner == 'Chia-Network'
name: Starts release process in Glue API
runs-on: [glue-notify]
steps:
+1
View File
@@ -11,6 +11,7 @@ permissions:
jobs:
start_release:
if: github.repository_owner == 'Chia-Network'
name: Starts Sync Test
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
# Checkout the code base #
##########################
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
+29 -11
View File
@@ -20,6 +20,10 @@ concurrency:
group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow_ref, github.event.pull_request.number) || github.run_id }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
test_scripts:
name: Native ${{ matrix.os.emoji }} ${{ matrix.arch.emoji }} ${{ matrix.development.name }} - ${{ matrix.editable.name }}
@@ -33,14 +37,14 @@ jobs:
- name: Linux
emoji: 🐧
runs-on:
arm: [Linux, ARM64]
arm: ubuntu-24.04-arm
intel: ubuntu-latest
matrix: linux
- name: macOS
emoji: 🍎
runs-on:
arm: macos-latest
intel: macos-13
arm: macos-15
intel: ${{ github.repository_owner == 'Chia-Network' && 'macos-13-intel' || 'macos-13' }}
matrix: macos
- name: Windows
emoji: 🪟
@@ -74,15 +78,28 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
- name: Setup Python environment
uses: Chia-Network/actions/setup-python@main
with:
python-version: ${{ matrix.python.major-dot-minor }}
- name: Setup Node per .nvmrc in GUI
uses: actions/setup-node@v5
with:
node-version-file: chia-blockchain-gui/.nvmrc
- name: Remove GUI submodule for gui script testing
env:
GUI_DIRECTORY: chia-blockchain-gui
run: |
[ -d "${GUI_DIRECTORY}" ]
rm -rf "${GUI_DIRECTORY}"
- uses: ./.github/actions/install
with:
python-version: ${{ matrix.python.major-dot-minor }}
@@ -103,6 +120,7 @@ jobs:
- name: Run install-gui script (Windows)
if: matrix.os.matrix == 'windows'
shell: pwsh
run: |
./Install-gui.ps1
@@ -144,7 +162,7 @@ jobs:
matrix: linux
runs-on:
intel: ubuntu-latest
arm: [linux, arm64]
arm: ubuntu-24.04-arm
distribution:
- name: amazonlinux:2023
type: amazon
@@ -196,13 +214,13 @@ jobs:
- name: Prepare Amazon Linux
if: ${{ matrix.distribution.type == 'amazon' }}
run: |
yum install --assumeyes git sudo
yum install --assumeyes git
- name: Prepare Arch
if: ${{ matrix.distribution.type == 'arch' }}
run: |
pacman --noconfirm -Syu
pacman --noconfirm -S base git sudo openssl-1.1
pacman --noconfirm -S base git openssl-1.1
# The behavior we follow in install.sh is unique with Arch in that
# we leave it to the user to install the appropriate version of python,
# so we need to install python here in order for the test to succeed.
@@ -214,7 +232,7 @@ jobs:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get --yes update
apt-get install --yes git lsb-release sudo python3-venv
apt-get install --yes git lsb-release python3-venv
- name: Prepare Fedora
if: ${{ matrix.distribution.type == 'fedora' }}
@@ -224,7 +242,7 @@ jobs:
- name: Prepare Rocky
if: ${{ matrix.distribution.type == 'rocky' }}
run: |
yum install --assumeyes git sudo python39
yum install --assumeyes git python39
- name: Prepare Ubuntu
if: ${{ matrix.distribution.type == 'ubuntu' }}
@@ -232,7 +250,7 @@ jobs:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get --yes update
apt-get install --yes git lsb-release sudo
apt-get install --yes git lsb-release
MINIMUM=3.9
if ! apt-get satisfy --yes "python3-venv (>= ${MINIMUM})"
then
@@ -244,7 +262,7 @@ jobs:
# after installing git so we use that copy
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
+7 -1
View File
@@ -118,6 +118,12 @@ jobs:
matrix: windows
configuration:
name: core.full_node
# TODO: skipping macos-15-intel timelord cases until fixed
# https://github.com/Chia-Network/chiavdf/pull/268
- os:
matrix: macos-intel
configuration:
install_timelord: true
- python:
exclude_from:
${{ inputs.matrix_mode }}: True
@@ -134,7 +140,7 @@ jobs:
git config --global core.autocrlf false
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
+51 -22
View File
@@ -40,8 +40,13 @@ on:
default: 1
required: false
type: number
run-linux:
description: "run Linux tests"
run-linux-intel:
description: "run Linux-intel tests"
default: true
required: false
type: boolean
run-linux-arm:
description: "run Linux-arm tests"
default: true
required: false
type: boolean
@@ -79,7 +84,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Python environment
uses: Chia-Network/actions/setup-python@main
@@ -120,7 +125,7 @@ jobs:
concurrency-name: macos-intel
configuration: ${{ needs.configure.outputs.configuration }}
matrix_mode: ${{ needs.configure.outputs.matrix_mode }}
runs-on: macos-13
runs-on: macos-15-intel
arch: intel
arch-emoji: 🌀
macos-arm:
@@ -139,22 +144,38 @@ jobs:
arch: arm
arch-emoji: 💪
collect-junit: false
ubuntu:
if: github.event_name != 'workflow_dispatch' || inputs.run-linux
ubuntu-intel:
if: github.event_name != 'workflow_dispatch' || inputs.run-linux-intel
uses: ./.github/workflows/test-single.yml
needs: configure
with:
os-emoji: 🐧
matrix: ubuntu
name: Ubuntu
file_name: ubuntu
concurrency-name: ubuntu
file_name: ubuntu-intel
concurrency-name: ubuntu-intel
configuration: ${{ needs.configure.outputs.configuration }}
matrix_mode: ${{ needs.configure.outputs.matrix_mode }}
runs-on: ubuntu-latest
arch: intel
arch-emoji: 🌀
collect-junit: false
ubuntu-arm:
if: github.event_name != 'workflow_dispatch' || inputs.run-linux-arm
uses: ./.github/workflows/test-single.yml
needs: configure
with:
os-emoji: 🐧
matrix: ubuntu
name: Ubuntu
file_name: ubuntu-arm
concurrency-name: ubuntu-arm
configuration: ${{ needs.configure.outputs.configuration }}
matrix_mode: ${{ needs.configure.outputs.matrix_mode }}
runs-on: ubuntu-24.04-arm
arch: arm
arch-emoji: 💪
collect-junit: false
windows:
if: github.event_name != 'workflow_dispatch' || inputs.run-windows
uses: ./.github/workflows/test-single.yml
@@ -173,12 +194,15 @@ jobs:
collect-junit: false
coverage:
if: always() && github.repository_owner == 'Chia-Network'
name: ${{ matrix.os.emoji }} Coverage - ${{ matrix.python.name }}
runs-on: ${{ matrix.os.runs-on }}
needs:
- configure
- macos-intel
- macos-arm
- ubuntu
- ubuntu-intel
- ubuntu-arm
- windows
strategy:
fail-fast: false
@@ -196,7 +220,12 @@ jobs:
matrix: "3.10"
steps:
- uses: actions/checkout@v4
- uses: re-actors/alls-green@release/v1.2
id: alls-green
with:
jobs: ${{ toJSON(needs) }}
- uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -207,7 +236,7 @@ jobs:
- name: Download Results
if: false
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
merge-multiple: true
pattern: junit-data-*
@@ -233,7 +262,7 @@ jobs:
if-no-files-found: error
- name: Download Coverage
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
merge-multiple: true
pattern: coverage-data-*
@@ -252,13 +281,13 @@ jobs:
- uses: chia-network/actions/activate-venv@main
- name: Add time out assert results to workflow summary
if: always() && false
if: always() && steps.alls-green.outputs.success == 'true' && false
run: |
python -m chia._tests.process_junit --limit 50 --type time_out_assert --xml junit-results/junit.xml --markdown --link-prefix ${{ github.event.repository.html_url }}/blob/${{ github.sha }}/ --link-line-separator \#L >> "$GITHUB_STEP_SUMMARY"
python -m chia._tests.process_junit --type time_out_assert --xml junit-results/junit.xml --markdown --link-prefix ${{ github.event.repository.html_url }}/blob/${{ github.sha }}/ --link-line-separator \#L >> junit-results/time_out_assert.md
- name: Publish JUnit results
if: always() && false
if: always() && steps.alls-green.outputs.success == 'true' && false
uses: actions/upload-artifact@v4
with:
name: junit-results
@@ -272,18 +301,18 @@ jobs:
coverage html --rcfile=.coveragerc --data-file=coverage-reports/.coverage --directory coverage-reports/html/
- uses: coverallsapp/github-action@v2
if: always()
if: always() && steps.alls-green.outputs.success == 'true'
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
- name: Coverage report (chia/)
if: always()
if: always() && steps.alls-green.outputs.success == 'true'
run: |
set -o pipefail
coverage report --rcfile=.coveragerc --data-file=coverage-reports/.coverage --include='chia/**/*' --omit='chia/_tests/**/*' --show-missing | tee coverage-reports/coverage-chia-stdout
- name: Coverage report (chia/_tests/)
if: always()
if: always() && steps.alls-green.outputs.success == 'true'
run: |
set -o pipefail
coverage report --rcfile=.coveragerc --data-file=coverage-reports/.coverage --include='chia/_tests/**/*' --show-missing | tee coverage-reports/coverage-tests-stdout
@@ -294,7 +323,7 @@ jobs:
echo "hash=$(git rev-parse HEAD~1)" >> "$GITHUB_OUTPUT"
- name: Coverage report (diff)
if: (github.base_ref != '' || github.event.before != '') && always()
if: (github.base_ref != '' || github.event.before != '') && always() && steps.alls-green.outputs.success == 'true'
env:
compare-branch: ${{ github.base_ref == '' && steps.parent-commit.outputs.hash || format('origin/{0}', github.base_ref) }}
run: |
@@ -307,7 +336,7 @@ jobs:
fi
- name: Remove previous coverage report comment and label from PR
if: github.base_ref != '' && always()
if: github.base_ref != '' && always() && steps.alls-green.outputs.success == 'true'
shell: bash
env:
COV_STATUS: ${{ env.COV_STATUS }}
@@ -328,7 +357,7 @@ jobs:
fi
- name: Add diff coverage report comment to PR
if: github.base_ref != '' && always()
if: github.base_ref != '' && always() && steps.alls-green.outputs.success == 'true'
env:
BRANCH_NAME: ${{ github.sha }}
COV_STATUS: ${{ env.COV_STATUS }}
@@ -378,12 +407,12 @@ jobs:
fi
- name: Add diff coverage report to workflow summary
if: (github.base_ref != '' || github.event.before != '') && always()
if: (github.base_ref != '' || github.event.before != '') && always() && steps.alls-green.outputs.success == 'true'
run: |
cat coverage-reports/diff-cover.md >> "$GITHUB_STEP_SUMMARY"
- name: Publish coverage reports
if: always()
if: always() && steps.alls-green.outputs.success == 'true'
uses: actions/upload-artifact@v4
with:
name: coverage-reports
+1
View File
@@ -21,6 +21,7 @@ permissions:
jobs:
trigger:
if: github.repository_owner == 'Chia-Network'
name: Trigger building a new dev tag for the chia-docker image
runs-on: ubuntu-latest
steps:
@@ -17,6 +17,7 @@ permissions:
jobs:
trigger:
if: github.repository_owner == 'Chia-Network'
name: Trigger building a new `main` tag for the chia-docker image
runs-on: ubuntu-latest
steps:
@@ -0,0 +1,45 @@
name: Update Mozilla cert.pem Nightly
on:
schedule:
- cron: "0 11 * * *"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
update_cert:
name: Update Mozilla cert.pem Nightly
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Download latest Mozilla CA cert.pem
run: |
curl --silent --show-error --location https://curl.se/ca/cacert.pem -o chia/ssl/cacert.pem
- name: Set up commit signing
uses: Chia-Network/actions/commit-sign/gpg@main
with:
gpg_private_key: ${{ secrets.CHIA_AUTOMATION_PRIVATE_GPG_KEY }}
passphrase: ${{ secrets.CHIA_AUTOMATION_PRIVATE_GPG_PASSPHRASE }}
- name: Create Pull Request if cacert.pem changed
uses: peter-evans/create-pull-request@v7
with:
base: main
commit-message: "Update cacert.pem from Mozilla CA bundle"
title: "Update cacert.pem from Mozilla CA bundle"
body: "Automated update of cacert.pem from https://curl.se/ca/cacert.pem."
branch: update-cacert-pem
add-paths: chia/ssl/cacert.pem
delete-branch: true
committer: "ChiaAutomation <automation@chia.net>"
author: "ChiaAutomation <automation@chia.net>"
+14 -6
View File
@@ -26,6 +26,7 @@ permissions:
jobs:
mypy:
if: github.repository_owner == 'Chia-Network'
name: ${{ matrix.os.emoji }} ${{ matrix.arch.emoji }} ${{ matrix.check.name }} - ${{ matrix.os.name }} ${{ matrix.python.major_dot_minor }}
runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }}
timeout-minutes: 20
@@ -38,12 +39,12 @@ jobs:
emoji: 🐧
runs-on:
intel: ubuntu-latest
arm: [linux, arm64]
arm: ubuntu-24.04-arm
- name: macOS
matrix: macos
emoji: 🍎
runs-on:
intel: macos-13
intel: macos-13-intel
arm: macos-13-arm64
- name: Windows
matrix: windows
@@ -73,6 +74,11 @@ jobs:
matrix: windows
arch:
matrix: arm
# Back to self hosted mac intel runners, which we have very few of
- os:
matrix: macos
arch:
matrix: intel
steps:
- uses: chia-network/actions/clean-workspace@main
@@ -80,7 +86,7 @@ jobs:
- name: Add safe git directory
uses: chia-network/actions/git-mark-workspace-safe@main
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
@@ -101,6 +107,7 @@ jobs:
${{ matrix.check.command }}
check:
if: github.repository_owner == 'Chia-Network'
name: ${{ matrix.os.emoji }} ${{ matrix.check.name }} - ${{ matrix.os.name }} ${{ matrix.arch.name }} ${{ matrix.python.major_dot_minor }}
runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }}
timeout-minutes: 20
@@ -113,7 +120,7 @@ jobs:
emoji: 🐧
runs-on:
intel: ubuntu-latest
arm: [linux, arm64]
arm: ubuntu-24.04-arm
arch:
- name: Intel
matrix: intel
@@ -145,7 +152,7 @@ jobs:
- name: Add safe git directory
uses: chia-network/actions/git-mark-workspace-safe@main
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
@@ -166,6 +173,7 @@ jobs:
${{ matrix.check.command }}
upload_source_dist:
if: github.repository_owner == 'Chia-Network'
name: Lint and Upload source distribution
runs-on: ${{ matrix.os }}
timeout-minutes: 30
@@ -183,7 +191,7 @@ jobs:
uses: chia-network/actions/git-mark-workspace-safe@main
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
-4
View File
@@ -2,7 +2,3 @@
path = chia-blockchain-gui
url = https://github.com/Chia-Network/chia-blockchain-gui.git
branch = main
[submodule "mozilla-ca"]
path = mozilla-ca
url = https://github.com/Chia-Network/mozilla-ca.git
branch = main
+2 -2
View File
@@ -50,12 +50,12 @@ repos:
- id: prettier
types_or: [ini, json, toml, yaml, markdown]
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.8.0-1
rev: v3.12.0-2
hooks:
- id: shfmt
args: ["--diff", "--write", "-i", "2"]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v6.0.0
hooks:
- id: check-yaml
- id: mixed-line-ending
+1
View File
@@ -1,3 +1,4 @@
var_overrides:
DEPENDABOT_ACTIONS_REVIEWERS: '["cmmarslender", "altendky"]'
DEPENDENCY_REVIEW_ALLOW_DEPENDENCIES_LICENSES: pkg:pypi/pyinstaller, pkg:pypi/mypy
DEPENDABOT_PIP_PULL_REQUEST_LIMIT: "30"
+118
View File
@@ -6,6 +6,124 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project does not yet adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
for setuptools_scm/PEP 440 reasons.
## 2.5.6 Chia blockchain 2025-9-24
## What's Changed
### Changed
- Supported previous harvester protocol for Dr. Plotter users
- Updated Crowdin translations
### Fixed
- Upgraded electron dependency to 37.3.1 to fix GTK4 issue on some versions of Linux
- Fixed GUI NFT offer uploads
- Fixed GUI pooling switch
## 2.5.5 Chia blockchain 2025-08-19
Note that protocol changes between node, farmer, and harvester will require all entities to be upgraded
at the same time to 2.5.5. On a simple one machine setup, this will be handled by the installer.
But if you have more complicated topologies with remote farmers or harvesters, you will need to upgrade
all components.
2.5.5 will also make changes to the blockchain database that are incompatible with previous versions. If you
run 2.5.5 but later wish to downgrade to an earlier version, you will need to downgrade your database schema by
running the following command:
```
python -c "import sqlite3, sys, os; conn = sqlite3.connect(os.path.expanduser(sys.argv[1])); cursor = conn.execute('UPDATE coin_record SET spent_index = 0 WHERE spent_index = -1'); print(f'Updated {cursor.rowcount} records'); conn.commit(); conn.close()" <path to the db>
```
Replacing `<path to the db>` with your actual database path.
## What's Changed
### Added
- Add `chia dev mempool` CLI commands to import, export, and benchmark the mempool
- Add new error logging if the block cost fails to compute
- Add dicts to streamable
- Add independant Full Node RPC Validation Tool (`tools\validate_rpcs.py`)
- Add Revocable CAT support to wallet (Chip 38)
- Add mempool optimizations and fixes in spend deduplication and singleton fast forward.
- Add mempool vault fast forward support
- Add unit tests to cover singleton fast forward dynamics
- Add new optional block creation algorithm to maximize transactions (set config.yaml `full_node:block_creation` to `1`)
- Add new config setting for block creation timeout (`full_node:block_creation_timeout`)
- Add preparation for new plot format and expected hard fork (Chip 48)
- Add canonical CLVM serialization requirement after expected hard fork
### Changed
- Harvester<->Farmer protocol change: compute plot filter on the harvester (Chip 48 prep)
- Farmer<->Node protocol change (Chip 48 prep)
- Backwards incompatible schema change for mempool fast-forward support
- Remove problematic `coins_added_at_height_cache` cache
- Remove no longer needed `item_inclusion_filter` in the mempool
- Significantly speedup mempool manager tests by not forcing them to request unneeded fixtures
- Refactor and add test for WSM puzzle hash endpoints
- Removed unneeded call to compute block cost
- Add puzzle hash generation to action scopes
- Remove direct secret key access from pool wallet
- Add ContextManager to FullNodePeers
- Simplify DB checks in `invariant_check_mempool`
- log how long it took to call `peak_post_processing_2`
- move `chia.types.aliases` to `chia.server.aliases`
- move tx processing queue entry classes
- Improve the serialisation of AddressManager
- Remove the dependency of `chia.consensus` on the module `chia.types`
- Adapt `test_check_removals_with_block_creation` to cover both block generator creation versions
- Use upstream miniupnpc 2.3.3
- Don't return addition coin records in CoinStore's new_block
- Avoid recomputing coin record names in `rollback_to_block` when we have access to them
- move sp broadcast outside of blockchain mutex
- use `pyproject.toml` `[project]` section (again)
- Simplify `test_set_spent`
- Leverage `execute_fetchall` in CoinStore's `rollback_to_block`
- Insert DB values in CoinStore's `new_block` without creating coin records
- Also log the VDF field being compacted (thanks @xearl4)
- Avoid recomputing coin IDs in `run_new_block_benchmark`
- Simplify `test_rollback`
- Extract `_add_coin_records` out of CoinStore and simplify it
- Simplify `test_num_unspent`
- Simplify `test_basic_coin_store`
- Optimize rolled back state construction in `_reconsider_peak`
- Leverage CoinStore's `new_block` in SpendSim's `farm_block` instead of custom coin store manipulation
- Port NFT, pooling, DID, and Datalayer RPCs to `@marshal` decorator
- Simplify SpendSim's `farm_block`
- Migrate away from `clvm` imports
- Pass coin IDs from Blockchain's `_reconsider_peak` to CoinStore's `new_block` to avoid recomputing them
- Unify fork peak and reward coins handling between ForkInfo's `include_spends` and `include_block`
- Replace `CATWallet.create_new_cat_wallet` in `test_cat_wallet.py`
- Change minimium node version to 20 and npm version to 10
- Timelord: dont skip same peak if in unfinished cache
- Set app minimum macos version to macOS 13
- Bump `chia-rs` to `0.27.0`
- Bump `chiavdf` to `1.1.11`
- Bump `clvm` to `0.9.14`
- Bump `clvm-tools-rs` to `0.1.48`
### Fixed
- Enable keccak softfork in the wallet (fixes #19480)
- Add some checks when trying to join the same pool already joined (fixes #7592)
- Allow DIDs from other wallets with NIL recovery lists (fixes #18947)
- Set AGG_SIG_ME_ADDITIONAL_DATA in config.yaml for simulator
- use index when fetching SP
- redact daemon websocket message logging
- less response failure error consumption
- Fixed some typos in comments (thanks @timesince)
- Fixed more typos in comments (thanks @racerole)
- Fixed yet more typos in comments (thanks @yetyear)
- Fixed typo in CONTRIBUTING.md (thanks @ctrlaltdel)
### Removed
- Testing and support for Ubuntu LTS 20.04
- Testing and support for Debian 11 "Bullseye"
## 2.5.4 Chia blockchain 2025-05-28
## What's Changed
-2
View File
@@ -37,8 +37,6 @@ if ($null -eq (Get-Command git -ErrorAction SilentlyContinue))
Exit 1
}
git submodule update --init mozilla-ca
if ($null -eq (Get-Command py -ErrorAction SilentlyContinue))
{
Write-Output "Unable to find py"
+1 -1
View File
@@ -44,7 +44,7 @@ Once installed, an [Intro to Chia][link-intro] guide is available in the [Chia D
[badge-commits]: https://img.shields.io/github/commit-activity/w/Chia-Network/chia-blockchain?logo=GitHub
[badge-contributors]: https://img.shields.io/github/contributors/Chia-Network/chia-blockchain?logo=GitHub
[badge-coverage]: https://img.shields.io/coverallsCoverage/github/Chia-Network/chia-blockchain?logo=Coveralls&logoColor=red&labelColor=%23212F39
[badge-discord]: https://dcbadge.vercel.app/api/server/chia?style=flat-square&theme=full-presence
[badge-discord]: https://img.shields.io/badge/discord-Chia%20Network-green.svg
[badge-discord2]: https://img.shields.io/discord/1034523881404370984.svg?label=Discord&logo=discord&colorB=1e2b2f
[badge-downloads]: https://img.shields.io/github/downloads/Chia-Network/chia-blockchain/total?logo=GitHub
[badge-rc]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-rc.json&query=%24.message&logo=chianetwork&logoColor=white&label=Latest%20RC&labelColor=%230d3349&color=%23474748
+2 -10
View File
@@ -86,17 +86,9 @@ jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json >temp.json &
echo "Building Linux(deb) Electron app"
PRODUCT_NAME="chia"
if [ "$PLATFORM" = "arm64" ]; then
# electron-builder does not work for arm64 as of Aug 16, 2022.
# This is a temporary fix.
# https://github.com/jordansissel/fpm/issues/1801#issuecomment-919877499
# @TODO Consolidates the process to amd64 if the issue of electron-builder is resolved
sudo apt-get -y install ruby ruby-dev
# ERROR: Error installing fpm:
# The last version of dotenv (>= 0) to support your Ruby & RubyGems was 2.8.1. Try installing it with `gem install dotenv -v 2.8.1` and then running the current command again
# dotenv requires Ruby version >= 3.0. The current ruby version is 2.7.0.0.
# @TODO Once ruby 3.0 can be installed on `apt install ruby`, installing dotenv below should be removed.
sudo gem install dotenv -v 2.8.1
sudo gem install fpm
# workaround for above now implemented in the image build at
# https://github.com/Chia-Network/build-images/blob/7c74d2f20739543c486c2522032cf09d96396d24/ubuntu-22.04/Dockerfile#L48-L61
echo USE_SYSTEM_FPM=true "${NPM_PATH}/electron-builder" build --linux deb --arm64 \
--config.extraMetadata.name=chia-blockchain \
--config.productName="$PRODUCT_NAME" --config.linux.desktop.Name="Chia Blockchain" \
-13
View File
@@ -1,13 +0,0 @@
{
"name": "npm_global",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"n": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/n/-/n-8.2.0.tgz",
"integrity": "sha512-qv+jwJoXaV94C+uASaLS/Qe/iprgvQKe+5hqg6YvUH8mqe5ysgjby875pUVLLWieGFywipEO/J/bgekYbC18Jw=="
}
}
}
-15
View File
@@ -1,15 +0,0 @@
{
"name": "npm_global",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"n": "^8.2.0"
}
}
+291 -43
View File
@@ -745,6 +745,18 @@
"node": ">=12.0.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1061,6 +1073,19 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1188,6 +1213,47 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1250,12 +1316,14 @@
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -1286,6 +1354,14 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -1294,6 +1370,41 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -1333,6 +1444,17 @@
"node": "*"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -1346,6 +1468,42 @@
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -1668,6 +1826,14 @@
"node": ">=10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -2194,14 +2360,11 @@
}
},
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"dependencies": {
"rimraf": "^3.0.0"
},
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"engines": {
"node": ">=8.17.0"
"node": ">=14.14"
}
},
"node_modules/tmp-promise": {
@@ -2212,20 +2375,6 @@
"tmp": "^0.2.0"
}
},
"node_modules/tmp/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -2961,6 +3110,15 @@
"sax": "^1.2.4"
}
},
"call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3190,6 +3348,16 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
}
},
"eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -3297,6 +3465,35 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
"es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"requires": {
"es-errors": "^1.3.0"
}
},
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -3343,12 +3540,14 @@
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
@@ -3373,11 +3572,42 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
}
},
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
}
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3410,6 +3640,11 @@
}
}
},
"gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -3420,6 +3655,27 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
"has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"requires": {
"has-symbols": "^1.0.3"
}
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"requires": {
"function-bind": "^1.1.2"
}
},
"hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -3668,6 +3924,11 @@
"yallist": "^4.0.0"
}
},
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -4057,22 +4318,9 @@
}
},
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"requires": {
"rimraf": "^3.0.0"
},
"dependencies": {
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
}
}
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="
},
"tmp-promise": {
"version": "3.0.3",
+291 -41
View File
@@ -738,6 +738,18 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1063,6 +1075,19 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1179,6 +1204,47 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1259,12 +1325,14 @@
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -1306,6 +1374,14 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -1314,6 +1390,41 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -1333,6 +1444,17 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -1346,6 +1468,42 @@
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -1636,6 +1794,14 @@
"node": ">=10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -1928,20 +2094,6 @@
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2191,14 +2343,11 @@
}
},
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"dependencies": {
"rimraf": "^3.0.0"
},
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
"engines": {
"node": ">=8.17.0"
"node": ">=14.14"
}
},
"node_modules/tmp-promise": {
@@ -2970,6 +3119,15 @@
"sax": "^1.2.4"
}
},
"call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3205,6 +3363,16 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
}
},
"eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -3281,6 +3449,35 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
"es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"requires": {
"es-errors": "^1.3.0"
}
},
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -3344,12 +3541,14 @@
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
@@ -3382,11 +3581,42 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
}
},
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
}
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3400,6 +3630,11 @@
"path-is-absolute": "^1.0.0"
}
},
"gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -3410,6 +3645,27 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
"has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"requires": {
"has-symbols": "^1.0.3"
}
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"requires": {
"function-bind": "^1.1.2"
}
},
"hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -3630,6 +3886,11 @@
"yallist": "^4.0.0"
}
},
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -3850,14 +4111,6 @@
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -4041,12 +4294,9 @@
}
},
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"requires": {
"rimraf": "^3.0.0"
}
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ=="
},
"tmp-promise": {
"version": "3.0.3",
+291 -43
View File
@@ -748,6 +748,18 @@
"node": ">=12.0.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1065,6 +1077,19 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1154,6 +1179,47 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1216,12 +1282,14 @@
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -1252,6 +1320,14 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -1260,6 +1336,41 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -1300,6 +1411,17 @@
"node": "*"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -1313,6 +1435,42 @@
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -1636,6 +1794,14 @@
"node": ">=10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -2162,14 +2328,11 @@
}
},
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"dependencies": {
"rimraf": "^3.0.0"
},
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
"engines": {
"node": ">=8.17.0"
"node": ">=14.14"
}
},
"node_modules/tmp-promise": {
@@ -2180,20 +2343,6 @@
"tmp": "^0.2.0"
}
},
"node_modules/tmp/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -2967,6 +3116,15 @@
"sax": "^1.2.4"
}
},
"call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3196,6 +3354,16 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
}
},
"eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -3272,6 +3440,35 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
"es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"requires": {
"es-errors": "^1.3.0"
}
},
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -3318,12 +3515,14 @@
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
@@ -3348,11 +3547,42 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
}
},
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
}
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3385,6 +3615,11 @@
}
}
},
"gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
},
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -3395,6 +3630,27 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
},
"has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"requires": {
"has-symbols": "^1.0.3"
}
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"requires": {
"function-bind": "^1.1.2"
}
},
"hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -3643,6 +3899,11 @@
"yallist": "^4.0.0"
}
},
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -4032,22 +4293,9 @@
}
},
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"requires": {
"rimraf": "^3.0.0"
},
"dependencies": {
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
}
}
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ=="
},
"tmp-promise": {
"version": "3.0.3",
+1 -2
View File
@@ -123,7 +123,6 @@ datas.append((f"{ROOT}/chia/util/initial-config.yaml", "chia/util"))
for path in sorted({path.parent for path in ROOT.joinpath("chia").rglob("*.hex")}):
datas.append((f"{path}/*.hex", path.relative_to(ROOT)))
datas.append((f"{ROOT}/chia/ssl/*", "chia/ssl"))
datas.append((f"{ROOT}/mozilla-ca/*", "mozilla-ca"))
datas.extend(version_data)
pathex = []
@@ -174,7 +173,7 @@ add_binary("chia", f"{ROOT}/chia/cmds/chia.py", COLLECT_ARGS)
add_binary("daemon", f"{ROOT}/chia/daemon/server.py", COLLECT_ARGS)
for server in SERVERS:
add_binary(f"start_{server}", f"{ROOT}/chia/server/start_{server}.py", COLLECT_ARGS)
add_binary(f"start_{server}", f"{ROOT}/chia/{server}/start_{server}.py", COLLECT_ARGS)
add_binary("start_crawler", f"{ROOT}/chia/seeder/start_crawler.py", COLLECT_ARGS)
add_binary("start_seeder", f"{ROOT}/chia/seeder/dns_server.py", COLLECT_ARGS)
@@ -22,7 +22,7 @@ async def check_block_store_invariant(bc: Blockchain):
in_chain = set()
max_height = -1
async with db_wrapper.writer_maybe_transaction() as conn:
async with bc.block_store.transaction() as conn:
async with conn.execute("SELECT height, in_main_chain FROM full_blocks") as cursor:
rows = await cursor.fetchall()
for row in rows:
@@ -76,7 +76,7 @@ async def _validate_and_add_block(
conds = None
else:
# fake the signature validation. Just say True here.
conds = SpendBundleConditions([], 0, 0, 0, None, None, [], 0, 0, 0, True, 0, 0)
conds = SpendBundleConditions([], 0, 0, 0, None, None, [], 0, 0, 0, True, 0, 0, 0, 0, 0)
results = PreValidationResult(None, uint64(1), conds, uint32(0))
else:
future = await pre_validate_block(
@@ -116,11 +116,10 @@ async def _validate_and_add_block(
if err is not None:
# Got an error
raise AssertionError(err)
else:
# Here we will enforce checking of the exact error
if err != expected_error:
# Did not get the right error, or did not get an error
raise AssertionError(f"Expected {expected_error} but got {err}")
# Here we will enforce checking of the exact error
elif err != expected_error:
# Did not get the right error, or did not get an error
raise AssertionError(f"Expected {expected_error} but got {err}")
if expected_result is not None and expected_result != result:
raise AssertionError(f"Expected {expected_result} but got {result}")
@@ -1,5 +1,6 @@
from __future__ import annotations
import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, ClassVar, Optional, cast
@@ -93,7 +94,7 @@ async def test_augmented_chain(default_10000_blocks: list[FullBlock]) -> None:
with pytest.raises(KeyError):
await abc.prev_block_hash([blocks[2].header_hash])
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await abc.lookup_block_generators(blocks[3].header_hash, {uint32(3)})
block_records = []
@@ -105,11 +106,11 @@ async def test_augmented_chain(default_10000_blocks: list[FullBlock]) -> None:
assert abc.height_to_block_record(uint32(1)) == block_records[1]
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await abc.lookup_block_generators(blocks[10].header_hash, {uint32(3), uint32(10)})
# block 1 exists in the chain, but it doesn't have a generator
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await abc.lookup_block_generators(blocks[1].header_hash, {uint32(1)})
expect_gen = blocks[2].transactions_generator
+10 -5
View File
@@ -4,6 +4,7 @@ import asyncio
import copy
import logging
import random
import re
import time
from collections.abc import AsyncIterator, Awaitable
from contextlib import asynccontextmanager
@@ -3647,7 +3648,7 @@ class TestReorgs:
heights.append(block.height)
await _validate_and_add_block(b, block)
blocks = await b.get_block_records_at(heights, batch_size=2)
blocks = await b.get_block_records_at(heights)
assert blocks
assert len(blocks) == 200
assert blocks[-1].height == 199
@@ -4158,24 +4159,24 @@ async def test_lookup_block_generators(
# make sure we don't cross the forks
if clear_cache:
b.clean_block_records()
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await b.lookup_block_generators(peak_1.prev_header_hash, {uint32(516)})
if clear_cache:
b.clean_block_records()
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await b.lookup_block_generators(peak_2.prev_header_hash, {uint32(503)})
# make sure we fail when looking up a non-transaction block from the main
# chain, regardless of which chain we start at
if clear_cache:
b.clean_block_records()
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await b.lookup_block_generators(peak_1.prev_header_hash, {uint32(8)})
if clear_cache:
b.clean_block_records()
with pytest.raises(ValueError, match="Err.GENERATOR_REF_HAS_NO_GENERATOR"):
with pytest.raises(ValueError, match=re.escape(Err.GENERATOR_REF_HAS_NO_GENERATOR.name)):
await b.lookup_block_generators(peak_2.prev_header_hash, {uint32(8)})
# if we try to look up generators starting from a disconnected block, we
@@ -4336,6 +4337,7 @@ async def test_include_spends_same_as_parent(
0,
0,
0,
b"",
)
],
0,
@@ -4350,6 +4352,9 @@ async def test_include_spends_same_as_parent(
True,
0,
0,
0,
0,
0,
)
# Now let's run the test
test_setup.fork_info.include_spends(conds, test_setup.test_block, test_setup.test_block.header_hash)
+1 -1
View File
@@ -73,7 +73,7 @@ class CoinStore:
assert result.conds is not None
for spend in result.conds.spends:
for puzzle_hash, amount, hint in spend.create_coin:
coin = Coin(bytes32(spend.coin_id), bytes32(puzzle_hash), uint64(amount))
coin = Coin(spend.coin_id, puzzle_hash, uint64(amount))
name = coin.name()
ephemeral_db[name] = CoinRecord(
coin,
+74 -90
View File
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional, cast
from chia_rs import BlockRecord, Coin, G2Element
from chia_rs import BlockRecord, Coin, G1Element, G2Element
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
@@ -22,7 +22,6 @@ from chia.farmer.farmer_rpc_client import FarmerRpcClient
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
from chia.rpc.rpc_client import RpcClient
from chia.simulator.simulator_full_node_rpc_client import SimulatorFullNodeRpcClient
from chia.types.coin_record import CoinRecord
from chia.types.signing_mode import SigningMode
from chia.util.bech32m import encode_puzzle_hash
from chia.util.config import load_config
@@ -31,10 +30,16 @@ from chia.wallet.nft_wallet.nft_info import NFTInfo
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig
from chia.wallet.util.tx_config import TXConfig
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet_request_types import (
CATAssetIDToName,
CATAssetIDToNameResponse,
CATGetName,
CATGetNameResponse,
GetSyncStatusResponse,
GetTransaction,
GetTransactionResponse,
GetWallets,
GetWalletsResponse,
NFTCalculateRoyalties,
@@ -42,6 +47,10 @@ from chia.wallet.wallet_request_types import (
NFTGetInfo,
NFTGetInfoResponse,
SendTransactionMultiResponse,
SignMessageByAddress,
SignMessageByAddressResponse,
SignMessageByID,
SignMessageByIDResponse,
WalletInfoResponse,
)
from chia.wallet.wallet_rpc_client import WalletRpcClient
@@ -115,55 +124,77 @@ class TestWalletRpcClient(TestRpcClient):
raise ValueError(f"Invalid fingerprint: {self.fingerprint}")
return GetWalletsResponse([WalletInfoResponse(id=uint32(1), name="", type=uint8(w_type.value), data="")])
async def get_transaction(self, transaction_id: bytes32) -> TransactionRecord:
self.add_to_log("get_transaction", (transaction_id,))
return TransactionRecord(
confirmed_at_height=uint32(1),
created_at_time=uint64(1234),
to_puzzle_hash=bytes32([1] * 32),
amount=uint64(12345678),
fee_amount=uint64(1234567),
confirmed=False,
sent=uint32(0),
spend_bundle=WalletSpendBundle([], G2Element()),
additions=[Coin(bytes32([1] * 32), bytes32([2] * 32), uint64(12345678))],
removals=[Coin(bytes32([2] * 32), bytes32([4] * 32), uint64(12345678))],
wallet_id=uint32(1),
sent_to=[("aaaaa", uint8(1), None)],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32([2] * 32),
memos=[(bytes32([3] * 32), [bytes([4] * 32)])],
valid_times=ConditionValidTimes(),
async def get_transaction(self, request: GetTransaction) -> GetTransactionResponse:
self.add_to_log("get_transaction", (request,))
return GetTransactionResponse(
TransactionRecord(
confirmed_at_height=uint32(1),
created_at_time=uint64(1234),
to_puzzle_hash=bytes32([1] * 32),
to_address=encode_puzzle_hash(bytes32([1] * 32), "xch"),
amount=uint64(12345678),
fee_amount=uint64(1234567),
confirmed=False,
sent=uint32(0),
spend_bundle=WalletSpendBundle([], G2Element()),
additions=[Coin(bytes32([1] * 32), bytes32([2] * 32), uint64(12345678))],
removals=[Coin(bytes32([2] * 32), bytes32([4] * 32), uint64(12345678))],
wallet_id=uint32(1),
sent_to=[("aaaaa", uint8(1), None)],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32([2] * 32),
memos={bytes32([3] * 32): [bytes([4] * 32)]},
valid_times=ConditionValidTimes(),
),
bytes32([2] * 32),
)
async def get_cat_name(self, wallet_id: int) -> str:
self.add_to_log("get_cat_name", (wallet_id,))
return "test" + str(wallet_id)
async def get_cat_name(self, request: CATGetName) -> CATGetNameResponse:
self.add_to_log("get_cat_name", (request.wallet_id,))
return CATGetNameResponse(request.wallet_id, "test" + str(request.wallet_id))
async def sign_message_by_address(self, address: str, message: str) -> tuple[str, str, str]:
self.add_to_log("sign_message_by_address", (address, message))
pubkey = bytes([3] * 48).hex()
signature = bytes([6] * 576).hex()
async def sign_message_by_address(self, request: SignMessageByAddress) -> SignMessageByAddressResponse:
self.add_to_log("sign_message_by_address", (request.address, request.message))
pubkey = G1Element.from_bytes(
bytes.fromhex(
"b5acf3599bc5fa5da1c00f6cc3d5bcf1560def67778b7f50a8c373a83f78761505b6250ab776e38a292e26628009aec4"
)
)
signature = G2Element.from_bytes(
bytes.fromhex(
"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
)
)
signing_mode = SigningMode.CHIP_0002.value
return pubkey, signature, signing_mode
return SignMessageByAddressResponse(pubkey, signature, signing_mode)
async def sign_message_by_id(self, id: str, message: str) -> tuple[str, str, str]:
self.add_to_log("sign_message_by_id", (id, message))
pubkey = bytes([4] * 48).hex()
signature = bytes([7] * 576).hex()
async def sign_message_by_id(self, request: SignMessageByID) -> SignMessageByIDResponse:
self.add_to_log("sign_message_by_id", (request.id, request.message))
pubkey = G1Element.from_bytes(
bytes.fromhex(
"a9e652cb551d5978a9ee4b7aa52a4e826078a54b08a3d903c38611cb8a804a9a29c926e4f8549314a079e04ecde10cc1"
)
)
signature = G2Element.from_bytes(
bytes.fromhex(
"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
)
)
signing_mode = SigningMode.CHIP_0002.value
return pubkey, signature, signing_mode
return SignMessageByIDResponse(pubkey, signature, bytes32.zeros, signing_mode)
async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]:
async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse:
"""
if bytes32([1] * 32), return (uint32(2), "test1"), if bytes32([1] * 32), return (uint32(3), "test2")
"""
self.add_to_log("cat_asset_id_to_name", (asset_id,))
self.add_to_log("cat_asset_id_to_name", (request.asset_id,))
for i in range(256):
if asset_id == get_bytes32(i):
return uint32(i + 1), "test" + str(i)
return None
if request.asset_id == get_bytes32(i):
return CATAssetIDToNameResponse(uint32(i + 1), "test" + str(i))
return CATAssetIDToNameResponse(wallet_id=None, name=None)
async def get_nft_info(self, request: NFTGetInfo) -> NFTGetInfoResponse:
self.add_to_log("get_nft_info", (request.coin_id, request.latest))
@@ -204,54 +235,6 @@ class TestWalletRpcClient(TestRpcClient):
)
)
async def get_spendable_coins(
self,
wallet_id: int,
coin_selection_config: CoinSelectionConfig,
) -> tuple[list[CoinRecord], list[CoinRecord], list[Coin]]:
"""
We return a tuple containing: (confirmed records, unconfirmed removals, unconfirmed additions)
"""
self.add_to_log(
"get_spendable_coins",
(wallet_id, coin_selection_config),
)
confirmed_records = [
CoinRecord(
Coin(bytes32([1] * 32), bytes32([2] * 32), uint64(1234560000)),
uint32(123456),
uint32(0),
False,
uint64(0),
),
CoinRecord(
Coin(bytes32([3] * 32), bytes32([4] * 32), uint64(1234560000)),
uint32(123456),
uint32(0),
False,
uint64(0),
),
]
unconfirmed_removals = [
CoinRecord(
Coin(bytes32([5] * 32), bytes32([6] * 32), uint64(1234570000)),
uint32(123457),
uint32(0),
True,
uint64(0),
)
]
unconfirmed_additions = [Coin(bytes32([7] * 32), bytes32([8] * 32), uint64(1234580000))]
return confirmed_records, unconfirmed_removals, unconfirmed_additions
async def get_next_address(self, wallet_id: int, new_address: bool) -> str:
self.add_to_log("get_next_address", (wallet_id, new_address))
addr = encode_puzzle_hash(bytes32([self.wallet_index] * 32), "xch")
self.wallet_index += 1
if self.wallet_index > 254:
self.wallet_index = 1
return addr
async def send_transaction_multi(
self,
wallet_id: int,
@@ -271,6 +254,7 @@ class TestWalletRpcClient(TestRpcClient):
confirmed_at_height=uint32(1),
created_at_time=uint64(1234),
to_puzzle_hash=bytes32([1] * 32),
to_address=encode_puzzle_hash(bytes32([1] * 32), "xch"),
amount=uint64(12345678),
fee_amount=uint64(1234567),
confirmed=False,
@@ -283,7 +267,7 @@ class TestWalletRpcClient(TestRpcClient):
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=name,
memos=[(bytes32([3] * 32), [bytes([4] * 32)])],
memos={bytes32([3] * 32): [bytes([4] * 32)]},
valid_times=ConditionValidTimes(),
),
name,
+56 -2
View File
@@ -8,10 +8,12 @@ from _pytest.capture import CaptureFixture
from chia._tests.util.time_out_assert import time_out_assert
from chia.cmds.farm_funcs import summary
from chia.farmer.farmer import Farmer
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester import Harvester
from chia.server.aliases import FarmerService, HarvesterService, WalletService
from chia.harvester.harvester_service import HarvesterService
from chia.simulator.block_tools import BlockTools
from chia.simulator.start_simulator import SimulatorFullNodeService
from chia.wallet.wallet_service import WalletService
@pytest.mark.anyio
@@ -46,7 +48,15 @@ async def test_farm_summary_command(
wallet_rpc_port = wallet_service.rpc_server.webserver.listen_port
farmer_rpc_port = farmer_service.rpc_server.webserver.listen_port
await summary(full_node_rpc_port, wallet_rpc_port, None, farmer_rpc_port, bt.root_path)
# Test with include_pool_rewards=False (original test)
await summary(
rpc_port=full_node_rpc_port,
wallet_rpc_port=wallet_rpc_port,
harvester_rpc_port=None,
farmer_rpc_port=farmer_rpc_port,
include_pool_rewards=False,
root_path=bt.root_path,
)
captured = capsys.readouterr()
match = re.search(r"^.+(Farming status:.+)$", captured.out, re.DOTALL)
@@ -64,3 +74,47 @@ async def test_farm_summary_command(
assert "e (effective)" in lines[8]
assert "Estimated network space:" in lines[9]
assert "Expected time to win:" in lines[10]
# Test with include_pool_rewards=True
await summary(
rpc_port=full_node_rpc_port,
wallet_rpc_port=wallet_rpc_port,
harvester_rpc_port=None,
farmer_rpc_port=farmer_rpc_port,
include_pool_rewards=True,
root_path=bt.root_path,
)
captured = capsys.readouterr()
match = re.search(r"Farming status:.*", captured.out, re.DOTALL)
assert match, "no 'Farming status:' line"
output = match.group(0).strip()
lines = [line.strip() for line in output.splitlines()]
# always check these first six lines
assert lines[0].startswith("Farming status:")
assert lines[1].startswith("Total chia farmed:")
assert lines[2].startswith("User transaction fees:")
assert lines[3].startswith("Farmer rewards:")
assert lines[4].startswith("Pool rewards:")
assert lines[5].startswith("Total rewards:")
# decide where the harvester section starts
if "Current/Last height farmed:" in output:
# we saw the height-farmed block, so it occupies lines[6-8]
assert lines[6].startswith("Current/Last height farmed:")
assert lines[7].startswith("Blocks since last farmed:")
assert lines[8].startswith("Time since last farmed:")
harvester_idx = 9
else:
# no height block, so harvester begins at line 6
harvester_idx = 6
# now the harvester lines
assert lines[harvester_idx] == "Local Harvester"
assert "plots of size" in lines[harvester_idx + 1]
assert lines[harvester_idx + 2].startswith("Plot count for all harvesters:")
assert lines[harvester_idx + 3].startswith("Total size of plots:")
assert lines[harvester_idx + 4].startswith("Estimated network space:")
assert lines[harvester_idx + 5].startswith("Expected time to win:")
assert lines[harvester_idx + 6].startswith("Note:")
+3 -1
View File
@@ -4,6 +4,7 @@ from chia_rs import Coin, G2Element
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32, uint64
from chia.util.bech32m import encode_puzzle_hash
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.signer_protocol import KeyHints, SigningInstructions, TransactionInfo, UnsignedTransaction
from chia.wallet.transaction_record import TransactionRecord
@@ -27,6 +28,7 @@ STD_TX = TransactionRecord(
confirmed_at_height=uint32(1),
created_at_time=uint64(1234),
to_puzzle_hash=get_bytes32(1),
to_address=encode_puzzle_hash(get_bytes32(1), "xch"),
amount=uint64(12345678),
fee_amount=uint64(1234567),
confirmed=False,
@@ -39,7 +41,7 @@ STD_TX = TransactionRecord(
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=get_bytes32(2),
memos=[(get_bytes32(3), [bytes([4] * 32)])],
memos={get_bytes32(3): [bytes([4] * 32)]},
valid_times=ConditionValidTimes(),
)
+3 -8
View File
@@ -13,7 +13,6 @@ from chia._tests.cmds.wallet.test_consts import FINGERPRINT_ARG, STD_TX, STD_UTX
from chia.types.blockchain_format.program import NIL, Program
from chia.types.signing_mode import SigningMode
from chia.util.bech32m import encode_puzzle_hash
from chia.util.config import load_config
from chia.wallet.conditions import Condition, ConditionValidTimes, CreateCoinAnnouncement, CreatePuzzleAnnouncement
from chia.wallet.did_wallet.did_info import did_recovery_is_nil
from chia.wallet.util.curry_and_treehash import NIL_TREEHASH
@@ -117,8 +116,8 @@ def test_did_sign_message(capsys: object, get_test_cli_clients: tuple[TestRpcCli
# these are various things that should be in the output
assert_list = [
f"Message: {message.hex()}",
f"Public Key: {bytes([4] * 48).hex()}",
f"Signature: {bytes([7] * 576).hex()}",
"Public Key: a9e652cb551d5978a9ee4b7aa52a4e826078a54b08a3d903c38611cb8a804a9a29c926e4f8549314a079e04ecde10cc1",
"Signature: c0" + "00" * (42 - 1),
f"Signing Mode: {SigningMode.CHIP_0002.value}",
]
run_cli_command_and_assert(capsys, root_dir, [*command_args, f"-i{did_id}"], assert_list)
@@ -421,14 +420,10 @@ def test_did_transfer(capsys: object, get_test_cli_clients: tuple[TestRpcClients
"150",
]
# these are various things that should be in the output
config = load_config(
root_dir,
"config.yaml",
)
assert_list = [
f"Successfully transferred DID to {t_address}",
f"Transaction ID: {get_bytes32(2).hex()}",
f"Transaction: {STD_TX.to_json_dict_convenience(config)}",
f"Transaction: {STD_TX.to_json_dict()}",
]
run_cli_command_and_assert(capsys, root_dir, command_args, assert_list)
expected_calls: logType = {
+6 -6
View File
@@ -64,19 +64,19 @@ def test_nft_sign_message(capsys: object, get_test_cli_clients: tuple[TestRpcCli
inst_rpc_client = TestWalletRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
did_id = encode_puzzle_hash(get_bytes32(1), "nft")
nft_id = encode_puzzle_hash(get_bytes32(1), "nft")
message = b"hello nft world!!"
command_args = ["wallet", "did", "sign_message", FINGERPRINT_ARG, f"-m{message.hex()}"]
command_args = ["wallet", "nft", "sign_message", FINGERPRINT_ARG, f"-m{message.hex()}"]
# these are various things that should be in the output
assert_list = [
f"Message: {message.hex()}",
f"Public Key: {bytes([4] * 48).hex()}",
f"Signature: {bytes([7] * 576).hex()}",
"Public Key: a9e652cb551d5978a9ee4b7aa52a4e826078a54b08a3d903c38611cb8a804a9a29c926e4f8549314a079e04ecde10cc1",
"Signature: c0" + "00" * (42 - 1),
f"Signing Mode: {SigningMode.CHIP_0002.value}",
]
run_cli_command_and_assert(capsys, root_dir, [*command_args, f"-i{did_id}"], assert_list)
run_cli_command_and_assert(capsys, root_dir, [*command_args, f"-i{nft_id}"], assert_list)
expected_calls: logType = {
"sign_message_by_id": [(did_id, message.hex())], # xch std
"sign_message_by_id": [(nft_id, message.hex())], # xch std
}
test_rpc_clients.wallet_rpc_client.check_log(expected_calls)
+39 -21
View File
@@ -1,18 +1,23 @@
from __future__ import annotations
from pathlib import Path
from typing import Optional, cast
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32, uint64
from chia._tests.cmds.cmd_test_utils import TestRpcClients, TestWalletRpcClient, logType, run_cli_command_and_assert
from chia._tests.cmds.wallet.test_consts import FINGERPRINT, FINGERPRINT_ARG, get_bytes32
from chia._tests.cmds.wallet.test_consts import FINGERPRINT, FINGERPRINT_ARG, STD_TX, STD_UTX, get_bytes32
from chia.util.bech32m import encode_puzzle_hash
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.conditions import Condition, ConditionValidTimes
from chia.wallet.notification_store import Notification
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.wallet_request_types import GetNotifications, GetNotificationsResponse
from chia.wallet.util.tx_config import TXConfig
from chia.wallet.wallet_request_types import (
DeleteNotifications,
GetNotifications,
GetNotificationsResponse,
SendNotification,
SendNotificationResponse,
)
test_condition_valid_times: ConditionValidTimes = ConditionValidTimes(min_time=uint64(100), max_time=uint64(150))
@@ -26,20 +31,17 @@ def test_notifications_send(capsys: object, get_test_cli_clients: tuple[TestRpcC
class NotificationsSendRpcClient(TestWalletRpcClient):
async def send_notification(
self,
target: bytes32,
msg: bytes,
amount: uint64,
fee: uint64 = uint64(0),
push: bool = True,
request: SendNotification,
tx_config: TXConfig,
extra_conditions: tuple[Condition, ...] = tuple(),
timelock_info: ConditionValidTimes = ConditionValidTimes(),
) -> TransactionRecord:
self.add_to_log("send_notification", (target, msg, amount, fee, push, timelock_info))
) -> SendNotificationResponse:
self.add_to_log(
"send_notification",
(request.target, request.message, request.amount, request.fee, request.push, timelock_info),
)
class FakeTransactionRecord:
def __init__(self, name: str) -> None:
self.name = name
return cast(TransactionRecord, FakeTransactionRecord(get_bytes32(2).hex()))
return SendNotificationResponse([STD_UTX], [STD_TX], tx=STD_TX)
inst_rpc_client = NotificationsSendRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
@@ -111,15 +113,31 @@ def test_notifications_delete(capsys: object, get_test_cli_clients: tuple[TestRp
# set RPC Client
class NotificationsDeleteRpcClient(TestWalletRpcClient):
async def delete_notifications(self, ids: Optional[list[bytes32]] = None) -> bool:
self.add_to_log("delete_notifications", (ids,))
return True
async def delete_notifications(self, request: DeleteNotifications) -> None:
self.add_to_log("delete_notifications", (request.ids,))
inst_rpc_client = NotificationsDeleteRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
# Try all first
command_args = ["wallet", "notifications", "delete", FINGERPRINT_ARG, "--all"]
# these are various things that should be in the output
assert_list = ["Success: True"]
assert_list = ["Success!"]
run_cli_command_and_assert(capsys, root_dir, command_args, assert_list)
expected_calls: logType = {"delete_notifications": [(None,)]}
test_rpc_clients.wallet_rpc_client.check_log(expected_calls)
# Next try specifying IDs
command_args = [
"wallet",
"notifications",
"delete",
FINGERPRINT_ARG,
"--id",
bytes32.zeros.hex(),
"--id",
bytes32.zeros.hex(),
]
# these are various things that should be in the output
assert_list = ["Success!"]
run_cli_command_and_assert(capsys, root_dir, command_args, assert_list)
expected_calls = {"delete_notifications": [([bytes32.zeros, bytes32.zeros],)]}
test_rpc_clients.wallet_rpc_client.check_log(expected_calls)
+117 -94
View File
@@ -9,7 +9,7 @@ import importlib_resources
import pytest
from chia_rs import Coin, G2Element
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
from chia_rs.sized_ints import uint8, uint16, uint32, uint64, uint128
from click.testing import CliRunner
from chia._tests.cmds.cmd_test_utils import TestRpcClients, TestWalletRpcClient, logType, run_cli_command_and_assert
@@ -29,31 +29,52 @@ from chia.protocols.outbound_message import NodeType
from chia.types.blockchain_format.program import Program
from chia.types.signing_mode import SigningMode
from chia.util.bech32m import encode_puzzle_hash
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.conditions import Condition, ConditionValidTimes
from chia.wallet.trade_record import TradeRecord
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.transaction_sorting import SortKey
from chia.wallet.util.query_filter import HashFilter, TransactionTypeFilter
from chia.wallet.util.query_filter import HashFilter
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet_coin_store import GetCoinRecords
from chia.wallet.wallet_request_types import (
BalanceResponse,
CancelOfferResponse,
CATAssetIDToName,
CATAssetIDToNameResponse,
CATSetName,
CATSetNameResponse,
CATSpendResponse,
ClawbackPuzzleDecoratorOverride,
CreateOfferForIDsResponse,
DeleteUnconfirmedTransactions,
ExtendDerivationIndex,
ExtendDerivationIndexResponse,
FungibleAsset,
GetCurrentDerivationIndexResponse,
GetHeightInfoResponse,
GetNextAddress,
GetNextAddressResponse,
GetTransaction,
GetTransactions,
GetTransactionsResponse,
GetWalletBalance,
GetWalletBalanceResponse,
GetWallets,
GetWalletsResponse,
NFTCalculateRoyalties,
NFTGetWalletDID,
NFTGetWalletDIDResponse,
RoyaltyAsset,
SendTransaction,
SendTransactionResponse,
SpendClawbackCoins,
SpendClawbackCoinsResponse,
TakeOfferResponse,
TransactionRecordWithMetadata,
WalletInfoResponse,
)
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
@@ -97,9 +118,9 @@ def test_get_transaction(capsys: object, get_test_cli_clients: tuple[TestRpcClie
"get_wallets": [(GetWallets(type=None, include_data=True),)] * 3,
"get_cat_name": [(1,)],
"get_transaction": [
(bytes32.from_hexstr(bytes32_hexstr),),
(bytes32.from_hexstr(bytes32_hexstr),),
(bytes32.from_hexstr(bytes32_hexstr),),
(GetTransaction(bytes32.from_hexstr(bytes32_hexstr)),),
(GetTransaction(bytes32.from_hexstr(bytes32_hexstr)),),
(GetTransaction(bytes32.from_hexstr(bytes32_hexstr)),),
],
}
test_rpc_clients.wallet_rpc_client.check_log(expected_calls)
@@ -110,27 +131,17 @@ def test_get_transactions(capsys: object, get_test_cli_clients: tuple[TestRpcCli
# set RPC Client
class GetTransactionsWalletRpcClient(TestWalletRpcClient):
async def get_transactions(
self,
wallet_id: int,
start: int,
end: int,
sort_key: Optional[SortKey] = None,
reverse: bool = False,
to_address: Optional[str] = None,
type_filter: Optional[TransactionTypeFilter] = None,
confirmed: Optional[bool] = None,
) -> list[TransactionRecord]:
self.add_to_log(
"get_transactions", (wallet_id, start, end, sort_key, reverse, to_address, type_filter, confirmed)
)
async def get_transactions(self, request: GetTransactions) -> GetTransactionsResponse:
self.add_to_log("get_transactions", (request,))
l_tx_rec = []
for i in range(start, end):
t_type = TransactionType.INCOMING_CLAWBACK_SEND if i == end - 1 else TransactionType.INCOMING_TX
tx_rec = TransactionRecord(
assert request.start is not None and request.end is not None
for i in range(request.start, request.end):
t_type = TransactionType.INCOMING_CLAWBACK_SEND if i == request.end - 1 else TransactionType.INCOMING_TX
tx_rec = TransactionRecordWithMetadata(
confirmed_at_height=uint32(1 + i),
created_at_time=uint64(1234 + i),
to_puzzle_hash=bytes32([1 + i] * 32),
to_address=encode_puzzle_hash(bytes32([1 + i] * 32), "xch"),
amount=uint64(12345678 + i),
fee_amount=uint64(1234567 + i),
confirmed=False,
@@ -143,12 +154,12 @@ def test_get_transactions(capsys: object, get_test_cli_clients: tuple[TestRpcCli
trade_id=None,
type=uint32(t_type.value),
name=bytes32([2 + i] * 32),
memos=[(bytes32([3 + i] * 32), [bytes([4 + i] * 32)])],
memos={bytes32([3 + i] * 32): [bytes([4 + i] * 32)]},
valid_times=ConditionValidTimes(),
)
l_tx_rec.append(tx_rec)
return l_tx_rec
return GetTransactionsResponse(l_tx_rec, request.wallet_id)
async def get_coin_records(self, request: GetCoinRecords) -> dict[str, Any]:
self.add_to_log("get_coin_records", (request,))
@@ -197,8 +208,8 @@ def test_get_transactions(capsys: object, get_test_cli_clients: tuple[TestRpcCli
expected_calls: logType = {
"get_wallets": [(GetWallets(type=None, include_data=True),)] * 2,
"get_transactions": [
(1, 2, 4, SortKey.RELEVANCE, True, None, None, None),
(1, 2, 4, SortKey.RELEVANCE, True, None, None, None),
(GetTransactions(uint32(1), uint16(2), uint16(4), SortKey.RELEVANCE.name, True, None, None, None),),
(GetTransactions(uint32(1), uint16(2), uint16(4), SortKey.RELEVANCE.name, True, None, None, None),),
],
"get_coin_records": [
(GetCoinRecords(coin_id_filter=HashFilter.include([expected_coin_id])),),
@@ -242,19 +253,23 @@ def test_show(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Path])
self.add_to_log("get_height_info", ())
return GetHeightInfoResponse(uint32(10))
async def get_wallet_balance(self, wallet_id: int) -> dict[str, uint64]:
self.add_to_log("get_wallet_balance", (wallet_id,))
if wallet_id == 1:
amount = uint64(1000000000)
elif wallet_id == 2:
amount = uint64(2000000000)
async def get_wallet_balance(self, request: GetWalletBalance) -> GetWalletBalanceResponse:
self.add_to_log("get_wallet_balance", (request,))
if request.wallet_id == 1:
amount = uint128(1000000000)
elif request.wallet_id == 2:
amount = uint128(2000000000)
else:
amount = uint64(1)
return {
"confirmed_wallet_balance": amount,
"spendable_balance": amount,
"unconfirmed_wallet_balance": uint64(0),
}
amount = uint128(1)
return GetWalletBalanceResponse(
BalanceResponse(
wallet_id=request.wallet_id,
wallet_type=uint8(0), # Doesn't matter
confirmed_wallet_balance=amount,
spendable_balance=amount,
unconfirmed_wallet_balance=uint128(0),
)
)
async def get_nft_wallet_did(self, request: NFTGetWalletDID) -> NFTGetWalletDIDResponse:
self.add_to_log("get_nft_wallet_did", (request.wallet_id,))
@@ -307,7 +322,12 @@ def test_show(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Path])
],
"get_sync_status": [(), ()],
"get_height_info": [(), ()],
"get_wallet_balance": [(1,), (2,), (3,), (2,)],
"get_wallet_balance": [
(GetWalletBalance(wallet_id=uint32(1)),),
(GetWalletBalance(wallet_id=uint32(2)),),
(GetWalletBalance(wallet_id=uint32(3)),),
(GetWalletBalance(wallet_id=uint32(2)),),
],
"get_nft_wallet_did": [(3,)],
"get_connections": [(None,), (None,)],
}
@@ -321,25 +341,31 @@ def test_send(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Path])
class SendWalletRpcClient(TestWalletRpcClient):
async def send_transaction(
self,
wallet_id: int,
amount: uint64,
address: str,
request: SendTransaction,
tx_config: TXConfig,
fee: uint64 = uint64(0),
memos: Optional[list[str]] = None,
puzzle_decorator_override: Optional[list[dict[str, Union[str, int, bool]]]] = None,
push: bool = True,
extra_conditions: tuple[Condition, ...] = tuple(),
timelock_info: ConditionValidTimes = ConditionValidTimes(),
) -> SendTransactionResponse:
self.add_to_log(
"send_transaction",
(wallet_id, amount, address, tx_config, fee, memos, puzzle_decorator_override, push, timelock_info),
(
request.wallet_id,
request.amount,
request.address,
tx_config,
request.fee,
request.memos,
request.puzzle_decorator,
request.push,
timelock_info,
),
)
name = get_bytes32(2)
tx_rec = TransactionRecord(
confirmed_at_height=uint32(1),
created_at_time=uint64(1234),
to_puzzle_hash=get_bytes32(1),
to_address=encode_puzzle_hash(get_bytes32(1), "xch"),
amount=uint64(12345678),
fee_amount=uint64(1234567),
confirmed=False,
@@ -352,7 +378,7 @@ def test_send(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Path])
trade_id=None,
type=uint32(TransactionType.OUTGOING_CLAWBACK.value),
name=name,
memos=[(get_bytes32(3), [bytes([4] * 32)])],
memos={get_bytes32(3): [bytes([4] * 32)]},
valid_times=ConditionValidTimes(),
)
return SendTransactionResponse([STD_UTX], [STD_TX], tx_rec, name)
@@ -450,7 +476,7 @@ def test_send(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Path])
),
500000000000,
["0x6262626262626262626262626262626262626262626262626262626262626262"],
[{"decorator": "CLAWBACK", "clawback_timelock": 60}],
[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(60))],
True,
test_condition_valid_times,
)
@@ -476,7 +502,7 @@ def test_send(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Path])
test_condition_valid_times,
)
],
"get_transaction": [(get_bytes32(2),), (get_bytes32(2),)],
"get_transaction": [(GetTransaction(get_bytes32(2)),), (GetTransaction(get_bytes32(2)),)],
}
test_rpc_clients.wallet_rpc_client.check_log(expected_calls)
@@ -486,11 +512,11 @@ def test_get_address(capsys: object, get_test_cli_clients: tuple[TestRpcClients,
# set RPC Client
class GetAddressWalletRpcClient(TestWalletRpcClient):
async def get_next_address(self, wallet_id: int, new_address: bool) -> str:
self.add_to_log("get_next_address", (wallet_id, new_address))
if new_address:
return encode_puzzle_hash(get_bytes32(3), "xch")
return encode_puzzle_hash(get_bytes32(4), "xch")
async def get_next_address(self, request: GetNextAddress) -> GetNextAddressResponse:
self.add_to_log("get_next_address", (request.wallet_id, request.new_address))
if request.new_address:
return GetNextAddressResponse(request.wallet_id, encode_puzzle_hash(get_bytes32(3), "xch"))
return GetNextAddressResponse(request.wallet_id, encode_puzzle_hash(get_bytes32(4), "xch"))
inst_rpc_client = GetAddressWalletRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
@@ -519,30 +545,25 @@ def test_clawback(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Pa
class ClawbackWalletRpcClient(TestWalletRpcClient):
async def spend_clawback_coins(
self,
coin_ids: list[bytes32],
fee: int = 0,
force: bool = False,
push: bool = True,
request: SpendClawbackCoins,
tx_config: TXConfig,
extra_conditions: tuple[Condition, ...] = tuple(),
timelock_info: ConditionValidTimes = ConditionValidTimes(),
) -> dict[str, Any]:
self.add_to_log("spend_clawback_coins", (coin_ids, fee, force, push, timelock_info))
tx_hex_list = [get_bytes32(6).hex(), get_bytes32(7).hex(), get_bytes32(8).hex()]
return {
"transaction_ids": tx_hex_list,
"transactions": [
STD_TX.to_json_dict_convenience(
{
"selected_network": "mainnet",
"network_overrides": {"config": {"mainnet": {"address_prefix": "xch"}}},
}
)
],
}
) -> SpendClawbackCoinsResponse:
self.add_to_log(
"spend_clawback_coins", (request.coin_ids, request.fee, request.force, request.push, timelock_info)
)
tx_list = [get_bytes32(6), get_bytes32(7), get_bytes32(8)]
return SpendClawbackCoinsResponse(
transaction_ids=tx_list,
transactions=[STD_TX],
unsigned_transactions=[STD_UTX],
)
inst_rpc_client = ClawbackWalletRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
tx_ids = [get_bytes32(3), get_bytes32(4), get_bytes32(5)]
r_tx_ids_hex = [get_bytes32(6).hex(), get_bytes32(7).hex(), get_bytes32(8).hex()]
r_tx_ids_hex = ["0x" + get_bytes32(6).hex(), "0x" + get_bytes32(7).hex(), "0x" + get_bytes32(8).hex()]
command_args = [
"wallet",
"clawback",
@@ -556,7 +577,7 @@ def test_clawback(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Pa
"--expires-at",
"150",
]
run_cli_command_and_assert(capsys, root_dir, command_args, ["transaction_ids", str(r_tx_ids_hex)])
run_cli_command_and_assert(capsys, root_dir, command_args, ["transaction_ids", *r_tx_ids_hex])
# these are various things that should be in the output
expected_calls: logType = {
"spend_clawback_coins": [(tx_ids, 500000000000, False, True, test_condition_valid_times)],
@@ -569,8 +590,8 @@ def test_del_unconfirmed_tx(capsys: object, get_test_cli_clients: tuple[TestRpcC
# set RPC Client
class UnconfirmedTxRpcClient(TestWalletRpcClient):
async def delete_unconfirmed_transactions(self, wallet_id: int) -> None:
self.add_to_log("delete_unconfirmed_transactions", (wallet_id,))
async def delete_unconfirmed_transactions(self, request: DeleteUnconfirmedTransactions) -> None:
self.add_to_log("delete_unconfirmed_transactions", (request.wallet_id,))
inst_rpc_client = UnconfirmedTxRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
@@ -594,9 +615,9 @@ def test_get_derivation_index(capsys: object, get_test_cli_clients: tuple[TestRp
# set RPC Client
class GetDerivationIndexRpcClient(TestWalletRpcClient):
async def get_current_derivation_index(self) -> str:
async def get_current_derivation_index(self) -> GetCurrentDerivationIndexResponse:
self.add_to_log("get_current_derivation_index", ())
return str(520)
return GetCurrentDerivationIndexResponse(uint32(520))
inst_rpc_client = GetDerivationIndexRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
@@ -625,8 +646,9 @@ def test_sign_message(capsys: object, get_test_cli_clients: tuple[TestRpcClients
# these are various things that should be in the output
assert_list = [
f"Message: {message.hex()}",
f"Public Key: {bytes([3] * 48).hex()}",
f"Signature: {bytes([6] * 576).hex()}",
"Public Key: b5acf3599bc5fa5da1c00f6cc3d5bcf1560def67778b7f50a8c373a83f78761505b6250ab776e38a292e26628009aec4",
"Signature: c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
f"Signing Mode: {SigningMode.CHIP_0002.value}",
]
run_cli_command_and_assert(capsys, root_dir, [*command_args, f"-a{xch_addr}"], assert_list)
@@ -641,9 +663,9 @@ def test_update_derivation_index(capsys: object, get_test_cli_clients: tuple[Tes
# set RPC Client
class UpdateDerivationIndexRpcClient(TestWalletRpcClient):
async def extend_derivation_index(self, index: int) -> str:
self.add_to_log("extend_derivation_index", (index,))
return str(index)
async def extend_derivation_index(self, request: ExtendDerivationIndex) -> ExtendDerivationIndexResponse:
self.add_to_log("extend_derivation_index", (request.index,))
return ExtendDerivationIndexResponse(request.index)
inst_rpc_client = UpdateDerivationIndexRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
@@ -665,8 +687,9 @@ def test_add_token(capsys: object, get_test_cli_clients: tuple[TestRpcClients, P
self.add_to_log("create_wallet_for_existing_cat", (asset_id,))
return {"wallet_id": 3}
async def set_cat_name(self, wallet_id: int, name: str) -> None:
self.add_to_log("set_cat_name", (wallet_id, name))
async def set_cat_name(self, request: CATSetName) -> CATSetNameResponse:
self.add_to_log("set_cat_name", (request.wallet_id, request.name))
return CATSetNameResponse(wallet_id=request.wallet_id)
inst_rpc_client = AddTokenRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
@@ -1033,14 +1056,14 @@ def test_take_offer(capsys: object, get_test_cli_clients: tuple[TestRpcClients,
),
)
async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]:
self.add_to_log("cat_asset_id_to_name", (asset_id,))
if asset_id == cat_offered_id:
return uint32(2), "offered cat"
elif asset_id == cat_requested_id:
return uint32(3), "requested cat"
async def cat_asset_id_to_name(self, request: CATAssetIDToName) -> CATAssetIDToNameResponse:
self.add_to_log("cat_asset_id_to_name", (request.asset_id,))
if request.asset_id == cat_offered_id:
return CATAssetIDToNameResponse(uint32(2), "offered cat")
elif request.asset_id == cat_requested_id:
return CATAssetIDToNameResponse(uint32(3), "requested cat")
else:
return None
return CATAssetIDToNameResponse(wallet_id=None, name=None)
inst_rpc_client = TakeOfferRpcClient()
test_rpc_clients.wallet_rpc_client = inst_rpc_client
+51 -26
View File
@@ -50,19 +50,14 @@ from chia._tests.util.setup_nodes import (
from chia._tests.util.spend_sim import CostLogger
from chia._tests.util.time_out_assert import time_out_assert
from chia.farmer.farmer_rpc_client import FarmerRpcClient
from chia.farmer.farmer_service import FarmerService
from chia.full_node.full_node_api import FullNodeAPI
from chia.full_node.full_node_service import FullNodeService
from chia.harvester.harvester_rpc_client import HarvesterRpcClient
from chia.harvester.harvester_service import HarvesterService
from chia.seeder.crawler_service import CrawlerService
from chia.seeder.dns_server import DNSServer
from chia.server.aliases import (
CrawlerService,
FarmerService,
FullNodeService,
HarvesterService,
TimelordService,
WalletService,
)
from chia.server.server import ChiaServer
from chia.server.start_service import Service
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.simulator.setup_services import (
setup_crawler,
@@ -70,11 +65,14 @@ from chia.simulator.setup_services import (
setup_full_node,
setup_introducer,
setup_seeder,
setup_solver,
setup_timelord,
)
from chia.simulator.start_simulator import SimulatorFullNodeService
from chia.simulator.wallet_tools import WalletTool
from chia.types.peer_info import PeerInfo
from chia.solver.solver_service import SolverService
from chia.timelord.timelord_service import TimelordService
from chia.types.peer_info import PeerInfo, UnresolvedPeerInfo
from chia.util.config import create_default_chia_config, lock_and_load_config
from chia.util.db_wrapper import generate_in_memory_db_uri
from chia.util.keychain import Keychain
@@ -82,6 +80,7 @@ from chia.util.task_timing import main as task_instrumentation_main
from chia.util.task_timing import start_task_instrumentation, stop_task_instrumentation
from chia.wallet.wallet_node import WalletNode
from chia.wallet.wallet_rpc_client import WalletRpcClient
from chia.wallet.wallet_service import WalletService
# TODO: review how this is now after other imports and before some stdlib imports... :[
# Set spawn after stdlib imports, but before other imports
@@ -93,7 +92,7 @@ from pathlib import Path
from chia_rs.sized_ints import uint128
from chia._tests.environments.wallet import WalletEnvironment, WalletState, WalletTestFramework
from chia._tests.util.setup_nodes import setup_farmer_multi_harvester
from chia._tests.util.setup_nodes import setup_farmer_solver_multi_harvester
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
from chia.simulator.block_tools import BlockTools, create_block_tools_async, test_constants
from chia.simulator.keyring import TempKeyring
@@ -233,8 +232,8 @@ def blockchain_constants(consensus_mode: ConsensusMode) -> ConsensusConstants:
@pytest.fixture(scope="session", name="bt")
async def block_tools_fixture(get_keychain, blockchain_constants, anyio_backend) -> BlockTools:
# Note that this causes a lot of CPU and disk traffic - disk, DB, ports, process creation ...
_shared_block_tools = await create_block_tools_async(constants=blockchain_constants, keychain=get_keychain)
return _shared_block_tools
shared_block_tools = await create_block_tools_async(constants=blockchain_constants, keychain=get_keychain)
return shared_block_tools
# if you have a system that has an unusual hostname for localhost and you want
@@ -790,7 +789,7 @@ async def three_nodes_two_wallets(blockchain_constants: ConsensusConstants):
@pytest.fixture(scope="function")
async def one_node(
blockchain_constants: ConsensusConstants,
) -> AsyncIterator[tuple[list[Service], list[FullNodeSimulator], BlockTools]]:
) -> AsyncIterator[tuple[list[SimulatorFullNodeService], list[WalletService], BlockTools]]:
async with setup_simulators_and_wallets_service(1, 0, blockchain_constants) as _:
yield _
@@ -867,7 +866,7 @@ async def farmer_one_harvester_simulator_wallet(
]
]:
async with setup_simulators_and_wallets_service(1, 1, blockchain_constants) as (nodes, wallets, bt):
async with setup_farmer_multi_harvester(bt, 1, tmp_path, bt.constants, start_services=True) as (
async with setup_farmer_solver_multi_harvester(bt, 1, tmp_path, bt.constants, start_services=True) as (
harvester_services,
farmer_service,
_,
@@ -880,31 +879,54 @@ FarmerOneHarvester = tuple[list[HarvesterService], FarmerService, BlockTools]
@pytest.fixture(scope="function")
async def farmer_one_harvester(tmp_path: Path, get_b_tools: BlockTools) -> AsyncIterator[FarmerOneHarvester]:
async with setup_farmer_multi_harvester(get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True) as _:
async with setup_farmer_solver_multi_harvester(
get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True
) as _:
yield _
FarmerOneHarvesterSolver = tuple[list[HarvesterService], FarmerService, SolverService, BlockTools]
@pytest.fixture(scope="function")
async def farmer_one_harvester_solver(
tmp_path: Path, get_b_tools: BlockTools
) -> AsyncIterator[FarmerOneHarvesterSolver]:
async with setup_solver(tmp_path / "solver", get_b_tools, get_b_tools.constants) as solver_service:
solver_peer = UnresolvedPeerInfo(get_b_tools.config["self_hostname"], solver_service._server.get_port())
async with setup_farmer_solver_multi_harvester(
get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True, solver_peer=solver_peer
) as (harvester_services, farmer_service, bt):
yield harvester_services, farmer_service, solver_service, bt
@pytest.fixture(scope="function")
async def farmer_one_harvester_not_started(
tmp_path: Path, get_b_tools: BlockTools
) -> AsyncIterator[tuple[list[Service], Service]]:
async with setup_farmer_multi_harvester(get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=False) as _:
) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]:
async with setup_farmer_solver_multi_harvester(
get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=False
) as _:
yield _
@pytest.fixture(scope="function")
async def farmer_two_harvester_not_started(
tmp_path: Path, get_b_tools: BlockTools
) -> AsyncIterator[tuple[list[Service], Service]]:
async with setup_farmer_multi_harvester(get_b_tools, 2, tmp_path, get_b_tools.constants, start_services=False) as _:
) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]:
async with setup_farmer_solver_multi_harvester(
get_b_tools, 2, tmp_path, get_b_tools.constants, start_services=False
) as _:
yield _
@pytest.fixture(scope="function")
async def farmer_three_harvester_not_started(
tmp_path: Path, get_b_tools: BlockTools
) -> AsyncIterator[tuple[list[Service], Service]]:
async with setup_farmer_multi_harvester(get_b_tools, 3, tmp_path, get_b_tools.constants, start_services=False) as _:
) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]:
async with setup_farmer_solver_multi_harvester(
get_b_tools, 3, tmp_path, get_b_tools.constants, start_services=False
) as _:
yield _
@@ -1285,10 +1307,13 @@ async def farmer_harvester_2_simulators_zero_bits_plot_filter(
)
for index in range(len(bts))
]
[harvester_service], farmer_service, _ = await async_exit_stack.enter_async_context(
setup_farmer_multi_harvester(bt, 1, tmp_path, bt.constants, start_services=True)
)
async with setup_solver(tmp_path / "solver", bt, bt.constants) as solver_service:
solver_peer = UnresolvedPeerInfo(bt.config["self_hostname"], solver_service._server.get_port())
[harvester_service], farmer_service, _ = await async_exit_stack.enter_async_context(
setup_farmer_solver_multi_harvester(
bt, 1, tmp_path, bt.constants, start_services=True, solver_peer=solver_peer
)
)
yield farmer_service, harvester_service, simulators[0], simulators[1], bt
+4 -3
View File
@@ -1,12 +1,13 @@
from __future__ import annotations
from typing import Any, Optional
from typing import Any
import pytest
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32
from chia.cmds.wallet_funcs import print_offer_summary
from chia.wallet.wallet_request_types import CATAssetIDToName, CATAssetIDToNameResponse
TEST_DUCKSAUCE_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000001"
TEST_CRUNCHBERRIES_ASSET_ID = "1000000000000000000000000000000000000000000000000000000000000002"
@@ -19,8 +20,8 @@ TEST_ASSET_ID_NAME_MAPPING: dict[bytes32, tuple[uint32, str]] = {
}
async def cat_name_resolver(asset_id: bytes32) -> Optional[tuple[Optional[uint32], str]]:
return TEST_ASSET_ID_NAME_MAPPING.get(asset_id)
async def cat_name_resolver(request: CATAssetIDToName) -> CATAssetIDToNameResponse:
return CATAssetIDToNameResponse(*TEST_ASSET_ID_NAME_MAPPING.get(request.asset_id, (None, None)))
@pytest.mark.anyio
@@ -1,5 +1,6 @@
from __future__ import annotations
import pytest
from chia_rs import PlotSize
from chia_rs.sized_ints import uint8, uint16, uint32, uint64, uint128
from pytest import raises
@@ -67,7 +68,7 @@ class TestPotIterations:
ip_iters = calculate_ip_iters(test_constants, ssi, uint8(13), required_iters)
assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters
required_iters = uint64(int(ssi * 4 / 300))
required_iters = uint64(ssi * 4 / 300)
ip_iters = calculate_ip_iters(test_constants, ssi, uint8(13), required_iters)
assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters
assert sp_iters < ip_iters
@@ -83,67 +84,96 @@ class TestPotIterations:
assert ip_iters == (sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters) % ssi
assert sp_iters > ip_iters
def test_win_percentage(self):
@pytest.mark.parametrize(
"height",
[
uint32(0),
test_constants.HARD_FORK2_HEIGHT - 1,
test_constants.HARD_FORK2_HEIGHT,
test_constants.HARD_FORK2_HEIGHT + test_constants.PLOT_V1_PHASE_OUT,
test_constants.HARD_FORK2_HEIGHT + test_constants.PLOT_V1_PHASE_OUT + 1,
],
)
def test_win_percentage(self, height: uint32):
"""
Tests that the percentage of blocks won is proportional to the space of each farmer,
with the assumption that all farmers have access to the same VDF speed.
"""
farmer_ks = {
uint8(32): 100,
uint8(33): 100,
uint8(34): 100,
uint8(35): 100,
uint8(36): 100,
PlotSize.make_v1(32): 100,
PlotSize.make_v1(33): 100,
PlotSize.make_v1(34): 100,
PlotSize.make_v1(35): 100,
PlotSize.make_v1(36): 100,
PlotSize.make_v2(28): 200,
PlotSize.make_v2(30): 200,
PlotSize.make_v2(32): 200,
}
farmer_space = {k: _expected_plot_size(uint8(k)) * count for k, count in farmer_ks.items()}
total_space = sum(farmer_space.values())
percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()}
farmer_space = {k: _expected_plot_size(k) * count for k, count in farmer_ks.items()}
wins = {k: 0 for k in farmer_ks.keys()}
constants = test_constants.replace(DIFFICULTY_CONSTANT_FACTOR=uint128(2**25))
total_slots = 50
num_sps = 16
sp_interval_iters = uint64(100000000 // 32)
sub_slot_iters = uint64(100000000)
sp_interval_iters = calculate_sp_interval_iters(constants, sub_slot_iters)
difficulty = uint64(500000000000)
constants = test_constants.replace(DIFFICULTY_CONSTANT_FACTOR=uint128(2**25))
for slot_index in range(total_slots):
total_wins_in_slot = 0
for sp_index in range(num_sps):
sp_hash = std_hash(slot_index.to_bytes(4, "big") + sp_index.to_bytes(4, "big"))
for k, count in farmer_ks.items():
for farmer_index in range(count):
quality = std_hash(slot_index.to_bytes(4, "big") + k.to_bytes(1, "big") + bytes(farmer_index))
# TODO: todo_v2_plots
plot_k_val = k.size_v1 if k.size_v2 is None else k.size_v2
assert plot_k_val is not None
quality = std_hash(
slot_index.to_bytes(4, "big") + plot_k_val.to_bytes(1, "big") + bytes(farmer_index)
)
required_iters = calculate_iterations_quality(
constants, quality, PlotSize.make_v1(k), difficulty, sp_hash, uint64(100000000), uint32(0)
constants, quality, k, difficulty, sp_hash, sub_slot_iters, height
)
if required_iters < sp_interval_iters:
wins[k] += 1
total_wins_in_slot += 1
if height < test_constants.HARD_FORK2_HEIGHT + test_constants.PLOT_V1_PHASE_OUT:
total_space = sum(farmer_space.values())
percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()}
else:
# after the phase-out, v1 plots don't count
# all wins are by v2 plots
total_space = sum(0 if k.size_v2 is None else sp for k, sp in farmer_space.items())
percentage_space = {
k: 0.0 if k.size_v2 is None else float(sp / total_space) for k, sp in farmer_space.items()
}
win_percentage = {k: wins[k] / sum(wins.values()) for k in farmer_ks.keys()}
for k in farmer_ks.keys():
# Win rate is proportional to percentage of space
assert abs(win_percentage[k] - percentage_space[k]) < 0.01
def test_calculate_phase_out(self):
@pytest.mark.parametrize("sp_interval", [uint64(6250000000), uint64(1), uint64(2), uint64(10), uint64(10000000000)])
def test_calculate_phase_out(self, sp_interval: uint64):
constants = test_constants
sub_slot_iters = uint64(100000000000)
sp_interval = calculate_sp_interval_iters(constants, sub_slot_iters)
sub_slot_iters = uint64(sp_interval * constants.NUM_SPS_SUB_SLOT)
# Before or at HARD_FORK2_HEIGHT, should return 0
assert calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT - 1) == 0
assert calculate_phase_out(constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT - 1)) == 0
assert calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT) == 0
# after HARD_FORK2_HEIGHT, should return value = delta/phase_out_period * sp_interval
assert (
calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + 1)
calculate_phase_out(constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + 1))
== sp_interval // constants.PLOT_V1_PHASE_OUT
)
assert (
calculate_phase_out(
constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT // 2
constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT // 2)
)
== sp_interval // 2
)
assert (
calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT)
calculate_phase_out(
constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT)
)
== sp_interval
)
@@ -151,3 +181,19 @@ class TestPotIterations:
max_uint32_height = uint32(0xFFFFFFFF)
result_max_height = calculate_phase_out(constants, sub_slot_iters, max_uint32_height)
assert result_max_height == sp_interval # Should cap at sp_interval
def test_expected_plot_size_v1() -> None:
last_size = 2_400_000
for k in range(18, 50):
plot_size = _expected_plot_size(PlotSize.make_v1(k))
assert plot_size > last_size * 2
last_size = plot_size
def test_expected_plot_size_v2() -> None:
last_size = 100_000
for k in range(16, 32, 2):
plot_size = _expected_plot_size(PlotSize.make_v2(k))
assert plot_size > last_size * 2
last_size = plot_size
@@ -5,15 +5,17 @@ from dataclasses import dataclass
from typing import Optional
import pytest
from chia_rs import G1Element, PlotSize, ProofOfSpace
from chia_rs import G1Element, PlotSize
from chia_rs.sized_bytes import bytes32, bytes48
from chia_rs.sized_ints import uint8, uint32
from chia._tests.util.misc import Marks, datacases
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.types.blockchain_format.proof_of_space import (
calculate_plot_difficulty,
calculate_prefix_bits,
calculate_required_plot_strength,
check_plot_size,
make_pos,
passes_plot_filter,
verify_and_get_quality_string,
)
@@ -23,7 +25,7 @@ from chia.types.blockchain_format.proof_of_space import (
class ProofOfSpaceCase:
id: str
pos_challenge: bytes32
plot_size: uint8
plot_size: PlotSize
plot_public_key: G1Element
pool_public_key: Optional[G1Element] = None
pool_contract_puzzle_hash: Optional[bytes32] = None
@@ -45,14 +47,14 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Neither pool public key nor pool contract puzzle hash",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(0),
plot_size=PlotSize.make_v1(0),
plot_public_key=G1Element(),
expected_error="Expected pool public key or pool contract puzzle hash but got neither",
),
ProofOfSpaceCase(
id="Both pool public key and pool contract puzzle hash",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(0),
plot_size=PlotSize.make_v1(0),
plot_public_key=G1Element(),
pool_public_key=G1Element(),
pool_contract_puzzle_hash=bytes32(b"1" * 32),
@@ -61,7 +63,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Lower than minimum plot size",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(31),
plot_size=PlotSize.make_v1(31),
plot_public_key=G1Element(),
pool_public_key=G1Element(),
expected_error="Plot size is lower than the minimum",
@@ -69,7 +71,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Higher than maximum plot size",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(51),
plot_size=PlotSize.make_v1(51),
plot_public_key=G1Element(),
pool_public_key=G1Element(),
expected_error="Plot size is higher than the maximum",
@@ -77,7 +79,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Different challenge",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(42),
plot_size=PlotSize.make_v1(42),
pool_public_key=G1Element(),
plot_public_key=G1Element(),
expected_error="Calculated pos challenge doesn't match the provided one",
@@ -85,7 +87,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Not passing the plot filter with size 9",
pos_challenge=b32("08b23cc2844dfb92d2eedaa705a1ce665d571ee753bd81cbb67b92caa6d34722"),
plot_size=uint8(42),
plot_size=PlotSize.make_v1(42),
pool_public_key=g1(
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
),
@@ -98,7 +100,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Passing the plot filter with size 8",
pos_challenge=b32("08b23cc2844dfb92d2eedaa705a1ce665d571ee753bd81cbb67b92caa6d34722"),
plot_size=uint8(42),
plot_size=PlotSize.make_v1(42),
pool_public_key=g1(
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
),
@@ -110,7 +112,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="v2 plot size 0",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(0x80),
plot_size=PlotSize.make_v2(0),
plot_public_key=G1Element(),
pool_public_key=G1Element(),
expected_error="Plot size is lower than the minimum",
@@ -118,7 +120,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="v2 plot size 34",
pos_challenge=bytes32(b"1" * 32),
plot_size=uint8(0x80 | 34),
plot_size=PlotSize.make_v2(34),
plot_public_key=G1Element(),
pool_public_key=G1Element(),
expected_error="Plot size is higher than the maximum",
@@ -126,7 +128,7 @@ def b32(key: str) -> bytes32:
ProofOfSpaceCase(
id="Not passing the plot filter v2",
pos_challenge=b32("3d29ea79d19b3f7e99ebf764ae53697cbe143603909873946af6ab1ece606861"),
plot_size=uint8(0x80 | 32),
plot_size=PlotSize.make_v2(32),
pool_public_key=g1(
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
),
@@ -137,7 +139,7 @@ def b32(key: str) -> bytes32:
),
)
def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: ProofOfSpaceCase) -> None:
pos = ProofOfSpace(
pos = make_pos(
challenge=case.pos_challenge,
pool_public_key=case.pool_public_key,
pool_contract_puzzle_hash=case.pool_contract_puzzle_hash,
@@ -159,7 +161,7 @@ def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: P
@datacases(
ProofOfSpaceCase(
id="v2 plot are not implemented",
plot_size=uint8(0x80 | 30),
plot_size=PlotSize.make_v2(30),
pos_challenge=b32("47deb938e145d25d7b3b3c85ca9e3972b76c01aeeb78a02fe5d3b040d282317e"),
plot_public_key=g1(
"afa3aaf09c03885154be49216ee7fb2e4581b9c4a4d7e9cc402e27280bf0cfdbdf1b9ba674e301fd1d1450234b3b1868"
@@ -171,7 +173,7 @@ def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: P
),
)
def test_verify_and_get_quality_string_v2(caplog: pytest.LogCaptureFixture, case: ProofOfSpaceCase) -> None:
pos = ProofOfSpace(
pos = make_pos(
challenge=case.pos_challenge,
pool_public_key=case.pool_public_key,
pool_contract_puzzle_hash=case.pool_contract_puzzle_hash,
@@ -199,26 +201,54 @@ def test_verify_and_get_quality_string_v2(caplog: pytest.LogCaptureFixture, case
@pytest.mark.parametrize(
"height, difficulty",
"height, strength",
[
(0, 2),
(DEFAULT_CONSTANTS.HARD_FORK_HEIGHT, 2),
(DEFAULT_CONSTANTS.HARD_FORK2_HEIGHT, 2),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_4_HEIGHT - 1, 2),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_4_HEIGHT, 4),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_5_HEIGHT - 1, 4),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_5_HEIGHT, 5),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_6_HEIGHT - 1, 5),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_6_HEIGHT, 6),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_7_HEIGHT - 1, 6),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_7_HEIGHT, 7),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_8_HEIGHT - 1, 7),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_8_HEIGHT, 8),
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_8_HEIGHT + 1000000, 8),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_4_HEIGHT - 1, 2),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_4_HEIGHT, 4),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_5_HEIGHT - 1, 4),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_5_HEIGHT, 5),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_6_HEIGHT - 1, 5),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_6_HEIGHT, 6),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_7_HEIGHT - 1, 6),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_7_HEIGHT, 7),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_8_HEIGHT - 1, 7),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_8_HEIGHT, 8),
(DEFAULT_CONSTANTS.PLOT_STRENGTH_8_HEIGHT + 1000000, 8),
],
)
def test_calculate_plot_difficulty(height: uint32, difficulty: uint8) -> None:
assert calculate_plot_difficulty(DEFAULT_CONSTANTS, height) == difficulty
def test_calculate_plot_strength(height: uint32, strength: uint8) -> None:
assert calculate_required_plot_strength(DEFAULT_CONSTANTS, height) == strength
@pytest.mark.parametrize(
"size, valid",
[
(PlotSize.make_v1(31), False), # too small
(PlotSize.make_v1(32), True),
(PlotSize.make_v1(33), True),
(PlotSize.make_v1(34), True),
(PlotSize.make_v1(35), True),
(PlotSize.make_v1(36), True),
(PlotSize.make_v1(37), True),
(PlotSize.make_v1(49), True),
(PlotSize.make_v1(50), True),
(PlotSize.make_v1(51), False), # too large
(PlotSize.make_v2(26), False), # too small
(PlotSize.make_v2(27), False), # too small (and odd)
(PlotSize.make_v2(28), True),
(PlotSize.make_v2(29), False), # odd
(PlotSize.make_v2(30), True),
(PlotSize.make_v2(31), False), # odd
(PlotSize.make_v2(32), True),
(PlotSize.make_v2(33), False), # too large (and odd)
(PlotSize.make_v2(34), False), # too large
],
)
def test_check_plot_size(size: PlotSize, valid: bool) -> None:
assert check_plot_size(DEFAULT_CONSTANTS, size) == valid
class TestProofOfSpace:
+13 -8
View File
@@ -44,6 +44,7 @@ from chia.data_layer.data_layer import DataLayer
from chia.data_layer.data_layer_errors import KeyNotFoundError, OfferIntegrityError
from chia.data_layer.data_layer_rpc_api import DataLayerRpcApi
from chia.data_layer.data_layer_rpc_client import DataLayerRpcClient
from chia.data_layer.data_layer_service import DataLayerService
from chia.data_layer.data_layer_util import (
HashOnlyProof,
OfferStore,
@@ -56,8 +57,7 @@ from chia.data_layer.data_layer_util import (
from chia.data_layer.data_layer_wallet import DataLayerWallet, verify_offer
from chia.data_layer.data_store import DataStore
from chia.data_layer.download_data import get_delta_filename_path, get_full_tree_filename_path
from chia.server.aliases import DataLayerService, WalletService
from chia.server.start_data_layer import create_data_layer_service
from chia.data_layer.start_data_layer import create_data_layer_service
from chia.simulator.block_tools import BlockTools
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
@@ -73,8 +73,9 @@ from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_node import WalletNode
from chia.wallet.wallet_request_types import DLLatestSingleton
from chia.wallet.wallet_request_types import CheckOfferValidity, DLLatestSingleton
from chia.wallet.wallet_rpc_api import WalletRpcApi
from chia.wallet.wallet_service import WalletService
pytestmark = pytest.mark.data_layer
nodes = tuple[WalletNode, FullNodeSimulator]
@@ -190,7 +191,7 @@ async def is_transaction_confirmed(api: WalletRpcApi, tx_id: bytes32) -> bool:
except ValueError: # pragma: no cover
return False
return True if TransactionRecord.from_json_dict_convenience(val["transaction"]).confirmed else False # mypy
return True if TransactionRecord.from_json_dict(val["transaction"]).confirmed else False # mypy
async def farm_block_with_spend(
@@ -221,7 +222,9 @@ async def check_singleton_confirmed(dl: DataLayer, store_id: bytes32) -> bool:
async def process_block_and_check_offer_validity(offer: TradingOffer, offer_setup: OfferSetup) -> bool:
await offer_setup.full_node_api.farm_blocks_to_puzzlehash(count=1, guarantee_transaction_blocks=True)
return (await offer_setup.maker.data_layer.wallet_rpc.check_offer_validity(offer=offer))[1]
return (
await offer_setup.maker.data_layer.wallet_rpc.check_offer_validity(CheckOfferValidity(offer=offer.to_bech32()))
).valid
async def run_cli_cmd(*args: str, root_path: Path) -> asyncio.subprocess.Process:
@@ -1824,9 +1827,11 @@ async def test_make_and_cancel_offer(offer_setup: OfferSetup, reference: MakeAnd
for _ in range(10):
if not (
await offer_setup.maker.data_layer.wallet_rpc.check_offer_validity(
offer=TradingOffer.from_bytes(hexstr_to_bytes(maker_response["offer"]["offer"])),
CheckOfferValidity(
offer=TradingOffer.from_bytes(hexstr_to_bytes(maker_response["offer"]["offer"])).to_bech32()
),
)
)[1]:
).valid:
break
await offer_setup.full_node_api.farm_blocks_to_puzzlehash(count=1, guarantee_transaction_blocks=True)
await asyncio.sleep(0.5)
@@ -2277,7 +2282,7 @@ async def test_unsubscribe_unknown(
bare_data_layer_api: DataLayerRpcApi,
seeded_random: random.Random,
) -> None:
with pytest.raises(RuntimeError, match="No subscription found for the given store_id."):
with pytest.raises(RuntimeError, match="No subscription found for the given store_id"):
await bare_data_layer_api.unsubscribe(request={"id": bytes32.random(seeded_random).hex(), "retain": False})
@@ -594,7 +594,7 @@ async def test_ancestor_table_unique_inserts(data_store: DataStore, store_id: by
hash_2 = bytes32.from_hexstr("924be8ff27e84cba17f5bc918097f8410fab9824713a4668a21c8e060a8cab40")
await data_store._insert_ancestor_table(hash_1, hash_2, store_id, 2)
await data_store._insert_ancestor_table(hash_1, hash_2, store_id, 2)
with pytest.raises(Exception, match="^Requested insertion of ancestor"):
with pytest.raises(Exception, match=r"^Requested insertion of ancestor"):
await data_store._insert_ancestor_table(hash_1, hash_1, store_id, 2)
await data_store._insert_ancestor_table(hash_1, hash_2, store_id, 2)
@@ -2385,11 +2385,10 @@ async def test_get_leaf_at_minimum_height(
if isinstance(node, InternalNode):
heights[node.left_hash] = heights[node.hash] + 1
heights[node.right_hash] = heights[node.hash] + 1
elif min_leaf_height is not None:
min_leaf_height = min(min_leaf_height, heights[node.hash])
else:
if min_leaf_height is not None:
min_leaf_height = min(min_leaf_height, heights[node.hash])
else:
min_leaf_height = heights[node.hash]
min_leaf_height = heights[node.hash]
assert min_leaf_height is not None
if pre > 0:
@@ -418,8 +418,8 @@ async def test_get_generator(bt: BlockTools, db_version: int, use_cache: bool) -
store = await BlockStore.create(db_wrapper, use_cache=use_cache)
new_blocks = []
for i, block in enumerate(blocks):
block = block.replace(transactions_generator=generator(i))
for i, original_block in enumerate(blocks):
block = original_block.replace(transactions_generator=generator(i))
block_record = header_block_to_sub_block_record(
DEFAULT_CONSTANTS, uint64(0), block, uint64(0), False, uint8(0), uint32(max(0, block.height - 1)), None
)
@@ -560,7 +560,7 @@ class TestPeerManager:
# use tmp_path pytest fixture to create a temporary directory
async def test_serialization(self, tmp_path: Path):
addrman = AddressManagerTest()
now = int(math.floor(time.time()))
now = math.floor(time.time())
t_peer1 = TimestampedPeerInfo("250.7.1.1", uint16(8333), uint64(now - 10000))
t_peer2 = TimestampedPeerInfo("1050:0000:0000:0000:0005:0600:300c:326b", uint16(9999), uint64(now - 20000))
t_peer3 = TimestampedPeerInfo("250.7.3.3", uint16(9999), uint64(now - 30000))
@@ -587,7 +587,7 @@ class TestPeerManager:
@pytest.mark.anyio
async def test_bad_ip_encoding(self, tmp_path: Path):
addrman = AddressManagerTest()
now = int(math.floor(time.time()))
now = math.floor(time.time())
t_peer1 = TimestampedPeerInfo("250.7.1.1", uint16(8333), uint64(now - 10000))
t_peer2 = TimestampedPeerInfo("1050:0000:0000:0000:0005:0600:300c:326b", uint16(9999), uint64(now - 20000))
t_peer3 = TimestampedPeerInfo("250.7.3.3", uint16(9999), uint64(now - 30000))
@@ -725,7 +725,7 @@ class TestPeerManager:
# create a file with the old serialization, then migrate to new serialization
addrman = AddressManagerTest()
now = int(math.floor(time.time()))
now = math.floor(time.time())
t_peer1 = TimestampedPeerInfo("250.7.1.1", uint16(8333), uint64(now - 10000))
t_peer2 = TimestampedPeerInfo("1050:0000:0000:0000:0005:0600:300c:326b", uint16(9999), uint64(now - 20000))
t_peer3 = TimestampedPeerInfo("250.7.3.3", uint16(9999), uint64(now - 30000))
+91 -30
View File
@@ -112,11 +112,11 @@ from chia.wallet.wallet_spend_bundle import WalletSpendBundle
def test_pre_validation_result() -> None:
conds = SpendBundleConditions([], 0, 0, 0, None, None, [], 0, 0, 0, True, 0, 0)
conds = SpendBundleConditions([], 0, 0, 0, None, None, [], 0, 0, 0, True, 0, 0, 0, 0, 0)
results = PreValidationResult(None, uint64(1), conds, uint32(0))
assert results.validated_signature is True
conds = SpendBundleConditions([], 0, 0, 0, None, None, [], 0, 0, 0, False, 0, 0)
conds = SpendBundleConditions([], 0, 0, 0, None, None, [], 0, 0, 0, False, 0, 0, 0, 0, 0)
results = PreValidationResult(None, uint64(1), conds, uint32(0))
assert results.validated_signature is False
@@ -548,7 +548,7 @@ async def test_request_peers(
async def have_msgs(full_node_peers: FullNodePeers) -> bool:
assert full_node_peers.address_manager is not None
await full_node_peers.address_manager.add_to_new_table(
[TimestampedPeerInfo("127.0.0.1", uint16(1000), uint64(int(time.time()) - 1000))],
[TimestampedPeerInfo("127.0.0.1", uint16(1000), uint64(time.time() - 1000))],
None,
)
assert server_2._port is not None
@@ -919,6 +919,10 @@ async def test_new_peak(
@pytest.mark.anyio
@pytest.mark.limit_consensus_modes(
allowed=[ConsensusMode.HARD_FORK_2_0, ConsensusMode.HARD_FORK_3_0],
reason="We can no longer (reliably) farm blocks from before the hard fork",
)
async def test_new_transaction_and_mempool(
wallet_nodes: tuple[
FullNodeSimulator, FullNodeSimulator, ChiaServer, ChiaServer, WalletTool, WalletTool, BlockTools
@@ -946,8 +950,10 @@ async def test_new_transaction_and_mempool(
# Makes a bunch of coins
conditions_dict: dict[ConditionOpcode, list[ConditionWithArgs]] = {ConditionOpcode.CREATE_COIN: []}
# This should fit in one transaction
for _ in range(100):
# This should fit in one transaction. The test constants have a max block cost of 400,000,000
# and the default max *transaction* cost is half that, so 200,000,000. CREATE_COIN has a cost of
# 1,800,000, we create 80 coins
for _ in range(80):
receiver_puzzlehash = wallet_receiver.get_new_puzzlehash()
puzzle_hashes.append(receiver_puzzlehash)
output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [receiver_puzzlehash, int_to_bytes(10000000000)])
@@ -1046,8 +1052,8 @@ async def test_new_transaction_and_mempool(
# these numbers reflect the capacity of the mempool. In these
# tests MEMPOOL_BLOCK_BUFFER is 1. The other factors are COST_PER_BYTE
# and MAX_BLOCK_COST_CLVM
assert included_tx == 23
assert not_included_tx == 10
assert included_tx == 20
assert not_included_tx == 7
assert seen_bigger_transaction_has_high_fee
# Mempool is full
@@ -1882,7 +1888,9 @@ async def test_new_signage_point_caching(
) -> None:
full_node_1, _full_node_2, server_1, server_2, _wallet_a, _wallet_receiver, bt = wallet_nodes
blocks = await full_node_1.get_all_full_blocks()
assert full_node_1.full_node.full_node_store.get_signage_point_by_index_and_cc_output(
bytes32.zeros, full_node_1.full_node.constants.GENESIS_CHALLENGE, uint8(0)
) == SignagePoint(None, None, None, None)
peer = await connect_and_get_peer(server_1, server_2, self_hostname)
blocks = bt.get_consecutive_blocks(3, block_list_input=blocks, skip_slots=2)
await full_node_1.full_node.add_block(blocks[-3])
@@ -1951,10 +1959,6 @@ async def test_new_signage_point_caching(
is not None
)
assert full_node_1.full_node.full_node_store.get_signage_point_by_index_and_cc_output(
full_node_1.full_node.constants.GENESIS_CHALLENGE, bytes32.zeros, uint8(0)
) == SignagePoint(None, None, None, None)
@pytest.mark.anyio
async def test_slot_catch_up_genesis(
@@ -2678,16 +2682,15 @@ async def test_long_reorg_nodes(
blocks = default_10000_blocks[: 1600 - chain_length]
reorg_blocks = test_long_reorg_blocks_light[: 1600 - chain_length]
reorg_height = 2000
else:
if fork_point == 1500:
blocks = default_10000_blocks[: 1900 - chain_length]
reorg_blocks = test_long_reorg_1500_blocks[: 1900 - chain_length]
reorg_height = 2300
else: # pragma: no cover
pytest.skip("We rely on the light-blocks test for a 0 forkpoint")
blocks = default_10000_blocks[: 1100 - chain_length]
# reorg_blocks = test_long_reorg_blocks[: 1100 - chain_length]
reorg_height = 1600
elif fork_point == 1500:
blocks = default_10000_blocks[: 1900 - chain_length]
reorg_blocks = test_long_reorg_1500_blocks[: 1900 - chain_length]
reorg_height = 2300
else: # pragma: no cover
pytest.skip("We rely on the light-blocks test for a 0 forkpoint")
blocks = default_10000_blocks[: 1100 - chain_length]
# reorg_blocks = test_long_reorg_blocks[: 1100 - chain_length]
reorg_height = 1600
# this is a pre-requisite for a reorg to happen
assert default_10000_blocks[reorg_height].weight > reorg_blocks[-1].weight
@@ -3163,15 +3166,14 @@ async def declare_pos_unfinished_block(
challenge_chain_sp = block.reward_chain_block.challenge_chain_sp_vdf.output.get_hash()
if block.reward_chain_block.reward_chain_sp_vdf is not None:
reward_chain_sp = block.reward_chain_block.reward_chain_sp_vdf.output.get_hash()
elif len(block.finished_sub_slots) > 0:
reward_chain_sp = block.finished_sub_slots[-1].reward_chain.get_hash()
else:
if len(block.finished_sub_slots) > 0:
reward_chain_sp = block.finished_sub_slots[-1].reward_chain.get_hash()
else:
curr = blockchain.block_record(block.prev_header_hash)
while not curr.first_in_sub_slot:
curr = blockchain.block_record(curr.prev_hash)
assert curr.finished_reward_slot_hashes is not None
reward_chain_sp = curr.finished_reward_slot_hashes[-1]
curr = blockchain.block_record(block.prev_header_hash)
while not curr.first_in_sub_slot:
curr = blockchain.block_record(curr.prev_hash)
assert curr.finished_reward_slot_hashes is not None
reward_chain_sp = curr.finished_reward_slot_hashes[-1]
farmer_reward_address = block.foliage.foliage_block_data.farmer_reward_puzzle_hash
pool_target = block.foliage.foliage_block_data.pool_target
pool_target_signature = block.foliage.foliage_block_data.pool_signature
@@ -3296,3 +3298,62 @@ def compare_unfinished_blocks(block1: UnfinishedBlock, block2: UnfinishedBlock)
# Final assertion to check the entire block
assert block1 == block2, "The entire block objects are not identical"
return True
@pytest.mark.anyio
@pytest.mark.parametrize(
"condition, error",
[
(ConditionOpcode.ASSERT_HEIGHT_RELATIVE, "ASSERT_HEIGHT_RELATIVE_FAILED"),
(ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, "ASSERT_HEIGHT_ABSOLUTE_FAILED"),
],
)
async def test_pending_tx_cache_retry_on_new_peak(
condition: ConditionOpcode, error: str, blockchain_constants: ConsensusConstants, caplog: pytest.LogCaptureFixture
) -> None:
"""
Covers PendingTXCache items that are placed there due to unmet relative or
absolute height conditions, to make sure those items get retried at peak
post processing when those conditions are met.
"""
async with setup_simulators_and_wallets(1, 0, blockchain_constants) as new:
full_node_api = new.simulators[0].peer_api
bt = new.bt
wallet = WalletTool(test_constants)
ph = wallet.get_new_puzzlehash()
blocks = bt.get_consecutive_blocks(
3, guarantee_transaction_block=True, farmer_reward_puzzle_hash=ph, pool_reward_puzzle_hash=ph
)
for block in blocks:
await full_node_api.full_node.add_block(block)
peak = full_node_api.full_node.blockchain.get_peak()
assert peak is not None
current_height = peak.height
# Create a transaction with a height condition that makes it pending
coin = blocks[-1].get_included_reward_coins()[0]
if condition == ConditionOpcode.ASSERT_HEIGHT_RELATIVE:
condition_height = 1
else:
condition_height = current_height + 1
condition_dic = {condition: [ConditionWithArgs(condition, [int_to_bytes(condition_height)])]}
sb = wallet.generate_signed_transaction(uint64(42), ph, coin, condition_dic)
sb_name = sb.name()
# Send the transaction
res = await full_node_api.send_transaction(SendTransaction(sb))
assert res is not None
assert ProtocolMessageTypes(res.type) == ProtocolMessageTypes.transaction_ack
transaction_ack = TransactionAck.from_bytes(res.data)
assert transaction_ack.status == MempoolInclusionStatus.PENDING.value
assert transaction_ack.error == error
# Make sure it ends up in the pending cache, not the mempool
assert full_node_api.full_node.mempool_manager.get_mempool_item(sb_name, include_pending=False) is None
assert full_node_api.full_node.mempool_manager.get_mempool_item(sb_name, include_pending=True) is not None
# Advance peak to meet the asserted height condition
with caplog.at_level(logging.DEBUG):
blocks = bt.get_consecutive_blocks(2, block_list_input=blocks, guarantee_transaction_block=True)
for block in blocks:
await full_node_api.full_node.add_block(block)
# This should trigger peak post processing with the added transaction
assert f"Added transaction to mempool: {sb_name}\n" in caplog.text
# Make sure the transaction was retried and got added to the mempool
assert full_node_api.full_node.mempool_manager.get_mempool_item(sb_name, include_pending=False) is not None
@@ -8,7 +8,6 @@ from chia_rs import (
get_spends_for_trusted_block,
get_spends_for_trusted_block_with_conditions,
)
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32, uint64
from chia.consensus.generator_tools import tx_removals_and_additions
@@ -47,6 +46,7 @@ spends: list[SpendConditions] = [
0,
execution_cost=0,
condition_cost=0,
fingerprint=b"",
),
SpendConditions(
coin_ids[1],
@@ -74,19 +74,20 @@ spends: list[SpendConditions] = [
0,
execution_cost=0,
condition_cost=0,
fingerprint=b"",
),
]
def test_tx_removals_and_additions() -> None:
conditions = SpendBundleConditions(
spends, uint64(0), uint32(0), uint64(0), None, None, [], uint64(0), 0, 0, False, 0, 0
spends, uint64(0), uint32(0), uint64(0), None, None, [], uint64(0), 0, 0, False, 0, 0, 0, 0, 0
)
expected_rems = [coin_ids[0], coin_ids[1]]
expected_additions = []
for spend in spends:
for puzzle_hash, am, _ in spend.create_coin:
expected_additions.append(Coin(bytes32(spend.coin_id), bytes32(puzzle_hash), uint64(am)))
expected_additions.append(Coin(spend.coin_id, puzzle_hash, uint64(am)))
rems, adds = tx_removals_and_additions(conditions)
assert rems == expected_rems
assert adds == expected_additions
@@ -109,7 +110,7 @@ def test_get_spends_for_block(caplog: pytest.LogCaptureFixture) -> None:
conditions = get_spends_for_trusted_block(
test_constants, TEST_GENERATOR.program, TEST_GENERATOR.generator_refs, 100
)
assert conditions[0]["block_spends"] == []
assert conditions["block_spends"] == []
def test_get_spends_for_block_with_conditions(caplog: pytest.LogCaptureFixture) -> None:
+173 -80
View File
@@ -44,7 +44,6 @@ from chia.consensus.condition_costs import ConditionCost
from chia.consensus.cost_calculator import NPCResult
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.full_node.bitcoin_fee_estimator import create_bitcoin_fee_estimator
from chia.full_node.eligible_coin_spends import run_for_cost
from chia.full_node.fee_estimation import EmptyMempoolInfo, MempoolInfo
from chia.full_node.full_node_api import FullNodeAPI
from chia.full_node.mempool import Mempool
@@ -73,7 +72,7 @@ from chia.types.generator_types import BlockGenerator
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.types.mempool_item import MempoolItem, UnspentLineageInfo
from chia.util.casts import int_to_bytes
from chia.util.errors import Err
from chia.util.errors import Err, ValidationError
from chia.util.hash import std_hash
from chia.util.recursive_replace import recursive_replace
from chia.wallet.conditions import AssertCoinAnnouncement, AssertPuzzleAnnouncement
@@ -120,7 +119,7 @@ def make_item(
return MempoolItem(
SpendBundle([], G2Element()),
fee,
SpendBundleConditions([], 0, 0, 0, None, None, [], cost, 0, 0, False, 0, 0),
SpendBundleConditions([], 0, 0, 0, None, None, [], cost, 0, 0, False, 0, 0, 0, 0, 0),
spend_bundle_name,
uint32(0),
assert_height,
@@ -362,7 +361,10 @@ async def respond_transaction(
self.full_node.full_node_store.pending_tx_request.pop(spend_name)
if spend_name in self.full_node.full_node_store.peers_with_tx:
self.full_node.full_node_store.peers_with_tx.pop(spend_name)
ret = await self.full_node.add_transaction(tx.transaction, spend_name, peer, test)
try:
ret = await self.full_node.add_transaction(tx.transaction, spend_name, peer, test)
except ValidationError as e:
ret = (MempoolInclusionStatus.FAILED, e.code)
invariant_check_mempool(self.full_node.mempool_manager.mempool)
return ret
@@ -2866,8 +2868,9 @@ class TestMaliciousGenerators:
coin_spend_0 = make_spend(coin_0, cs.puzzle_reveal, cs.solution)
new_bundle = recursive_replace(spend_bundle, "coin_spends", [coin_spend_0, *spend_bundle.coin_spends[1:]])
assert spend_bundle is not None
res = await full_node_1.full_node.add_transaction(new_bundle, new_bundle.name(), test=True)
assert res == (MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH)
with pytest.raises(ValidationError) as e:
await full_node_1.full_node.add_transaction(new_bundle, new_bundle.name(), test=True)
assert e.value.code == Err.WRONG_PUZZLE_HASH
coins = make_test_coins()
@@ -2909,14 +2912,7 @@ coins = make_test_coins()
],
)
def test_items_by_feerate(items: list[MempoolItem], expected: list[Coin]) -> None:
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
mempool_info = MempoolInfo(
CLVMCost(uint64(11000000000 * 3)),
FeeRate(uint64(1000000)),
CLVMCost(uint64(11000000000)),
)
mempool = Mempool(mempool_info, fee_estimator)
mempool = construct_mempool()
for i in items:
mempool.add_to_pool(i)
@@ -2934,13 +2930,7 @@ def test_items_by_feerate(items: list[MempoolItem], expected: list[Coin]) -> Non
@pytest.mark.parametrize("old", [True, False])
def test_timeout(old: bool) -> None:
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
mempool_info = MempoolInfo(
CLVMCost(uint64(11000000000 * 3)),
FeeRate(uint64(1000000)),
CLVMCost(uint64(11000000000)),
)
mempool = Mempool(mempool_info, fee_estimator)
mempool = construct_mempool()
for i in range(50):
item = mk_item(coins[i : i + 1], flags=[0], fee=0, cost=50)
@@ -3107,13 +3097,7 @@ def test_limit_expiring_transactions(height: bool, items: list[int], expected: l
],
)
def test_get_items_by_coin_ids(items: list[MempoolItem], coin_ids: list[bytes32], expected: list[MempoolItem]) -> None:
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
mempool_info = MempoolInfo(
CLVMCost(uint64(11000000000 * 3)),
FeeRate(uint64(1000000)),
CLVMCost(uint64(11000000000)),
)
mempool = Mempool(mempool_info, fee_estimator)
mempool = construct_mempool()
for i in items:
mempool.add_to_pool(i)
invariant_check_mempool(mempool)
@@ -3127,74 +3111,99 @@ def make_test_spendbundle(coin: Coin, *, fee: int = 0, with_higher_cost: bool =
if with_higher_cost:
conditions.extend([[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, i] for i in range(3)])
actual_fee += 3
conditions.append([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, coin.amount - actual_fee])
conditions.append([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, uint64(coin.amount - actual_fee)])
sb = spend_bundle_from_conditions(conditions, coin)
return sb
def test_aggregating_on_a_solution_then_a_more_cost_saving_one_appears() -> None:
def agg_and_add_sb_returning_cost_info(mempool: Mempool, spend_bundles: list[SpendBundle]) -> uint64:
def construct_mempool() -> Mempool:
fee_estimator = create_bitcoin_fee_estimator(test_constants.MAX_BLOCK_COST_CLVM)
mempool_info = MempoolInfo(
CLVMCost(uint64(test_constants.MAX_BLOCK_COST_CLVM * 3)),
FeeRate(uint64(1000000)),
CLVMCost(test_constants.MAX_BLOCK_COST_CLVM),
)
return Mempool(mempool_info, fee_estimator)
def make_coin(idx: int) -> Coin:
return Coin(IDENTITY_PUZZLE_HASH, IDENTITY_PUZZLE_HASH, uint64(2_000_000_000 + idx * 2))
@pytest.mark.parametrize("old", [True, False])
def test_dedup_by_fee(old: bool) -> None:
"""
We pick the solution to use for dedup based on the spendbundle with the highest
fee per cost, not based on which one would give the overall best fee per cost
"""
mempool = construct_mempool()
def add_spend_bundles(spend_bundles: list[SpendBundle]) -> None:
sb = SpendBundle.aggregate(spend_bundles)
mi = mempool_item_from_spendbundle(sb)
mempool.add_to_pool(mi)
invariant_check_mempool(mempool)
saved_cost = run_for_cost(
sb.coin_spends[0].puzzle_reveal, sb.coin_spends[0].solution, len(mi.additions), mi.cost
)
return saved_cost
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
mempool_info = MempoolInfo(
CLVMCost(uint64(11000000000 * 3)),
FeeRate(uint64(1000000)),
CLVMCost(uint64(11000000000)),
)
mempool = Mempool(mempool_info, fee_estimator)
coins = [
Coin(IDENTITY_PUZZLE_HASH, IDENTITY_PUZZLE_HASH, uint64(amount)) for amount in range(2000000000, 2000000020, 2)
]
# Create a ~10 FPC item that spends the eligible coin[0]
sb_A = make_test_spendbundle(coins[0])
highest_fee = 58282830
sb_high_rate = make_test_spendbundle(coins[1], fee=highest_fee)
agg_and_add_sb_returning_cost_info(mempool, [sb_A, sb_high_rate])
invariant_check_mempool(mempool)
# Create a ~2 FPC item that spends the eligible coin using the same solution A
sb_low_rate = make_test_spendbundle(coins[2], fee=highest_fee // 5)
saved_cost_on_solution_A = agg_and_add_sb_returning_cost_info(mempool, [sb_A, sb_low_rate])
invariant_check_mempool(mempool)
result = mempool.create_bundle_from_mempool_items(test_constants, uint32(0))
DEDUP_COIN = make_coin(0)
COIN_A1 = make_coin(1)
COIN_A2 = make_coin(2)
# all other coins belong to solution B, the dedup alternative to solution A
# Create a spend bundle with a high fee, spending sb_A, which supports dedup
sb_A = make_test_spendbundle(DEDUP_COIN)
sb_high_rate = make_test_spendbundle(COIN_A1, fee=10)
add_spend_bundles([sb_A, sb_high_rate])
# Create a spend bundle, with a low fee, that spends the dedup coin using the same solution A
sb_low_rate = make_test_spendbundle(COIN_A2, fee=10)
add_spend_bundles([sb_A, sb_low_rate])
create_block = mempool.create_block_generator if old else mempool.create_block_generator2
# validate that dedup happens at all for sb_A
result = create_block(test_constants, uint32(0), 5.0)
assert result is not None
agg, _ = result
# Make sure both items would be processed
assert [c.coin for c in agg.coin_spends] == [coins[0], coins[1], coins[2]]
# Now let's add 3 x ~3 FPC items that spend the eligible coin differently
# (solution B). It creates a higher (saved) cost than solution A
sb_B = make_test_spendbundle(coins[0], with_higher_cost=True)
for i in range(3, 6):
# We're picking this fee to get a ~3 FPC, and get picked after sb_A1
# (which has ~10 FPC) but before sb_A2 (which has ~2 FPC)
sb_mid_rate = make_test_spendbundle(coins[i], fee=38004852 - i)
saved_cost_on_solution_B = agg_and_add_sb_returning_cost_info(mempool, [sb_B, sb_mid_rate])
invariant_check_mempool(mempool)
# We'd save more cost if we went with solution B instead of A
assert saved_cost_on_solution_B > saved_cost_on_solution_A
# If we process everything now, the 3 x ~3 FPC items get skipped because
# sb_A1 gets picked before them (~10 FPC), so from then on only sb_A2 (~2 FPC)
# would get picked
result = mempool.create_bundle_from_mempool_items(test_constants, uint32(0))
assert result.removals == [DEDUP_COIN, COIN_A1, COIN_A2]
# Now we add a bunch of alternative spends for coin 0, with lower fees
# Even though the total fee would be higher if we deduped on this solution,
# we won't.
sb_B = make_test_spendbundle(DEDUP_COIN, with_higher_cost=True)
for i in range(3, 600):
sb_high_rate = make_test_spendbundle(make_coin(i), fee=10)
add_spend_bundles([sb_B, sb_high_rate])
result = create_block(test_constants, uint32(0), 5.0)
assert result is not None
agg, _ = result
# The 3 items got skipped here
# We ran with solution A and missed bigger savings on solution B
assert mempool.size() == 5
assert [c.coin for c in agg.coin_spends] == [coins[0], coins[1], coins[2]]
invariant_check_mempool(mempool)
# we've added 599 spend bundles now. 2 with solution A and 598 with solution B
assert mempool.size() == 599
assert result.removals == [DEDUP_COIN, COIN_A1, COIN_A2]
# Now, if we add a high fee per-cost-for sb_B, it should be picked
sb_high_rate = make_test_spendbundle(make_coin(600), fee=1_000_000_000)
add_spend_bundles([sb_B, sb_high_rate])
result = create_block(test_constants, uint32(0), 5.0)
assert result is not None
# The 3 items got skipped here
# We ran with solution B
# we've added 600 spend bundles now. 2 with solution A and 599 with solution B
assert mempool.size() == 600
spends_in_block = set(result.removals)
assert DEDUP_COIN in spends_in_block
assert COIN_A1 not in spends_in_block
assert COIN_A2 not in spends_in_block
for i in range(3, 601):
assert make_coin(i) in spends_in_block
def test_get_puzzle_and_solution_for_coin_failure() -> None:
with pytest.raises(
ValueError, match=f"Failed to get puzzle and solution for coin {TEST_COIN}, error: \\('coin not found', '80'\\)"
ValueError,
match=f"Failed to get puzzle and solution for coin {TEST_COIN}, "
"error: \\('InvalidOperatorArg: coin not found', '80'\\)",
):
try:
get_puzzle_and_solution_for_coin(
@@ -3208,8 +3217,40 @@ def test_get_puzzle_and_solution_for_coin_failure() -> None:
raise ValueError(f"Failed to get puzzle and solution for coin {TEST_COIN}, error: {e}") from e
# this puzzle just creates coins, however many are requested by the solution
# (mod (A)
# (defun loop (n)
# (if (= n 1)
# (list)
# (c (list 51 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff n) (loop (- n 1))))
# )
# (loop A)
# )
create_coins_loop: str = (
"ff02ffff01ff02ff02ffff04ff02ffff04ff05ff80808080ffff04ffff01ff02"
"ffff03ffff09ff05ffff010180ff80ffff01ff04ffff04ffff0133ffff04ffff"
"01a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"ffffffff04ff05ff80808080ffff02ff02ffff04ff02ffff04ffff11ff05ffff"
"010180ff808080808080ff0180ff018080"
)
# this test uses artificial puzzles just to exercise the block creation. These
# spends are expected not to verify any signatures
# This is to keep the test simple.
@pytest.mark.parametrize(
"puzzle, solution",
[
# create 2000 coins
(create_coins_loop, "ff8207d180"),
# create 1000 coins
(create_coins_loop, "ff8203e980"),
# create 500 coins
(create_coins_loop, "ff8201f580"),
],
)
@pytest.mark.parametrize("old", [True, False])
def test_create_block_generator(old: bool) -> None:
def test_create_block_generator_custom_spend(puzzle: str, solution: str, old: bool) -> None:
mempool_info = MempoolInfo(
CLVMCost(uint64(11000000000 * 3)),
FeeRate(uint64(1000000)),
@@ -3217,7 +3258,59 @@ def test_create_block_generator(old: bool) -> None:
)
fee_estimator = create_bitcoin_fee_estimator(test_constants.MAX_BLOCK_COST_CLVM)
solution_str = SerializedProgram.fromhex(solution)
puzzle_reveal = SerializedProgram.fromhex(puzzle)
puzzle_hash = puzzle_reveal.get_tree_hash()
mempool = Mempool(mempool_info, fee_estimator)
coins = [Coin(bytes32.random(), puzzle_hash, uint64(amount)) for amount in range(100000000, 100000022)]
spend_bundles = [
SpendBundle(
coin_spends=[CoinSpend(coin, puzzle_reveal=puzzle_reveal, solution=solution_str)],
aggregated_signature=G2Element(),
)
for coin in coins
]
for sb in spend_bundles:
mi = mempool_item_from_spendbundle(sb)
mempool.add_to_pool(mi)
invariant_check_mempool(mempool)
create_block = mempool.create_block_generator if old else mempool.create_block_generator2
generator = create_block(test_constants, test_constants.HARD_FORK2_HEIGHT, 10.0)
assert generator is not None
assert generator.signature == G2Element()
removals = set(generator.removals)
err, conds = run_block_generator2(
bytes(generator.program),
generator.generator_refs,
test_constants.MAX_BLOCK_COST_CLVM,
0,
generator.signature,
None,
test_constants,
)
assert err is None
assert conds is not None
assert len(conds.spends) == len(removals)
for spend in conds.spends:
removal = Coin(spend.parent_id, spend.puzzle_hash, uint64(spend.coin_amount))
assert removal in coins
assert removal in removals
invariant_check_mempool(mempool)
@pytest.mark.parametrize("old", [True, False])
def test_create_block_generator(old: bool) -> None:
mempool = construct_mempool()
coins = [
Coin(IDENTITY_PUZZLE_HASH, IDENTITY_PUZZLE_HASH, uint64(amount)) for amount in range(2000000000, 2000000020, 2)
]
@@ -23,7 +23,6 @@ async def test_basics() -> None:
cost = uint64(5000000)
for i in range(300, 700):
i = uint32(i)
items = []
for _ in range(2, 100):
fee = uint64(10000000)
@@ -50,7 +49,7 @@ async def test_basics() -> None:
)
items.append(mempool_item2)
fee_tracker.process_block(i, items)
fee_tracker.process_block(uint32(i), items)
short, med, long = fee_tracker.estimate_fees()
@@ -72,7 +71,6 @@ async def test_fee_increase() -> None:
estimator = SmartFeeEstimator(fee_tracker, uint64(test_constants.MAX_BLOCK_COST_CLVM))
random = Random(x=1)
for i in range(300, 700):
i = uint32(i)
items = []
for _ in range(20):
fee = uint64(0)
@@ -85,7 +83,7 @@ async def test_fee_increase() -> None:
)
items.append(mempool_item)
fee_tracker.process_block(i, items)
fee_tracker.process_block(uint32(i), items)
short, med, long = fee_tracker.estimate_fees()
mempool_info = mempool_manager.mempool.fee_estimator.get_mempool_info()
+210 -56
View File
@@ -34,7 +34,6 @@ from chia.full_node.eligible_coin_spends import (
DedupCoinSpend,
IdenticalSpendDedup,
SkipDedup,
run_for_cost,
)
from chia.full_node.mempool import MAX_SKIPPED_ITEMS, PRIORITY_TX_THRESHOLD
from chia.full_node.mempool_manager import (
@@ -360,6 +359,7 @@ def make_test_conds(
flags,
execution_cost=0,
condition_cost=0,
fingerprint=b"",
)
for coin_id, parent_id, puzzle_hash, amount, flags, create_coin in spend_info
],
@@ -375,6 +375,9 @@ def make_test_conds(
False,
0,
0,
0,
0,
0,
)
@@ -568,27 +571,30 @@ def make_bundle_spends_map_and_fee(
removals_amount += coin_spend.coin.amount
spend_conds = spend_conditions.pop(coin_id)
additions_amount += coin_spend.coin.amount
additions = []
for puzzle_hash, amount, _ in spend_conds.create_coin:
additions.append(Coin(coin_id, puzzle_hash, uint64(amount)))
additions_amount += amount
bundle_coin_spends[coin_id] = BundleCoinSpend(
coin_spend=coin_spend,
eligible_for_dedup=bool(spend_conds.flags & ELIGIBLE_FOR_DEDUP),
eligible_for_fast_forward=bool(spend_conds.flags & ELIGIBLE_FOR_FF),
additions=additions,
cost=uint64(spend_conds.condition_cost + spend_conds.execution_cost),
latest_singleton_lineage=UnspentLineageInfo(coin_id, coin_spend.coin.parent_coin_info, bytes32([0] * 32))
if bool(spend_conds.flags & ELIGIBLE_FOR_FF)
else None,
)
assert additions_amount == conds.addition_amount
assert removals_amount == conds.removal_amount
fee = uint64(removals_amount - additions_amount)
return bundle_coin_spends, fee
def mempool_item_from_spendbundle(spend_bundle: SpendBundle) -> MempoolItem:
conds = get_conditions_from_spendbundle(spend_bundle, INFINITE_COST, DEFAULT_CONSTANTS, uint32(0))
conds = get_conditions_from_spendbundle(
spend_bundle, INFINITE_COST, DEFAULT_CONSTANTS, DEFAULT_CONSTANTS.HARD_FORK2_HEIGHT
)
bundle_coin_spends, fee = make_bundle_spends_map_and_fee(spend_bundle, conds)
return MempoolItem(
spend_bundle=spend_bundle,
@@ -883,8 +889,13 @@ def mk_bcs(coin_spend: CoinSpend, flags: int = 0) -> BundleCoinSpend:
return BundleCoinSpend(
coin_spend=coin_spend,
eligible_for_dedup=bool(flags & ELIGIBLE_FOR_DEDUP),
eligible_for_fast_forward=bool(flags & ELIGIBLE_FOR_FF),
additions=[],
cost=uint64(0),
latest_singleton_lineage=UnspentLineageInfo(
coin_spend.coin.name(), coin_spend.coin.parent_coin_info, bytes32([0] * 32)
)
if flags & ELIGIBLE_FOR_FF
else None,
)
@@ -1111,8 +1122,7 @@ coins = make_test_coins()
],
)
def test_can_replace(existing_items: list[MempoolItem], new_item: MempoolItem, expected: bool) -> None:
removals = {c.name() for c in new_item.spend_bundle.removals()}
assert can_replace(existing_items, removals, new_item) == expected
assert can_replace(existing_items, new_item) == expected
@pytest.mark.anyio
@@ -1578,21 +1588,6 @@ async def test_replacing_one_with_an_eligible_coin() -> None:
assert_sb_in_pool(mempool_manager, sb123e4)
@pytest.mark.parametrize("amount", [0, 1])
def test_run_for_cost(amount: int) -> None:
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, amount]]
solution = SerializedProgram.to(conditions)
cost = run_for_cost(IDENTITY_PUZZLE, solution, additions_count=1, max_cost=uint64(10000000))
assert cost == uint64(1800044)
def test_run_for_cost_max_cost() -> None:
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, 1]]
solution = SerializedProgram.to(conditions)
with pytest.raises(ValueError, match="cost exceeded"):
run_for_cost(IDENTITY_PUZZLE, solution, additions_count=1, max_cost=uint64(43))
def test_dedup_info_nothing_to_do() -> None:
# No eligible coins, nothing to deduplicate, item gets considered normally
@@ -1608,7 +1603,7 @@ def test_dedup_info_nothing_to_do() -> None:
mempool_item = mempool_item_from_spendbundle(sb)
dedup_coin_spends = IdenticalSpendDedup()
unique_coin_spends, cost_saving, unique_additions = dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=mempool_item.bundle_coin_spends, max_cost=mempool_item.conds.cost
bundle_coin_spends=mempool_item.bundle_coin_spends
)
assert unique_coin_spends == sb.coin_spends
assert cost_saving == 0
@@ -1628,7 +1623,7 @@ def test_dedup_info_eligible_1st_time() -> None:
dedup_coin_spends = IdenticalSpendDedup()
solution = SerializedProgram.to(conditions)
unique_coin_spends, cost_saving, unique_additions = dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=mempool_item.bundle_coin_spends, max_cost=mempool_item.conds.cost
bundle_coin_spends=mempool_item.bundle_coin_spends
)
assert unique_coin_spends == sb.coin_spends
assert cost_saving == 0
@@ -1636,7 +1631,10 @@ def test_dedup_info_eligible_1st_time() -> None:
Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, uint64(1)),
Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, uint64(TEST_COIN_AMOUNT - 1)),
}
assert dedup_coin_spends == IdenticalSpendDedup({TEST_COIN_ID: DedupCoinSpend(solution=solution, cost=None)})
expected_cost = mempool_item.bundle_coin_spends[TEST_COIN_ID].cost
assert dedup_coin_spends == IdenticalSpendDedup(
{TEST_COIN_ID: DedupCoinSpend(solution=solution, cost=expected_cost)}
)
def test_dedup_info_eligible_but_different_solution() -> None:
@@ -1646,14 +1644,12 @@ def test_dedup_info_eligible_but_different_solution() -> None:
[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT],
]
initial_solution = SerializedProgram.to(initial_conditions)
dedup_coin_spends = IdenticalSpendDedup({TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=None)})
dedup_coin_spends = IdenticalSpendDedup({TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=uint64(10))})
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT]]
sb = spend_bundle_from_conditions(conditions, TEST_COIN)
mempool_item = mempool_item_from_spendbundle(sb)
with pytest.raises(SkipDedup, match="Solution is different from what we're deduplicating on"):
dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=mempool_item.bundle_coin_spends, max_cost=mempool_item.conds.cost
)
dedup_coin_spends.get_deduplication_info(bundle_coin_spends=mempool_item.bundle_coin_spends)
def test_dedup_info_eligible_2nd_time_and_another_1st_time() -> None:
@@ -1663,7 +1659,10 @@ def test_dedup_info_eligible_2nd_time_and_another_1st_time() -> None:
[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT - 1],
]
initial_solution = SerializedProgram.to(initial_conditions)
dedup_coin_spends = IdenticalSpendDedup({TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=None)})
test_coin_cost = uint64(1337)
dedup_coin_spends = IdenticalSpendDedup(
{TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=test_coin_cost)}
)
sb1 = spend_bundle_from_conditions(initial_conditions, TEST_COIN)
second_conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT2]]
second_solution = SerializedProgram.to(second_conditions)
@@ -1672,19 +1671,19 @@ def test_dedup_info_eligible_2nd_time_and_another_1st_time() -> None:
mempool_item = mempool_item_from_spendbundle(sb)
assert mempool_item.conds is not None
unique_coin_spends, cost_saving, unique_additions = dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=mempool_item.bundle_coin_spends, max_cost=mempool_item.conds.cost
bundle_coin_spends=mempool_item.bundle_coin_spends
)
# Only the eligible one that we encountered more than once gets deduplicated
assert unique_coin_spends == sb2.coin_spends
saved_cost = uint64(3600044)
assert cost_saving == saved_cost
assert cost_saving == test_coin_cost
assert unique_additions == [Coin(TEST_COIN_ID2, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT2)]
# The coin we encountered a second time has its cost and additions properly updated
# The coin we encountered for the first time gets cost None and an empty set of additions
# The coin we encountered a second time is already in the map
# The coin we encountered for the first time gets added with its solution and cost
test_coin2_cost = mempool_item.bundle_coin_spends[TEST_COIN_ID2].cost
expected_dedup_coin_spends = IdenticalSpendDedup(
{
TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=saved_cost),
TEST_COIN_ID2: DedupCoinSpend(solution=second_solution, cost=None),
TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=test_coin_cost),
TEST_COIN_ID2: DedupCoinSpend(solution=second_solution, cost=test_coin2_cost),
}
)
assert dedup_coin_spends == expected_dedup_coin_spends
@@ -1699,11 +1698,12 @@ def test_dedup_info_eligible_3rd_time_another_2nd_time_and_one_non_eligible() ->
initial_solution = SerializedProgram.to(initial_conditions)
second_conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT2]]
second_solution = SerializedProgram.to(second_conditions)
saved_cost = uint64(3600044)
test_coin_cost = uint64(42)
test_coin2_cost = uint64(1337)
dedup_coin_spends = IdenticalSpendDedup(
{
TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=saved_cost),
TEST_COIN_ID2: DedupCoinSpend(solution=second_solution, cost=None),
TEST_COIN_ID: DedupCoinSpend(solution=initial_solution, cost=test_coin_cost),
TEST_COIN_ID2: DedupCoinSpend(solution=second_solution, cost=test_coin2_cost),
}
)
sb1 = spend_bundle_from_conditions(initial_conditions, TEST_COIN)
@@ -1720,16 +1720,16 @@ def test_dedup_info_eligible_3rd_time_another_2nd_time_and_one_non_eligible() ->
mempool_item = mempool_item_from_spendbundle(sb)
assert mempool_item.conds is not None
unique_coin_spends, cost_saving, unique_additions = dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=mempool_item.bundle_coin_spends, max_cost=mempool_item.conds.cost
bundle_coin_spends=mempool_item.bundle_coin_spends
)
assert unique_coin_spends == sb3.coin_spends
saved_cost2 = uint64(1800044)
assert cost_saving == saved_cost + saved_cost2
assert cost_saving == test_coin_cost + test_coin2_cost
assert unique_additions == [Coin(TEST_COIN_ID3, IDENTITY_PUZZLE_HASH, TEST_COIN_AMOUNT3)]
# TEST_COIN_ID3 is non-eligible, so it doesn't end up in this map
expected_dedup_coin_spends = IdenticalSpendDedup(
{
TEST_COIN_ID: DedupCoinSpend(initial_solution, saved_cost),
TEST_COIN_ID2: DedupCoinSpend(second_solution, saved_cost2),
TEST_COIN_ID: DedupCoinSpend(initial_solution, test_coin_cost),
TEST_COIN_ID2: DedupCoinSpend(second_solution, test_coin2_cost),
}
)
assert dedup_coin_spends == expected_dedup_coin_spends
@@ -1799,18 +1799,21 @@ async def test_bundle_coin_spends() -> None:
await send_spendbundle(mempool_manager, sb123e)
mi123e = mempool_manager.get_mempool_item(sb123e.name())
assert mi123e is not None
execution_cost = 44
for i in range(3):
assert mi123e.bundle_coin_spends[coins[i].name()] == BundleCoinSpend(
coin_spend=sb123.coin_spends[i],
eligible_for_dedup=False,
eligible_for_fast_forward=False,
additions=[Coin(coins[i].name(), IDENTITY_PUZZLE_HASH, coins[i].amount)],
cost=uint64(ConditionCost.CREATE_COIN.value + ConditionCost.AGG_SIG.value + execution_cost),
latest_singleton_lineage=None,
)
assert mi123e.bundle_coin_spends[coins[3].name()] == BundleCoinSpend(
coin_spend=eligible_sb.coin_spends[0],
eligible_for_dedup=True,
eligible_for_fast_forward=False,
additions=[Coin(coins[3].name(), IDENTITY_PUZZLE_HASH, coins[3].amount)],
cost=uint64(ConditionCost.CREATE_COIN.value + execution_cost),
latest_singleton_lineage=None,
)
@@ -2462,7 +2465,7 @@ async def test_new_peak_ff_eviction(
item = mempool_manager.get_mempool_item(bundle.name())
assert item is not None
singleton_name = singleton_spend.coin.name()
assert item.bundle_coin_spends[singleton_name].eligible_for_fast_forward
assert item.bundle_coin_spends[singleton_name].supports_fast_forward
latest_singleton_lineage = item.bundle_coin_spends[singleton_name].latest_singleton_lineage
assert latest_singleton_lineage is not None
assert latest_singleton_lineage.coin_id == singleton_name
@@ -2494,7 +2497,7 @@ async def test_new_peak_ff_eviction(
else:
item = mempool_manager.get_mempool_item(bundle.name())
assert item is not None
assert item.bundle_coin_spends[singleton_spend.coin.name()].eligible_for_fast_forward
assert item.bundle_coin_spends[singleton_spend.coin.name()].supports_fast_forward
latest_singleton_lineage = item.bundle_coin_spends[singleton_spend.coin.name()].latest_singleton_lineage
assert latest_singleton_lineage is not None
assert latest_singleton_lineage.coin_id == singleton_spend.coin.name()
@@ -2551,9 +2554,9 @@ async def test_multiple_ff(use_optimization: bool) -> None:
item = mempool_manager.get_mempool_item(bundle.name())
assert item is not None
assert item.bundle_coin_spends[singleton_spend1.coin.name()].eligible_for_fast_forward
assert item.bundle_coin_spends[singleton_spend2.coin.name()].eligible_for_fast_forward
assert not item.bundle_coin_spends[coin_spend.coin.name()].eligible_for_fast_forward
assert item.bundle_coin_spends[singleton_spend1.coin.name()].supports_fast_forward
assert item.bundle_coin_spends[singleton_spend2.coin.name()].supports_fast_forward
assert not item.bundle_coin_spends[coin_spend.coin.name()].supports_fast_forward
# spend the singleton coin2 and make coin3 the latest version
coins.update_lineage(singleton_ph, singleton_spend3.coin)
@@ -2615,7 +2618,7 @@ async def test_advancing_ff(use_optimization: bool) -> None:
item = mempool_manager.get_mempool_item(bundle.name())
assert item is not None
spend = item.bundle_coin_spends[spend_a.coin.name()]
assert spend.eligible_for_fast_forward
assert spend.supports_fast_forward
assert spend.latest_singleton_lineage is not None
assert spend.latest_singleton_lineage.coin_id == spend_a.coin.name()
@@ -2627,7 +2630,7 @@ async def test_advancing_ff(use_optimization: bool) -> None:
item = mempool_manager.get_mempool_item(bundle.name())
assert item is not None
spend = item.bundle_coin_spends[spend_a.coin.name()]
assert spend.eligible_for_fast_forward
assert spend.supports_fast_forward
assert spend.latest_singleton_lineage is not None
assert spend.latest_singleton_lineage.coin_id == spend_b.coin.name()
@@ -2639,7 +2642,7 @@ async def test_advancing_ff(use_optimization: bool) -> None:
item = mempool_manager.get_mempool_item(bundle.name())
assert item is not None
spend = item.bundle_coin_spends[spend_a.coin.name()]
assert spend.eligible_for_fast_forward
assert spend.supports_fast_forward
assert spend.latest_singleton_lineage is not None
assert spend.latest_singleton_lineage.coin_id == spend_c.coin.name()
@@ -3156,3 +3159,154 @@ def test_check_removals(case: CheckRemovalsCase) -> None:
assert err == expected_err
assert len(conflicts) == len(expected_conflicts)
assert set(conflicts) == set(expected_conflicts)
@pytest.mark.anyio
async def test_new_peak_deferred_ff_items() -> None:
"""
Covers the case where we update lineage info for multiple fast forward
singletons at new peak.
"""
singleton_spend1 = make_singleton_spend(bytes32([1] * 32))
singleton1_id = singleton_spend1.coin.name()
singleton_spend2 = make_singleton_spend(bytes32([2] * 32))
singleton2_id = singleton_spend2.coin.name()
coins = TestCoins(
[singleton_spend1.coin, singleton_spend2.coin, TEST_COIN, TEST_COIN2],
{
singleton_spend1.coin.puzzle_hash: singleton_spend1.coin,
singleton_spend2.coin.puzzle_hash: singleton_spend2.coin,
},
)
mempool_manager = await setup_mempool(coins)
# Let's submit the two singletons transactions to the mempool
sb_names = []
for singleton_spend, regular_coin in [(singleton_spend1, TEST_COIN), (singleton_spend2, TEST_COIN2)]:
sb = SpendBundle([singleton_spend, mk_coin_spend(regular_coin)], G2Element())
sb_name = sb.name()
await mempool_manager.add_spend_bundle(
sb,
make_test_conds(spend_ids=[(singleton_spend.coin, ELIGIBLE_FOR_FF), (regular_coin, 0)], cost=1337),
sb_name,
uint32(1),
)
assert mempool_manager.get_mempool_item(sb_name) is not None
sb_names.append(sb_name)
# Let's advance the mempool by spending these singletons into new lineages
singleton1_new_latest = Coin(singleton1_id, singleton_spend1.coin.puzzle_hash, singleton_spend1.coin.amount)
coins.update_lineage(singleton_spend1.coin.puzzle_hash, singleton1_new_latest)
singleton2_new_latest = Coin(singleton2_id, singleton_spend2.coin.puzzle_hash, singleton_spend2.coin.amount)
coins.update_lineage(singleton_spend2.coin.puzzle_hash, singleton2_new_latest)
await advance_mempool(mempool_manager, [singleton1_id, singleton2_id], use_optimization=True)
# Both items should get updated with their related latest lineages
mi1 = mempool_manager.get_mempool_item(sb_names[0])
assert mi1 is not None
latest_singleton_lineage1 = mi1.bundle_coin_spends[singleton1_id].latest_singleton_lineage
assert latest_singleton_lineage1 is not None
assert latest_singleton_lineage1.coin_id == singleton1_new_latest.name()
mi2 = mempool_manager.get_mempool_item(sb_names[1])
assert mi2 is not None
latest_singleton_lineage2 = mi2.bundle_coin_spends[singleton2_id].latest_singleton_lineage
assert latest_singleton_lineage2 is not None
assert latest_singleton_lineage2.coin_id == singleton2_new_latest.name()
@pytest.mark.anyio
async def test_different_ff_versions() -> None:
"""
Covers the case where we send an item with an older ff singleton version
while the mempool is aware of a newer lineage.
"""
launcher_id = bytes32([1] * 32)
singleton_spend1 = make_singleton_spend(launcher_id, bytes32([2] * 32))
version1_id = singleton_spend1.coin.name()
singleton_spend2 = make_singleton_spend(launcher_id, bytes32([3] * 32))
version2_id = singleton_spend2.coin.name()
singleton_ph = singleton_spend2.coin.puzzle_hash
coins = TestCoins(
[singleton_spend1.coin, singleton_spend2.coin, TEST_COIN, TEST_COIN2], {singleton_ph: singleton_spend2.coin}
)
mempool_manager = await setup_mempool(coins)
mempool_items: list[MempoolItem] = []
for singleton_spend, regular_coin in [(singleton_spend1, TEST_COIN), (singleton_spend2, TEST_COIN2)]:
sb = SpendBundle([singleton_spend, mk_coin_spend(regular_coin)], G2Element())
sb_name = sb.name()
await mempool_manager.add_spend_bundle(
sb,
make_test_conds(spend_ids=[(singleton_spend.coin, ELIGIBLE_FOR_FF), (regular_coin, 0)], cost=1337),
sb_name,
uint32(1),
)
mi = mempool_manager.get_mempool_item(sb_name)
assert mi is not None
mempool_items.append(mi)
[mi1, mi2] = mempool_items
latest_lineage_id = version2_id
assert latest_lineage_id != version1_id
# Bundle coin spends key points to version 1 but the lineage is latest (v2)
latest_singleton_lineage1 = mi1.bundle_coin_spends[version1_id].latest_singleton_lineage
assert latest_singleton_lineage1 is not None
assert latest_singleton_lineage1.coin_id == latest_lineage_id
# Both the bundle coin spends key and the lineage point to latest (v2)
latest_singleton_lineage2 = mi2.bundle_coin_spends[version2_id].latest_singleton_lineage
assert latest_singleton_lineage2 is not None
assert latest_singleton_lineage2.coin_id == latest_lineage_id
# Let's update the lineage with a new version of the singleton
new_latest_lineage = Coin(version2_id, singleton_ph, singleton_spend2.coin.amount)
new_latest_lineage_id = new_latest_lineage.name()
coins.update_lineage(singleton_ph, new_latest_lineage)
await advance_mempool(mempool_manager, [version1_id, version2_id], use_optimization=True)
# Both items should get updated with the latest lineage
new_mi1 = mempool_manager.get_mempool_item(mi1.spend_bundle_name)
assert new_mi1 is not None
latest_singleton_lineage1 = new_mi1.bundle_coin_spends[version1_id].latest_singleton_lineage
assert latest_singleton_lineage1 is not None
assert latest_singleton_lineage1.coin_id == new_latest_lineage_id
new_mi2 = mempool_manager.get_mempool_item(mi2.spend_bundle_name)
assert new_mi2 is not None
latest_singleton_lineage2 = new_mi2.bundle_coin_spends[version2_id].latest_singleton_lineage
assert latest_singleton_lineage2 is not None
assert latest_singleton_lineage2.coin_id == new_latest_lineage_id
@pytest.mark.anyio
@pytest.mark.parametrize(
"condition_and_error",
[
(ConditionOpcode.ASSERT_HEIGHT_RELATIVE, Err.ASSERT_HEIGHT_RELATIVE_FAILED),
(ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, Err.ASSERT_HEIGHT_ABSOLUTE_FAILED),
],
)
@pytest.mark.parametrize("optimized_path", [True, False])
async def test_new_peak_txs_added(condition_and_error: tuple[ConditionOpcode, Err], optimized_path: bool) -> None:
"""
Tests that deferred transactions because of time-lock are retried once the
time-lock allows them to be reconsidered.
"""
coins = TestCoins([TEST_COIN], {})
mempool_manager = await setup_mempool(coins)
# Add an item that should go to the pending cache
assert mempool_manager.peak is not None
condition_height = mempool_manager.peak.height + 1
condition, expected_error = condition_and_error
_, sb_name, result = await generate_and_add_spendbundle(mempool_manager, [[condition, condition_height]])
_, status, error = result
assert status == MempoolInclusionStatus.PENDING
assert error == expected_error
# Advance the mempool beyond the asserted height to retry the test item
if optimized_path:
spent_coins: Optional[list[bytes32]] = []
new_peak_info = await mempool_manager.new_peak(
create_test_block_record(height=uint32(condition_height)), spent_coins
)
# We're not there yet (needs to be higher, not equal)
assert new_peak_info.spend_bundle_ids == []
assert mempool_manager.get_mempool_item(sb_name, include_pending=False) is None
else:
spent_coins = None
new_peak_info = await mempool_manager.new_peak(
create_test_block_record(height=uint32(condition_height + 1)), spent_coins
)
# The item gets retried successfully now
assert new_peak_info.spend_bundle_ids == [sb_name]
assert mempool_manager.get_mempool_item(sb_name, include_pending=False) is not None
@@ -52,7 +52,7 @@ def test_process_fast_forward_spends_nothing_to_do() -> None:
sb = spend_bundle_from_conditions(conditions, TEST_COIN, sig)
item = mempool_item_from_spendbundle(sb)
# This coin is not eligible for fast forward
assert item.bundle_coin_spends[TEST_COIN_ID].eligible_for_fast_forward is False
assert not item.bundle_coin_spends[TEST_COIN_ID].supports_fast_forward
internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends)
original_version = dataclasses.replace(internal_mempool_item)
singleton_ff = SingletonFastForward()
@@ -63,28 +63,6 @@ def test_process_fast_forward_spends_nothing_to_do() -> None:
assert bundle_coin_spends == original_version.bundle_coin_spends
def test_process_fast_forward_spends_unknown_ff() -> None:
"""
This tests the case when we process for the first time but we are unable
to lookup the latest version from the item's latest singleton lineage
"""
test_coin = Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, uint64(1))
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, 1]]
sb = spend_bundle_from_conditions(conditions, test_coin)
item = mempool_item_from_spendbundle(sb)
# The coin is eligible for fast forward
assert item.bundle_coin_spends[test_coin.name()].eligible_for_fast_forward is True
item.bundle_coin_spends[test_coin.name()].latest_singleton_lineage = None
internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends)
singleton_ff = SingletonFastForward()
# We have no fast forward records yet, so we'll process this coin for the
# first time here, but the item's latest singleton lineage returns None
with pytest.raises(ValueError, match="Cannot proceed with singleton spend fast forward."):
singleton_ff.process_fast_forward_spends(
mempool_item=internal_mempool_item, height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS
)
def test_process_fast_forward_spends_latest_unspent() -> None:
"""
This tests the case when we are the latest singleton version already, so
@@ -103,7 +81,7 @@ def test_process_fast_forward_spends_latest_unspent() -> None:
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, test_amount]]
sb = spend_bundle_from_conditions(conditions, test_coin)
item = mempool_item_from_spendbundle(sb)
assert item.bundle_coin_spends[test_coin.name()].eligible_for_fast_forward is True
assert item.bundle_coin_spends[test_coin.name()].supports_fast_forward
item.bundle_coin_spends[test_coin.name()].latest_singleton_lineage = test_unspent_lineage_info
internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends)
original_version = dataclasses.replace(internal_mempool_item)
@@ -168,12 +146,12 @@ def test_perform_the_fast_forward() -> None:
"517b0dadb0c310ded24dd86dff8205398080ff808080"
)
test_coin_spend = CoinSpend(test_coin, test_puzzle_reveal, test_solution)
test_spend_data = BundleCoinSpend(test_coin_spend, False, True, [test_child_coin])
test_unspent_lineage_info = UnspentLineageInfo(
coin_id=latest_unspent_coin.name(),
parent_id=latest_unspent_coin.parent_coin_info,
parent_parent_id=test_child_coin.parent_coin_info,
)
test_spend_data = BundleCoinSpend(test_coin_spend, False, [test_child_coin], uint64(0), test_unspent_lineage_info)
# Start from a fresh state of fast forward spends
fast_forward_spends: dict[bytes32, UnspentLineageInfo] = {}
# Perform the fast forward on the test coin (the grandparent)
@@ -663,4 +641,4 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None:
status, error = await make_and_send_spend_bundle(sim, sim_client, [singleton_coin_spend], aggsig=sig)
# It fails validation because it doesn't currently have a latest unspent
assert status == MempoolInclusionStatus.FAILED
assert error == Err.DOUBLE_SPEND
assert error == Err.UNKNOWN_UNSPENT
+359 -306
View File
@@ -1,22 +1,24 @@
from __future__ import annotations
import asyncio
from dataclasses import dataclass
from typing import Union
import pytest
from chia_rs.sized_ints import uint32
from chia._tests.conftest import node_with_params
from chia._tests.util.misc import boolean_datacases
from chia._tests.util.time_out_assert import time_out_assert
from chia.protocols.full_node_protocol import RejectBlock, RejectBlocks, RespondBlock, RespondBlocks
from chia.protocols.outbound_message import make_msg
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.protocols.shared_protocol import Capability
from chia.server.rate_limit_numbers import compose_rate_limits, get_rate_limits_to_use
from chia.server.rate_limit_numbers import rate_limits as rl_numbers
from chia.server.rate_limit_numbers import RLSettings, Unlimited, get_rate_limits_to_use
from chia.server.rate_limits import RateLimiter
from chia.server.server import ChiaServer
from chia.server.ws_connection import WSChiaConnection
from chia.simulator.block_tools import BlockTools
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.types.peer_info import PeerInfo
rl_v2 = [Capability.BASE, Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2]
@@ -25,355 +27,402 @@ node_with_params_b = node_with_params
test_different_versions_results: list[int] = []
class TestRateLimits:
@pytest.mark.anyio
async def test_get_rate_limits_to_use(self):
assert get_rate_limits_to_use(rl_v2, rl_v2) != get_rate_limits_to_use(rl_v2, rl_v1)
assert get_rate_limits_to_use(rl_v1, rl_v1) == get_rate_limits_to_use(rl_v2, rl_v1)
assert get_rate_limits_to_use(rl_v1, rl_v1) == get_rate_limits_to_use(rl_v1, rl_v2)
@dataclass
class SimClock:
current_time: float = 1000.0
@pytest.mark.anyio
async def test_too_many_messages(self):
# Too many messages
r = RateLimiter(incoming=True)
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
for i in range(4999):
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2) is None
def monotonic(self) -> float:
return self.current_time
saw_disconnect = False
for i in range(4999):
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
def advance(self, duration: float) -> None:
self.current_time += duration
# Non-tx message
r = RateLimiter(incoming=True)
new_peak_message = make_msg(ProtocolMessageTypes.new_peak, bytes([1] * 40))
for i in range(200):
assert r.process_msg_and_check(new_peak_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(200):
response = r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.anyio
async def test_get_rate_limits_to_use() -> None:
assert get_rate_limits_to_use(rl_v2, rl_v2) != get_rate_limits_to_use(rl_v2, rl_v1)
assert get_rate_limits_to_use(rl_v2, rl_v2) != get_rate_limits_to_use(rl_v1, rl_v2)
assert get_rate_limits_to_use(rl_v1, rl_v1) == get_rate_limits_to_use(rl_v2, rl_v1)
assert get_rate_limits_to_use(rl_v1, rl_v1) == get_rate_limits_to_use(rl_v1, rl_v2)
@pytest.mark.anyio
async def test_large_message(self):
# Large tx
small_tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
large_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 3 * 1024 * 1024))
r = RateLimiter(incoming=True)
assert r.process_msg_and_check(small_tx_message, rl_v2, rl_v2) is None
assert r.process_msg_and_check(large_tx_message, rl_v2, rl_v2) is not None
# we want to exercise every possibly limit we may hit
# they are:
# * total number of messages / 60 seconds for non-transaction messages
# * total number of bytes / 60 seconds for non-transaction messages
# * number of messages / 60 seconds for "transaction" messages
# * number of bytes / 60 seconds for transaction messages
small_vdf_message = make_msg(ProtocolMessageTypes.respond_signage_point, bytes([1] * 5 * 1024))
large_vdf_message = make_msg(ProtocolMessageTypes.respond_signage_point, bytes([1] * 600 * 1024))
large_blocks_message = make_msg(ProtocolMessageTypes.respond_blocks, bytes([1] * 51 * 1024 * 1024))
r = RateLimiter(incoming=True)
assert r.process_msg_and_check(small_vdf_message, rl_v2, rl_v2) is None
assert r.process_msg_and_check(small_vdf_message, rl_v2, rl_v2) is None
assert r.process_msg_and_check(large_vdf_message, rl_v2, rl_v2) is not None
# this limit applies even though this message type is unlimited
assert r.process_msg_and_check(large_blocks_message, rl_v2, rl_v2) is not None
@pytest.mark.anyio
async def test_too_much_data(self):
# Too much data
r = RateLimiter(incoming=True)
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
for i in range(40):
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is None
@pytest.mark.anyio
@boolean_datacases(name="incoming", true="incoming", false="outgoing")
@boolean_datacases(name="tx_msg", true="tx", false="non-tx")
@boolean_datacases(name="limit_size", true="size-limit", false="count-limit")
async def test_limits_v2(incoming: bool, tx_msg: bool, limit_size: bool, monkeypatch: pytest.MonkeyPatch) -> None:
# this test uses a single message type, and alters the rate limit settings
# for it to hit the different cases
saw_disconnect = False
for i in range(300):
response = r.process_msg_and_check(tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
count = 1000
message_data = b"\0" * 1024
msg_type = ProtocolMessageTypes.new_transaction
r = RateLimiter(incoming=True)
block_message = make_msg(ProtocolMessageTypes.respond_unfinished_block, bytes([1] * 1024 * 1024))
for i in range(10):
assert r.process_msg_and_check(block_message, rl_v2, rl_v2) is None
limits: dict[ProtocolMessageTypes, Union[RLSettings, Unlimited]]
saw_disconnect = False
for i in range(40):
response = r.process_msg_and_check(block_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
if limit_size:
agg_limit = RLSettings(False, count * 2, len(message_data), count * len(message_data))
else:
agg_limit = RLSettings(False, count, len(message_data), count * 2 * len(message_data))
@pytest.mark.anyio
async def test_non_tx_aggregate_limits(self):
# Frequency limits
r = RateLimiter(incoming=True)
message_1 = make_msg(ProtocolMessageTypes.coin_state_update, bytes([1] * 32))
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 64))
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 64))
if limit_size:
limits = {msg_type: RLSettings(not tx_msg, count * 2, len(message_data), count * len(message_data))}
else:
limits = {msg_type: RLSettings(not tx_msg, count, len(message_data), count * 2 * len(message_data))}
for i in range(500):
assert r.process_msg_and_check(message_1, rl_v2, rl_v2) is None
def mock_get_limits(
our_capabilities: list[Capability], peer_capabilities: list[Capability]
) -> tuple[dict[ProtocolMessageTypes, Union[RLSettings, Unlimited]], RLSettings]:
return limits, agg_limit
for i in range(500):
assert r.process_msg_and_check(message_2, rl_v2, rl_v2) is None
import chia.server.rate_limits
saw_disconnect = False
for i in range(500):
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
monkeypatch.setattr(chia.server.rate_limits, "get_rate_limits_to_use", mock_get_limits)
# Size limits
r = RateLimiter(incoming=True)
message_4 = make_msg(ProtocolMessageTypes.respond_proof_of_weight, bytes([1] * 49 * 1024 * 1024))
message_5 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 49 * 1024 * 1024))
r = RateLimiter(incoming=incoming, get_time=lambda: 0)
msg = make_msg(msg_type, message_data)
for i in range(2):
assert r.process_msg_and_check(message_4, rl_v2, rl_v2) is None
for i in range(count):
assert r.process_msg_and_check(msg, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(2):
response = r.process_msg_and_check(message_5, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
expected_msg = ""
@pytest.mark.anyio
async def test_periodic_reset(self):
r = RateLimiter(True, 5)
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
for i in range(10):
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is None
if limit_size:
if not tx_msg:
expected_msg += "non-tx size:"
else:
expected_msg += "cumulative size:"
expected_msg += f" {(count + 1) * len(message_data)} > {count * len(message_data) * 1.0}"
else:
if not tx_msg:
expected_msg += "non-tx count:"
else:
expected_msg += "message count:"
expected_msg += f" {count + 1} > {count * 1.0}"
expected_msg += " (scale factor: 1.0)"
saw_disconnect = False
for i in range(300):
response = r.process_msg_and_check(tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is not None
await asyncio.sleep(6)
response = r.process_msg_and_check(msg, rl_v2, rl_v2)
assert response == expected_msg
for _ in range(10):
response = r.process_msg_and_check(msg, rl_v2, rl_v2)
# we can't stop incoming messages from arriving, counters keep
# increasing for incoming messages. For outgoing messages, we expect
# them not to be sent when hitting the rate limit, so those counters in
# the returned message stay the same
if incoming:
assert response is not None
else:
assert response == expected_msg
@pytest.mark.anyio
async def test_large_message() -> None:
# Large tx
small_tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
large_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 3 * 1024 * 1024))
r = RateLimiter(incoming=True, get_time=lambda: 0)
assert r.process_msg_and_check(small_tx_message, rl_v2, rl_v2) is None
assert r.process_msg_and_check(large_tx_message, rl_v2, rl_v2) is not None
small_vdf_message = make_msg(ProtocolMessageTypes.respond_signage_point, bytes([1] * 5 * 1024))
large_vdf_message = make_msg(ProtocolMessageTypes.respond_signage_point, bytes([1] * 600 * 1024))
large_blocks_message = make_msg(ProtocolMessageTypes.respond_blocks, bytes([1] * 51 * 1024 * 1024))
r = RateLimiter(incoming=True, get_time=lambda: 0)
assert r.process_msg_and_check(small_vdf_message, rl_v2, rl_v2) is None
assert r.process_msg_and_check(small_vdf_message, rl_v2, rl_v2) is None
assert r.process_msg_and_check(large_vdf_message, rl_v2, rl_v2) is not None
# this limit applies even though this message type is unlimited
assert r.process_msg_and_check(large_blocks_message, rl_v2, rl_v2) is not None
@pytest.mark.anyio
async def test_too_much_data() -> None:
# Too much data
r = RateLimiter(incoming=True, get_time=lambda: 0)
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
for i in range(40):
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is None
# Counts reset also
r = RateLimiter(True, 5)
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
for i in range(4999):
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(300):
response = r.process_msg_and_check(tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
saw_disconnect = False
for i in range(4999):
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
await asyncio.sleep(6)
r = RateLimiter(incoming=True, get_time=lambda: 0)
block_message = make_msg(ProtocolMessageTypes.respond_unfinished_block, bytes([1] * 1024 * 1024))
for i in range(10):
assert r.process_msg_and_check(block_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(40):
response = r.process_msg_and_check(block_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.anyio
async def test_non_tx_aggregate_limits() -> None:
# Frequency limits
r = RateLimiter(incoming=True, get_time=lambda: 0)
message_1 = make_msg(ProtocolMessageTypes.coin_state_update, bytes([1] * 32))
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 64))
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 64))
for i in range(500):
assert r.process_msg_and_check(message_1, rl_v2, rl_v2) is None
for i in range(500):
assert r.process_msg_and_check(message_2, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(500):
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
# Size limits
r = RateLimiter(incoming=True, get_time=lambda: 0)
message_4 = make_msg(ProtocolMessageTypes.respond_proof_of_weight, bytes([1] * 49 * 1024 * 1024))
message_5 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 49 * 1024 * 1024))
for i in range(2):
assert r.process_msg_and_check(message_4, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(2):
response = r.process_msg_and_check(message_5, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.anyio
async def test_periodic_reset() -> None:
timer = SimClock()
r = RateLimiter(True, 5, get_time=timer.monotonic)
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
for i in range(10):
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(300):
response = r.process_msg_and_check(tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is not None
timer.advance(6)
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2) is None
# Counts reset also
r = RateLimiter(True, 5, get_time=timer.monotonic)
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
for i in range(4999):
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2) is None
@pytest.mark.anyio
async def test_percentage_limits(self):
r = RateLimiter(True, 60, 40)
new_peak_message = make_msg(ProtocolMessageTypes.new_peak, bytes([1] * 40))
for i in range(50):
assert r.process_msg_and_check(new_peak_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(4999):
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
timer.advance(6)
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(50):
response = r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
r = RateLimiter(True, 60, 40)
block_message = make_msg(ProtocolMessageTypes.respond_unfinished_block, bytes([1] * 1024 * 1024))
for i in range(5):
assert r.process_msg_and_check(block_message, rl_v2, rl_v2) is None
@pytest.mark.anyio
async def test_percentage_limits() -> None:
r = RateLimiter(True, 60, 40, get_time=lambda: 0)
new_peak_message = make_msg(ProtocolMessageTypes.new_peak, bytes([1] * 40))
for i in range(50):
assert r.process_msg_and_check(new_peak_message, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(5):
response = r.process_msg_and_check(block_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
saw_disconnect = False
for i in range(50):
response = r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
# Aggregate percentage limit count
r = RateLimiter(True, 60, 40)
message_1 = make_msg(ProtocolMessageTypes.coin_state_update, bytes([1] * 5))
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 32))
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 32))
r = RateLimiter(True, 60, 40, get_time=lambda: 0)
block_message = make_msg(ProtocolMessageTypes.respond_unfinished_block, bytes([1] * 1024 * 1024))
for i in range(5):
assert r.process_msg_and_check(block_message, rl_v2, rl_v2) is None
for i in range(180):
assert r.process_msg_and_check(message_1, rl_v2, rl_v2) is None
for i in range(180):
assert r.process_msg_and_check(message_2, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(5):
response = r.process_msg_and_check(block_message, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
saw_disconnect = False
for i in range(100):
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
# Aggregate percentage limit count
r = RateLimiter(True, 60, 40, get_time=lambda: 0)
message_1 = make_msg(ProtocolMessageTypes.coin_state_update, bytes([1] * 5))
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 32))
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 32))
# Aggregate percentage limit max total size
r = RateLimiter(True, 60, 40)
message_4 = make_msg(ProtocolMessageTypes.respond_proof_of_weight, bytes([1] * 18 * 1024 * 1024))
message_5 = make_msg(ProtocolMessageTypes.respond_unfinished_block, bytes([1] * 24 * 1024 * 1024))
for i in range(180):
assert r.process_msg_and_check(message_1, rl_v2, rl_v2) is None
for i in range(180):
assert r.process_msg_and_check(message_2, rl_v2, rl_v2) is None
for i in range(2):
assert r.process_msg_and_check(message_4, rl_v2, rl_v2) is None
saw_disconnect = False
for i in range(100):
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
saw_disconnect = False
for i in range(2):
response = r.process_msg_and_check(message_5, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
# Aggregate percentage limit max total size
r = RateLimiter(True, 60, 40, get_time=lambda: 0)
message_4 = make_msg(ProtocolMessageTypes.respond_proof_of_weight, bytes([1] * 18 * 1024 * 1024))
message_5 = make_msg(ProtocolMessageTypes.respond_unfinished_block, bytes([1] * 24 * 1024 * 1024))
@pytest.mark.anyio
async def test_too_many_outgoing_messages(self):
# Too many messages
r = RateLimiter(incoming=False)
new_peers_message = make_msg(ProtocolMessageTypes.respond_peers, bytes([1]))
non_tx_freq = get_rate_limits_to_use(rl_v2, rl_v2)["non_tx_freq"]
for i in range(2):
assert r.process_msg_and_check(message_4, rl_v2, rl_v2) is None
passed = 0
blocked = 0
for i in range(non_tx_freq):
if r.process_msg_and_check(new_peers_message, rl_v2, rl_v2) is None:
passed += 1
else:
blocked += 1
saw_disconnect = False
for i in range(2):
response = r.process_msg_and_check(message_5, rl_v2, rl_v2)
if response is not None:
saw_disconnect = True
assert saw_disconnect
assert passed == 10
assert blocked == non_tx_freq - passed
# ensure that *another* message type is not blocked because of this
@pytest.mark.anyio
async def test_too_many_outgoing_messages() -> None:
# Too many messages
r = RateLimiter(incoming=False, get_time=lambda: 0)
new_peers_message = make_msg(ProtocolMessageTypes.respond_peers, bytes([1]))
_, agg_limit = get_rate_limits_to_use(rl_v2, rl_v2)
new_signatures_message = make_msg(ProtocolMessageTypes.respond_signatures, bytes([1]))
assert r.process_msg_and_check(new_signatures_message, rl_v2, rl_v2) is None
passed = 0
blocked = 0
for i in range(agg_limit.frequency):
if r.process_msg_and_check(new_peers_message, rl_v2, rl_v2) is None:
passed += 1
else:
blocked += 1
@pytest.mark.anyio
async def test_too_many_incoming_messages(self):
# Too many messages
r = RateLimiter(incoming=True)
new_peers_message = make_msg(ProtocolMessageTypes.respond_peers, bytes([1]))
non_tx_freq = get_rate_limits_to_use(rl_v2, rl_v2)["non_tx_freq"]
assert passed == 10
assert blocked == agg_limit.frequency - passed
passed = 0
blocked = 0
for i in range(non_tx_freq):
if r.process_msg_and_check(new_peers_message, rl_v2, rl_v2) is None:
passed += 1
else:
blocked += 1
# ensure that *another* message type is not blocked because of this
assert passed == 10
assert blocked == non_tx_freq - passed
new_signatures_message = make_msg(ProtocolMessageTypes.respond_signatures, bytes([1]))
assert r.process_msg_and_check(new_signatures_message, rl_v2, rl_v2) is None
# ensure that other message types *are* blocked because of this
new_signatures_message = make_msg(ProtocolMessageTypes.respond_signatures, bytes([1]))
assert r.process_msg_and_check(new_signatures_message, rl_v2, rl_v2) is not None
@pytest.mark.anyio
async def test_too_many_incoming_messages() -> None:
# Too many messages
r = RateLimiter(incoming=True, get_time=lambda: 0)
new_peers_message = make_msg(ProtocolMessageTypes.respond_peers, bytes([1]))
_, agg_limit = get_rate_limits_to_use(rl_v2, rl_v2)
@pytest.mark.parametrize(
"node_with_params",
[
pytest.param(
dict(
disable_capabilities=[Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2],
),
id="V1",
passed = 0
blocked = 0
for i in range(agg_limit.frequency):
if r.process_msg_and_check(new_peers_message, rl_v2, rl_v2) is None:
passed += 1
else:
blocked += 1
assert passed == 10
assert blocked == agg_limit.frequency - passed
# ensure that other message types *are* blocked because of this
new_signatures_message = make_msg(ProtocolMessageTypes.respond_signatures, bytes([1]))
assert r.process_msg_and_check(new_signatures_message, rl_v2, rl_v2) is not None
@pytest.mark.parametrize(
"node_with_params",
[
pytest.param(
dict(
disable_capabilities=[Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2],
),
pytest.param(
dict(
disable_capabilities=[],
),
id="V2",
id="V1",
),
pytest.param(
dict(
disable_capabilities=[],
),
],
indirect=True,
id="V2",
),
],
indirect=True,
)
@pytest.mark.parametrize(
"node_with_params_b",
[
pytest.param(
dict(
disable_capabilities=[Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2],
),
id="V1",
),
pytest.param(
dict(
disable_capabilities=[],
),
id="V2",
),
],
indirect=True,
)
@pytest.mark.anyio
@pytest.mark.limit_consensus_modes(reason="save time")
async def test_different_versions(
node_with_params: FullNodeSimulator, node_with_params_b: FullNodeSimulator, self_hostname: str
) -> None:
node_a = node_with_params
node_b = node_with_params_b
full_node_server_a: ChiaServer = node_a.full_node.server
full_node_server_b: ChiaServer = node_b.full_node.server
await full_node_server_b.start_client(PeerInfo(self_hostname, full_node_server_a.get_port()), None)
assert len(full_node_server_b.get_connections()) == 1
assert len(full_node_server_a.get_connections()) == 1
a_con: WSChiaConnection = full_node_server_a.get_connections()[0]
b_con: WSChiaConnection = full_node_server_b.get_connections()[0]
print(a_con.local_capabilities, a_con.peer_capabilities)
print(b_con.local_capabilities, b_con.peer_capabilities)
# The two nodes will use the same rate limits even if their versions are different
assert get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities) == get_rate_limits_to_use(
b_con.local_capabilities, b_con.peer_capabilities
)
@pytest.mark.parametrize(
"node_with_params_b",
[
pytest.param(
dict(
disable_capabilities=[Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2],
),
id="V1",
),
pytest.param(
dict(
disable_capabilities=[],
),
id="V2",
),
],
indirect=True,
)
@pytest.mark.anyio
@pytest.mark.limit_consensus_modes(reason="save time")
async def test_different_versions(self, node_with_params, node_with_params_b, self_hostname):
node_a = node_with_params
node_b = node_with_params_b
full_node_server_a: ChiaServer = node_a.full_node.server
full_node_server_b: ChiaServer = node_b.full_node.server
# The following code checks whether all of the runs resulted in the same number of items in "rate_limits_tx",
# which would mean the same rate limits are always used. This should not happen, since two nodes with V2
# will use V2.
total_tx_msg_count = len(get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities))
await full_node_server_b.start_client(PeerInfo(self_hostname, full_node_server_a.get_port()), None)
assert len(full_node_server_b.get_connections()) == 1
assert len(full_node_server_a.get_connections()) == 1
a_con: WSChiaConnection = full_node_server_a.get_connections()[0]
b_con: WSChiaConnection = full_node_server_b.get_connections()[0]
print(a_con.local_capabilities, a_con.peer_capabilities)
print(b_con.local_capabilities, b_con.peer_capabilities)
# The two nodes will use the same rate limits even if their versions are different
assert get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities) == get_rate_limits_to_use(
b_con.local_capabilities, b_con.peer_capabilities
)
# The following code checks whether all of the runs resulted in the same number of items in "rate_limits_tx",
# which would mean the same rate limits are always used. This should not happen, since two nodes with V2
# will use V2.
total_tx_msg_count = len(
get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities)["rate_limits_tx"]
)
test_different_versions_results.append(total_tx_msg_count)
if len(test_different_versions_results) >= 4:
assert len(set(test_different_versions_results)) >= 2
@pytest.mark.anyio
async def test_compose(self):
rl_1 = rl_numbers[1]
rl_2 = rl_numbers[2]
assert ProtocolMessageTypes.respond_children in rl_1["rate_limits_other"]
assert ProtocolMessageTypes.respond_children not in rl_1["rate_limits_tx"]
assert ProtocolMessageTypes.respond_children not in rl_2["rate_limits_other"]
assert ProtocolMessageTypes.respond_children in rl_2["rate_limits_tx"]
assert ProtocolMessageTypes.request_block in rl_1["rate_limits_other"]
assert ProtocolMessageTypes.request_block not in rl_1["rate_limits_tx"]
assert ProtocolMessageTypes.request_block not in rl_2["rate_limits_other"]
assert ProtocolMessageTypes.request_block not in rl_2["rate_limits_tx"]
comps = compose_rate_limits(rl_1, rl_2)
# v2 limits are used if present
assert ProtocolMessageTypes.respond_children not in comps["rate_limits_other"]
assert ProtocolMessageTypes.respond_children in comps["rate_limits_tx"]
# Otherwise, fall back to v1
assert ProtocolMessageTypes.request_block in rl_1["rate_limits_other"]
assert ProtocolMessageTypes.request_block not in rl_1["rate_limits_tx"]
test_different_versions_results.append(total_tx_msg_count)
if len(test_different_versions_results) >= 4:
assert len(set(test_different_versions_results)) >= 2
@pytest.mark.anyio
@@ -386,8 +435,8 @@ class TestRateLimits:
(ProtocolMessageTypes.reject_block, 90),
],
)
async def test_unlimited(msg_type: ProtocolMessageTypes, size: int):
r = RateLimiter(incoming=False)
async def test_unlimited(msg_type: ProtocolMessageTypes, size: int) -> None:
r = RateLimiter(incoming=False, get_time=lambda: 0)
message = make_msg(msg_type, bytes([1] * size))
@@ -443,8 +492,12 @@ async def test_unlimited(msg_type: ProtocolMessageTypes, size: int):
indirect=True,
)
async def test_unsolicited_responses(
node_with_params, node_with_params_b, self_hostname: str, msg_type: ProtocolMessageTypes, bt: BlockTools
):
node_with_params: FullNodeSimulator,
node_with_params_b: FullNodeSimulator,
self_hostname: str,
msg_type: ProtocolMessageTypes,
bt: BlockTools,
) -> None:
node_a = node_with_params
node_b = node_with_params_b
+2 -2
View File
@@ -14,6 +14,7 @@ from chia._tests.connection_utils import connect_and_get_peer
from chia._tests.util.setup_nodes import SimulatorsAndWalletsServices
from chia._tests.util.time_out_assert import time_out_assert
from chia.full_node.full_node_api import FullNodeAPI
from chia.full_node.start_full_node import create_full_node_service
from chia.protocols.full_node_protocol import RejectBlock, RequestBlock, RequestTransaction
from chia.protocols.outbound_message import NodeType, make_msg
from chia.protocols.protocol_message_types import ProtocolMessageTypes
@@ -21,12 +22,11 @@ from chia.protocols.shared_protocol import Error, protocol_version
from chia.protocols.wallet_protocol import RejectHeaderRequest
from chia.server.api_protocol import ApiMetadata
from chia.server.server import ChiaServer
from chia.server.start_full_node import create_full_node_service
from chia.server.start_wallet import create_wallet_service
from chia.server.ws_connection import WSChiaConnection, error_response_version
from chia.simulator.block_tools import BlockTools
from chia.types.peer_info import PeerInfo
from chia.util.errors import ApiError, Err
from chia.wallet.start_wallet import create_wallet_service
@dataclass
+7 -7
View File
@@ -86,15 +86,15 @@ async def test_daemon_terminates(signal_number: signal.Signals, chia_root: ChiaR
@pytest.mark.parametrize(
argnames=["create_service", "module_path", "service_config_name"],
argvalues=[
[DataLayerRpcClient.create_as_context, "chia.server.start_data_layer", "data_layer"],
[FarmerRpcClient.create_as_context, "chia.server.start_farmer", "farmer"],
[FullNodeRpcClient.create_as_context, "chia.server.start_full_node", "full_node"],
[HarvesterRpcClient.create_as_context, "chia.server.start_harvester", "harvester"],
[WalletRpcClient.create_as_context, "chia.server.start_wallet", "wallet"],
[None, "chia.server.start_introducer", "introducer"],
[DataLayerRpcClient.create_as_context, "chia.data_layer.start_data_layer", "data_layer"],
[FarmerRpcClient.create_as_context, "chia.farmer.start_farmer", "farmer"],
[FullNodeRpcClient.create_as_context, "chia.full_node.start_full_node", "full_node"],
[HarvesterRpcClient.create_as_context, "chia.harvester.start_harvester", "harvester"],
[WalletRpcClient.create_as_context, "chia.wallet.start_wallet", "wallet"],
[None, "chia.introducer.start_introducer", "introducer"],
# TODO: fails... make it not do that
# [None, "chia.seeder.start_crawler", "crawler"],
[None, "chia.server.start_timelord", "timelord"],
[None, "chia.timelord.start_timelord", "timelord"],
pytest.param(
None,
"chia.timelord.timelord_launcher",
+4 -4
View File
@@ -16,8 +16,8 @@ from chia.protocols.full_node_protocol import NewPeak
from chia.protocols.outbound_message import make_msg
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.protocols.wallet_protocol import RequestChildren
from chia.seeder.crawler_service import CrawlerService
from chia.seeder.peer_record import PeerRecord, PeerReliability
from chia.server.aliases import CrawlerService
from chia.types.peer_info import PeerInfo
@@ -111,7 +111,7 @@ async def test_crawler_to_db(crawler_service_no_loop: CrawlerService, one_node:
uint64(0),
uint32(0),
uint64(0),
uint64(int(time.time())),
uint64(time.time()),
uint64(0),
"undefined",
uint64(0),
@@ -153,8 +153,8 @@ async def test_crawler_peer_cleanup(
uint64(0),
uint32(0),
uint64(0),
uint64(int(time.time())),
uint64(int((datetime.now() - timedelta(days=idx * 10)).timestamp())),
uint64(time.time()),
uint64((datetime.now() - timedelta(days=idx * 10)).timestamp()),
"undefined",
uint64(0),
tls_version="unknown",
+34 -1
View File
@@ -30,11 +30,15 @@ from chia.farmer.farmer_rpc_api import (
plot_matches_filter,
)
from chia.farmer.farmer_rpc_client import FarmerRpcClient
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester_service import HarvesterService
from chia.plot_sync.receiver import Receiver, get_list_or_len
from chia.plotting.util import add_plot_directory
from chia.protocols import farmer_protocol
from chia.protocols.harvester_protocol import Plot
from chia.simulator.block_tools import get_plot_dir
from chia.rpc.rpc_client import ResponseFailureError
from chia.simulator.block_tools import BlockTools, get_plot_dir
from chia.solver.solver_service import SolverService
from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
from chia.util.config import load_config, lock_and_load_config, save_config
from chia.util.hash import std_hash
@@ -503,3 +507,32 @@ async def test_harvester_add_plot_directory(harvester_farmer_environment: Harves
added_directories = await harvester_rpc_client.get_plot_directories()
assert str(test_path) in added_directories
assert str(test_path_other) in added_directories
@pytest.mark.anyio
async def test_farmer_connect_to_solver(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, solver_service, bt = farmer_one_harvester_solver
assert farmer_service.rpc_server is not None
farmer_rpc_client = await FarmerRpcClient.create(
bt.config["self_hostname"],
farmer_service.rpc_server.listen_port,
farmer_service.root_path,
farmer_service.config,
)
try:
# Test successful connection to existing solver
solver_host = bt.config["self_hostname"]
solver_port = solver_service._server.get_port()
result = await farmer_rpc_client.connect_to_solver(solver_host, solver_port)
assert result["success"] is True
# Test connection failure to non-existent solver
with pytest.raises(ResponseFailureError) as exc_info:
await farmer_rpc_client.connect_to_solver("localhost", 65000)
assert "Could not connect to solver" in exc_info.value.response["error"]
finally:
farmer_rpc_client.close()
await farmer_rpc_client.await_closed()
+3 -2
View File
@@ -457,9 +457,9 @@ async def test_signage_points(
full_node_service_1.config,
) as client:
# Only provide one
with pytest.raises(ValueError, match="sp_hash or challenge_hash must be provided."):
with pytest.raises(ValueError, match="sp_hash or challenge_hash must be provided"):
await client.get_recent_signage_point_or_eos(None, None)
with pytest.raises(ValueError, match="Either sp_hash or challenge_hash must be provided, not both."):
with pytest.raises(ValueError, match="Either sp_hash or challenge_hash must be provided, not both"):
await client.get_recent_signage_point_or_eos(std_hash(b"0"), std_hash(b"1"))
# Not found
with pytest.raises(ValueError, match="in cache"):
@@ -548,6 +548,7 @@ async def test_signage_points(
# Signage point is no longer in the blockchain
res = await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None)
assert res is not None
assert res["reverted"]
assert res["signage_point"] == sp
assert "eos" not in res
+1 -1
View File
@@ -83,7 +83,7 @@ def database_peers() -> dict[str, PeerReliability]:
uint64(0),
uint32(0),
uint64(0),
uint64(int(time.time())),
uint64(time.time()),
uint64(0),
"undefined",
uint64(0),
+2 -2
View File
@@ -220,9 +220,9 @@ class TestKeychain:
test_vectors_path = importlib_resources.files(chia._tests.util.__name__).joinpath("bip39_test_vectors.json")
all_vectors = json.loads(test_vectors_path.read_text(encoding="utf-8"))
for idx, [entropy_hex, full_mnemonic, seed, short_mnemonic] in enumerate(all_vectors["english"]):
for idx, [entropy_hex, full_mnemonic, seed_hex, short_mnemonic] in enumerate(all_vectors["english"]):
entropy_bytes = bytes.fromhex(entropy_hex)
seed = bytes.fromhex(seed)
seed = bytes.fromhex(seed_hex)
assert mnemonic_from_short_words(short_mnemonic) == full_mnemonic
assert bytes_from_mnemonic(short_mnemonic) == entropy_bytes
+16
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
import unittest
import pytest
from chia.util.lru_cache import LRUCache
@@ -54,3 +56,17 @@ class TestLRUCache(unittest.TestCase):
assert len(cache.cache) == 5
assert cache.get(b"0") is None
assert cache.get(b"1") == 1
@pytest.mark.parametrize(argnames="capacity", argvalues=[-10, -1, 0])
def test_with_zero_capacity(capacity: int) -> None:
cache: LRUCache[bytes, int] = LRUCache(capacity=capacity)
cache.put(b"0", 1)
assert cache.get(b"0") is None
assert len(cache.cache) == 0
@pytest.mark.parametrize(argnames="capacity", argvalues=[-10, -1, 0, 1, 5, 10])
def test_get_capacity(capacity: int) -> None:
cache: LRUCache[object, object] = LRUCache(capacity=capacity)
assert cache.get_capacity() == capacity
+82 -1
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import io
import re
from dataclasses import dataclass, field, fields
from enum import Enum
from typing import Any, Callable, ClassVar, Optional, get_type_hints
import pytest
@@ -27,6 +28,7 @@ from chia.util.streamable import (
function_to_parse_one_item,
function_to_stream_one_item,
is_type_Dict,
is_type_Enum,
is_type_List,
is_type_SpecificOptional,
is_type_Tuple,
@@ -39,6 +41,7 @@ from chia.util.streamable import (
parse_uint32,
recurse_jsonify,
streamable,
streamable_enum,
streamable_from_dict,
write_uint32,
)
@@ -376,6 +379,25 @@ def test_basic_optional() -> None:
assert not is_type_SpecificOptional(list[int])
class BasicEnum(Enum):
A = 1
B = 2
def test_basic_enum() -> None:
assert is_type_Enum(BasicEnum)
assert not is_type_Enum(list[int])
def test_enum_needs_proxy() -> None:
with pytest.raises(UnsupportedType):
@streamable
@dataclass(frozen=True)
class EnumStreamable(Streamable):
enum: BasicEnum
@streamable
@dataclass(frozen=True)
class PostInitTestClassBasic(Streamable):
@@ -423,6 +445,25 @@ class PostInitTestClassDict(Streamable):
b: dict[bytes32, dict[uint8, str]]
@streamable_enum(uint32)
class IntegerEnum(Enum):
A = 1
B = 2
@streamable_enum(str)
class StringEnum(Enum):
A = "foo"
B = "bar"
@streamable
@dataclass(frozen=True)
class PostInitTestClassEnum(Streamable):
a: IntegerEnum
b: StringEnum
@pytest.mark.parametrize(
"test_class, args",
[
@@ -433,6 +474,7 @@ class PostInitTestClassDict(Streamable):
(PostInitTestClassTuple, ((1, "test"), ((200, "test_2"), b"\xba" * 32))),
(PostInitTestClassDict, ({1: "bar"}, {bytes32.zeros: {1: "bar"}})),
(PostInitTestClassOptional, (12, None, 13, None)),
(PostInitTestClassEnum, (IntegerEnum.A, StringEnum.B)),
],
)
def test_post_init_valid(test_class: type[Any], args: tuple[Any, ...]) -> None:
@@ -453,6 +495,8 @@ def test_post_init_valid(test_class: type[Any], args: tuple[Any, ...]) -> None:
return validate_item_type(key_type, next(iter(item.keys()))) and validate_item_type(
value_type, next(iter(item.values()))
)
if is_type_Enum(type_in):
return validate_item_type(type_in._streamable_proxy, type_in._streamable_proxy(item.value)) # type: ignore[attr-defined]
return isinstance(item, type_in)
test_object = test_class(*args)
@@ -497,6 +541,8 @@ def test_basic() -> None:
f: Optional[uint32]
g: tuple[uint32, str, bytes]
h: dict[uint32, str]
i: IntegerEnum
j: StringEnum
# we want to test invalid here, hence the ignore.
a = TestClass(
@@ -508,6 +554,8 @@ def test_basic() -> None:
None,
(uint32(383), "hello", b"goodbye"),
{uint32(1): "foo"},
IntegerEnum.A,
StringEnum.B,
)
b: bytes = bytes(a)
@@ -619,10 +667,21 @@ def test_ambiguous_deserialization_int() -> None:
a: uint32
# Does not have the required uint size
with pytest.raises(ValueError):
with pytest.raises(ValueError, match=re.escape("uint32.from_bytes() requires 4 bytes but got: 2")):
TestClassUint.from_bytes(b"\x00\x00")
def test_ambiguous_deserialization_int_enum() -> None:
@streamable
@dataclass(frozen=True)
class TestClassIntegerEnum(Streamable):
a: IntegerEnum
# passed bytes are incorrect size for serialization proxy
with pytest.raises(ValueError, match=re.escape("uint32.from_bytes() requires 4 bytes but got: 2")):
TestClassIntegerEnum.from_bytes(b"\x00\x00")
def test_ambiguous_deserialization_list() -> None:
@streamable
@dataclass(frozen=True)
@@ -656,6 +715,28 @@ def test_ambiguous_deserialization_str() -> None:
TestClassStr.from_bytes(bytes([0, 0, 100, 24, 52]))
def test_ambiguous_deserialization_str_enum() -> None:
@streamable
@dataclass(frozen=True)
class TestClassStr(Streamable):
a: StringEnum
# passed bytes are incorrect size for serialization proxy
with pytest.raises(AssertionError):
TestClassStr.from_bytes(bytes([0, 0, 100, 24, 52]))
def test_deserialization_to_invalid_enum() -> None:
@streamable
@dataclass(frozen=True)
class TestClassStr(Streamable):
a: StringEnum
# encodes the string "baz" which is not a valid value for StringEnum
with pytest.raises(ValueError, match=re.escape("'baz' is not a valid StringEnum")):
TestClassStr.from_bytes(bytes([0, 0, 0, 3, 98, 97, 122]))
def test_ambiguous_deserialization_bytes() -> None:
@streamable
@dataclass(frozen=True)
+4 -1
View File
@@ -25,6 +25,7 @@ from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_node import Balance, WalletNode
from chia.wallet.wallet_node_api import WalletNodeAPI
from chia.wallet.wallet_request_types import GetWalletBalance
from chia.wallet.wallet_rpc_api import WalletRpcApi
from chia.wallet.wallet_rpc_client import WalletRpcClient
from chia.wallet.wallet_state_manager import WalletStateManager
@@ -169,7 +170,9 @@ class WalletEnvironment:
else {}
),
}
balance_response: dict[str, int] = await self.rpc_client.get_wallet_balance(wallet_id)
balance_response: dict[str, int] = (
await self.rpc_client.get_wallet_balance(GetWalletBalance(wallet_id))
).wallet_balance.to_json_dict()
if not expected_result.items() <= balance_response.items():
for key, value in expected_result.items():
+8 -6
View File
@@ -9,7 +9,7 @@ from typing import Any, Optional, Union, cast
from unittest.mock import ANY
import pytest
from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey, ProofOfSpace
from chia_rs import AugSchemeMPL, G1Element, G2Element, PlotSize, PrivateKey, ProofOfSpace
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
from pytest_mock import MockerFixture
@@ -21,15 +21,17 @@ from chia._tests.conftest import HarvesterFarmerEnvironment
from chia._tests.util.misc import DataCase, Marks, datacases
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.farmer.farmer import UPDATE_POOL_FARMER_INFO_INTERVAL, Farmer, increment_pool_stats, strip_old_entries
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester_service import HarvesterService
from chia.pools.pool_config import PoolWalletConfig
from chia.protocols import farmer_protocol, harvester_protocol
from chia.protocols.harvester_protocol import NewProofOfSpace, RespondSignatures
from chia.protocols.pool_protocol import PoolErrorCode
from chia.server.aliases import FarmerService, HarvesterService
from chia.server.ws_connection import WSChiaConnection
from chia.simulator.block_tools import BlockTools
from chia.types.blockchain_format.proof_of_space import (
generate_plot_public_key,
make_pos,
verify_and_get_quality_string,
)
from chia.util.config import load_config, save_config
@@ -132,7 +134,7 @@ class NewProofOfSpaceCase:
plot_identifier: str
signage_point_index: uint8
plot_id: bytes32
plot_size: uint8
plot_size: PlotSize
plot_challenge: bytes32
plot_public_key: G1Element
pool_public_key: Optional[G1Element]
@@ -187,7 +189,7 @@ class NewProofOfSpaceCase:
plot_identifier="test",
signage_point_index=uint8(1),
plot_id=bytes32.fromhex("baaa6780c53d4b3739b8807b4ae79a76644ddf0d9e03dc7d0a6a0e613e764d9f"),
plot_size=uint8(32),
plot_size=PlotSize.make_v1(32),
plot_challenge=bytes32.fromhex("7580e4c366dc2c94c37ce44943f9629a3cd6e027d7b24cd014adeaa578d4b0a2"),
plot_public_key=G1Element.from_bytes(
bytes.fromhex(
@@ -573,7 +575,7 @@ async def test_farmer_new_proof_of_space_for_pool_stats(
peak_height=uint32(1),
last_tx_height=uint32(0),
)
pos = ProofOfSpace(
pos = make_pos(
challenge=case.plot_challenge,
pool_public_key=case.pool_public_key,
pool_contract_puzzle_hash=case.pool_contract_puzzle_hash,
@@ -714,7 +716,7 @@ def create_valid_pos(farmer: Farmer) -> tuple[farmer_protocol.NewSignagePoint, P
peak_height=uint32(1),
last_tx_height=uint32(0),
)
pos = ProofOfSpace(
pos = make_pos(
challenge=case.plot_challenge,
pool_public_key=case.pool_public_key,
pool_contract_puzzle_hash=case.pool_contract_puzzle_hash,
@@ -1,9 +1,11 @@
from __future__ import annotations
import asyncio
import unittest.mock
from math import floor
from pathlib import Path
from typing import Any, Optional
from unittest.mock import AsyncMock, Mock
import pytest
from chia_rs import G1Element
@@ -15,19 +17,41 @@ from chia._tests.util.split_managers import split_async_manager
from chia._tests.util.time_out_assert import time_out_assert
from chia.cmds.cmds_util import get_any_service_client
from chia.farmer.farmer import Farmer
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester_rpc_client import HarvesterRpcClient
from chia.harvester.harvester_service import HarvesterService
from chia.plotting.util import PlotsRefreshParameter
from chia.protocols import farmer_protocol, harvester_protocol
from chia.protocols import farmer_protocol, harvester_protocol, solver_protocol
from chia.protocols.outbound_message import NodeType, make_msg
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.aliases import FarmerService, HarvesterService
from chia.simulator.block_tools import BlockTools
from chia.solver.solver_service import SolverService
from chia.types.peer_info import UnresolvedPeerInfo
from chia.util.config import load_config
from chia.util.hash import std_hash
from chia.util.keychain import generate_mnemonic
async def get_harvester_peer(farmer: Farmer) -> Any:
"""wait for harvester connection and return the peer"""
def has_harvester_connection() -> bool:
return len(farmer.server.get_connections(NodeType.HARVESTER)) > 0
await time_out_assert(10, has_harvester_connection, True)
return farmer.server.get_connections(NodeType.HARVESTER)[0]
async def get_solver_peer(farmer: Farmer) -> Any:
"""wait for solver connection and return the peer"""
def has_solver_connection() -> bool:
return len(farmer.server.get_connections(NodeType.SOLVER)) > 0
await time_out_assert(60, has_solver_connection, True)
return farmer.server.get_connections(NodeType.SOLVER)[0]
def farmer_is_started(farmer: Farmer) -> bool:
return farmer.started
@@ -143,9 +167,6 @@ async def test_farmer_respond_signatures(
# messages even though it didn't request them, to cover when the farmer doesn't know
# about an sp_hash, so it fails at the sp record check.
def log_is_ready() -> bool:
return len(caplog.text) > 0
_, _, harvester_service, _, _ = harvester_farmer_environment
# We won't have an sp record for this one
challenge_hash = bytes32(b"1" * 32)
@@ -160,11 +181,16 @@ async def test_farmer_respond_signatures(
include_source_signature_data=False,
farmer_reward_address_override=None,
)
expected_error = f"Do not have challenge hash {challenge_hash}"
def expected_log_is_ready() -> bool:
return expected_error in caplog.text
msg = make_msg(ProtocolMessageTypes.respond_signatures, response)
await harvester_service._node.server.send_to_all([msg], NodeType.FARMER)
await time_out_assert(5, log_is_ready)
# We fail the sps record check
expected_error = f"Do not have challenge hash {challenge_hash}"
await time_out_assert(10, expected_log_is_ready)
# We should find the error message
assert expected_error in caplog.text
@@ -297,3 +323,275 @@ async def test_harvester_has_no_server(
harvester_server = harvesters[0]._server
assert harvester_server.webserver is None
@pytest.mark.anyio
async def test_v2_partial_proofs_new_sp_hash(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _solver_service, _bt = farmer_one_harvester_solver
farmer_api = farmer_service._api
farmer = farmer_api.farmer
sp_hash = bytes32(b"1" * 32)
partial_proofs = harvester_protocol.PartialProofsData(
challenge_hash=bytes32(b"2" * 32),
sp_hash=sp_hash,
plot_identifier="test_plot_id",
partial_proofs=[b"test_partial_proof_1"],
signage_point_index=uint8(0),
plot_size=uint8(32),
pool_public_key=None,
pool_contract_puzzle_hash=bytes32(b"4" * 32),
plot_public_key=G1Element(),
)
harvester_peer = await get_harvester_peer(farmer)
await farmer_api.partial_proofs(partial_proofs, harvester_peer)
assert sp_hash in farmer.number_of_responses
assert farmer.number_of_responses[sp_hash] == 0
assert sp_hash in farmer.cache_add_time
@pytest.mark.anyio
async def test_v2_partial_proofs_missing_sp_hash(
caplog: pytest.LogCaptureFixture,
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _, _ = farmer_one_harvester_solver
farmer_api = farmer_service._api
sp_hash = bytes32(b"1" * 32)
partial_proofs = harvester_protocol.PartialProofsData(
challenge_hash=bytes32(b"2" * 32),
sp_hash=sp_hash,
plot_identifier="test_plot_id",
partial_proofs=[b"test_partial_proof_1"],
signage_point_index=uint8(0),
plot_size=uint8(32),
pool_public_key=None,
pool_contract_puzzle_hash=bytes32(b"4" * 32),
plot_public_key=G1Element(),
)
harvester_peer = await get_harvester_peer(farmer_api.farmer)
await farmer_api.partial_proofs(partial_proofs, harvester_peer)
assert f"Received partial proofs for a signage point that we do not have {sp_hash}" in caplog.text
@pytest.mark.anyio
async def test_v2_partial_proofs_with_existing_sp(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _, _ = farmer_one_harvester_solver
farmer_api = farmer_service._api
farmer = farmer_api.farmer
sp_hash = bytes32(b"1" * 32)
challenge_hash = bytes32(b"2" * 32)
sp = farmer_protocol.NewSignagePoint(
challenge_hash=challenge_hash,
challenge_chain_sp=sp_hash,
reward_chain_sp=std_hash(b"1"),
difficulty=uint64(1000),
sub_slot_iters=uint64(1000),
signage_point_index=uint8(0),
peak_height=uint32(1),
last_tx_height=uint32(0),
)
farmer.sps[sp_hash] = [sp]
partial_proofs = harvester_protocol.PartialProofsData(
challenge_hash=challenge_hash,
sp_hash=sp_hash,
plot_identifier="test_plot_id",
partial_proofs=[b"test_partial_proof_1", b"test_partial_proof_2"],
signage_point_index=uint8(0),
plot_size=uint8(32),
pool_public_key=G1Element(),
pool_contract_puzzle_hash=bytes32(b"4" * 32),
plot_public_key=G1Element(),
)
harvester_peer = await get_harvester_peer(farmer)
await farmer_api.partial_proofs(partial_proofs, harvester_peer)
# should store 2 pending requests (one per partial proof)
assert len(farmer.pending_solver_requests) == 2
assert sp_hash in farmer.cache_add_time
@pytest.mark.anyio
async def test_solution_response_handler(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _, _ = farmer_one_harvester_solver
farmer_api = farmer_service._api
farmer = farmer_api.farmer
# set up a pending request
sp_hash = bytes32(b"1" * 32)
challenge_hash = bytes32(b"2" * 32)
partial_proofs = harvester_protocol.PartialProofsData(
challenge_hash=challenge_hash,
sp_hash=sp_hash,
plot_identifier="test_plot_id",
partial_proofs=[b"test_partial_proof_for_quality"],
signage_point_index=uint8(0),
plot_size=uint8(32),
pool_public_key=G1Element(),
pool_contract_puzzle_hash=bytes32(b"4" * 32),
plot_public_key=G1Element(),
)
harvester_peer = await get_harvester_peer(farmer)
# manually add pending request
farmer.pending_solver_requests[partial_proofs.partial_proofs[0]] = {
"proof_data": partial_proofs,
"peer": harvester_peer,
}
# create solution response
solution_response = solver_protocol.SolverResponse(
partial_proof=partial_proofs.partial_proofs[0], proof=b"test_proof_from_solver"
)
solver_peer = Mock()
solver_peer.peer_node_id = "solver_peer"
with unittest.mock.patch.object(farmer_api, "new_proof_of_space", new_callable=AsyncMock) as mock_new_proof:
await farmer_api.solution_response(solution_response, solver_peer)
# verify new_proof_of_space was called with correct proof
mock_new_proof.assert_called_once()
call_args = mock_new_proof.call_args[0]
new_proof_of_space = call_args[0]
original_peer = call_args[1]
assert new_proof_of_space.proof.proof == b"test_proof_from_solver"
assert original_peer == harvester_peer
# verify pending request was removed
assert partial_proofs.partial_proofs[0] not in farmer.pending_solver_requests
@pytest.mark.anyio
async def test_solution_response_unknown_quality(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _, _ = farmer_one_harvester_solver
farmer_api = farmer_service._api
farmer = farmer_api.farmer
# get real solver peer connection
solver_peer = await get_solver_peer(farmer)
# create solution response with unknown quality
solution_response = solver_protocol.SolverResponse(partial_proof=bytes(b"1" * 32), proof=b"test_proof")
with unittest.mock.patch.object(farmer_api, "new_proof_of_space", new_callable=AsyncMock) as mock_new_proof:
await farmer_api.solution_response(solution_response, solver_peer)
# verify new_proof_of_space was NOT called
mock_new_proof.assert_not_called()
# verify pending requests unchanged
assert len(farmer.pending_solver_requests) == 0
@pytest.mark.anyio
async def test_solution_response_empty_proof(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _solver_service, _ = farmer_one_harvester_solver
farmer_api = farmer_service._api
farmer = farmer_api.farmer
# set up a pending request
sp_hash = bytes32(b"1" * 32)
challenge_hash = bytes32(b"2" * 32)
partial_proofs = harvester_protocol.PartialProofsData(
challenge_hash=challenge_hash,
sp_hash=sp_hash,
plot_identifier="test_plot_id",
partial_proofs=[b"test_partial_proof_for_quality"],
signage_point_index=uint8(0),
plot_size=uint8(32),
pool_public_key=G1Element(),
pool_contract_puzzle_hash=bytes32(b"4" * 32),
plot_public_key=G1Element(),
)
harvester_peer = Mock()
harvester_peer.peer_node_id = "harvester_peer"
# manually add pending request
farmer.pending_solver_requests[partial_proofs.partial_proofs[0]] = {
"proof_data": partial_proofs.partial_proofs[0],
"peer": harvester_peer,
}
# get real solver peer connection
solver_peer = await get_solver_peer(farmer)
# create solution response with empty proof
solution_response = solver_protocol.SolverResponse(partial_proof=partial_proofs.partial_proofs[0], proof=b"")
with unittest.mock.patch.object(farmer_api, "new_proof_of_space", new_callable=AsyncMock) as mock_new_proof:
await farmer_api.solution_response(solution_response, solver_peer)
# verify new_proof_of_space was NOT called
mock_new_proof.assert_not_called()
# verify pending request was removed (cleanup still happens)
assert partial_proofs.partial_proofs[0] not in farmer.pending_solver_requests
@pytest.mark.anyio
async def test_v2_partial_proofs_solver_exception(
farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools],
) -> None:
_, farmer_service, _solver_service, _ = farmer_one_harvester_solver
farmer_api = farmer_service._api
farmer = farmer_api.farmer
sp_hash = bytes32(b"1" * 32)
challenge_hash = bytes32(b"2" * 32)
sp = farmer_protocol.NewSignagePoint(
challenge_hash=challenge_hash,
challenge_chain_sp=sp_hash,
reward_chain_sp=std_hash(b"1"),
difficulty=uint64(1000),
sub_slot_iters=uint64(1000),
signage_point_index=uint8(0),
peak_height=uint32(1),
last_tx_height=uint32(0),
)
farmer.sps[sp_hash] = [sp]
partial_proofs = harvester_protocol.PartialProofsData(
challenge_hash=challenge_hash,
sp_hash=sp_hash,
plot_identifier="test_plot_id",
partial_proofs=[b"test_partial_proof_1"],
signage_point_index=uint8(0),
plot_size=uint8(32),
pool_public_key=G1Element(),
pool_contract_puzzle_hash=bytes32(b"4" * 32),
plot_public_key=G1Element(),
)
harvester_peer = await get_harvester_peer(farmer)
# Mock send_to_all to raise an exception
with unittest.mock.patch.object(farmer.server, "send_to_all", side_effect=Exception("Solver connection failed")):
await farmer_api.partial_proofs(partial_proofs, harvester_peer)
# verify pending request was cleaned up after exception
assert partial_proofs.partial_proofs[0] not in farmer.pending_solver_requests
@@ -11,13 +11,13 @@ from chia_rs.sized_ints import uint8, uint32, uint64
from chia._tests.conftest import ConsensusMode
from chia._tests.core.test_farmer_harvester_rpc import wait_for_plot_sync
from chia._tests.util.setup_nodes import setup_farmer_multi_harvester
from chia._tests.util.setup_nodes import setup_farmer_solver_multi_harvester
from chia._tests.util.time_out_assert import time_out_assert
from chia.farmer.farmer_api import FarmerAPI
from chia.farmer.farmer_rpc_client import FarmerRpcClient
from chia.harvester.harvester_rpc_client import HarvesterRpcClient
from chia.harvester.harvester_service import HarvesterService
from chia.protocols import farmer_protocol
from chia.server.aliases import HarvesterService
from chia.simulator.block_tools import create_block_tools_async, test_constants
from chia.types.blockchain_format.proof_of_space import get_plot_id, passes_plot_filter
from chia.util.keychain import Keychain
@@ -60,7 +60,7 @@ async def farmer_harvester_with_filter_size_9(
)
new_config = local_b_tools._config
local_b_tools.change_config(new_config)
async with setup_farmer_multi_harvester(
async with setup_farmer_solver_multi_harvester(
local_b_tools, 1, tmp_path, local_b_tools.constants, start_services=True
) as (harvesters, farmer_service, _):
harvester_service = harvesters[0]
@@ -31,15 +31,17 @@ from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_dif
from chia.consensus.multiprocess_validation import PreValidationResult, pre_validate_block
from chia.farmer.farmer import Farmer, calculate_harvester_fee_quality
from chia.farmer.farmer_api import FarmerAPI
from chia.farmer.farmer_service import FarmerService
from chia.full_node.full_node import FullNode
from chia.full_node.full_node_api import FullNodeAPI
from chia.full_node.full_node_service import FullNodeService
from chia.harvester.harvester import Harvester
from chia.harvester.harvester_api import HarvesterAPI
from chia.harvester.harvester_service import HarvesterService
from chia.protocols import farmer_protocol, full_node_protocol, harvester_protocol, timelord_protocol
from chia.protocols.harvester_protocol import ProofOfSpaceFeeInfo, RespondSignatures, SigningDataKind
from chia.protocols.outbound_message import Message, NodeType, make_msg
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.aliases import FarmerService, FullNodeService, HarvesterService
from chia.server.server import ChiaServer
from chia.server.ws_connection import WSChiaConnection
from chia.simulator.block_tools import BlockTools
@@ -82,17 +84,16 @@ async def test_harvester_receive_source_signing_data(
full_node_1: FullNode = full_node_service_1._node
full_node_2: FullNode = full_node_service_2._node
await time_out_assert(60, node_type_connected, True, farmer.server, NodeType.HARVESTER)
# Connect peers to each other
farmer_service.add_peer(
UnresolvedPeerInfo(str(full_node_service_2.self_hostname), full_node_service_2._server.get_port())
)
await time_out_assert(60, node_type_connected, True, farmer.server, NodeType.FULL_NODE)
full_node_service_2.add_peer(
UnresolvedPeerInfo(str(full_node_service_1.self_hostname), full_node_service_1._server.get_port())
)
await wait_until_node_type_connected(farmer.server, NodeType.FULL_NODE)
await wait_until_node_type_connected(farmer.server, NodeType.HARVESTER) # Should already be connected
await wait_until_node_type_connected(full_node_1.server, NodeType.FULL_NODE)
await time_out_assert(60, node_type_connected, True, full_node_1.server, NodeType.FULL_NODE)
# Prepare test data
blocks: list[FullBlock]
@@ -427,12 +428,11 @@ async def scan_log_for_message(caplog: pytest.LogCaptureFixture, find_message: s
return False
async def wait_until_node_type_connected(server: ChiaServer, node_type: NodeType) -> WSChiaConnection:
while True:
for peer in server.all_connections.values():
if peer.connection_type == node_type.value:
return peer
await asyncio.sleep(1)
def node_type_connected(server: ChiaServer, node_type: NodeType) -> bool:
for peer in server.all_connections.values():
if peer.connection_type == node_type.value:
return True
return False
def decode_sp(
@@ -40,7 +40,7 @@ def make_mempoolitem() -> MempoolItem:
fee = uint64(10000000)
spends: list[SpendConditions] = []
conds = SpendBundleConditions(spends, 0, 0, 0, None, None, [], cost, 0, 0, False, 0, 0)
conds = SpendBundleConditions(spends, 0, 0, 0, None, None, [], cost, 0, 0, False, 0, 0, 0, 0, 0)
mempool_item = MempoolItem(
spend_bundle,
fee,
@@ -10,7 +10,6 @@ from chia_rs.sized_ints import uint64
from chia.full_node.full_node_rpc_api import FullNodeRpcApi
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
from chia.server.aliases import WalletService
from chia.simulator.block_tools import BlockTools
from chia.simulator.full_node_simulator import FullNodeSimulator
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
@@ -18,6 +17,7 @@ from chia.simulator.start_simulator import SimulatorFullNodeService
from chia.simulator.wallet_tools import WalletTool
from chia.types.blockchain_format.coin import Coin
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
from chia.wallet.wallet_service import WalletService
@pytest.fixture(scope="function")
@@ -68,8 +68,7 @@ def test_steady_fee_pressure() -> None:
estimates_during = []
start_from = 250
for height in range(start, end):
height = uint32(height)
items = make_block(height, 1, cost, fee, num_blocks_wait_in_mempool)
items = make_block(uint32(height), 1, cost, fee, num_blocks_wait_in_mempool)
estimator.new_block(FeeBlockInfo(uint32(height), items))
if height >= start_from:
estimation = estimator.estimate_fee_rate(time_offset_seconds=time_offset_seconds * (height - start_from))
+2 -1
View File
@@ -146,7 +146,7 @@ class TestROM:
before_seconds_relative=None,
birth_height=None,
birth_seconds=None,
create_coin=[(bytes([0] * 31 + [1]), 500, None)],
create_coin=[(bytes32([0] * 31 + [1]), 500, None)],
agg_sig_me=[],
agg_sig_parent=[],
agg_sig_puzzle=[],
@@ -159,6 +159,7 @@ class TestROM:
# execution cost, just in run_block_generator2()
execution_cost=0 if softfork_height < DEFAULT_CONSTANTS.HARD_FORK_HEIGHT else 44,
condition_cost=1800000,
fingerprint=b"",
)
assert npc_result.conds.spends == [spend]
+4
View File
@@ -0,0 +1,4 @@
from __future__ import annotations
job_timeout = 70
checkout_blocks_and_plots = True
+157
View File
@@ -0,0 +1,157 @@
from __future__ import annotations
from collections.abc import AsyncGenerator, Iterator
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from chia_rs import ConsensusConstants, FullBlock, ProofOfSpace
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint64
from chia._tests.conftest import HarvesterFarmerEnvironment
from chia._tests.plotting.util import get_test_plots
from chia._tests.util.time_out_assert import time_out_assert
from chia.harvester.harvester_api import HarvesterAPI
from chia.plotting.util import PlotInfo
from chia.protocols import harvester_protocol
from chia.protocols.harvester_protocol import PoolDifficulty
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.ws_connection import WSChiaConnection
@dataclass
class HarvesterTestEnvironment:
"""Test environment with real plots loaded for harvester testing."""
harvester_api: HarvesterAPI
plot_info: PlotInfo
plot_path: Path
@pytest.fixture(scope="function")
async def harvester_environment(
harvester_farmer_environment: HarvesterFarmerEnvironment,
) -> AsyncGenerator[HarvesterTestEnvironment, None]:
"""Create a test environment with real plots loaded."""
_, _, harvester_service, _, _ = harvester_farmer_environment
harvester_api = harvester_service._server.api
assert isinstance(harvester_api, HarvesterAPI)
test_plots = get_test_plots()
assert len(test_plots) > 0, "no test plots available"
plot_manager = harvester_api.harvester.plot_manager
plot_manager.start_refreshing()
await time_out_assert(10, lambda: len(plot_manager.plots) > 0, True)
plot_path, plot_info = next(iter(plot_manager.plots.items()))
yield HarvesterTestEnvironment(harvester_api, plot_info, plot_path)
plot_manager.stop_refreshing()
def signage_point_from_block(
block: FullBlock, constants: ConsensusConstants
) -> harvester_protocol.NewSignagePointHarvester2:
sp_index = block.reward_chain_block.signage_point_index
challenge_hash = block.reward_chain_block.pos_ss_cc_challenge_hash
sp_hash = (
block.reward_chain_block.reward_chain_sp_vdf.output.get_hash()
if block.reward_chain_block.reward_chain_sp_vdf
else challenge_hash
)
return harvester_protocol.NewSignagePointHarvester2(
challenge_hash=challenge_hash,
difficulty=uint64(constants.DIFFICULTY_STARTING),
sub_slot_iters=uint64(constants.SUB_SLOT_ITERS_STARTING),
signage_point_index=sp_index,
sp_hash=sp_hash,
pool_difficulties=[],
peak_height=block.height,
last_tx_height=block.height,
)
def create_test_setup(
harvester_environment: HarvesterTestEnvironment,
default_400_blocks: list[FullBlock],
blockchain_constants: ConsensusConstants,
) -> tuple[HarvesterTestEnvironment, harvester_protocol.NewSignagePointHarvester2, MagicMock]:
env = harvester_environment
block = default_400_blocks[2]
new_challenge = signage_point_from_block(block, blockchain_constants)
mock_peer = MagicMock(spec=WSChiaConnection)
return env, new_challenge, mock_peer
@contextmanager
def mock_successful_proof(plot_info: PlotInfo) -> Iterator[None]:
with patch.object(plot_info.prover, "get_full_proof") as mock_get_proof:
mock_proof = MagicMock(spec=ProofOfSpace)
mock_get_proof.return_value = mock_proof, None
yield
def assert_farming_info_sent(mock_peer: MagicMock) -> None:
mock_peer.send_message.assert_called()
farming_info_calls = [
call
for call in mock_peer.send_message.call_args_list
if call[0][0].type == ProtocolMessageTypes.farming_info.value
]
assert len(farming_info_calls) == 1
@pytest.mark.anyio
async def test_new_signage_point_harvester(
harvester_environment: HarvesterTestEnvironment,
default_400_blocks: list[FullBlock],
blockchain_constants: ConsensusConstants,
) -> None:
env, new_challenge, mock_peer = create_test_setup(harvester_environment, default_400_blocks, blockchain_constants)
with mock_successful_proof(env.plot_info):
await env.harvester_api.new_signage_point_harvester(new_challenge, mock_peer)
assert_farming_info_sent(mock_peer)
@pytest.mark.anyio
async def test_new_signage_point_harvester_pool_difficulty(
harvester_environment: HarvesterTestEnvironment,
default_400_blocks: list[FullBlock],
blockchain_constants: ConsensusConstants,
) -> None:
env, new_challenge, mock_peer = create_test_setup(harvester_environment, default_400_blocks, blockchain_constants)
pool_puzzle_hash = bytes32(b"pool" + b"0" * 28)
env.plot_info.pool_contract_puzzle_hash = pool_puzzle_hash
pool_difficulty = PoolDifficulty(
pool_contract_puzzle_hash=pool_puzzle_hash,
difficulty=uint64(500),
sub_slot_iters=uint64(67108864),
)
new_challenge = harvester_protocol.NewSignagePointHarvester2(
challenge_hash=new_challenge.challenge_hash,
difficulty=new_challenge.difficulty,
sub_slot_iters=new_challenge.sub_slot_iters,
signage_point_index=new_challenge.signage_point_index,
sp_hash=new_challenge.sp_hash,
pool_difficulties=[pool_difficulty], # add pool difficulty
peak_height=new_challenge.peak_height,
last_tx_height=new_challenge.last_tx_height,
)
with mock_successful_proof(env.plot_info):
await env.harvester_api.new_signage_point_harvester(new_challenge, mock_peer)
assert_farming_info_sent(mock_peer)
@pytest.mark.anyio
async def test_new_signage_point_harvester_prover_error(
harvester_environment: HarvesterTestEnvironment,
default_400_blocks: list[FullBlock],
blockchain_constants: ConsensusConstants,
) -> None:
env, new_challenge, mock_peer = create_test_setup(harvester_environment, default_400_blocks, blockchain_constants)
with patch.object(env.plot_info.prover, "get_qualities_for_challenge", side_effect=RuntimeError("test error")):
# should not raise exception, should handle error gracefully
await env.harvester_api.new_signage_point_harvester(new_challenge, mock_peer)
+6 -3
View File
@@ -20,7 +20,9 @@ from chia._tests.plotting.util import get_test_plots
from chia._tests.util.split_managers import SplitAsyncManager, split_async_manager
from chia._tests.util.time_out_assert import time_out_assert
from chia.farmer.farmer import Farmer
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester import Harvester
from chia.harvester.harvester_service import HarvesterService
from chia.plot_sync.delta import Delta, PathListDelta, PlotListDelta
from chia.plot_sync.receiver import Receiver
from chia.plot_sync.sender import Sender
@@ -29,7 +31,6 @@ from chia.plotting.manager import PlotManager
from chia.plotting.util import add_plot_directory, remove_plot_directory
from chia.protocols.harvester_protocol import Plot
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.aliases import FarmerService, HarvesterService
from chia.simulator.block_tools import BlockTools
from chia.util.config import create_default_chia_config, lock_and_load_config, save_config
from chia.util.streamable import _T_Streamable
@@ -194,7 +195,8 @@ class Environment:
plot = harvester.plot_manager.plots.get(Path(path), None)
assert plot is not None
assert plot.prover.get_filename() == delta.valid.additions[path].filename
assert plot.prover.get_size() == delta.valid.additions[path].size
# TODO: todo_v2_plots support v2 plots
assert plot.prover.get_size().size_v1 == delta.valid.additions[path].size
assert plot.prover.get_id() == delta.valid.additions[path].plot_id
assert plot.prover.get_compression_level() == delta.valid.additions[path].compression_level
assert plot.pool_public_key == delta.valid.additions[path].pool_public_key
@@ -255,7 +257,8 @@ class Environment:
for path, plot_info in plot_manager.plots.items():
assert str(path) in receiver.plots()
assert plot_info.prover.get_filename() == receiver.plots()[str(path)].filename
assert plot_info.prover.get_size() == receiver.plots()[str(path)].size
# TODO: todo_v2_plots support v2 plots
assert plot_info.prover.get_size().size_v1 == receiver.plots()[str(path)].size
assert plot_info.prover.get_id() == receiver.plots()[str(path)].plot_id
assert plot_info.prover.get_compression_level() == receiver.plots()[str(path)].compression_level
assert plot_info.pool_public_key == receiver.plots()[str(path)].pool_public_key
+16 -4
View File
@@ -7,7 +7,7 @@ import time
from typing import Any, Callable, Union
import pytest
from chia_rs import G1Element
from chia_rs import G1Element, PlotSize
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint32, uint64
@@ -185,8 +185,12 @@ def plot_sync_setup(seeded_random: random.Random) -> tuple[Receiver, list[SyncSt
# Manually add the plots we want to remove in tests
receiver._plots = {plot_info.filename: plot_info for plot_info in plot_info_list[0:10]}
receiver._total_plot_size = sum(plot.file_size for plot in receiver.plots().values())
# TODO: todo_v2_plots support v2 plots
receiver._total_effective_plot_size = int(
sum(UI_ACTUAL_SPACE_CONSTANT_FACTOR * int(_expected_plot_size(plot.size)) for plot in receiver.plots().values())
sum(
UI_ACTUAL_SPACE_CONSTANT_FACTOR * int(_expected_plot_size(PlotSize.make_v1(plot.size)))
for plot in receiver.plots().values()
)
)
sync_steps: list[SyncStepData] = [
SyncStepData(
@@ -266,7 +270,11 @@ async def test_to_dict(counts_only: bool, seeded_random: random.Random) -> None:
assert get_list_or_len(plot_sync_dict_1["duplicates"], not counts_only) == 0
assert plot_sync_dict_1["total_plot_size"] == sum(plot.file_size for plot in receiver.plots().values())
assert plot_sync_dict_1["total_effective_plot_size"] == int(
sum(UI_ACTUAL_SPACE_CONSTANT_FACTOR * int(_expected_plot_size(plot.size)) for plot in receiver.plots().values())
# TODO: todo_v2_plots support v2 plots
sum(
UI_ACTUAL_SPACE_CONSTANT_FACTOR * int(_expected_plot_size(PlotSize.make_v1(plot.size)))
for plot in receiver.plots().values()
)
)
assert plot_sync_dict_1["syncing"] is None
assert plot_sync_dict_1["last_sync_time"] is None
@@ -312,8 +320,12 @@ async def test_to_dict(counts_only: bool, seeded_random: random.Random) -> None:
assert get_list_or_len(sync_steps[State.duplicates].args[0], counts_only) == plot_sync_dict_3["duplicates"]
assert plot_sync_dict_3["total_plot_size"] == sum(plot.file_size for plot in receiver.plots().values())
# TODO: todo_v2_plots support v2 plots
assert plot_sync_dict_3["total_effective_plot_size"] == int(
sum(UI_ACTUAL_SPACE_CONSTANT_FACTOR * int(_expected_plot_size(plot.size)) for plot in receiver.plots().values())
sum(
UI_ACTUAL_SPACE_CONSTANT_FACTOR * int(_expected_plot_size(PlotSize.make_v1(plot.size)))
for plot in receiver.plots().values()
)
)
assert plot_sync_dict_3["last_sync_time"] > 0
assert plot_sync_dict_3["syncing"] is None
+2 -2
View File
@@ -75,7 +75,7 @@ def test_set_response(bt: BlockTools) -> None:
def new_response_message(sync_id: int, message_id: int, message_type: ProtocolMessageTypes) -> PlotSyncResponse:
return PlotSyncResponse(
plot_sync_identifier(uint64(sync_id), uint64(message_id)), int16(int(message_type.value)), None
plot_sync_identifier(uint64(sync_id), uint64(message_id)), int16(message_type.value), None
)
response_message = new_response_message(0, 1, ProtocolMessageTypes.plot_sync_start)
@@ -97,7 +97,7 @@ def test_set_response(bt: BlockTools) -> None:
expected_response.identifier.sync_id,
expected_response.identifier.message_id,
)
expired_message = PlotSyncResponse(expired_identifier, int16(int(ProtocolMessageTypes.plot_sync_start.value)), None)
expired_message = PlotSyncResponse(expired_identifier, int16(ProtocolMessageTypes.plot_sync_start.value), None)
assert not sender.set_response(expired_message)
# Test invalid sync-id
sender._response = new_expected_response(2, 0, ProtocolMessageTypes.plot_sync_start)
+15 -13
View File
@@ -20,16 +20,18 @@ from chia_rs.sized_ints import int16, uint8, uint64
from chia._tests.plot_sync.util import start_harvester_service
from chia._tests.util.time_out_assert import time_out_assert
from chia.farmer.farmer import Farmer
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester import Harvester
from chia.harvester.harvester_service import HarvesterService
from chia.plot_sync.receiver import Receiver
from chia.plot_sync.sender import Sender
from chia.plot_sync.util import Constants
from chia.plotting.manager import PlotManager
from chia.plotting.prover import V1Prover
from chia.plotting.util import PlotInfo
from chia.protocols.harvester_protocol import PlotSyncError, PlotSyncResponse
from chia.protocols.outbound_message import make_msg
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.server.aliases import FarmerService, HarvesterService
from chia.server.ws_connection import WSChiaConnection
from chia.simulator.block_tools import BlockTools
from chia.util.batches import to_batches
@@ -68,17 +70,17 @@ class TestData:
initial: bool,
) -> None:
for plot_info in loaded:
assert plot_info.prover.get_filename() not in self.plots
assert Path(plot_info.prover.get_filename()) not in self.plots
for plot_info in removed:
assert plot_info.prover.get_filename() in self.plots
assert Path(plot_info.prover.get_filename()) in self.plots
self.invalid = invalid
self.keys_missing = keys_missing
self.duplicates = duplicates
removed_paths: list[Path] = [p.prover.get_filename() for p in removed] if removed is not None else []
invalid_dict: dict[Path, int] = {p.prover.get_filename(): 0 for p in self.invalid}
keys_missing_set: set[Path] = {p.prover.get_filename() for p in self.keys_missing}
removed_paths: list[Path] = [Path(p.prover.get_filename()) for p in removed] if removed is not None else []
invalid_dict: dict[Path, int] = {Path(p.prover.get_filename()): 0 for p in self.invalid}
keys_missing_set: set[Path] = {Path(p.prover.get_filename()) for p in self.keys_missing}
duplicates_set: set[str] = {p.prover.get_filename() for p in self.duplicates}
# Inject invalid plots into `PlotManager` of the harvester so that the callback calls below can use them
@@ -122,9 +124,9 @@ class TestData:
await time_out_assert(60, sync_done)
for plot_info in loaded:
self.plots[plot_info.prover.get_filename()] = plot_info
self.plots[Path(plot_info.prover.get_filename())] = plot_info
for plot_info in removed:
del self.plots[plot_info.prover.get_filename()]
del self.plots[Path(plot_info.prover.get_filename())]
def validate_plot_sync(self) -> None:
assert len(self.plots) == len(self.plot_sync_receiver.plots())
@@ -140,7 +142,7 @@ class TestData:
assert plot_info.pool_contract_puzzle_hash == synced_plot.pool_contract_puzzle_hash
assert plot_info.plot_public_key == synced_plot.plot_public_key
assert plot_info.file_size == synced_plot.file_size
assert uint64(int(plot_info.time_modified)) == synced_plot.time_modified
assert uint64(plot_info.time_modified) == synced_plot.time_modified
for plot_info in self.invalid:
assert plot_info.prover.get_filename() not in self.plot_sync_receiver.plots()
assert plot_info.prover.get_filename() in self.plot_sync_receiver.invalid()
@@ -284,7 +286,7 @@ def create_example_plots(count: int, seeded_random: random.Random) -> list[PlotI
return [
PlotInfo(
prover=DiskProver(f"{x}", bytes32.random(seeded_random), 25 + x % 26),
prover=V1Prover(DiskProver(f"{x}", bytes32.random(seeded_random), 25 + x % 26)),
pool_public_key=None,
pool_contract_puzzle_hash=None,
plot_public_key=G1Element(),
@@ -416,7 +418,7 @@ async def test_sync_reset_cases(
# Inject some data into `PlotManager` of the harvester so that we can validate the reset worked and triggered a
# fresh sync of all available data of the plot manager
for plot_info in plots[0:10]:
test_data.plots[plot_info.prover.get_filename()] = plot_info
test_data.plots[Path(plot_info.prover.get_filename())] = plot_info
plot_manager.plots = test_data.plots
test_data.invalid = plots[10:20]
test_data.keys_missing = plots[20:30]
@@ -424,8 +426,8 @@ async def test_sync_reset_cases(
sender: Sender = test_runner.test_data[0].plot_sync_sender
started_sync_id: uint64 = uint64(0)
plot_manager.failed_to_open_filenames = {p.prover.get_filename(): 0 for p in test_data.invalid}
plot_manager.no_key_filenames = {p.prover.get_filename() for p in test_data.keys_missing}
plot_manager.failed_to_open_filenames = {Path(p.prover.get_filename()): 0 for p in test_data.invalid}
plot_manager.no_key_filenames = {Path(p.prover.get_filename()) for p in test_data.keys_missing}
async def wait_for_reset() -> bool:
assert started_sync_id != 0
+3 -2
View File
@@ -11,11 +11,12 @@ from chia_rs.sized_ints import uint16, uint64
from chia._tests.util.split_managers import SplitAsyncManager, split_async_manager
from chia._tests.util.time_out_assert import time_out_assert
from chia.farmer.farmer_service import FarmerService
from chia.harvester.harvester import Harvester
from chia.harvester.harvester_service import HarvesterService
from chia.plot_sync.sender import Sender
from chia.protocols.harvester_protocol import PlotSyncIdentifier
from chia.protocols.outbound_message import Message, NodeType
from chia.server.aliases import FarmerService, HarvesterService
from chia.types.peer_info import PeerInfo, UnresolvedPeerInfo
@@ -38,7 +39,7 @@ def get_dummy_connection(node_type: NodeType, peer_id: bytes32) -> WSChiaConnect
def plot_sync_identifier(current_sync_id: uint64, message_id: uint64) -> PlotSyncIdentifier:
return PlotSyncIdentifier(uint64(int(time.time())), current_sync_id, message_id)
return PlotSyncIdentifier(uint64(time.time()), current_sync_id, message_id)
@contextlib.asynccontextmanager
+21 -5
View File
@@ -13,12 +13,14 @@ from typing import Callable, Optional, cast
import pytest
from chia_rs import G1Element
from chia_rs.sized_ints import uint16, uint32
from chiapos import DiskProver
from chia._tests.plotting.util import get_test_plots
from chia._tests.util.misc import boolean_datacases
from chia._tests.util.time_out_assert import time_out_assert
from chia.plotting.cache import CURRENT_VERSION, CacheDataV1
from chia.plotting.manager import Cache, PlotManager
from chia.plotting.prover import V1Prover
from chia.plotting.util import (
PlotInfo,
PlotRefreshEvents,
@@ -115,10 +117,9 @@ class PlotRefreshTester:
if plot_info.prover.get_filename() == value.prover.get_filename():
values_found += 1
continue
else:
if value in expected_list:
values_found += 1
continue
elif value in expected_list:
values_found += 1
continue
if values_found != len(expected_list):
log.error(f"{name} invalid: values_found {values_found} expected {len(expected_list)}")
return
@@ -507,7 +508,8 @@ async def test_plot_info_caching(environment, bt):
assert plot_manager.plots[path].prover.get_filename() == plot_info.prover.get_filename()
assert plot_manager.plots[path].prover.get_id() == plot_info.prover.get_id()
assert plot_manager.plots[path].prover.get_memo() == plot_info.prover.get_memo()
assert plot_manager.plots[path].prover.get_size() == plot_info.prover.get_size()
assert plot_manager.plots[path].prover.get_size().size_v1 == plot_info.prover.get_size().size_v1
assert plot_manager.plots[path].prover.get_size().size_v2 == plot_info.prover.get_size().size_v2
assert plot_manager.plots[path].prover.get_compression_level() == plot_info.prover.get_compression_level()
assert plot_manager.plots[path].pool_public_key == plot_info.pool_public_key
assert plot_manager.plots[path].pool_contract_puzzle_hash == plot_info.pool_contract_puzzle_hash
@@ -743,6 +745,20 @@ async def test_recursive_plot_scan(environment: Environment) -> None:
await env.refresh_tester.run(expected_result)
@pytest.mark.anyio
async def test_disk_prover_from_bytes(environment: Environment):
env: Environment = environment
expected_result = PlotRefreshResult()
expected_result.loaded = env.dir_1.plot_info_list() # type: ignore[assignment]
expected_result.processed = len(env.dir_1)
add_plot_directory(env.root_path, str(env.dir_1.path))
await env.refresh_tester.run(expected_result)
_, plot_info = next(iter(env.refresh_tester.plot_manager.plots.items()))
recreated_prover = V1Prover(DiskProver.from_bytes(bytes(plot_info.prover)))
assert recreated_prover.get_id() == plot_info.prover.get_id()
assert recreated_prover.get_filename() == plot_info.prover.get_filename()
@boolean_datacases(name="follow_links", false="no_follow", true="follow")
@pytest.mark.anyio
async def test_recursive_plot_scan_symlinks(environment: Environment, follow_links: bool) -> None:
+106
View File
@@ -0,0 +1,106 @@
from __future__ import annotations
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8
from chia.plotting.prover import PlotVersion, V1Prover, V2Prover, get_prover_from_bytes, get_prover_from_file
class TestProver:
def test_v2_prover_init_with_nonexistent_file(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
assert prover.get_version() == PlotVersion.V2
assert prover.get_filename() == "/nonexistent/path/test.plot2"
def test_v2_prover_get_size_raises_error(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
prover.get_size()
def test_v2_prover_get_memo_raises_error(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
prover.get_memo()
def test_v2_prover_get_compression_level(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
assert prover.get_compression_level() == uint8(0)
def test_v2_prover_get_id_raises_error(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
prover.get_id()
def test_v2_prover_get_qualities_for_challenge_raises_error(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
with pytest.raises(
AssertionError, match="V2 plot format does not support qualities directly, use partial proofs"
):
prover.get_qualities_for_challenge(bytes32(b"1" * 32))
def test_v2_prover_get_full_proof_raises_error(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
with pytest.raises(AssertionError, match="V2 plot format require solver to get full proof"):
prover.get_full_proof(bytes32(b"1" * 32), 0)
def test_v2_prover_bytes_raises_error(self) -> None:
prover = V2Prover("/nonexistent/path/test.plot2")
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
bytes(prover)
def test_v2_prover_from_bytes_raises_error(self) -> None:
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
V2Prover.from_bytes(b"test_data")
def test_get_prover_from_file(self) -> None:
prover = get_prover_from_file("/nonexistent/path/test.plot2")
assert prover.get_version() == PlotVersion.V2
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
prover.get_size()
def test_get_prover_from_file_with_plot1_still_works(self) -> None:
with tempfile.NamedTemporaryFile(suffix=".plot", delete=False) as f:
temp_path = f.name
try:
with pytest.raises(Exception) as exc_info:
get_prover_from_file(temp_path)
assert not isinstance(exc_info.value, NotImplementedError)
finally:
Path(temp_path).unlink()
def test_unsupported_file_extension_raises_value_error(self) -> None:
with pytest.raises(ValueError, match="Unsupported plot file"):
get_prover_from_file("/nonexistent/path/test.txt")
class TestV1Prover:
def test_v1_prover_get_version(self) -> None:
"""Test that V1Prover.get_version() returns PlotVersion.V1"""
mock_disk_prover = MagicMock()
prover = V1Prover(mock_disk_prover)
assert prover.get_version() == PlotVersion.V1
class TestGetProverFromBytes:
def test_get_prover_from_bytes_v2_plot(self) -> None:
with patch("chia.plotting.prover.V2Prover.from_bytes") as mock_v2_from_bytes:
mock_prover = MagicMock()
mock_v2_from_bytes.return_value = mock_prover
result = get_prover_from_bytes("test.plot2", b"test_data")
assert result == mock_prover
def test_get_prover_from_bytes_v1_plot(self) -> None:
with patch("chia.plotting.prover.DiskProver") as mock_disk_prover_class:
mock_disk_prover = MagicMock()
mock_disk_prover_class.from_bytes.return_value = mock_disk_prover
result = get_prover_from_bytes("test.plot", b"test_data")
assert isinstance(result, V1Prover)
def test_get_prover_from_bytes_unsupported_extension(self) -> None:
with pytest.raises(ValueError, match="Unsupported plot file"):
get_prover_from_bytes("test.txt", b"test_data")
+2 -1
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
import re
from dataclasses import dataclass
from io import StringIO
from typing import Optional, cast
@@ -484,7 +485,7 @@ async def test_plotnft_cli_join(
wallet_id = await create_new_plotnft(wallet_environments)
# Test joining the same pool again
with pytest.raises(click.ClickException, match="already farming to pool http://pool.example.com"):
with pytest.raises(click.ClickException, match=re.escape("already farming to pool http://pool.example.com")):
await JoinPlotNFTCMD(
rpc_info=NeedsWalletRPC(
client_info=client_info,
@@ -1,6 +1,7 @@
from __future__ import annotations
import copy
import re
from unittest import TestCase
import pytest
@@ -31,6 +32,7 @@ from chia.pools.pool_wallet_info import PoolState
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.coin_spend import make_spend
from chia.util.errors import Err
from chia.wallet.puzzles import singleton_top_layer
from chia.wallet.puzzles.p2_conditions import puzzle_for_conditions
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
@@ -242,7 +244,8 @@ class TestPoolPuzzles(TestCase):
)
# Spend it and hope it fails!
with pytest.raises(
BadSpendBundleError, match="condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED"
BadSpendBundleError,
match=re.escape(f"condition validation failure {Err.ASSERT_ANNOUNCE_CONSUMED_FAILED!s}"),
):
coin_db.update_coin_store_for_spend_bundle(
SpendBundle([singleton_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM
@@ -269,7 +272,8 @@ class TestPoolPuzzles(TestCase):
)
# Spend it and hope it fails!
with pytest.raises(
BadSpendBundleError, match="condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED"
BadSpendBundleError,
match=re.escape(f"condition validation failure {Err.ASSERT_ANNOUNCE_CONSUMED_FAILED!s}"),
):
coin_db.update_coin_store_for_spend_bundle(
SpendBundle([singleton_coinsol, bad_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM
@@ -320,7 +324,10 @@ class TestPoolPuzzles(TestCase):
(data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA),
)
# Spend it and hope it fails!
with pytest.raises(BadSpendBundleError, match="condition validation failure Err.ASSERT_HEIGHT_RELATIVE_FAILED"):
with pytest.raises(
BadSpendBundleError,
match=re.escape(f"condition validation failure {Err.ASSERT_HEIGHT_RELATIVE_FAILED!s}"),
):
coin_db.update_coin_store_for_spend_bundle(
SpendBundle([return_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM
)
+50 -34
View File
@@ -24,7 +24,6 @@ from chia._tests.util.setup_nodes import setup_simulators_and_wallets_service
from chia._tests.util.time_out_assert import time_out_assert
from chia.pools.pool_wallet_info import PoolSingletonState, PoolWalletInfo
from chia.rpc.rpc_client import ResponseFailureError
from chia.server.aliases import WalletService
from chia.simulator.add_blocks_in_batches import add_blocks_in_batches
from chia.simulator.block_tools import BlockTools, get_plot_dir
from chia.simulator.full_node_simulator import FullNodeSimulator
@@ -41,8 +40,19 @@ from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet_node import WalletNode
from chia.wallet.wallet_request_types import GetWallets, PWAbsorbRewards, PWJoinPool, PWSelfPool, PWStatus
from chia.wallet.wallet_request_types import (
DeleteUnconfirmedTransactions,
GetTransactions,
GetWalletBalance,
GetWallets,
PWAbsorbRewards,
PWJoinPool,
PWSelfPool,
PWStatus,
SendTransaction,
)
from chia.wallet.wallet_rpc_client import WalletRpcClient
from chia.wallet.wallet_service import WalletService
from chia.wallet.wallet_state_manager import WalletStateManager
# TODO: Compare deducted fees in all tests against reported total_fee
@@ -455,7 +465,7 @@ class TestPoolWalletRpc:
def mempool_empty() -> bool:
return full_node_api.full_node.mempool_manager.mempool.size() == 0
await client.delete_unconfirmed_transactions(1)
await client.delete_unconfirmed_transactions(DeleteUnconfirmedTransactions(uint32(1)))
await full_node_api.process_all_wallet_transactions(wallet=wallet)
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
@@ -468,8 +478,8 @@ class TestPoolWalletRpc:
assert len(asset_id) > 0
await full_node_api.process_all_wallet_transactions(wallet=wallet)
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
bal_0 = await client.get_wallet_balance(cat_0_id)
assert bal_0["confirmed_wallet_balance"] == 20
bal_0 = (await client.get_wallet_balance(GetWalletBalance(cat_0_id))).wallet_balance
assert bal_0.confirmed_wallet_balance == 20
# Test creation of many pool wallets. Use untrusted since that is the more complicated protocol, but don't
# run this code more than once, since it's slow.
@@ -535,8 +545,8 @@ class TestPoolWalletRpc:
await add_blocks_in_batches(blocks[-3:], full_node_api.full_node)
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 2 * 1_750_000_000_000
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 2 * 1_750_000_000_000
# Claim 2 * 1.75, and farm a new 1.75
absorb_txs = (
@@ -561,8 +571,8 @@ class TestPoolWalletRpc:
new_status: PoolWalletInfo = (await client.pw_status(PWStatus(uint32(2)))).state
assert status.current == new_status.current
assert status.tip_singleton_coin_id != new_status.tip_singleton_coin_id
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 1 * 1_750_000_000_000
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 1 * 1_750_000_000_000
# Claim another 1.75
absorb_txs1 = (
@@ -575,14 +585,20 @@ class TestPoolWalletRpc:
await full_node_api.farm_blocks_to_puzzlehash(count=2, farm_to=our_ph, guarantee_transaction_blocks=True)
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 0
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 0
assert len(await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(2)) == 0
tr: TransactionRecord = (
await client.send_transaction(
1, uint64(100), encode_puzzle_hash(status.p2_singleton_puzzle_hash, "txch"), DEFAULT_TX_CONFIG
SendTransaction(
wallet_id=uint32(1),
amount=uint64(100),
address=encode_puzzle_hash(status.p2_singleton_puzzle_hash, "txch"),
push=True,
),
DEFAULT_TX_CONFIG,
)
).transaction
@@ -590,15 +606,15 @@ class TestPoolWalletRpc:
await full_node_api.farm_blocks_to_puzzlehash(count=2, farm_to=our_ph, guarantee_transaction_blocks=True)
# Balance ignores non coinbase TX
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 0
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 0
with pytest.raises(ValueError):
await client.pw_absorb_rewards(
PWAbsorbRewards(wallet_id=uint32(2), fee=uint64(fee), push=True), DEFAULT_TX_CONFIG
)
tx1 = await client.get_transactions(1)
tx1 = (await client.get_transactions(GetTransactions(uint32(1)))).transactions
assert (250_000_000_000 + fee) in [tx.amount for tx in tx1]
@pytest.mark.anyio
@@ -626,8 +642,8 @@ class TestPoolWalletRpc:
pool_expected_confirmed_balance = 0
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
main_bal = await client.get_wallet_balance(1)
assert main_bal["confirmed_wallet_balance"] == main_expected_confirmed_balance
main_bal = (await client.get_wallet_balance(GetWalletBalance(uint32(1)))).wallet_balance
assert main_bal.confirmed_wallet_balance == main_expected_confirmed_balance
status: PoolWalletInfo = (await client.pw_status(PWStatus(uint32(2)))).state
assert status.current.state == PoolSingletonState.SELF_POOLING.value
@@ -650,10 +666,10 @@ class TestPoolWalletRpc:
pool_expected_confirmed_balance += block_count * 1_750_000_000_000
main_expected_confirmed_balance += block_count * 250_000_000_000
main_bal = await client.get_wallet_balance(1)
assert main_bal["confirmed_wallet_balance"] == main_expected_confirmed_balance
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == pool_expected_confirmed_balance
main_bal = (await client.get_wallet_balance(GetWalletBalance(uint32(1)))).wallet_balance
assert main_bal.confirmed_wallet_balance == main_expected_confirmed_balance
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == pool_expected_confirmed_balance
# Claim
absorb_txs = (
@@ -671,10 +687,10 @@ class TestPoolWalletRpc:
new_status: PoolWalletInfo = (await client.pw_status(PWStatus(uint32(2)))).state
assert status.current == new_status.current
assert status.tip_singleton_coin_id != new_status.tip_singleton_coin_id
main_bal = await client.get_wallet_balance(1)
pool_bal = await client.get_wallet_balance(2)
assert pool_bal["confirmed_wallet_balance"] == pool_expected_confirmed_balance
assert main_bal["confirmed_wallet_balance"] == main_expected_confirmed_balance # 10499999999999
main_bal = (await client.get_wallet_balance(GetWalletBalance(uint32(1)))).wallet_balance
pool_bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert pool_bal.confirmed_wallet_balance == pool_expected_confirmed_balance
assert main_bal.confirmed_wallet_balance == main_expected_confirmed_balance # 10499999999999
@pytest.mark.anyio
async def test_absorb_pooling(
@@ -726,8 +742,8 @@ class TestPoolWalletRpc:
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
# Pooled plots don't have balance
main_expected_confirmed_balance += block_count * 250_000_000_000
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 0
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 0
# Claim block_count * 1.75
ret = await client.pw_absorb_rewards(
@@ -751,12 +767,12 @@ class TestPoolWalletRpc:
await time_out_assert(20, status_updated)
new_status = (await client.pw_status(PWStatus(uint32(2)))).state
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 0
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 0
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
bal = await client.get_wallet_balance(2)
assert bal["confirmed_wallet_balance"] == 0
bal = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal.confirmed_wallet_balance == 0
assert len(await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(2)) == 0
peak = full_node_api.full_node.blockchain.get_peak()
assert peak is not None
@@ -798,8 +814,8 @@ class TestPoolWalletRpc:
status = (await client.pw_status(PWStatus(uint32(2)))).state
assert ret.fee_transaction is None
bal2 = await client.get_wallet_balance(2)
assert bal2["confirmed_wallet_balance"] == 0
bal2 = (await client.get_wallet_balance(GetWalletBalance(uint32(2)))).wallet_balance
assert bal2.confirmed_wallet_balance == 0
@pytest.mark.anyio
async def test_self_pooling_to_pooling(self, setup: Setup, fee: uint64, self_hostname: str) -> None:
@@ -1252,7 +1268,7 @@ class TestPoolWalletRpc:
mock.return_value = False
# Test joining the same pool via the RPC client
with pytest.raises(ResponseFailureError, match="Wallet needs to be fully synced."):
with pytest.raises(ResponseFailureError, match="Wallet needs to be fully synced"):
await wallet_rpc.pw_join_pool(
PWJoinPool(
wallet_id=uint32(wallet_id),
View File
+4
View File
@@ -0,0 +1,4 @@
from __future__ import annotations
job_timeout = 70
checkout_blocks_and_plots = True
+29
View File
@@ -0,0 +1,29 @@
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
import pytest
from chia_rs import ConsensusConstants
from chia.protocols.outbound_message import Message
from chia.protocols.solver_protocol import SolverInfo
from chia.simulator.block_tools import create_block_tools_async
from chia.simulator.keyring import TempKeyring
from chia.simulator.setup_services import setup_solver
@pytest.mark.anyio
async def test_solver_api_methods(blockchain_constants: ConsensusConstants, tmp_path: Path) -> None:
with TempKeyring(populate=True) as keychain:
bt = await create_block_tools_async(constants=blockchain_constants, keychain=keychain)
async with setup_solver(tmp_path, bt, blockchain_constants) as solver_service:
solver = solver_service._node
solver_api = solver_service._api
assert solver_api.ready() is True
test_info = SolverInfo(partial_proof=b"test_partial_proof_42")
expected_proof = b"test_proof_data_12345"
with patch.object(solver, "solve", return_value=expected_proof):
api_result = await solver_api.solve(test_info)
assert api_result is not None
assert isinstance(api_result, Message)

Some files were not shown because too many files have changed in this diff Show More