mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
Merge branch 'main' into retain_chiabip
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>"
|
||||
@@ -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
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
start_release:
|
||||
if: github.repository_owner == 'Chia-Network'
|
||||
name: Starts Sync Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Checkout the code base #
|
||||
##########################
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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>"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
Submodule chia-blockchain-gui updated: c5ff1694a5...2bbcad1b20
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:")
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
job_timeout = 70
|
||||
checkout_blocks_and_plots = True
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
job_timeout = 70
|
||||
checkout_blocks_and_plots = True
|
||||
@@ -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
Reference in New Issue
Block a user