mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
Merge remote-tracking branch 'origin/main' into quex.wsm.extract_sync_handlers
This commit is contained in:
@@ -59,7 +59,7 @@ jobs:
|
||||
python-version: ["3.12"]
|
||||
env:
|
||||
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
|
||||
BLOCKS_AND_PLOTS_VERSION: 0.45.29
|
||||
BLOCKS_AND_PLOTS_VERSION: 0.45.32
|
||||
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
|
||||
@@ -10,6 +10,9 @@ on:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
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
|
||||
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Configure AWS credentials
|
||||
if: steps.check_secrets.outputs.HAS_AWS_SECRET
|
||||
uses: aws-actions/configure-aws-credentials@v6.2.0
|
||||
uses: aws-actions/configure-aws-credentials@v6.2.3
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload
|
||||
aws-region: us-west-2
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
if: github.repository_owner == 'Chia-Network'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
operations-per-run: 10000
|
||||
ascending: true
|
||||
|
||||
@@ -52,7 +52,7 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BLOCKS_AND_PLOTS_VERSION: 0.45.29
|
||||
BLOCKS_AND_PLOTS_VERSION: 0.45.32
|
||||
|
||||
jobs:
|
||||
# Download the test blocks and plots exactly once per runner OS and store them
|
||||
|
||||
@@ -301,7 +301,7 @@ jobs:
|
||||
coverage xml --rcfile=.coveragerc --data-file=coverage-reports/.coverage -o coverage-reports/coverage.xml
|
||||
coverage html --rcfile=.coveragerc --data-file=coverage-reports/.coverage --directory coverage-reports/html/
|
||||
|
||||
- uses: coverallsapp/github-action@v2.3.6
|
||||
- uses: coverallsapp/github-action@v2.3.8
|
||||
if: always() && steps.alls-green.outputs.success == 'true' && github.event.repository.visibility == 'public'
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
- major_dot_minor: "3.12"
|
||||
check:
|
||||
- name: ruff
|
||||
command: ruff format --check --diff .
|
||||
command: ruff format --check --diff chia benchmarks tools
|
||||
- name: generated protocol tests
|
||||
command: |
|
||||
python3 -m chia._tests.util.build_network_protocol_files
|
||||
|
||||
@@ -33,6 +33,16 @@ try {
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
npm ci --loglevel=error
|
||||
npm audit fix
|
||||
|
||||
# Work around Electron's postinstall being silently skipped in the workspaces/
|
||||
# hoisted install, which leaves node_modules/electron present but without its
|
||||
# platform binary (no path.txt). Re-run Electron's own installer if needed.
|
||||
# Otherwise 'npm run electron' fails with "Electron failed to install correctly".
|
||||
if (-not (Test-Path "node_modules/electron/path.txt")) {
|
||||
Write-Output "Electron binary is missing; running Electron's install script."
|
||||
node node_modules/electron/install.js
|
||||
}
|
||||
|
||||
npm run build
|
||||
py ..\installhelper.py
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class BlockInfo:
|
||||
prev_header_hash: bytes32
|
||||
transactions_generator: SerializedProgram | None
|
||||
transactions_generator_ref_list: list[uint32]
|
||||
transactions_generator_buffer: list[uint8] | None = None
|
||||
transactions_generator_buffer: bytes | None = None
|
||||
version: uint8 = uint8(0)
|
||||
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ def compare_results(
|
||||
if data not in old:
|
||||
continue
|
||||
print(f"\ncompare: {data}, old: {old_commit_hash}, new: {new_commit_hash}")
|
||||
print_compare_row("mode", "µs/iteration old", "µs/iteration new", "diff %") # noqa: RUF001
|
||||
print_compare_row("mode", "µs/iteration old", "µs/iteration new", "diff %") # ruff: ignore[ambiguous-unicode-character-string]
|
||||
for mode, results in modes.items():
|
||||
if mode not in old[data]:
|
||||
continue
|
||||
@@ -247,8 +247,8 @@ def run(data: Data, mode: Mode, runs: int, ms: int, live: bool, output: TextIO,
|
||||
)
|
||||
print_row(
|
||||
mode="mode",
|
||||
us_per_iteration="µs/iteration", # noqa: RUF001
|
||||
stdev_us_per_iteration="stdev µs/iteration %", # noqa: RUF001
|
||||
us_per_iteration="µs/iteration", # ruff: ignore[ambiguous-unicode-character-string]
|
||||
stdev_us_per_iteration="stdev µs/iteration %", # ruff: ignore[ambiguous-unicode-character-string]
|
||||
avg_iterations="avg iterations/run",
|
||||
stdev_iterations="stdev iterations/run %",
|
||||
)
|
||||
|
||||
+50
-58
@@ -36,10 +36,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -255,10 +254,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@electron/universal/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -796,15 +794,14 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"license": "MIT",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
@@ -1196,10 +1193,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dir-compare/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -1521,9 +1517,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -1533,8 +1529,7 @@
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
]
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
@@ -1569,10 +1564,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -1723,10 +1717,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3063,10 +3056,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||
"license": "MIT",
|
||||
"version": "6.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
|
||||
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
@@ -3256,9 +3248,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3418,9 +3410,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -3791,9 +3783,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"requires": {
|
||||
"balanced-match": "^4.0.2"
|
||||
}
|
||||
@@ -4057,9 +4049,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4291,9 +4283,9 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-uri": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg=="
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="
|
||||
},
|
||||
"fdir": {
|
||||
"version": "6.5.0",
|
||||
@@ -4315,9 +4307,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -4422,9 +4414,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -5307,9 +5299,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="
|
||||
"version": "6.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
|
||||
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "8.3.0",
|
||||
|
||||
+56
-65
@@ -37,10 +37,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -256,10 +255,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@electron/universal/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -840,15 +838,14 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"license": "MIT",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
@@ -1282,10 +1279,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dir-compare/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -1669,9 +1665,9 @@
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -1681,8 +1677,7 @@
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
]
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
@@ -1717,10 +1712,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -1871,10 +1865,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -2201,9 +2194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
|
||||
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
|
||||
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2214,7 +2207,6 @@
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
@@ -3273,10 +3265,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||
"license": "MIT",
|
||||
"version": "6.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
|
||||
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
@@ -3480,9 +3471,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3642,9 +3633,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -4052,9 +4043,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"requires": {
|
||||
"balanced-match": "^4.0.2"
|
||||
}
|
||||
@@ -4344,9 +4335,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4603,9 +4594,9 @@
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"fast-uri": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg=="
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="
|
||||
},
|
||||
"fdir": {
|
||||
"version": "6.5.0",
|
||||
@@ -4627,9 +4618,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -4734,9 +4725,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4943,9 +4934,9 @@
|
||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
|
||||
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
|
||||
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
@@ -5658,9 +5649,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="
|
||||
"version": "6.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
|
||||
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.1",
|
||||
|
||||
+43
-49
@@ -36,10 +36,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -255,10 +254,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@electron/universal/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -796,15 +794,14 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"license": "MIT",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
@@ -1196,10 +1193,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dir-compare/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -1521,9 +1517,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -1568,10 +1564,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -1722,10 +1717,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"license": "MIT",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3254,9 +3248,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3416,9 +3410,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -3789,9 +3783,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"requires": {
|
||||
"balanced-match": "^4.0.2"
|
||||
}
|
||||
@@ -4055,9 +4049,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4289,9 +4283,9 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-uri": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw=="
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="
|
||||
},
|
||||
"fdir": {
|
||||
"version": "6.5.0",
|
||||
@@ -4313,9 +4307,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -4420,9 +4414,9 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
||||
+1
-1
Submodule chia-blockchain-gui updated: 8572ccd426...984738669b
@@ -5,6 +5,7 @@ from chia_rs.sized_ints import uint32, uint64
|
||||
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_generator_info import block_has_transactions_generator
|
||||
from chia.consensus.blockchain import AddBlockResult, Blockchain
|
||||
from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty
|
||||
from chia.consensus.multiprocess_validation import PreValidationResult, pre_validate_block
|
||||
@@ -76,7 +77,7 @@ async def _validate_and_add_block(
|
||||
await check_block_store_invariant(blockchain)
|
||||
|
||||
if skip_prevalidation:
|
||||
if block.transactions_generator is None:
|
||||
if not block_has_transactions_generator(block):
|
||||
conds = None
|
||||
else:
|
||||
# fake the signature validation. Just say True here.
|
||||
|
||||
@@ -12,6 +12,7 @@ from chia_rs.sized_ints import uint32
|
||||
from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block
|
||||
from chia._tests.util.blockchain import create_blockchain
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain, AugmentedBlockchainValidationError
|
||||
from chia.consensus.block_generator_info import get_transactions_generator_bytes
|
||||
from chia.consensus.blockchain_interface import MMRManagerProtocol
|
||||
from chia.consensus.stub_mmr_manager import StubMMRManager
|
||||
from chia.simulator.block_tools import BlockTools
|
||||
@@ -143,9 +144,9 @@ async def test_augmented_chain(default_10000_blocks: list[FullBlock]) -> None:
|
||||
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
|
||||
expect_gen = get_transactions_generator_bytes(blocks[2])
|
||||
assert expect_gen is not None
|
||||
assert await abc.lookup_block_generators(blocks[5].prev_header_hash, {uint32(2)}) == {uint32(2): bytes(expect_gen)}
|
||||
assert await abc.lookup_block_generators(blocks[5].prev_header_hash, {uint32(2)}) == {uint32(2): expect_gen}
|
||||
|
||||
for i in range(1, 5):
|
||||
assert await abc.prev_block_hash([blocks[i].header_hash]) == [blocks[i - 1].header_hash]
|
||||
@@ -365,9 +366,9 @@ async def test_read_only_snapshot_preserves_generator_lookup(default_10000_block
|
||||
abc = AugmentedBlockchain(underlying)
|
||||
abc.add_extra_block(blocks[2], BR(blocks[2]))
|
||||
|
||||
generator = blocks[2].transactions_generator
|
||||
generator = get_transactions_generator_bytes(blocks[2])
|
||||
assert generator is not None
|
||||
expected = {uint32(2): bytes(generator)}
|
||||
expected = {uint32(2): generator}
|
||||
|
||||
snapshot = abc.read_only_snapshot()
|
||||
assert await abc.lookup_block_generators(blocks[2].header_hash, {uint32(2)}) == expected
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
from collections.abc import AsyncIterator, Awaitable
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from chia_rs import (
|
||||
@@ -45,6 +46,11 @@ from chia._tests.util.blockchain import create_blockchain
|
||||
from chia._tests.util.get_name_puzzle_conditions import get_name_puzzle_conditions
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkAdd, ForkInfo
|
||||
from chia.consensus.block_generator_info import (
|
||||
block_has_transactions_generator,
|
||||
get_transactions_generator_bytes,
|
||||
get_transactions_generator_program,
|
||||
)
|
||||
from chia.consensus.block_header_validation import validate_finished_header_block
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward
|
||||
from chia.consensus.blockchain import AddBlockResult, Blockchain
|
||||
@@ -56,7 +62,7 @@ from chia.consensus.get_block_generator import get_block_generator
|
||||
from chia.consensus.multiprocess_validation import PreValidationResult, pre_validate_block
|
||||
from chia.consensus.pot_iterations import is_overflow_block
|
||||
from chia.full_node.coin_store import CoinStore
|
||||
from chia.simulator.block_tools import BlockTools, create_block_tools_async
|
||||
from chia.simulator.block_tools import BlockTools, create_block_tools_async, make_unfinished_block
|
||||
from chia.simulator.keyring import TempKeyring
|
||||
from chia.simulator.vdf_prover import get_vdf_info_and_proof
|
||||
from chia.simulator.wallet_tools import WalletTool
|
||||
@@ -330,16 +336,16 @@ class TestBlockHeaderValidation:
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
conds = None
|
||||
# if this assert fires, remove it along with the pragma for the block
|
||||
# below
|
||||
assert unf.transactions_generator is None
|
||||
if unf.transactions_generator is not None: # pragma: no cover
|
||||
# if this assert fires, remove it along with the block below
|
||||
assert not block_has_transactions_generator(unf)
|
||||
if block_has_transactions_generator(unf): # pragma: no cover
|
||||
block_generator = await get_block_generator(blockchain.lookup_block_generators, unf)
|
||||
assert block_generator is not None
|
||||
assert unf.transactions_info is not None
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
unf.transactions_info.cost,
|
||||
@@ -366,16 +372,16 @@ class TestBlockHeaderValidation:
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
conds = None
|
||||
# if this assert fires, remove it along with the pragma for the block
|
||||
# below
|
||||
assert unf.transactions_generator is None
|
||||
if unf.transactions_generator is not None: # pragma: no cover
|
||||
# if this assert fires, remove it along with the block below
|
||||
assert not block_has_transactions_generator(unf)
|
||||
if block_has_transactions_generator(unf):
|
||||
block_generator = await get_block_generator(blockchain.lookup_block_generators, unf)
|
||||
assert block_generator is not None
|
||||
assert unf.transactions_info is not None
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
unf.transactions_info.cost,
|
||||
@@ -463,16 +469,16 @@ class TestBlockHeaderValidation:
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
conds = None
|
||||
# if this assert fires, remove it along with the pragma for the block
|
||||
# below
|
||||
assert block.transactions_generator is None
|
||||
if block.transactions_generator is not None: # pragma: no cover
|
||||
# if this assert fires, remove it along with the block below
|
||||
assert not block_has_transactions_generator(block)
|
||||
if block_has_transactions_generator(block):
|
||||
block_generator = await get_block_generator(blockchain.lookup_block_generators, unf)
|
||||
assert block_generator is not None
|
||||
assert unf.transactions_info is not None
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
unf.transactions_info.cost,
|
||||
@@ -2409,7 +2415,10 @@ class TestBodyValidation:
|
||||
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
|
||||
original_block: FullBlock = blocks[-1]
|
||||
|
||||
block = recursive_replace(original_block, "transactions_generator", SerializedProgram.to(None))
|
||||
if original_block.version == 0:
|
||||
block = recursive_replace(original_block, "transactions_generator", SerializedProgram.to(None))
|
||||
else:
|
||||
block = recursive_replace(original_block, "transactions_generator_buffer", b"\xff")
|
||||
await _validate_and_add_block(
|
||||
empty_blockchain, block, expected_error=Err.NOT_BLOCK_BUT_HAS_DATA, skip_prevalidation=True
|
||||
)
|
||||
@@ -2429,6 +2438,35 @@ class TestBodyValidation:
|
||||
empty_blockchain, block, expected_error=Err.NOT_BLOCK_BUT_HAS_DATA, skip_prevalidation=True
|
||||
)
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("wrong_version", [uint8(0), uint8(1), uint8(2)])
|
||||
@pytest.mark.parametrize("transaction_block", [True, False])
|
||||
async def test_invalid_block_version(
|
||||
self, empty_blockchain: Blockchain, bt: BlockTools, wrong_version: uint8, transaction_block: bool
|
||||
) -> None:
|
||||
# Version 1 is required after HF2 (keyed on prev tx height); version 0 before.
|
||||
# Across consensus modes this covers both directions: reject v1 pre-fork and v0 post-fork.
|
||||
# Unknown versions (e.g. 2) are always rejected. Applies to tx and non-tx blocks.
|
||||
blocks = bt.get_consecutive_blocks(1)
|
||||
while transaction_block != (blocks[-1].foliage_transaction_block is not None):
|
||||
await _validate_and_add_block(empty_blockchain, blocks[-1])
|
||||
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
|
||||
|
||||
block = blocks[-1]
|
||||
assert block.is_transaction_block() == transaction_block
|
||||
if wrong_version == block.version:
|
||||
pytest.skip(f"version {wrong_version} is valid for this consensus mode")
|
||||
|
||||
bad_block = recursive_replace(block, "version", wrong_version)
|
||||
await _validate_and_add_block(
|
||||
empty_blockchain, bad_block, expected_error=Err.INVALID_BLOCK_VERSION, skip_prevalidation=True
|
||||
)
|
||||
|
||||
unf = make_unfinished_block(block, bt.constants)
|
||||
bad_unf = recursive_replace(unf, "version", wrong_version)
|
||||
_, err = await empty_blockchain.validate_unfinished_block_header(bad_unf)
|
||||
assert err == Err.INVALID_BLOCK_VERSION
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tx_block_missing_data(self, empty_blockchain: Blockchain, bt: BlockTools) -> None:
|
||||
# 2
|
||||
@@ -2621,6 +2659,126 @@ class TestBodyValidation:
|
||||
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
|
||||
await _validate_and_add_block(b, block_2, expected_error=Err.INVALID_TRANSACTIONS_GENERATOR_HASH)
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_prevalidation_fast_fail(
|
||||
self,
|
||||
empty_blockchain: Blockchain,
|
||||
bt: BlockTools,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
consensus_mode: ConsensusMode,
|
||||
) -> None:
|
||||
# Adversarial fast-fail: a peer can take a valid block, keep all
|
||||
# farmed/signed header fields and commitments intact, and swap only
|
||||
# the generator for malicious CLVM. Prevalidation must reject this with
|
||||
# the cheap generator_root hash check
|
||||
# (INVALID_TRANSACTIONS_GENERATOR_HASH) BEFORE executing the generator,
|
||||
# so the expensive CLVM run is never performed for the bad block. This
|
||||
# mirrors the unfinished-block path, which already gates CLVM on these
|
||||
# commitments. Covers both pre-HF3 (transactions_generator) and
|
||||
# post-HF3 (transactions_generator_buffer) block formats.
|
||||
b = empty_blockchain
|
||||
blocks = bt.get_consecutive_blocks(2, guarantee_transaction_block=True)
|
||||
await _validate_and_add_block(b, blocks[0])
|
||||
await _validate_and_add_block(b, blocks[1])
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
2,
|
||||
block_list_input=blocks,
|
||||
guarantee_transaction_block=True,
|
||||
farmer_reward_puzzle_hash=bt.pool_ph,
|
||||
)
|
||||
await _validate_and_add_block(b, blocks[2])
|
||||
await _validate_and_add_block(b, blocks[3])
|
||||
|
||||
wt: WalletTool = bt.get_pool_wallet_tool()
|
||||
coin = find_reward_coin(blocks[-1], bt.pool_ph)
|
||||
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin)
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
|
||||
)
|
||||
block: FullBlock = blocks[-1]
|
||||
assert block_has_transactions_generator(block)
|
||||
assert block.transactions_info is not None
|
||||
if consensus_mode >= ConsensusMode.HARD_FORK_3_0:
|
||||
assert block.version == 1
|
||||
else:
|
||||
assert block.version == 0
|
||||
|
||||
def replace_generator(blk: FullBlock, generator: SerializedProgram) -> FullBlock:
|
||||
if blk.version == 0:
|
||||
return cast(FullBlock, recursive_replace(blk, "transactions_generator", generator))
|
||||
return cast(FullBlock, recursive_replace(blk, "transactions_generator_buffer", bytes(generator)))
|
||||
|
||||
# Swap only the generator bytes; leave transactions_info.generator_root
|
||||
# and all foliage/signatures pointing at the original generator. This
|
||||
# is the attack: commitments unchanged, generator replaced (the peer
|
||||
# cannot re-sign without the plot key).
|
||||
malicious_generator = SerializedProgram.fromhex("80")
|
||||
mutated = replace_generator(block, malicious_generator)
|
||||
# Sanity: the swap actually breaks the commitment.
|
||||
mutated_generator_bytes = get_transactions_generator_bytes(mutated)
|
||||
assert mutated_generator_bytes is not None
|
||||
assert mutated.transactions_info is not None
|
||||
assert std_hash(mutated_generator_bytes) != mutated.transactions_info.generator_root
|
||||
|
||||
# Trap _run_block: if the generator is executed, raise. _pre_validate_block's
|
||||
# outer try/except would convert that into UNKNOWN, so observing
|
||||
# INVALID_TRANSACTIONS_GENERATOR_HASH instead proves CLVM did not run.
|
||||
def _trap_run_block(*args: object, **kwargs: object) -> None:
|
||||
raise AssertionError("CLVM generator execution must not run for a mutated generator")
|
||||
|
||||
monkeypatch.setattr("chia.consensus.multiprocess_validation._run_block", _trap_run_block)
|
||||
|
||||
await _validate_and_add_block(b, mutated, expected_error=Err.INVALID_TRANSACTIONS_GENERATOR_HASH)
|
||||
|
||||
# Missing transactions_info must be reported as an invalid block rather
|
||||
# than escaping prevalidation as an AssertionError.
|
||||
mutated = recursive_replace(block, "transactions_info", None)
|
||||
|
||||
await _validate_and_add_block(b, mutated, expected_error=Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA)
|
||||
|
||||
# Removing the foliage transaction block must not bypass the
|
||||
# transactions-info commitment. Make the replacement generator and its
|
||||
# direct root self-consistent; the missing signed link must still reject
|
||||
# the block before CLVM execution.
|
||||
mutated = replace_generator(block, malicious_generator)
|
||||
mutated = recursive_replace(mutated, "transactions_info.generator_root", std_hash(bytes(malicious_generator)))
|
||||
mutated = recursive_replace(mutated, "foliage_transaction_block", None)
|
||||
|
||||
await _validate_and_add_block(b, mutated, expected_error=Err.INVALID_TRANSACTIONS_INFO_HASH)
|
||||
|
||||
# Reject structurally invalid reference lists before attempting any
|
||||
# generator lookup.
|
||||
async def _trap_generator_lookup(*args: object, **kwargs: object) -> None:
|
||||
raise AssertionError("generator lookup must not run for too many references")
|
||||
|
||||
monkeypatch.setattr(AugmentedBlockchain, "lookup_block_generators", _trap_generator_lookup)
|
||||
mutated = recursive_replace(
|
||||
block,
|
||||
"transactions_generator_ref_list",
|
||||
[uint32(0)] * (b.constants.MAX_GENERATOR_REF_LIST_SIZE + 1),
|
||||
)
|
||||
|
||||
await _validate_and_add_block(b, mutated, expected_error=Err.TOO_MANY_GENERATOR_REFS)
|
||||
|
||||
# A failed generator resolution must not leave the candidate block in
|
||||
# the speculative chain.
|
||||
augmented_blockchain = AugmentedBlockchain(b)
|
||||
|
||||
async def _fail_generator_resolution(*args: object, **kwargs: object) -> None:
|
||||
assert augmented_blockchain.try_block_record(block.header_hash) is None
|
||||
raise ValueError("generator reference is unavailable")
|
||||
|
||||
monkeypatch.setattr("chia.consensus.multiprocess_validation.get_block_generator", _fail_generator_resolution)
|
||||
|
||||
await _validate_and_add_block(
|
||||
b,
|
||||
block,
|
||||
expected_error=Err.FAILED_GETTING_GENERATOR_MULTIPROCESSING,
|
||||
augmented_blockchain=augmented_blockchain,
|
||||
)
|
||||
|
||||
assert augmented_blockchain.try_block_record(block.header_hash) is None
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_invalid_transactions_ref_list(
|
||||
self, empty_blockchain: Blockchain, bt: BlockTools, consensus_mode: ConsensusMode
|
||||
@@ -2661,7 +2819,7 @@ class TestBodyValidation:
|
||||
# after the hard fork activation, we no longer allow block references
|
||||
expected_error = Err.TOO_MANY_GENERATOR_REFS
|
||||
|
||||
await _validate_and_add_block(b, block_2, expected_error=expected_error, skip_prevalidation=True)
|
||||
await _validate_and_add_block(b, block_2, expected_error=expected_error)
|
||||
|
||||
# Hash should be correct when there is a ref list
|
||||
await _validate_and_add_block(b, blocks[-1])
|
||||
@@ -2676,7 +2834,7 @@ class TestBodyValidation:
|
||||
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
|
||||
)
|
||||
await _validate_and_add_block(b, blocks[-1])
|
||||
assert blocks[-1].transactions_generator is not None
|
||||
assert block_has_transactions_generator(blocks[-1])
|
||||
|
||||
# after the 3.0 hard fork, we no longer allow block references, so the
|
||||
# block_refs parameter is no longer valid, nor this test
|
||||
@@ -2728,9 +2886,10 @@ class TestBodyValidation:
|
||||
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
|
||||
)
|
||||
|
||||
assert blocks[-1].transactions_generator is not None
|
||||
generator = get_transactions_generator_program(blocks[-1])
|
||||
assert generator is not None
|
||||
assert blocks[-1].transactions_info is not None
|
||||
block_generator = BlockGenerator(blocks[-1].transactions_generator, [])
|
||||
block_generator = BlockGenerator(generator, [])
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
b.constants.MAX_BLOCK_COST_CLVM * 1000,
|
||||
@@ -2809,8 +2968,9 @@ class TestBodyValidation:
|
||||
assert new_m is not None
|
||||
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
|
||||
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
|
||||
assert block_2.transactions_generator is not None
|
||||
block_generator = BlockGenerator(block_2.transactions_generator, [])
|
||||
generator = get_transactions_generator_program(block_2)
|
||||
assert generator is not None
|
||||
block_generator = BlockGenerator(generator, [])
|
||||
assert block.transactions_info is not None
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
@@ -2844,8 +3004,9 @@ class TestBodyValidation:
|
||||
assert new_m is not None
|
||||
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
|
||||
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
|
||||
assert block_2.transactions_generator is not None
|
||||
block_generator = BlockGenerator(block_2.transactions_generator, [])
|
||||
generator = get_transactions_generator_program(block_2)
|
||||
assert generator is not None
|
||||
block_generator = BlockGenerator(generator, [])
|
||||
assert block.transactions_info is not None
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
@@ -2879,8 +3040,9 @@ class TestBodyValidation:
|
||||
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
|
||||
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
|
||||
|
||||
assert block_2.transactions_generator is not None
|
||||
block_generator = BlockGenerator(block_2.transactions_generator, [])
|
||||
generator = get_transactions_generator_program(block_2)
|
||||
assert generator is not None
|
||||
block_generator = BlockGenerator(generator, [])
|
||||
max_cost = min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost)
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
block_generator,
|
||||
@@ -3863,7 +4025,10 @@ class TestReorgs:
|
||||
generator = SerializedProgram.fromhex("c00101")
|
||||
assert not is_canonical_serialization(bytes(generator))
|
||||
|
||||
block = recursive_replace(original_block, "transactions_generator", generator)
|
||||
if original_block.version == 0:
|
||||
block = recursive_replace(original_block, "transactions_generator", generator)
|
||||
else:
|
||||
block = recursive_replace(original_block, "transactions_generator_buffer", bytes(generator))
|
||||
block = recursive_replace(block, "transactions_info.generator_root", std_hash(bytes(generator)))
|
||||
block = recursive_replace(
|
||||
block, "foliage_transaction_block.transactions_info_hash", std_hash(bytes(block.transactions_info))
|
||||
@@ -4282,9 +4447,10 @@ async def test_get_tx_peak(default_400_blocks: list[FullBlock], empty_blockchain
|
||||
assert bc.get_tx_peak() == last_tx_block_record
|
||||
|
||||
|
||||
def to_bytes(gen: SerializedProgram | None) -> bytes:
|
||||
def get_gen(block: FullBlock) -> bytes:
|
||||
gen = get_transactions_generator_bytes(block)
|
||||
assert gen is not None
|
||||
return bytes(gen)
|
||||
return gen
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -4337,7 +4503,7 @@ async def test_lookup_block_generators(
|
||||
b.clean_block_records()
|
||||
generators = await b.lookup_block_generators(peak.prev_header_hash, {uint32(2)})
|
||||
assert generators == {
|
||||
uint32(2): to_bytes(blocks_1[2].transactions_generator),
|
||||
uint32(2): get_gen(blocks_1[2]),
|
||||
}
|
||||
|
||||
# multiple generators from the shared part of the chain
|
||||
@@ -4346,21 +4512,21 @@ async def test_lookup_block_generators(
|
||||
b.clean_block_records()
|
||||
generators = await b.lookup_block_generators(peak.prev_header_hash, {uint32(2), uint32(10), uint32(26)})
|
||||
assert generators == {
|
||||
uint32(2): to_bytes(blocks_1[2].transactions_generator),
|
||||
uint32(10): to_bytes(blocks_1[10].transactions_generator),
|
||||
uint32(26): to_bytes(blocks_1[26].transactions_generator),
|
||||
uint32(2): get_gen(blocks_1[2]),
|
||||
uint32(10): get_gen(blocks_1[10]),
|
||||
uint32(26): get_gen(blocks_1[26]),
|
||||
}
|
||||
|
||||
# lookups from the past the fork
|
||||
if clear_cache:
|
||||
b.clean_block_records()
|
||||
generators = await b.lookup_block_generators(peak_1.prev_header_hash, {uint32(503)})
|
||||
assert generators == {uint32(503): to_bytes(blocks_1[503].transactions_generator)}
|
||||
assert generators == {uint32(503): get_gen(blocks_1[503])}
|
||||
|
||||
if clear_cache:
|
||||
b.clean_block_records()
|
||||
generators = await b.lookup_block_generators(peak_2.prev_header_hash, {uint32(516)})
|
||||
assert generators == {uint32(516): to_bytes(blocks_2[516].transactions_generator)}
|
||||
assert generators == {uint32(516): get_gen(blocks_2[516])}
|
||||
|
||||
# make sure we don't cross the forks
|
||||
if clear_cache:
|
||||
|
||||
@@ -4,6 +4,7 @@ import pytest
|
||||
from chia_rs import Coin, ConsensusConstants, FullBlock, additions_and_removals, get_flags_for_height_and_constants
|
||||
from chia_rs.sized_ints import uint64
|
||||
|
||||
from chia.consensus.block_generator_info import block_has_transactions_generator, get_transactions_generator_bytes
|
||||
from chia.simulator.block_tools import BlockTools
|
||||
|
||||
# These test targets are used to trigger a build of the test chains.
|
||||
@@ -66,10 +67,12 @@ def validate_coins(constants: ConsensusConstants, blocks: list[FullBlock]) -> No
|
||||
for block in blocks:
|
||||
rewards = block.get_included_reward_coins()
|
||||
|
||||
if block.transactions_generator is not None:
|
||||
if block_has_transactions_generator(block):
|
||||
flags = get_flags_for_height_and_constants(block.height, constants)
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
assert generator_bytes is not None
|
||||
additions, removals = additions_and_removals(
|
||||
bytes(block.transactions_generator),
|
||||
generator_bytes,
|
||||
[],
|
||||
flags,
|
||||
constants,
|
||||
|
||||
@@ -17,7 +17,7 @@ class BR:
|
||||
prev_header_hash: bytes32
|
||||
transactions_generator: SerializedProgram | None
|
||||
transactions_generator_ref_list: list[uint32]
|
||||
transactions_generator_buffer: list[uint8] | None = None
|
||||
transactions_generator_buffer: bytes | None = None
|
||||
version: uint8 = uint8(0)
|
||||
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_deserialization_simple_list() -> None:
|
||||
|
||||
|
||||
def test_deserialization_password_coin() -> None:
|
||||
# (i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (q 51) (c 5 (c (q 100) (q ())))) (q "wrong password")) # noqa
|
||||
# (i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (q 51) (c 5 (c (q 100) (q ())))) (q "wrong password")) # ruff: ignore[line-too-long]
|
||||
b = hexstr_to_bytes(
|
||||
"ff04ffff0affff0bff0280ffff01ffa02cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b98248080ffff05ffff01ff3380ffff05ff05ffff05ffff01ff6480ffff01ff8080808080ffff01ff8e77726f6e672070617373776f72648080"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ def test_deserialization_password_coin() -> None:
|
||||
|
||||
|
||||
def test_deserialization_large_numbers() -> None:
|
||||
# '(99999999999999999999999999999999999999999999999999999999999999999 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -99999999999999999999999999999999999999999999999999999999999999999999999999999)' # noqa
|
||||
# '(99999999999999999999999999999999999999999999999999999999999999999 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -99999999999999999999999999999999999999999999999999999999999999999999999999999)' # ruff: ignore[line-too-long]
|
||||
b = hexstr_to_bytes(
|
||||
"ff9c00f316271c7fc3908a8bef464e3945ef7a253609ffffffffffffffffffb00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1ff22ea0179500526edb610f148ec0c614155678491902d6000000000000000000180"
|
||||
)
|
||||
|
||||
@@ -47,6 +47,8 @@ from chia.wallet.wallet_request_types import (
|
||||
GetTransactionResponse,
|
||||
GetWallets,
|
||||
GetWalletsResponse,
|
||||
LogIn,
|
||||
LogInResponse,
|
||||
NFTCalculateRoyalties,
|
||||
NFTCalculateRoyaltiesResponse,
|
||||
NFTGetInfo,
|
||||
@@ -105,6 +107,10 @@ class TestWalletRpcClient(TestRpcClient):
|
||||
fingerprint: int = field(init=False, default=0)
|
||||
wallet_index: int = field(init=False, default=0)
|
||||
|
||||
async def log_in(self, request: LogIn) -> LogInResponse:
|
||||
self.fingerprint = request.fingerprint
|
||||
return LogInResponse(fingerprint=request.fingerprint)
|
||||
|
||||
async def get_sync_status(self) -> GetSyncStatusResponse:
|
||||
self.add_to_log("get_sync_status", ())
|
||||
return GetSyncStatusResponse(synced=True, syncing=False)
|
||||
|
||||
@@ -14,7 +14,7 @@ from chia_rs.sized_ints import uint64
|
||||
from click.testing import CliRunner
|
||||
|
||||
from chia._tests.environments.wallet import STANDARD_TX_ENDPOINT_ARGS, WalletTestFramework
|
||||
from chia._tests.wallet.conftest import * # noqa
|
||||
from chia._tests.wallet.conftest import * # ruff: ignore[undefined-local-with-import-star]
|
||||
from chia.cmds.cmd_classes import ChiaCliContext, ChiaCommand, chia_command, option
|
||||
from chia.cmds.cmd_helpers import (
|
||||
_TRANSACTION_ENDPOINT_DECORATOR_APPLIED,
|
||||
|
||||
@@ -1234,7 +1234,7 @@ def test_take_offer(capsys: object, get_test_cli_clients: tuple[TestRpcClients,
|
||||
"take_offer": [
|
||||
(
|
||||
Offer.from_bech32(test_offer_file_bech32),
|
||||
DEFAULT_TX_CONFIG,
|
||||
DEFAULT_TX_CONFIG.override(reuse_puzhash=True),
|
||||
None,
|
||||
500000000000,
|
||||
True,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ruff: noqa: E402 # See imports after multiprocessing.set_start_method
|
||||
# ruff: file-ignore[module-import-not-at-top-of-file] # See imports after multiprocessing.set_start_method
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
@@ -1575,4 +1575,5 @@ async def wallet_environments(
|
||||
for service, rpc_client, wallet_state in zip(wallet_services, wallet_rpc_clients, wallet_states)
|
||||
],
|
||||
tx_config,
|
||||
request.param.get("reorg_exempt", False),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from chia_rs import VDFInfo, VDFProof
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint64
|
||||
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.types.blockchain_format import vdf
|
||||
from chia.types.blockchain_format.classgroup import ClassgroupElement
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("witness_type", "witness_size"),
|
||||
[
|
||||
(uint8(0), 100),
|
||||
(uint8(1), 241),
|
||||
(uint8(63), 8983),
|
||||
],
|
||||
)
|
||||
def test_expected_witness_sizes_reach_verifier(
|
||||
monkeypatch: pytest.MonkeyPatch, witness_type: uint8, witness_size: int
|
||||
) -> None:
|
||||
verifier_calls = 0
|
||||
|
||||
def accept_proof(*args: object) -> bool:
|
||||
nonlocal verifier_calls
|
||||
verifier_calls += 1
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(vdf, "get_discriminant", lambda *args: 1)
|
||||
monkeypatch.setattr(vdf, "verify_vdf", accept_proof)
|
||||
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
proof = VDFProof(witness_type, bytes(witness_size), False)
|
||||
|
||||
assert vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info)
|
||||
assert verifier_calls == 1
|
||||
|
||||
|
||||
def test_oversized_witness_rejected_before_cached_verifier() -> None:
|
||||
cache_info_before = vdf.verify_vdf.cache_info()
|
||||
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
proof = VDFProof(uint8(0), bytes(2_000_000), False)
|
||||
|
||||
assert not vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info)
|
||||
assert vdf.verify_vdf.cache_info() == cache_info_before
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("witness_type", "witness_size"),
|
||||
[
|
||||
# Empty / truncated / one-byte-long for witness_type 0 (expected 100).
|
||||
(uint8(0), 0),
|
||||
(uint8(0), 99),
|
||||
(uint8(0), 101),
|
||||
# Correct length for a different witness_type.
|
||||
(uint8(1), 100),
|
||||
(uint8(0), 241),
|
||||
# Off-by-one around witness_type 1 (expected 241).
|
||||
(uint8(1), 240),
|
||||
(uint8(1), 242),
|
||||
# Off-by-one around witness_type 63 (expected 8983).
|
||||
(uint8(63), 8982),
|
||||
(uint8(63), 8984),
|
||||
],
|
||||
ids=[
|
||||
"type0_empty",
|
||||
"type0_one_short",
|
||||
"type0_one_long",
|
||||
"type1_with_type0_size",
|
||||
"type0_with_type1_size",
|
||||
"type1_one_short",
|
||||
"type1_one_long",
|
||||
"type63_one_short",
|
||||
"type63_one_long",
|
||||
],
|
||||
)
|
||||
def test_invalid_witness_size_rejected(witness_type: uint8, witness_size: int) -> None:
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
proof = VDFProof(witness_type, bytes(witness_size), False)
|
||||
|
||||
assert not vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info)
|
||||
|
||||
|
||||
def test_witness_type_above_max_rejected() -> None:
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
# MAX_VDF_WITNESS_SIZE is 64; witness_type + 1 must be <= 64, so 64 is rejected.
|
||||
proof = VDFProof(uint8(64), bytes(100), False)
|
||||
|
||||
assert not vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info)
|
||||
|
||||
|
||||
def test_target_vdf_info_mismatch_rejected() -> None:
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
target = VDFInfo(bytes32(b"\x01" * 32), uint64(1), classgroup_element)
|
||||
proof = VDFProof(uint8(0), bytes(100), False)
|
||||
|
||||
assert not vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info, target)
|
||||
|
||||
|
||||
def test_verifier_failure_rejected(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(vdf, "get_discriminant", lambda *args: 1)
|
||||
monkeypatch.setattr(vdf, "verify_vdf", lambda *args: False)
|
||||
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
proof = VDFProof(uint8(0), bytes(100), False)
|
||||
|
||||
assert not vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info)
|
||||
|
||||
|
||||
def test_verifier_exception_rejected(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(vdf, "get_discriminant", lambda *args: 1)
|
||||
|
||||
def raise_error(*args: object) -> bool:
|
||||
raise RuntimeError("chiavdf blew up")
|
||||
|
||||
monkeypatch.setattr(vdf, "verify_vdf", raise_error)
|
||||
|
||||
classgroup_element = ClassgroupElement.get_default_element()
|
||||
info = VDFInfo(bytes32.zeros, uint64(1), classgroup_element)
|
||||
proof = VDFProof(uint8(0), bytes(100), False)
|
||||
|
||||
assert not vdf.validate_vdf(proof, DEFAULT_CONSTANTS, classgroup_element, info)
|
||||
File diff suppressed because one or more lines are too long
@@ -25,7 +25,7 @@ from chia_rs.sized_bytes import bytes32
|
||||
|
||||
from chia._tests.core.data_layer.util import Example, add_0123_example, add_01234567_example
|
||||
from chia._tests.util.misc import BenchmarkRunner, Marks, boolean_datacases, datacases
|
||||
from chia.data_layer.data_layer_errors import KeyNotFoundError, TreeGenerationIncrementingError
|
||||
from chia.data_layer.data_layer_errors import KeyNotFoundError, MerkleBlobNotFoundError, TreeGenerationIncrementingError
|
||||
from chia.data_layer.data_layer_util import (
|
||||
DiffData,
|
||||
InternalNode,
|
||||
@@ -1201,6 +1201,91 @@ async def test_unsubscribe_suppresses_blob_cleanup_oserror(
|
||||
assert await data_store.store_id_exists(store_id) is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_clear_store_roots_wipes_all_roots(data_store: DataStore, store_id: bytes32) -> None:
|
||||
await add_0123_example(data_store, store_id)
|
||||
await data_store.add_node_hashes(store_id)
|
||||
await data_store.create_tree(store_id=store_id, status=Status.PENDING)
|
||||
assert await data_store.get_pending_root(store_id=store_id) is not None
|
||||
assert await data_store.store_id_exists(store_id) is True
|
||||
|
||||
await data_store.clear_store_roots(store_id=store_id)
|
||||
|
||||
assert await data_store.store_id_exists(store_id) is False
|
||||
assert await data_store.get_pending_root(store_id=store_id) is None
|
||||
for table in ["root", "nodes"]:
|
||||
async with data_store.db_wrapper.reader() as reader:
|
||||
async with reader.execute(
|
||||
f"SELECT COUNT(*) FROM {table} WHERE {'tree_id' if table == 'root' else 'store_id'} == ?",
|
||||
(store_id,),
|
||||
) as cursor:
|
||||
row_count = await cursor.fetchone()
|
||||
assert row_count is not None
|
||||
assert row_count[0] == 0
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_reset_store_to_empty_root_reseeds_gen0(data_store: DataStore, store_id: bytes32) -> None:
|
||||
await add_0123_example(data_store, store_id)
|
||||
await data_store.add_node_hashes(store_id)
|
||||
assert (await data_store.get_tree_root(store_id)).generation > 0
|
||||
|
||||
await data_store.reset_store_to_empty_root(store_id)
|
||||
|
||||
root = await data_store.get_tree_root(store_id)
|
||||
assert root.generation == 0
|
||||
assert root.node_hash is None
|
||||
assert await data_store.get_tree_generation(store_id) == 0
|
||||
assert await data_store.store_id_exists(store_id) is True
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_reset_store_to_empty_root_with_pending_gen0_root(raw_data_store: DataStore, store_id: bytes32) -> None:
|
||||
await raw_data_store.create_tree(store_id=store_id, status=Status.PENDING)
|
||||
assert await raw_data_store.get_pending_root(store_id=store_id) is not None
|
||||
assert await raw_data_store.store_id_exists(store_id) is False
|
||||
|
||||
await raw_data_store.reset_store_to_empty_root(store_id)
|
||||
|
||||
root = await raw_data_store.get_tree_root(store_id)
|
||||
assert root.generation == 0
|
||||
assert root.node_hash is None
|
||||
assert await raw_data_store.get_pending_root(store_id=store_id) is None
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_reset_store_to_empty_root_clears_recent_merkle_cache(data_store: DataStore, store_id: bytes32) -> None:
|
||||
data_store.recent_merkle_blobs = LRUCache(capacity=128)
|
||||
await add_0123_example(data_store, store_id)
|
||||
root = await data_store.get_tree_root(store_id)
|
||||
assert root.node_hash is not None
|
||||
|
||||
await data_store.get_merkle_blob(store_id=store_id, root_hash=root.node_hash)
|
||||
assert data_store.recent_merkle_blobs.get((store_id, root.node_hash)) is not None
|
||||
data_store.get_merkle_path(store_id=store_id, root_hash=root.node_hash).unlink()
|
||||
|
||||
await data_store.reset_store_to_empty_root(store_id)
|
||||
|
||||
assert data_store.recent_merkle_blobs.get((store_id, root.node_hash)) is None
|
||||
with pytest.raises(MerkleBlobNotFoundError):
|
||||
await data_store.get_merkle_blob(store_id=store_id, root_hash=root.node_hash)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_merkle_blob_available_ignores_cache(data_store: DataStore, store_id: bytes32) -> None:
|
||||
data_store.recent_merkle_blobs = LRUCache(capacity=128)
|
||||
await add_0123_example(data_store, store_id)
|
||||
root = await data_store.get_tree_root(store_id)
|
||||
assert root.node_hash is not None
|
||||
|
||||
await data_store.get_merkle_blob(store_id=store_id, root_hash=root.node_hash)
|
||||
assert data_store.recent_merkle_blobs.get((store_id, root.node_hash)) is not None
|
||||
data_store.get_merkle_path(store_id=store_id, root_hash=root.node_hash).unlink()
|
||||
|
||||
assert data_store.merkle_blob_available(store_id, root.node_hash) is False
|
||||
assert data_store.merkle_blob_available(store_id, None) is False
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_server_selection(data_store: DataStore, store_id: bytes32) -> None:
|
||||
start_timestamp = 1000
|
||||
|
||||
@@ -158,7 +158,7 @@ class ChiaRoot:
|
||||
kwargs["stderr"] = stderr
|
||||
|
||||
try:
|
||||
return subprocess.run(*final_args, **kwargs) # noqa: PLW1510
|
||||
return subprocess.run(*final_args, **kwargs) # ruff: ignore[subprocess-run-without-check]
|
||||
except OSError as e:
|
||||
raise Exception(f"failed to run:\n {final_args}\n {kwargs}") from e
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, cast
|
||||
@@ -29,6 +30,7 @@ from chia.util.casts import int_to_bytes
|
||||
from chia.util.errors import ConsensusError
|
||||
from chia.util.hash import std_hash
|
||||
from chia.util.recursive_replace import recursive_replace
|
||||
from chia.util.task_referencer import create_referenced_task
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -453,6 +455,121 @@ async def test_short_sync_batch_bans_peer_answering_wrong_block_range(
|
||||
assert peer.peer_node_id not in node.sync_store.batch_syncing
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="save time")
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("first_block", ["height_zero", "height_above_peak", "missing"])
|
||||
async def test_short_sync_batch_releases_slot_when_first_block_does_not_connect(
|
||||
two_nodes: tuple[FullNodeAPI, FullNodeAPI, ChiaServer, ChiaServer, BlockTools],
|
||||
consensus_mode: ConsensusMode,
|
||||
first_block: str,
|
||||
) -> None:
|
||||
# The first block's height comes from the peer's response, so it need not be the
|
||||
# height we asked for. Only a height whose parent is in our height map can connect,
|
||||
# so every other answer must return False and free the peer's batch_syncing slot.
|
||||
_full_node_1, full_node_2, _server_1, _server_2, bt = two_nodes
|
||||
node = full_node_2.full_node
|
||||
|
||||
blocks = bt.get_consecutive_blocks(3)
|
||||
for block in blocks:
|
||||
await node.add_block(block)
|
||||
|
||||
response: full_node_protocol.RespondBlock | None
|
||||
if first_block == "height_zero":
|
||||
assert blocks[0].height == 0
|
||||
response = full_node_protocol.RespondBlock(blocks[0])
|
||||
elif first_block == "height_above_peak":
|
||||
above_peak = bt.get_consecutive_blocks(2, block_list_input=blocks)[-1]
|
||||
assert node.blockchain.height_to_hash(uint32(above_peak.height - 1)) is None
|
||||
response = full_node_protocol.RespondBlock(above_peak)
|
||||
else:
|
||||
response = None
|
||||
|
||||
class DummyPeer:
|
||||
peer_node_id = bytes32(b"\x05" * 32)
|
||||
|
||||
async def call_api(self, api_function: Any, request: object) -> full_node_protocol.RespondBlock | None:
|
||||
assert isinstance(request, full_node_protocol.RequestBlock)
|
||||
return response
|
||||
|
||||
peer = cast(WSChiaConnection, DummyPeer())
|
||||
# start_height > 0 so the peer-controlled first-block fetch runs.
|
||||
result = await node.short_sync_batch(peer, uint32(3), uint32(4))
|
||||
|
||||
assert result is False
|
||||
assert peer.peer_node_id not in node.sync_store.batch_syncing
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="save time")
|
||||
@pytest.mark.anyio
|
||||
async def test_short_sync_batch_releases_slot_when_first_block_request_raises(
|
||||
two_nodes: tuple[FullNodeAPI, FullNodeAPI, ChiaServer, ChiaServer, BlockTools],
|
||||
consensus_mode: ConsensusMode,
|
||||
) -> None:
|
||||
# A failure fetching the first block must release the batch_syncing slot before the
|
||||
# exception propagates, so a later attempt from the same peer is not skipped.
|
||||
_full_node_1, full_node_2, _server_1, _server_2, _bt = two_nodes
|
||||
node = full_node_2.full_node
|
||||
|
||||
class DummyPeer:
|
||||
peer_node_id = bytes32(b"\x06" * 32)
|
||||
|
||||
async def call_api(self, api_function: Any, request: object) -> full_node_protocol.RespondBlock:
|
||||
raise ValueError("request failed")
|
||||
|
||||
peer = cast(WSChiaConnection, DummyPeer())
|
||||
with pytest.raises(ValueError, match="request failed"):
|
||||
await node.short_sync_batch(peer, uint32(3), uint32(4))
|
||||
|
||||
assert peer.peer_node_id not in node.sync_store.batch_syncing
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN], reason="save time")
|
||||
@pytest.mark.anyio
|
||||
async def test_short_sync_batch_reaps_segment_tasks(
|
||||
two_nodes: tuple[FullNodeAPI, FullNodeAPI, ChiaServer, ChiaServer, BlockTools],
|
||||
consensus_mode: ConsensusMode,
|
||||
) -> None:
|
||||
# Once the first block connects, pending sub epoch segment tasks are reaped: finished
|
||||
# ones are dropped from the list, unfinished ones are cancelled.
|
||||
_full_node_1, full_node_2, _server_1, _server_2, bt = two_nodes
|
||||
node = full_node_2.full_node
|
||||
|
||||
blocks = bt.get_consecutive_blocks(3)
|
||||
for block in blocks:
|
||||
await node.add_block(block)
|
||||
connecting_block = bt.get_consecutive_blocks(1, block_list_input=blocks)[-1]
|
||||
|
||||
async def noop() -> None:
|
||||
return None
|
||||
|
||||
done_task = create_referenced_task(noop())
|
||||
await asyncio.sleep(0)
|
||||
assert done_task.done()
|
||||
running_task = create_referenced_task(asyncio.sleep(60))
|
||||
node._segment_task_list.extend([done_task, running_task])
|
||||
|
||||
class DummyPeer:
|
||||
peer_node_id = bytes32(b"\x07" * 32)
|
||||
|
||||
def get_peer_logging(self) -> PeerInfo:
|
||||
return PeerInfo("127.0.0.1", uint16(0))
|
||||
|
||||
async def call_api(self, api_function: Any, request: object) -> full_node_protocol.RespondBlock:
|
||||
return full_node_protocol.RespondBlock(connecting_block)
|
||||
|
||||
peer = cast(WSChiaConnection, DummyPeer())
|
||||
# start == target leaves the batch download loop empty, so the run stops after the reaping.
|
||||
assert await node.short_sync_batch(peer, uint32(3), uint32(3)) is True
|
||||
|
||||
assert peer.peer_node_id not in node.sync_store.batch_syncing
|
||||
assert done_task not in node._segment_task_list
|
||||
assert running_task in node._segment_task_list
|
||||
# wait_for keeps this from blocking for the task's full sleep if the cancel is dropped.
|
||||
with contextlib.suppress(asyncio.CancelledError):
|
||||
await asyncio.wait_for(running_task, timeout=5)
|
||||
assert running_task.cancelled()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_backtrack_sync_1(
|
||||
two_nodes: tuple[FullNodeAPI, FullNodeAPI, ChiaServer, ChiaServer, BlockTools], self_hostname: str
|
||||
|
||||
@@ -19,6 +19,7 @@ from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block
|
||||
from chia._tests.core.full_node.test_full_node import find_reward_coin
|
||||
from chia._tests.util.db_connection import DBConnection, PathDBConnection
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_generator_info import block_has_transactions_generator, get_transactions_generator_bytes
|
||||
from chia.consensus.block_height_map import BlockHeightMap
|
||||
from chia.consensus.blockchain import AddBlockResult, Blockchain
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
@@ -43,13 +44,6 @@ def use_cache(request: SubRequest) -> bool:
|
||||
return cast(bool, request.param)
|
||||
|
||||
|
||||
def maybe_serialize(gen: SerializedProgram | None) -> bytes | None:
|
||||
if gen is None:
|
||||
return None
|
||||
else:
|
||||
return bytes(gen)
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="save time")
|
||||
@pytest.mark.anyio
|
||||
async def test_block_store(tmp_dir: Path, db_version: int, bt: BlockTools, use_cache: bool) -> None:
|
||||
@@ -93,9 +87,13 @@ async def test_block_store(tmp_dir: Path, db_version: int, bt: BlockTools, use_c
|
||||
assert block == await store.get_full_block(block.header_hash)
|
||||
assert bytes(block) == await store.get_full_block_bytes(block.header_hash)
|
||||
assert GeneratorBlockInfo(
|
||||
block.foliage.prev_block_hash, block.transactions_generator, block.transactions_generator_ref_list
|
||||
block.foliage.prev_block_hash,
|
||||
block.transactions_generator,
|
||||
block.transactions_generator_ref_list,
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
) == await store.get_block_info(block.header_hash)
|
||||
assert maybe_serialize(block.transactions_generator) == await store.get_generator(block.header_hash)
|
||||
assert get_transactions_generator_bytes(block) == await store.get_generator(block.header_hash)
|
||||
assert block_record == (await store.get_block_record(block_record_hh))
|
||||
await store.set_in_chain([(block_record.header_hash,)])
|
||||
await store.set_peak(block_record.header_hash)
|
||||
@@ -107,10 +105,10 @@ async def test_block_store(tmp_dir: Path, db_version: int, bt: BlockTools, use_c
|
||||
assert FullBlock.from_bytes(buf) == block
|
||||
|
||||
assert await store.get_full_blocks_at([block.height]) == [block]
|
||||
if block.transactions_generator is not None:
|
||||
assert await store.get_generators_at({block.height}) == {
|
||||
block.height: bytes(block.transactions_generator)
|
||||
}
|
||||
if block_has_transactions_generator(block):
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
assert generator_bytes is not None
|
||||
assert await store.get_generators_at({block.height}) == {block.height: generator_bytes}
|
||||
else:
|
||||
with pytest.raises(ValueError, match="GENERATOR_REF_HAS_NO_GENERATOR"):
|
||||
await store.get_generators_at({block.height})
|
||||
@@ -430,13 +428,13 @@ async def test_get_generator(bt: BlockTools, db_version: int, use_cache: bool) -
|
||||
await store.set_peak(block_record.header_hash)
|
||||
new_blocks.append(block)
|
||||
|
||||
expected_generators = {b.height: maybe_serialize(b.transactions_generator) for b in new_blocks[1:10]}
|
||||
expected_generators = {b.height: get_transactions_generator_bytes(b) for b in new_blocks[1:10]}
|
||||
generators = await store.get_generators_at({uint32(x) for x in range(1, 10)})
|
||||
assert generators == expected_generators
|
||||
|
||||
# test out-of-order heights
|
||||
expected_generators = {
|
||||
b.height: maybe_serialize(b.transactions_generator) for b in [new_blocks[i] for i in [4, 8, 3, 9]]
|
||||
b.height: get_transactions_generator_bytes(b) for b in [new_blocks[i] for i in [4, 8, 3, 9]]
|
||||
}
|
||||
generators = await store.get_generators_at({uint32(4), uint32(8), uint32(3), uint32(9)})
|
||||
assert generators == expected_generators
|
||||
@@ -446,10 +444,10 @@ async def test_get_generator(bt: BlockTools, db_version: int, use_cache: bool) -
|
||||
|
||||
assert await store.get_generators_at(set()) == {}
|
||||
|
||||
assert await store.get_generator(blocks[2].header_hash) == maybe_serialize(new_blocks[2].transactions_generator)
|
||||
assert await store.get_generator(blocks[4].header_hash) == maybe_serialize(new_blocks[4].transactions_generator)
|
||||
assert await store.get_generator(blocks[6].header_hash) == maybe_serialize(new_blocks[6].transactions_generator)
|
||||
assert await store.get_generator(blocks[7].header_hash) == maybe_serialize(new_blocks[7].transactions_generator)
|
||||
assert await store.get_generator(blocks[2].header_hash) == get_transactions_generator_bytes(new_blocks[2])
|
||||
assert await store.get_generator(blocks[4].header_hash) == get_transactions_generator_bytes(new_blocks[4])
|
||||
assert await store.get_generator(blocks[6].header_hash) == get_transactions_generator_bytes(new_blocks[6])
|
||||
assert await store.get_generator(blocks[7].header_hash) == get_transactions_generator_bytes(new_blocks[7])
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="save time")
|
||||
|
||||
@@ -15,6 +15,7 @@ from chia._tests.util.coin_store import add_coin_records_to_db
|
||||
from chia._tests.util.db_connection import DBConnection
|
||||
from chia._tests.util.misc import Marks, datacases
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_generator_info import block_has_transactions_generator, get_transactions_generator_bytes
|
||||
from chia.consensus.block_height_map import BlockHeightMap
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
|
||||
from chia.consensus.blockchain import AddBlockResult, Blockchain
|
||||
@@ -97,12 +98,12 @@ async def test_basic_coin_store(db_version: int, softfork_height: uint32, bt: Bl
|
||||
should_be_included.add(pool_coin)
|
||||
if not block.is_transaction_block():
|
||||
continue
|
||||
if block.transactions_generator is not None:
|
||||
if block_has_transactions_generator(block):
|
||||
assert block.transactions_info is not None
|
||||
flags = get_flags_for_height_and_constants(block.height, bt.constants)
|
||||
additions, removals = additions_and_removals(
|
||||
bytes(block.transactions_generator), [], flags, bt.constants
|
||||
)
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
assert generator_bytes is not None
|
||||
additions, removals = additions_and_removals(generator_bytes, [], flags, bt.constants)
|
||||
tx_removals = [name for name, _ in removals]
|
||||
tx_additions = [(addition.name(), addition, False) for addition, _ in additions]
|
||||
else:
|
||||
|
||||
@@ -218,9 +218,9 @@ async def test_basic_store(
|
||||
block.foliage_transaction_block,
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_ref_list,
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ from chia._tests.util.setup_nodes import (
|
||||
from chia._tests.util.time_out_assert import time_out_assert, time_out_assert_custom_interval, time_out_messages
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_generator_info import (
|
||||
block_has_transactions_generator,
|
||||
get_transactions_generator_bytes,
|
||||
get_transactions_generator_program,
|
||||
)
|
||||
from chia.consensus.blockchain import Blockchain
|
||||
from chia.consensus.coin_store_protocol import CoinStoreProtocol
|
||||
from chia.consensus.get_block_challenge import get_block_challenge
|
||||
@@ -263,7 +268,9 @@ async def test_block_compression(
|
||||
await time_out_assert(30, check_transaction_confirmed, True, tr)
|
||||
|
||||
# Confirm generator is not compressed
|
||||
program: SerializedProgram | None = (await full_node_1.get_all_full_blocks())[-1].transactions_generator
|
||||
program: SerializedProgram | None = get_transactions_generator_program(
|
||||
(await full_node_1.get_all_full_blocks())[-1]
|
||||
)
|
||||
assert program is not None
|
||||
assert len((await full_node_1.get_all_full_blocks())[-1].transactions_generator_ref_list) == 0
|
||||
|
||||
@@ -292,7 +299,7 @@ async def test_block_compression(
|
||||
await time_out_assert(10, check_transaction_confirmed, True, tr)
|
||||
|
||||
# Confirm generator is compressed
|
||||
program = (await full_node_1.get_all_full_blocks())[-1].transactions_generator
|
||||
program = get_transactions_generator_program((await full_node_1.get_all_full_blocks())[-1])
|
||||
assert program is not None
|
||||
num_blocks = len((await full_node_1.get_all_full_blocks())[-1].transactions_generator_ref_list)
|
||||
# since the hard fork, we don't use this compression mechanism
|
||||
@@ -373,7 +380,7 @@ async def test_block_compression(
|
||||
await time_out_assert(10, check_transaction_confirmed, True, tr)
|
||||
|
||||
# Confirm generator is compressed
|
||||
program = (await full_node_1.get_all_full_blocks())[-1].transactions_generator
|
||||
program = get_transactions_generator_program((await full_node_1.get_all_full_blocks())[-1])
|
||||
assert program is not None
|
||||
num_blocks = len((await full_node_1.get_all_full_blocks())[-1].transactions_generator_ref_list)
|
||||
# since the hard fork, we don't use this compression mechanism
|
||||
@@ -426,7 +433,7 @@ async def test_block_compression(
|
||||
|
||||
# Confirm generator is not compressed, #CAT creation has a cat spend
|
||||
all_blocks = await full_node_1.get_all_full_blocks()
|
||||
program = all_blocks[-1].transactions_generator
|
||||
program = get_transactions_generator_program(all_blocks[-1])
|
||||
assert program is not None
|
||||
assert len(all_blocks[-1].transactions_generator_ref_list) == 0
|
||||
|
||||
@@ -473,7 +480,7 @@ async def test_block_compression(
|
||||
await full_node_1.wait_for_wallet_synced(wallet_node=wallet_node_1, timeout=30)
|
||||
|
||||
# Confirm generator is not compressed
|
||||
program = (await full_node_1.get_all_full_blocks())[-1].transactions_generator
|
||||
program = get_transactions_generator_program((await full_node_1.get_all_full_blocks())[-1])
|
||||
assert program is not None
|
||||
assert len((await full_node_1.get_all_full_blocks())[-1].transactions_generator_ref_list) == 0
|
||||
|
||||
@@ -914,9 +921,9 @@ async def test_respond_unfinished(
|
||||
assert result.conds.cost > 0
|
||||
|
||||
assert not full_node_1.full_node.blockchain.contains_block(block.header_hash, block.height)
|
||||
assert block.transactions_generator is not None
|
||||
block_no_transactions = block.replace(transactions_generator=None)
|
||||
assert block_no_transactions.transactions_generator is None
|
||||
assert block_has_transactions_generator(block)
|
||||
block_no_transactions = block.replace(transactions_generator=None, transactions_generator_buffer=None)
|
||||
assert not block_has_transactions_generator(block_no_transactions)
|
||||
|
||||
await full_node_1.full_node.add_block(block_no_transactions)
|
||||
assert full_node_1.full_node.blockchain.contains_block(block.header_hash, block.height)
|
||||
@@ -1656,13 +1663,13 @@ async def test_request_block(
|
||||
res = await full_node_1.request_block(fnp.RequestBlock(blocks[-1].height, False))
|
||||
assert res is not None
|
||||
assert res.type != ProtocolMessageTypes.reject_block.value
|
||||
assert fnp.RespondBlock.from_bytes(res.data).block.transactions_generator is None
|
||||
assert not block_has_transactions_generator(fnp.RespondBlock.from_bytes(res.data).block)
|
||||
|
||||
# Ask with transactions
|
||||
res = await full_node_1.request_block(fnp.RequestBlock(blocks[-1].height, True))
|
||||
assert res is not None
|
||||
assert res.type != ProtocolMessageTypes.reject_block.value
|
||||
assert fnp.RespondBlock.from_bytes(res.data).block.transactions_generator is not None
|
||||
assert block_has_transactions_generator(fnp.RespondBlock.from_bytes(res.data).block)
|
||||
|
||||
# Ask for another one
|
||||
res = await full_node_1.request_block(fnp.RequestBlock(uint32(blocks[-1].height - 1), True))
|
||||
@@ -1728,14 +1735,14 @@ async def test_request_blocks(
|
||||
fetched_blocks = fnp.RespondBlocks.from_bytes(res.data).blocks
|
||||
assert len(fetched_blocks) == 6
|
||||
for b in fetched_blocks:
|
||||
assert b.transactions_generator is None
|
||||
assert not block_has_transactions_generator(b)
|
||||
|
||||
# Ask with transactions
|
||||
res = await full_node_1.request_blocks(fnp.RequestBlocks(uint32(peak_height - 5), uint32(peak_height), True))
|
||||
assert res is not None
|
||||
fetched_blocks = fnp.RespondBlocks.from_bytes(res.data).blocks
|
||||
assert len(fetched_blocks) == 6
|
||||
assert fetched_blocks[-1].transactions_generator is not None
|
||||
assert block_has_transactions_generator(fetched_blocks[-1])
|
||||
assert std_hash(fetched_blocks[-1]) == std_hash(blocks_t[-1])
|
||||
|
||||
|
||||
@@ -2064,6 +2071,7 @@ async def test_unfinished_block_with_replaced_generator(
|
||||
if committment > 6:
|
||||
generator_refs = [uint32(n) for n in range(600)]
|
||||
|
||||
use_v1 = block.version == 1
|
||||
unf = UnfinishedBlock(
|
||||
block.finished_sub_slots[:] if not overflow else block.finished_sub_slots[:-1],
|
||||
reward_chain_block,
|
||||
@@ -2072,10 +2080,10 @@ async def test_unfinished_block_with_replaced_generator(
|
||||
foliage,
|
||||
transaction_block,
|
||||
transactions_info,
|
||||
replaced_generator,
|
||||
None if use_v1 else replaced_generator,
|
||||
generator_refs,
|
||||
None,
|
||||
uint8(0),
|
||||
bytes(replaced_generator) if use_v1 else None,
|
||||
block.version,
|
||||
)
|
||||
|
||||
_, header_error = await full_node_1.full_node.blockchain.validate_unfinished_block_header(unf)
|
||||
@@ -2126,7 +2134,8 @@ async def test_double_blocks_same_pospace(
|
||||
new_m = block_2.foliage.foliage_transaction_block_hash
|
||||
new_fbh_sig = bt.get_plot_signature(new_m, blocks[-1].reward_chain_block.proof_of_space.plot_public_key)
|
||||
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fbh_sig)
|
||||
block_2 = recursive_replace(block_2, "transactions_generator", None)
|
||||
block_2 = block_2.replace(transactions_generator=None, transactions_generator_buffer=None)
|
||||
assert not block_has_transactions_generator(block_2)
|
||||
|
||||
rb_task = create_referenced_task(full_node_2.full_node.add_block(block_2, dummy_peer))
|
||||
|
||||
@@ -3556,7 +3565,8 @@ async def validate_coin_set(coin_store: CoinStoreProtocol, blocks: list[FullBloc
|
||||
assert rec.coin == reward
|
||||
assert rec.coinbase
|
||||
|
||||
if block.transactions_generator is None:
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
if generator_bytes is None:
|
||||
if len(records) > 0: # pragma: no cover
|
||||
print(f"height: {block.height} unexpected coins in the DB: {records} TX: No")
|
||||
print_coin_records(records)
|
||||
@@ -3568,7 +3578,7 @@ async def validate_coin_set(coin_store: CoinStoreProtocol, blocks: list[FullBloc
|
||||
# assert False
|
||||
|
||||
flags = get_flags_for_height_and_constants(block.height, test_constants)
|
||||
additions, removals = additions_and_removals(bytes(block.transactions_generator), [], flags, test_constants)
|
||||
additions, removals = additions_and_removals(generator_bytes, [], flags, test_constants)
|
||||
|
||||
for add, hint in additions:
|
||||
rec = records.pop(add.name())
|
||||
@@ -4360,7 +4370,11 @@ def compare_unfinished_blocks(block1: UnfinishedBlock, block2: UnfinishedBlock)
|
||||
assert block1.foliage_transaction_block == block2.foliage_transaction_block, "Mismatch in foliage_transaction_block"
|
||||
assert block1.transactions_info == block2.transactions_info, "Mismatch in transactions_info"
|
||||
assert block1.transactions_generator == block2.transactions_generator, "Mismatch in transactions_generator"
|
||||
assert block1.transactions_generator_buffer == block2.transactions_generator_buffer, (
|
||||
"Mismatch in transactions_generator_buffer"
|
||||
)
|
||||
assert block1.transactions_generator_ref_list == block2.transactions_generator_ref_list
|
||||
assert block1.version == block2.version, "Mismatch in version"
|
||||
|
||||
# Final assertion to check the entire block
|
||||
assert block1 == block2, "The entire block objects are not identical"
|
||||
@@ -5039,7 +5053,7 @@ async def test_declare_proof_of_space_empty_block_no_new_tx_window_yet(
|
||||
)
|
||||
unfinished_block = await declare_pos_unfinished_block(full_node_api, dummy_peer, candidate_block)
|
||||
# Make sure we created an empty block
|
||||
assert unfinished_block.transactions_generator is None
|
||||
assert not block_has_transactions_generator(unfinished_block)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -5073,6 +5087,6 @@ async def test_declare_proof_of_space_unfinished_block_includes_block_generator(
|
||||
)
|
||||
candidate_block = blocks[-1]
|
||||
unfinished_block = await declare_pos_unfinished_block(full_node_api, dummy_peer, candidate_block)
|
||||
assert unfinished_block.transactions_generator is not None
|
||||
assert block_has_transactions_generator(unfinished_block)
|
||||
assert unfinished_block.transactions_info is not None
|
||||
assert unfinished_block.transactions_info.cost > 0
|
||||
|
||||
@@ -224,6 +224,7 @@ def _make_block_ns(
|
||||
is_transaction_block=lambda: is_tx_block,
|
||||
foliage_transaction_block=ftb,
|
||||
transactions_generator=b"gen" if has_generator else None,
|
||||
transactions_generator_buffer=None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@ async def test_lookup_coin_ids(bt: BlockTools, empty_blockchain: Blockchain) ->
|
||||
scs = StateChangeSummary(br, uint32(0), [], removals, additions, rewards)
|
||||
|
||||
# Removal ID and addition PH
|
||||
has_coin_sub = lambda c: c == coin_ids[1] # noqa: E731
|
||||
has_ph_sub = lambda ph: ph == phs[4] # noqa: E731
|
||||
has_coin_sub = lambda c: c == coin_ids[1] # ruff: ignore[lambda-assignment]
|
||||
has_ph_sub = lambda ph: ph == phs[4] # ruff: ignore[lambda-assignment]
|
||||
|
||||
_, lookup_coin_ids = get_hints_and_subscription_coin_ids(scs, has_coin_sub, has_ph_sub)
|
||||
|
||||
@@ -75,8 +75,8 @@ async def test_lookup_coin_ids(bt: BlockTools, empty_blockchain: Blockchain) ->
|
||||
assert set(lookup_coin_ids) == {coin_ids[1], first_coin_id, second_coin_id}
|
||||
|
||||
# Removal PH and addition ID
|
||||
has_coin_sub = lambda c: c == first_coin_id # noqa: E731
|
||||
has_ph_sub = lambda ph: ph == phs[0] # noqa: E731
|
||||
has_coin_sub = lambda c: c == first_coin_id # ruff: ignore[lambda-assignment]
|
||||
has_ph_sub = lambda ph: ph == phs[0] # ruff: ignore[lambda-assignment]
|
||||
|
||||
_, lookup_coin_ids = get_hints_and_subscription_coin_ids(scs, has_coin_sub, has_ph_sub)
|
||||
assert set(lookup_coin_ids) == {first_coin_id, coin_ids[0], coin_ids[2]}
|
||||
@@ -84,19 +84,19 @@ async def test_lookup_coin_ids(bt: BlockTools, empty_blockchain: Blockchain) ->
|
||||
# Subscribe to hint
|
||||
third_coin_id: bytes32 = Coin(bytes32(coin_ids[2]), phs[9], uint64(123)).name()
|
||||
|
||||
has_ph_sub = lambda ph: ph == bytes32(b"1" * 32) # noqa: E731
|
||||
has_ph_sub = lambda ph: ph == bytes32(b"1" * 32) # ruff: ignore[lambda-assignment]
|
||||
|
||||
_, lookup_coin_ids = get_hints_and_subscription_coin_ids(scs, no_sub, has_ph_sub)
|
||||
assert set(lookup_coin_ids) == {first_coin_id, third_coin_id}
|
||||
|
||||
# Reward PH
|
||||
has_ph_sub = lambda ph: ph == rewards[0].puzzle_hash # noqa: E731
|
||||
has_ph_sub = lambda ph: ph == rewards[0].puzzle_hash # ruff: ignore[lambda-assignment]
|
||||
|
||||
_, lookup_coin_ids = get_hints_and_subscription_coin_ids(scs, no_sub, has_ph_sub)
|
||||
assert set(lookup_coin_ids) == {rewards[0].name(), rewards[2].name()}
|
||||
|
||||
# Reward coin id + reward ph
|
||||
has_coin_sub = lambda c: c == rewards[1].name() # noqa: E731
|
||||
has_coin_sub = lambda c: c == rewards[1].name() # ruff: ignore[lambda-assignment]
|
||||
|
||||
_, lookup_coin_ids = get_hints_and_subscription_coin_ids(scs, has_coin_sub, has_ph_sub)
|
||||
assert set(lookup_coin_ids) == {rewards[1].name(), rewards[0].name(), rewards[2].name()}
|
||||
|
||||
@@ -5,7 +5,7 @@ import random
|
||||
|
||||
import pytest
|
||||
from chia_rs import BlockRecord, UnfinishedBlock
|
||||
from chia_rs.sized_ints import uint8, uint64
|
||||
from chia_rs.sized_ints import uint64
|
||||
|
||||
from chia._tests.connection_utils import add_dummy_connection
|
||||
from chia._tests.core.full_node.stores.test_coin_store import get_future_reward_coins
|
||||
@@ -150,9 +150,9 @@ class TestPerformance:
|
||||
block.foliage_transaction_block,
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_ref_list,
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
|
||||
with benchmark_runner.assert_runtime(seconds=0.1, label="unfinished"):
|
||||
@@ -162,7 +162,7 @@ class TestPerformance:
|
||||
|
||||
with benchmark_runner.assert_runtime(seconds=0.1, label="full block"):
|
||||
# No transactions generator, the full node already cached it from the unfinished block
|
||||
block_small = block.replace(transactions_generator=None)
|
||||
block_small = block.replace(transactions_generator=None, transactions_generator_buffer=None)
|
||||
res = await full_node_1.full_node.add_block(block_small)
|
||||
|
||||
log.warning(f"Res: {res}")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from chia_rs.sized_ints import uint32
|
||||
|
||||
from chia.full_node.full_node_api import ses_intervals_for_range
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ses_heights, start, end, expected",
|
||||
[
|
||||
# Too few SES entries → empty.
|
||||
([], 10, 20, []),
|
||||
([100], 50, 150, []),
|
||||
# start before the first SES height.
|
||||
([100, 200, 300, 400], 50, 150, []),
|
||||
# start after the last SES height.
|
||||
([100, 200, 300, 400], 400, 500, []),
|
||||
([100, 200, 300, 400], 500, 600, []),
|
||||
# Entire request inside one SES interval.
|
||||
([100, 200, 300, 400], 100, 150, [(100, 200)]),
|
||||
([100, 200, 300, 400], 150, 199, [(100, 200)]),
|
||||
# end on the next boundary is treated as spanning (strict upper bound).
|
||||
([100, 200, 300, 400], 150, 200, [(100, 200), (200, 300)]),
|
||||
# Request spans two SES intervals.
|
||||
([100, 200, 300, 400], 150, 250, [(100, 200), (200, 300)]),
|
||||
# start exactly on an SES boundary.
|
||||
([100, 200, 300, 400], 200, 250, [(200, 300)]),
|
||||
([100, 200, 300, 400], 200, 350, [(200, 300), (300, 400)]),
|
||||
# Last interval: cannot append a following SES even if end is past it.
|
||||
([100, 200, 300, 400], 350, 999, [(300, 400)]),
|
||||
],
|
||||
ids=[
|
||||
"empty_heights",
|
||||
"single_height",
|
||||
"before_first",
|
||||
"at_last_height",
|
||||
"after_last",
|
||||
"first_interval_at_start",
|
||||
"first_interval_interior",
|
||||
"end_on_next_boundary_spans",
|
||||
"spans_two",
|
||||
"on_boundary_same_interval",
|
||||
"on_boundary_spans",
|
||||
"last_interval_only",
|
||||
],
|
||||
)
|
||||
def test_ses_intervals_for_range(
|
||||
ses_heights: list[int],
|
||||
start: int,
|
||||
end: int,
|
||||
expected: list[tuple[int, int]],
|
||||
) -> None:
|
||||
result = ses_intervals_for_range(
|
||||
[uint32(h) for h in ses_heights],
|
||||
uint32(start),
|
||||
uint32(end),
|
||||
)
|
||||
assert [(int(a), int(b)) for a, b in result] == expected
|
||||
@@ -45,9 +45,10 @@ from chia._tests.util.time_out_assert import time_out_assert
|
||||
from chia.consensus.condition_costs import ConditionCost
|
||||
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 DedupCoinSpend, IdenticalSpendDedup
|
||||
from chia.full_node.fee_estimation import EmptyMempoolInfo, MempoolInfo
|
||||
from chia.full_node.full_node_api import FullNodeAPI
|
||||
from chia.full_node.mempool import MAX_SPENDS_PER_BLOCK, Mempool
|
||||
from chia.full_node.mempool import MAX_BLOCK_ATOMS, MAX_BLOCK_PAIRS, MAX_SKIPPED_ITEMS, MAX_SPENDS_PER_BLOCK, Mempool
|
||||
from chia.full_node.mempool_manager import MEMPOOL_MIN_FEE_INCREASE, LineageInfoCache
|
||||
from chia.full_node.pending_tx_cache import ConflictTxCache, PendingTxCache
|
||||
from chia.protocols import full_node_protocol, wallet_protocol
|
||||
@@ -71,7 +72,7 @@ from chia.types.condition_with_args import ConditionWithArgs
|
||||
from chia.types.fee_rate import FeeRate
|
||||
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.types.mempool_item import BundleCoinSpend, MempoolItem, UnspentLineageInfo
|
||||
from chia.util.casts import int_to_bytes
|
||||
from chia.util.errors import Err, ValidationError
|
||||
from chia.util.hash import std_hash
|
||||
@@ -2242,7 +2243,7 @@ def generator_condition_tester(
|
||||
height: uint32,
|
||||
coin_amount: int = 123,
|
||||
) -> NPCResult:
|
||||
prg = f"(q ((0x0101010101010101010101010101010101010101010101010101010101010101 {'(q ' if quote else ''} {conditions} {')' if quote else ''} {coin_amount} (() (q . ())))))" # noqa
|
||||
prg = f"(q ((0x0101010101010101010101010101010101010101010101010101010101010101 {'(q ' if quote else ''} {conditions} {')' if quote else ''} {coin_amount} (() (q . ())))))" # ruff: ignore[line-too-long]
|
||||
print(f"program: {prg}")
|
||||
program = SerializedProgram.from_bytes(binutils.assemble(prg).as_bin())
|
||||
generator = BlockGenerator(program, [])
|
||||
@@ -2499,7 +2500,7 @@ class TestGeneratorConditions:
|
||||
puzzle_hash = "abababababababababababababababab"
|
||||
program = SerializedProgram.from_bytes(
|
||||
binutils.assemble(
|
||||
f'(q ((0x0101010101010101010101010101010101010101010101010101010101010101 (q (51 "{puzzle_hash}" 10)) 123 (() (q . ())))(0x0101010101010101010101010101010101010101010101010101010101010102 (q (51 "{puzzle_hash}" 10)) 123 (() (q . ()))) ))' # noqa
|
||||
f'(q ((0x0101010101010101010101010101010101010101010101010101010101010101 (q (51 "{puzzle_hash}" 10)) 123 (() (q . ())))(0x0101010101010101010101010101010101010101010101010101010101010102 (q (51 "{puzzle_hash}" 10)) 123 (() (q . ()))) ))' # ruff: ignore[line-too-long]
|
||||
).as_bin()
|
||||
)
|
||||
generator = BlockGenerator(program, [])
|
||||
@@ -2623,7 +2624,7 @@ class TestGeneratorConditions:
|
||||
# )
|
||||
# with A=28 and B specified as {num}
|
||||
|
||||
SINGLE_ARG_INT_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) ())) (c 11 ())))) (c (q (a (i 11 (q 4 5 (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 28 {num})))" # noqa
|
||||
SINGLE_ARG_INT_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) ())) (c 11 ())))) (c (q (a (i 11 (q 4 5 (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 28 {num})))" # ruff: ignore[line-too-long]
|
||||
|
||||
# this program:
|
||||
# (mod (A B)
|
||||
@@ -2638,7 +2639,7 @@ SINGLE_ARG_INT_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (concat (a 6 (c 2
|
||||
# truncates the first byte of the large string being passed down for each
|
||||
# iteration, in an attempt to defeat any caching of integers by node ID.
|
||||
# substr is cheap, and no memory is copied, so we can perform a lot of these
|
||||
SINGLE_ARG_INT_SUBSTR_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c 5 ())) (a 4 (c 2 (c (substr 5 (q . 1)) (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 28 {num})))" # noqa
|
||||
SINGLE_ARG_INT_SUBSTR_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c 5 ())) (a 4 (c 2 (c (substr 5 (q . 1)) (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 28 {num})))" # ruff: ignore[line-too-long]
|
||||
|
||||
# this program:
|
||||
# (mod (A B)
|
||||
@@ -2650,7 +2651,7 @@ SINGLE_ARG_INT_SUBSTR_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {fille
|
||||
# )
|
||||
# (iter (concat (large_string 0x00 A) (q . 0xffffffff)) B)
|
||||
# )
|
||||
SINGLE_ARG_INT_SUBSTR_TAIL_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c 5 ())) (a 4 (c 2 (c (substr 5 () (- (strlen 5) (q . 1))) (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 25 {num})))" # noqa
|
||||
SINGLE_ARG_INT_SUBSTR_TAIL_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (q . {val})) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c 5 ())) (a 4 (c 2 (c (substr 5 () (- (strlen 5) (q . 1))) (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 25 {num})))" # ruff: ignore[line-too-long]
|
||||
|
||||
# (mod (A B)
|
||||
# (defun large_string (V N)
|
||||
@@ -2661,7 +2662,7 @@ SINGLE_ARG_INT_SUBSTR_TAIL_COND = "(a (q 2 4 (c 2 (c (concat (a 6 (c 2 (c (q . {
|
||||
# )
|
||||
# (iter (large_string 0x00 A) B)
|
||||
# )
|
||||
SINGLE_ARG_INT_LADDER_COND = "(a (q 2 4 (c 2 (c (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c (concat 5 11) ())) (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 24 {num})))" # noqa
|
||||
SINGLE_ARG_INT_LADDER_COND = "(a (q 2 4 (c 2 (c (a 6 (c 2 (c (q . {filler}) (c 5 ())))) (c 11 ())))) (c (q (a (i 11 (q 4 (c (q . {opcode}) (c (concat 5 11) ())) (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 2 (i 11 (q 2 6 (c 2 (c (concat 5 5) (c (- 11 (q . 1)) ())))) (q . 5)) 1) (q 24 {num})))" # ruff: ignore[line-too-long]
|
||||
|
||||
# this program:
|
||||
# (mod (A B)
|
||||
@@ -2675,7 +2676,7 @@ SINGLE_ARG_INT_LADDER_COND = "(a (q 2 4 (c 2 (c (a 6 (c 2 (c (q . {filler}) (c 5
|
||||
# )
|
||||
# with B set to {num}
|
||||
|
||||
CREATE_ANNOUNCE_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (a 6 (c 2 (c 5 ()))) ())) (c 11 ())))) (c (q (a (i 11 (q 4 5 (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 23 (q . 97) 5) (q 8184 {num})))" # noqa
|
||||
CREATE_ANNOUNCE_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (a 6 (c 2 (c 5 ()))) ())) (c 11 ())))) (c (q (a (i 11 (q 4 5 (a 4 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) 23 (q . 97) 5) (q 8184 {num})))" # ruff: ignore[line-too-long]
|
||||
|
||||
# this program:
|
||||
# (mod (A)
|
||||
@@ -2684,7 +2685,7 @@ CREATE_ANNOUNCE_COND = "(a (q 2 4 (c 2 (c (c (q . {opcode}) (c (a 6 (c 2 (c 5 ()
|
||||
# )
|
||||
# (iter (q 51 "abababababababababababababababab" 1) A)
|
||||
# )
|
||||
CREATE_COIN = '(a (q 2 2 (c 2 (c (q 51 "abababababababababababababababab" 1) (c 5 ())))) (c (q 2 (i 11 (q 4 5 (a 2 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # noqa
|
||||
CREATE_COIN = '(a (q 2 2 (c 2 (c (q 51 "abababababababababababababababab" 1) (c 5 ())))) (c (q 2 (i 11 (q 4 5 (a 2 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # ruff: ignore[line-too-long]
|
||||
|
||||
# this program:
|
||||
# (mod (A)
|
||||
@@ -2700,7 +2701,7 @@ CREATE_COIN = '(a (q 2 2 (c 2 (c (q 51 "abababababababababababababababab" 1) (c
|
||||
# (iter (q 51 "abababababababababababababababab") A)
|
||||
# )
|
||||
# creates {num} CREATE_COIN conditions, each with a different amount
|
||||
CREATE_UNIQUE_COINS = '(a (q 2 6 (c 2 (c (q 51 "abababababababababababababababab") (c 5 ())))) (c (q (a (i 5 (q 4 9 (a 4 (c 2 (c 13 (c 11 ()))))) (q 4 11 ())) 1) 2 (i 11 (q 4 (a 4 (c 2 (c 5 (c 11 ())))) (a 6 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # noqa
|
||||
CREATE_UNIQUE_COINS = '(a (q 2 6 (c 2 (c (q 51 "abababababababababababababababab") (c 5 ())))) (c (q (a (i 5 (q 4 9 (a 4 (c 2 (c 13 (c 11 ()))))) (q 4 11 ())) 1) 2 (i 11 (q 4 (a 4 (c 2 (c 5 (c 11 ())))) (a 6 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # ruff: ignore[line-too-long]
|
||||
|
||||
|
||||
# some of the malicious tests will fail post soft-fork, this function helps test
|
||||
@@ -3472,6 +3473,82 @@ def test_max_spends_per_block(old: bool) -> None:
|
||||
assert len(generator.removals) == MAX_SPENDS_PER_BLOCK
|
||||
|
||||
|
||||
@pytest.mark.parametrize("old", [True, False])
|
||||
@pytest.mark.parametrize("limit", ["atoms", "pairs"])
|
||||
def test_block_atom_and_pair_limits(old: bool, limit: str) -> None:
|
||||
max_cost = uint64(11_000_000_000)
|
||||
fee_estimator = create_bitcoin_fee_estimator(max_cost)
|
||||
mempool_info = MempoolInfo(
|
||||
CLVMCost(uint64(max_cost * 10)),
|
||||
FeeRate(uint64(1000000)),
|
||||
CLVMCost(max_cost),
|
||||
)
|
||||
mempool = Mempool(mempool_info, fee_estimator)
|
||||
|
||||
# Each item accrues a third of the block limit, so exactly 3 items fit
|
||||
# (the limit is inclusive), and any further item must be skipped. The other
|
||||
# limit (cost, spends, and the counterpart of atoms/pairs) is kept small so
|
||||
# only the limit under test is binding.
|
||||
block_limit = MAX_BLOCK_ATOMS if limit == "atoms" else MAX_BLOCK_PAIRS
|
||||
per_item = block_limit // 3
|
||||
num_atoms = per_item if limit == "atoms" else 0
|
||||
num_pairs = per_item if limit == "pairs" else 0
|
||||
|
||||
num_items = 6
|
||||
for i in range(num_items):
|
||||
item = mk_item([make_coin(i)], cost=1_000_000, fee=100, num_atoms=num_atoms, num_pairs=num_pairs)
|
||||
info = mempool.add_to_pool(item)
|
||||
assert info.error is None
|
||||
|
||||
assert mempool.size() == num_items
|
||||
|
||||
create_block = mempool.create_block_generator if old else mempool.create_block_generator2
|
||||
generator = create_block(test_constants, uint32(0), 30.0)
|
||||
assert generator is not None
|
||||
# 3 * per_item == block_limit, so 3 items fit and the rest are skipped.
|
||||
assert len(generator.removals) == 3
|
||||
|
||||
|
||||
def test_block_atom_saturation_stops_scanning(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
max_cost = uint64(11_000_000_000)
|
||||
fee_estimator = create_bitcoin_fee_estimator(max_cost)
|
||||
mempool_info = MempoolInfo(
|
||||
CLVMCost(uint64(max_cost * 10)),
|
||||
FeeRate(uint64(1000000)),
|
||||
CLVMCost(max_cost),
|
||||
)
|
||||
mempool = Mempool(mempool_info, fee_estimator)
|
||||
|
||||
# Each item takes a third of the atom budget, so only 3 fit; the rest are
|
||||
# skipped. Without an early exit, every later item would still be scanned.
|
||||
per_item = MAX_BLOCK_ATOMS // 3
|
||||
num_items = 3 + MAX_SKIPPED_ITEMS + 5
|
||||
for i in range(num_items):
|
||||
item = mk_item([make_coin(i)], cost=1_000_000, fee=100, num_atoms=per_item)
|
||||
assert mempool.add_to_pool(item).error is None
|
||||
assert mempool.size() == num_items
|
||||
|
||||
dedup_calls = 0
|
||||
original_get_deduplication_info = IdenticalSpendDedup.get_deduplication_info
|
||||
|
||||
def counting_get_deduplication_info(
|
||||
self: IdenticalSpendDedup, *, bundle_coin_spends: dict[bytes32, BundleCoinSpend]
|
||||
) -> tuple[list[CoinSpend], uint64, list[Coin], dict[bytes32, DedupCoinSpend]]:
|
||||
nonlocal dedup_calls
|
||||
dedup_calls += 1
|
||||
return original_get_deduplication_info(self, bundle_coin_spends=bundle_coin_spends)
|
||||
|
||||
monkeypatch.setattr(IdenticalSpendDedup, "get_deduplication_info", counting_get_deduplication_info)
|
||||
|
||||
generator = mempool.create_block_generator2(test_constants, uint32(0), 30.0)
|
||||
assert generator is not None
|
||||
# Only 3 items fit the atom budget.
|
||||
assert len(generator.removals) == 3
|
||||
# Scanning stops after MAX_SKIPPED_ITEMS skips: 3 fitting + MAX_SKIPPED_ITEMS
|
||||
# skipped items are processed, and none beyond that.
|
||||
assert dedup_calls == 3 + MAX_SKIPPED_ITEMS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("old", [True, False])
|
||||
def test_max_spends_per_block_with_dedup(old: bool) -> None:
|
||||
max_cost = uint64(11_000_000_000)
|
||||
|
||||
@@ -78,6 +78,7 @@ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
DEFAULT_HIDDEN_PUZZLE_HASH,
|
||||
calculate_synthetic_secret_key,
|
||||
)
|
||||
from chia.wallet.puzzles.singleton_top_layer_v1_1 import MELT_CONDITION, puzzle_for_singleton, solution_for_singleton
|
||||
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
@@ -343,6 +344,8 @@ def make_test_conds(
|
||||
before_seconds_relative: int | None = None,
|
||||
before_seconds_absolute: int | None = None,
|
||||
cost: int = 0,
|
||||
num_atoms: int = 0,
|
||||
num_pairs: int = 0,
|
||||
spend_ids: Sequence[tuple[bytes32 | Coin, int]] = [(TEST_COIN_ID, 0)],
|
||||
created_coins: list[list[CreateCoin]] | None = None,
|
||||
) -> SpendBundleConditions:
|
||||
@@ -397,8 +400,8 @@ def make_test_conds(
|
||||
False,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
num_atoms,
|
||||
num_pairs,
|
||||
0,
|
||||
)
|
||||
|
||||
@@ -949,6 +952,8 @@ def mk_item(
|
||||
*,
|
||||
cost: int = 1,
|
||||
fee: int = 0,
|
||||
num_atoms: int = 0,
|
||||
num_pairs: int = 0,
|
||||
assert_height: int | None = None,
|
||||
assert_before_height: int | None = None,
|
||||
assert_before_seconds: int | None = None,
|
||||
@@ -970,7 +975,7 @@ def mk_item(
|
||||
coin_spends.append(coin_spend)
|
||||
bundle_coin_spends[coin_id] = mk_bcs(coin_spend, f)
|
||||
spend_bundle = SpendBundle(coin_spends, G2Element())
|
||||
conds = make_test_conds(cost=cost, spend_ids=spend_ids)
|
||||
conds = make_test_conds(cost=cost, num_atoms=num_atoms, num_pairs=num_pairs, spend_ids=spend_ids)
|
||||
return MempoolItem(
|
||||
aggregated_signature=spend_bundle.aggregated_signature,
|
||||
fee=uint64(fee),
|
||||
@@ -2498,11 +2503,8 @@ class TestCoins:
|
||||
|
||||
# creates a CoinSpend of a made up
|
||||
def make_singleton_spend(
|
||||
launcher_id: bytes32, parent_parent_id: bytes32 = bytes32([3] * 32), child_amount: int = 1
|
||||
launcher_id: bytes32, parent_parent_id: bytes32 = bytes32([3] * 32), child_amount: int = 1, *, melt: bool = False
|
||||
) -> CoinSpend:
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.puzzles.singleton_top_layer_v1_1 import puzzle_for_singleton, solution_for_singleton
|
||||
|
||||
singleton_puzzle = puzzle_for_singleton(launcher_id, Program.to(1)).to_serialized()
|
||||
|
||||
PARENT_COIN = Coin(parent_parent_id, singleton_puzzle.get_tree_hash(), uint64(1))
|
||||
@@ -2510,7 +2512,9 @@ def make_singleton_spend(
|
||||
|
||||
lineage_proof = LineageProof(parent_parent_id, IDENTITY_PUZZLE_HASH, uint64(1))
|
||||
|
||||
inner_solution = Program.to([[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, uint64(child_amount)]])
|
||||
inner_solution = Program.to(
|
||||
[MELT_CONDITION] if melt else [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, uint64(child_amount)]]
|
||||
)
|
||||
singleton_solution = solution_for_singleton(lineage_proof, uint64(1), inner_solution).to_serialized()
|
||||
|
||||
ret = CoinSpend(COIN, singleton_puzzle, singleton_solution)
|
||||
@@ -3573,3 +3577,28 @@ async def test_mempool_item_to_spend_bundle() -> None:
|
||||
result = mi.to_spend_bundle()
|
||||
assert result == sb
|
||||
assert result.name() == sb_name
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_bundle_with_old_ff_spend_plus_melt_ff_spend() -> None:
|
||||
"""
|
||||
Covers the scenario where a spend bundle has a coin that gets spent by
|
||||
another spend immediately after fast forward, to make sure we reject that
|
||||
spend bundle.
|
||||
"""
|
||||
launcher_id = bytes32([1] * 32)
|
||||
version_1_spend = make_singleton_spend(launcher_id, bytes32([2] * 32))
|
||||
version_2_spend = make_singleton_spend(launcher_id, bytes32([3] * 32))
|
||||
version_3_spend = make_singleton_spend(launcher_id, bytes32([4] * 32), melt=True)
|
||||
coins = TestCoins(
|
||||
[version_1_spend.coin, version_2_spend.coin, version_3_spend.coin],
|
||||
{version_3_spend.coin.puzzle_hash: version_3_spend},
|
||||
)
|
||||
coins.spend_coin(version_1_spend.coin.name())
|
||||
coins.spend_coin(version_2_spend.coin.name())
|
||||
sb = SpendBundle([version_1_spend, version_3_spend], G2Element())
|
||||
async with setup_mempool(coins) as mempool_manager:
|
||||
_, status, error = await add_spendbundle(mempool_manager, sb, sb.name())
|
||||
assert status == MempoolInclusionStatus.FAILED
|
||||
assert error == Err.DOUBLE_SPEND
|
||||
assert_sb_not_in_pool(mempool_manager, sb)
|
||||
|
||||
@@ -58,7 +58,7 @@ async def main() -> None:
|
||||
|
||||
async def dun() -> None:
|
||||
# TODO: switch to event driven code
|
||||
while shutdown_path.exists(): # noqa: ASYNC110
|
||||
while shutdown_path.exists(): # ruff: ignore[async-busy-wait]
|
||||
await asyncio.sleep(0.25)
|
||||
|
||||
task.cancel()
|
||||
|
||||
@@ -80,7 +80,7 @@ async def async_main(
|
||||
|
||||
async def dun() -> None:
|
||||
# TODO: switch to event driven code
|
||||
while shutdown_path.exists(): # noqa: ASYNC110
|
||||
while shutdown_path.exists(): # ruff: ignore[async-busy-wait]
|
||||
await asyncio.sleep(0.25)
|
||||
|
||||
thread_end_event.set()
|
||||
@@ -98,7 +98,7 @@ async def async_main(
|
||||
try:
|
||||
try:
|
||||
# TODO: switch to event driven code
|
||||
while not thread_end_event.is_set(): # noqa: ASYNC110
|
||||
while not thread_end_event.is_set(): # ruff: ignore[async-busy-wait]
|
||||
await asyncio.sleep(0.1)
|
||||
finally:
|
||||
# the test checks explicitly for this
|
||||
|
||||
@@ -31,18 +31,18 @@ def test_base_event_loop_has_methods() -> None:
|
||||
method = getattr(base_selector_event_loop, "create_server")
|
||||
assert inspect.ismethod(method)
|
||||
if sys.version_info >= (3, 13):
|
||||
expected_signature = "(protocol_factory, host=None, port=None, *, family=<AddressFamily.AF_UNSPEC: 0>, flags=<AddressInfo.AI_PASSIVE: 1>, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, keep_alive=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True)" # noqa: E501
|
||||
expected_signature = "(protocol_factory, host=None, port=None, *, family=<AddressFamily.AF_UNSPEC: 0>, flags=<AddressInfo.AI_PASSIVE: 1>, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, keep_alive=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True)" # ruff: ignore[line-too-long]
|
||||
elif sys.version_info >= (3, 11):
|
||||
expected_signature = "(protocol_factory, host=None, port=None, *, family=<AddressFamily.AF_UNSPEC: 0>, flags=<AddressInfo.AI_PASSIVE: 1>, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True)" # noqa: E501
|
||||
expected_signature = "(protocol_factory, host=None, port=None, *, family=<AddressFamily.AF_UNSPEC: 0>, flags=<AddressInfo.AI_PASSIVE: 1>, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True)" # ruff: ignore[line-too-long]
|
||||
else:
|
||||
expected_signature = "(protocol_factory, host=None, port=None, *, family=<AddressFamily.AF_UNSPEC: 0>, flags=<AddressInfo.AI_PASSIVE: 1>, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, start_serving=True)" # noqa: E501
|
||||
expected_signature = "(protocol_factory, host=None, port=None, *, family=<AddressFamily.AF_UNSPEC: 0>, flags=<AddressInfo.AI_PASSIVE: 1>, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, start_serving=True)" # ruff: ignore[line-too-long]
|
||||
assert str(inspect.signature(method)) == expected_signature
|
||||
|
||||
assert hasattr(base_selector_event_loop, "_start_serving")
|
||||
method = getattr(base_selector_event_loop, "_start_serving")
|
||||
assert inspect.ismethod(method)
|
||||
if sys.version_info >= (3, 11):
|
||||
expected_signature = "(protocol_factory, sock, sslcontext=None, server=None, backlog=100, ssl_handshake_timeout=60.0, ssl_shutdown_timeout=30.0)" # noqa: E501
|
||||
expected_signature = "(protocol_factory, sock, sslcontext=None, server=None, backlog=100, ssl_handshake_timeout=60.0, ssl_shutdown_timeout=30.0)" # ruff: ignore[line-too-long]
|
||||
else:
|
||||
expected_signature = (
|
||||
"(protocol_factory, sock, sslcontext=None, server=None, backlog=100, ssl_handshake_timeout=60.0)"
|
||||
@@ -60,11 +60,11 @@ def test_base_event_loop_has_methods() -> None:
|
||||
assert inspect.ismethod(method)
|
||||
assert (
|
||||
inspect.getsource(method)
|
||||
== " async def create_server(self, *args, **kwargs) -> PausableServer: # type: ignore[no-untyped-def]\n return await _chia_create_server(super(), *args, **kwargs)\n" # noqa: E501
|
||||
== " async def create_server(self, *args, **kwargs) -> PausableServer: # type: ignore[no-untyped-def]\n return await _chia_create_server(super(), *args, **kwargs)\n" # ruff: ignore[line-too-long]
|
||||
)
|
||||
|
||||
assert inspect.isfunction(_chia_create_server)
|
||||
expected_signature = "(cls: 'Any', protocol_factory: '_ProtocolFactory', host: 'Any', port: 'Any', *, family: 'socket.AddressFamily' = <AddressFamily.AF_UNSPEC: 0>, flags: 'socket.AddressInfo' = <AddressInfo.AI_PASSIVE: 1>, sock: 'Any' = None, backlog: 'int' = 100, ssl: '_SSLContext' = None, reuse_address: 'bool | None' = None, reuse_port: 'bool | None' = None, ssl_handshake_timeout: 'float | None' = 30, start_serving: 'bool' = True) -> 'PausableServer'" # noqa: E501
|
||||
expected_signature = "(cls: 'Any', protocol_factory: '_ProtocolFactory', host: 'Any', port: 'Any', *, family: 'socket.AddressFamily' = <AddressFamily.AF_UNSPEC: 0>, flags: 'socket.AddressInfo' = <AddressInfo.AI_PASSIVE: 1>, sock: 'Any' = None, backlog: 'int' = 100, ssl: '_SSLContext' = None, reuse_address: 'bool | None' = None, reuse_port: 'bool | None' = None, ssl_handshake_timeout: 'float | None' = 30, start_serving: 'bool' = True) -> 'PausableServer'" # ruff: ignore[line-too-long]
|
||||
assert str(inspect.signature(_chia_create_server)) == expected_signature
|
||||
|
||||
class EchoProtocol(asyncio.Protocol):
|
||||
|
||||
@@ -166,7 +166,7 @@ async def test_loop(tmp_path: pathlib.Path) -> None:
|
||||
|
||||
logger.info(" ==== launching serve.py")
|
||||
# TODO: is there some reason not to use an async process here?
|
||||
with subprocess.Popen( # noqa: ASYNC220
|
||||
with subprocess.Popen( # ruff: ignore[create-subprocess-in-async-function]
|
||||
[sys.executable, "-m", "chia._tests.core.server.serve", os.fspath(serve_file)],
|
||||
):
|
||||
logger.info(" ==== serve.py running")
|
||||
@@ -175,7 +175,7 @@ async def test_loop(tmp_path: pathlib.Path) -> None:
|
||||
|
||||
logger.info(" ==== launching flood.py")
|
||||
# TODO: is there some reason not to use an async process here?
|
||||
with subprocess.Popen( # noqa: ASYNC220
|
||||
with subprocess.Popen( # ruff: ignore[create-subprocess-in-async-function]
|
||||
[sys.executable, "-m", "chia._tests.core.server.flood", os.fspath(flood_file)],
|
||||
):
|
||||
logger.info(" ==== flood.py running")
|
||||
|
||||
@@ -218,7 +218,7 @@ async def test_connection_string_conversion(
|
||||
_, _, server_1, server_2, _ = two_nodes_one_block
|
||||
peer = await connect_and_get_peer(server_1, server_2, self_hostname)
|
||||
# 1100 is based on the current implementation (example below), should be reconsidered/adjusted if this test fails
|
||||
# WSChiaConnection(local_type=<NodeType.FULL_NODE: 1>, local_port=50632, local_capabilities=[<Capability.BASE: 1>, <Capability.BLOCK_HEADERS: 2>, <Capability.RATE_LIMITS_V2: 3>, <Capability.MEMPOOL_UPDATES: 5>, <Capability.HARD_FORK_2: 6>], peer_info=PeerInfo(_ip=IPv4Address('127.0.0.1'), _port=50640), peer_node_id=<bytes32: 566a318f0f656125b4fef0e85fbddcf9bc77f8003d35293c392479fc5d067f4d>, outbound_rate_limiter=<chia.server.rate_limits.RateLimiter object at 0x114a13f50>, inbound_rate_limiter=<chia.server.rate_limits.RateLimiter object at 0x114a13e90>, is_outbound=False, creation_time=1675271096.275591, bytes_read=68, bytes_written=162, last_message_time=1675271096.276271, peer_server_port=50636, closed=False, connection_type=<NodeType.FULL_NODE: 1>, request_nonce=32768, peer_capabilities=[<Capability.BASE: 1>, <Capability.BLOCK_HEADERS: 2>, <Capability.RATE_LIMITS_V2: 3>, <Capability.MEMPOOL_UPDATES: 5>, <Capability.HARD_FORK_2: 6>], version='', protocol_version=<Version('0.0.36')>) # noqa
|
||||
# WSChiaConnection(local_type=<NodeType.FULL_NODE: 1>, local_port=50632, local_capabilities=[<Capability.BASE: 1>, <Capability.BLOCK_HEADERS: 2>, <Capability.RATE_LIMITS_V2: 3>, <Capability.MEMPOOL_UPDATES: 5>, <Capability.HARD_FORK_2: 6>], peer_info=PeerInfo(_ip=IPv4Address('127.0.0.1'), _port=50640), peer_node_id=<bytes32: 566a318f0f656125b4fef0e85fbddcf9bc77f8003d35293c392479fc5d067f4d>, outbound_rate_limiter=<chia.server.rate_limits.RateLimiter object at 0x114a13f50>, inbound_rate_limiter=<chia.server.rate_limits.RateLimiter object at 0x114a13e90>, is_outbound=False, creation_time=1675271096.275591, bytes_read=68, bytes_written=162, last_message_time=1675271096.276271, peer_server_port=50636, closed=False, connection_type=<NodeType.FULL_NODE: 1>, request_nonce=32768, peer_capabilities=[<Capability.BASE: 1>, <Capability.BLOCK_HEADERS: 2>, <Capability.RATE_LIMITS_V2: 3>, <Capability.MEMPOOL_UPDATES: 5>, <Capability.HARD_FORK_2: 6>], version='', protocol_version=<Version('0.0.36')>) # ruff: ignore[line-too-long]
|
||||
converted = method(peer)
|
||||
print(converted)
|
||||
assert len(converted) < 1200
|
||||
|
||||
@@ -13,6 +13,7 @@ from clvm_tools import binutils
|
||||
from chia._tests.core.make_block_generator import make_block_generator
|
||||
from chia._tests.util.get_name_puzzle_conditions import NPCResult, get_name_puzzle_conditions
|
||||
from chia._tests.util.misc import BenchmarkRunner
|
||||
from chia.consensus.block_generator_info import get_transactions_generator_program
|
||||
from chia.consensus.condition_costs import ConditionCost
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.full_node.bundle_tools import simple_solution_generator
|
||||
@@ -98,8 +99,10 @@ async def test_basics(softfork_height: int, bt: BlockTools) -> None:
|
||||
|
||||
condition_cost = ConditionCost.CREATE_COIN.value + ConditionCost.AGG_SIG.value
|
||||
if softfork_height >= bt.constants.HARD_FORK2_HEIGHT:
|
||||
# CREATE_COIN == NEW_CREATE_COIN + SPEND_COST after HF2
|
||||
condition_cost += ConditionCost.MESSAGE_CONDITION_COST.value
|
||||
clvm_cost = 27360
|
||||
# NEW_COST_MODEL (HF2) increases CLVM execution cost for this spend
|
||||
clvm_cost = 54800
|
||||
elif softfork_height >= bt.constants.HARD_FORK_HEIGHT:
|
||||
clvm_cost = 27360
|
||||
else:
|
||||
@@ -293,11 +296,10 @@ async def test_get_puzzle_and_solution_for_coin_performance(benchmark_runner: Be
|
||||
|
||||
DESERIALIZE_MOD = Program.from_bytes(CHIALISP_DESERIALISATION)
|
||||
|
||||
assert LARGE_BLOCK.transactions_generator is not None
|
||||
generator = get_transactions_generator_program(LARGE_BLOCK)
|
||||
assert generator is not None
|
||||
# first, list all spent coins in the block
|
||||
_, result = run_with_cost(
|
||||
LARGE_BLOCK.transactions_generator, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, [DESERIALIZE_MOD, []]
|
||||
)
|
||||
_, result = run_with_cost(generator, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, [DESERIALIZE_MOD, []])
|
||||
|
||||
coin_spends = result.first()
|
||||
spent_coins: list[Coin] = []
|
||||
@@ -313,13 +315,13 @@ async def test_get_puzzle_and_solution_for_coin_performance(benchmark_runner: Be
|
||||
|
||||
# benchmark the function to pick out the puzzle and solution for a specific
|
||||
# coin
|
||||
generator = BlockGenerator(LARGE_BLOCK.transactions_generator, [])
|
||||
block_generator = BlockGenerator(generator, [])
|
||||
with benchmark_runner.assert_runtime(seconds=8.5):
|
||||
for _ in range(3):
|
||||
for c in spent_coins:
|
||||
puz, _solution = get_puzzle_and_solution_for_coin(
|
||||
generator.program,
|
||||
generator.generator_refs,
|
||||
block_generator.program,
|
||||
block_generator.generator_refs,
|
||||
test_constants.MAX_BLOCK_COST_CLVM,
|
||||
c,
|
||||
get_flags_for_height_and_constants(0, test_constants),
|
||||
|
||||
@@ -78,9 +78,9 @@ async def test1(
|
||||
block.foliage_transaction_block,
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_ref_list,
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
await full_node_api_1.full_node.add_unfinished_block(unf, None)
|
||||
await full_node_api_1.full_node.add_block(block, None)
|
||||
@@ -661,9 +661,9 @@ async def test_get_blockchain_state(
|
||||
block.foliage_transaction_block,
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_ref_list,
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
await full_node_api_1.full_node.add_unfinished_block(unf, None)
|
||||
await full_node_api_1.full_node.add_block(block, None)
|
||||
@@ -776,9 +776,9 @@ async def test_coin_name_found_in_mempool(one_node: SimulatorsAndWalletsServices
|
||||
block.foliage_transaction_block,
|
||||
block.transactions_info,
|
||||
block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block.transactions_generator_ref_list,
|
||||
block.transactions_generator_buffer,
|
||||
block.version,
|
||||
)
|
||||
await full_node_api.full_node.add_unfinished_block(unf, None)
|
||||
await full_node_api.full_node.add_block(block, None)
|
||||
|
||||
@@ -183,7 +183,7 @@ class TestKeychain:
|
||||
# This must be compared to a tuple because the `.mnemonic` property is a list which makes the
|
||||
# class unhashable. We should eventually add support in streamable for varadic tuples and maybe remove
|
||||
# support for the mutable `list`.
|
||||
key_data in (key_data_0, key_data_1, key_data_2) # noqa: PLR6201
|
||||
key_data in (key_data_0, key_data_1, key_data_2) # ruff: ignore[literal-membership]
|
||||
for key_data in keychain.get_keys(include_secrets=True)
|
||||
)
|
||||
|
||||
@@ -235,10 +235,10 @@ class TestKeychain:
|
||||
# Copyright (c) 2017 mruddy
|
||||
# https://github.com/trezor/python-mnemonic/blob/master/tests/test_mnemonic.py
|
||||
# The same sentence in various UTF-8 forms
|
||||
words_nfkd = "Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a" # noqa: E501
|
||||
words_nfc = "P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f" # noqa: E501
|
||||
words_nfkc = "P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f" # noqa: E501
|
||||
words_nfd = "Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a" # noqa: E501
|
||||
words_nfkd = "Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a" # ruff: ignore[line-too-long]
|
||||
words_nfc = "P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f" # ruff: ignore[line-too-long]
|
||||
words_nfkc = "P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f" # ruff: ignore[line-too-long]
|
||||
words_nfd = "Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a" # ruff: ignore[line-too-long]
|
||||
|
||||
seed_nfkd = mnemonic_to_seed(words_nfkd)
|
||||
seed_nfc = mnemonic_to_seed(words_nfc)
|
||||
@@ -435,7 +435,7 @@ async def test_set_label(get_temp_keyring: Keychain) -> None:
|
||||
# This must be compared to a tuple because the `.mnemonic` property is a list which makes the
|
||||
# class unhashable. We should eventually add support in streamable for varadic tuples and maybe remove
|
||||
# support for the mutable `list`.
|
||||
assert all(key_data in (key_data_0, key_data_1) for key_data in keychain.get_keys(include_secrets=True)) # noqa: PLR6201
|
||||
assert all(key_data in (key_data_0, key_data_1) for key_data in keychain.get_keys(include_secrets=True)) # ruff: ignore[literal-membership]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -19,6 +19,7 @@ from chia.rpc.rpc_server import RpcServer
|
||||
from chia.server.server import ChiaServer
|
||||
from chia.server.start_service import Service
|
||||
from chia.simulator.full_node_simulator import FullNodeSimulator
|
||||
from chia.simulator.simulator_protocol import ReorgProtocol
|
||||
from chia.wallet.transaction_record import LightTransactionRecord
|
||||
from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES
|
||||
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig
|
||||
@@ -28,6 +29,7 @@ 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_spend_bundle import WalletSpendBundle
|
||||
from chia.wallet.wallet_state_manager import WalletStateManager
|
||||
|
||||
STANDARD_TX_ENDPOINT_ARGS: dict[str, Any] = TransactionEndpoint(
|
||||
@@ -309,6 +311,7 @@ class WalletTestFramework:
|
||||
trusted_full_node: bool
|
||||
environments: list[WalletEnvironment]
|
||||
tx_config: TXConfig = DEFAULT_TX_CONFIG
|
||||
reorg_exempt: bool = False
|
||||
|
||||
def cmd_tx_endpoint_args(self, env: WalletEnvironment) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -343,7 +346,12 @@ class WalletTestFramework:
|
||||
yield
|
||||
|
||||
async def process_pending_states(
|
||||
self, state_transitions: list[WalletStateTransition], invalid_transactions: list[bytes32] = []
|
||||
self,
|
||||
state_transitions: list[WalletStateTransition],
|
||||
invalid_transactions: list[bytes32] = [],
|
||||
post_reorg_balance_differences: list[WalletStateTransition] = [],
|
||||
bundles_to_repush: list[WalletSpendBundle] = [],
|
||||
reorg_exempt: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
This is the main entry point for processing state in wallet tests. It does the following things:
|
||||
@@ -356,6 +364,8 @@ class WalletTestFramework:
|
||||
6) Checks that if `reuse_puzhash` was set, no new derivations were created
|
||||
7) Ensures the wallet is in a synced state before progressing to the rest of the test
|
||||
"""
|
||||
if len(post_reorg_balance_differences) == 0:
|
||||
post_reorg_balance_differences = [WalletStateTransition()] * len(self.environments)
|
||||
# Take note of the number of puzzle hashes if we're supposed to be reusing
|
||||
if self.tx_config.reuse_puzhash:
|
||||
puzzle_hash_indexes: list[dict[uint32, int]] = []
|
||||
@@ -365,67 +375,92 @@ class WalletTestFramework:
|
||||
ph_indexes[wallet_id] = await env.wallet_state_manager.puzzle_store.get_used_count(wallet_id)
|
||||
puzzle_hash_indexes.append(ph_indexes)
|
||||
|
||||
pending_txs: list[list[LightTransactionRecord]] = []
|
||||
peak = self.full_node.full_node.blockchain.get_peak_height()
|
||||
assert peak is not None
|
||||
# Check balances prior to block
|
||||
try:
|
||||
for i, env in enumerate(self.environments):
|
||||
await self.full_node.wait_for_wallet_synced(wallet_node=env.node, timeout=20, peak_height=peak)
|
||||
try:
|
||||
pending_txs.append(
|
||||
await env.wait_for_transactions_to_settle(
|
||||
self.full_node, _exclude_from_mempool_check=invalid_transactions
|
||||
)
|
||||
)
|
||||
except TimeoutError: # pragma: no cover
|
||||
raise TimeoutError(f"All TXs for env-{i} were not found in mempool or marked as in mempool")
|
||||
for i, (env, transition) in enumerate(zip(self.environments, state_transitions)):
|
||||
try:
|
||||
async with env.wallet_state_manager.db_wrapper.reader_no_transaction():
|
||||
await env.change_balances(transition.pre_block_balance_updates)
|
||||
await env.check_balances(transition.pre_block_additional_balance_info)
|
||||
except Exception:
|
||||
raise ValueError(f"Error with env index {i}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error before block was farmed: {e}") from e
|
||||
|
||||
# Farm block
|
||||
await self.full_node.farm_blocks_to_puzzlehash(count=1, guarantee_transaction_blocks=True)
|
||||
|
||||
# Check balances after block
|
||||
try:
|
||||
for i, (env, local_pending_txs) in enumerate(zip(self.environments, pending_txs)):
|
||||
await self.full_node.wait_for_wallet_synced(
|
||||
wallet_node=env.node, timeout=20, peak_height=uint32(peak + 1)
|
||||
)
|
||||
try:
|
||||
await env.wait_for_transactions_to_settle(
|
||||
self.full_node,
|
||||
_exclude_from_mempool_check=invalid_transactions + [tx.name for tx in local_pending_txs],
|
||||
)
|
||||
except TimeoutError: # pragma: no cover
|
||||
raise TimeoutError(f"All TXs for env-{i} were not found in mempool or marked as in mempool")
|
||||
for i, (env, transition) in enumerate(zip(self.environments, state_transitions)):
|
||||
try:
|
||||
async with env.wallet_state_manager.db_wrapper.reader_no_transaction():
|
||||
await env.change_balances(transition.post_block_balance_updates)
|
||||
await env.check_balances(transition.post_block_additional_balance_info)
|
||||
except Exception:
|
||||
raise ValueError(f"Error with env {i}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error after block was farmed: {e}") from e
|
||||
|
||||
# Make sure all pending txs from before the block are now confirmed
|
||||
for i, (env, txs) in enumerate(zip(self.environments, pending_txs)):
|
||||
balances_pre_block_updates: list[dict[uint32, WalletState]] = []
|
||||
# Check balances after block (and reorg)
|
||||
for reorg_status in ("no",) if reorg_exempt or self.reorg_exempt else ("before", "during"):
|
||||
pending_txs: list[list[LightTransactionRecord]] = []
|
||||
if reorg_status == "during":
|
||||
for bundle in bundles_to_repush:
|
||||
await self.full_node_rpc_client.push_tx(bundle)
|
||||
peak = self.full_node.full_node.blockchain.get_peak_height()
|
||||
assert peak is not None
|
||||
# Check balances prior to block
|
||||
try:
|
||||
await self.full_node.check_transactions_confirmed(env.wallet_state_manager, txs)
|
||||
except TimeoutError: # pragma: no cover
|
||||
unconfirmed: list[
|
||||
LightTransactionRecord
|
||||
] = await env.wallet_state_manager.tx_store.get_all_unconfirmed()
|
||||
raise TimeoutError(
|
||||
f"ENV-{i} TXs not confirmed: {[tx.to_json_dict() for tx in unconfirmed if tx in txs]}"
|
||||
for i, env in enumerate(self.environments):
|
||||
await self.full_node.wait_for_wallet_synced(wallet_node=env.node, timeout=20, peak_height=peak)
|
||||
try:
|
||||
pending_txs.append(
|
||||
await env.wait_for_transactions_to_settle(
|
||||
self.full_node, _exclude_from_mempool_check=invalid_transactions
|
||||
)
|
||||
)
|
||||
except TimeoutError: # pragma: no cover
|
||||
raise TimeoutError(f"All TXs for env-{i} were not found in mempool or marked as in mempool")
|
||||
for i, (env, transition) in enumerate(zip(self.environments, state_transitions)):
|
||||
try:
|
||||
async with env.wallet_state_manager.db_wrapper.reader_no_transaction():
|
||||
if reorg_status == "during":
|
||||
env.wallet_states = balances_pre_block_updates[i]
|
||||
await env.change_balances(post_reorg_balance_differences[i].pre_block_balance_updates)
|
||||
else:
|
||||
await env.change_balances(transition.pre_block_balance_updates)
|
||||
balances_pre_block_updates.append(env.wallet_states)
|
||||
await env.check_balances(transition.pre_block_additional_balance_info)
|
||||
except Exception:
|
||||
raise ValueError(f"Error with env index {i} - {reorg_status} reorg check")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error before block was farmed: {e}") from e
|
||||
|
||||
# Farm block
|
||||
await self.full_node.farm_blocks_to_puzzlehash(count=1, guarantee_transaction_blocks=True)
|
||||
|
||||
try:
|
||||
for i, (env, local_pending_txs) in enumerate(zip(self.environments, pending_txs)):
|
||||
await self.full_node.wait_for_wallet_synced(
|
||||
wallet_node=env.node, timeout=20, peak_height=uint32(peak + 1)
|
||||
)
|
||||
try:
|
||||
await env.wait_for_transactions_to_settle(
|
||||
self.full_node,
|
||||
_exclude_from_mempool_check=invalid_transactions + [tx.name for tx in local_pending_txs],
|
||||
)
|
||||
except TimeoutError: # pragma: no cover
|
||||
raise TimeoutError(f"All TXs for env-{i} were not found in mempool or marked as in mempool")
|
||||
for i, (env, transition) in enumerate(zip(self.environments, state_transitions)):
|
||||
try:
|
||||
async with env.wallet_state_manager.db_wrapper.reader_no_transaction():
|
||||
await env.change_balances(transition.post_block_balance_updates)
|
||||
if reorg_status == "during":
|
||||
await env.change_balances(post_reorg_balance_differences[i].post_block_balance_updates)
|
||||
await env.check_balances(transition.post_block_additional_balance_info)
|
||||
if reorg_status == "before":
|
||||
for id, balance_updates in transition.post_block_balance_updates.items():
|
||||
if balance_updates.get("init", False):
|
||||
balances_pre_block_updates[i][env.dealias_wallet_id(id)] = WalletState(
|
||||
balance=Balance()
|
||||
)
|
||||
except Exception:
|
||||
raise ValueError(f"Error with env {i} - {reorg_status} reorg check")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error after block was farmed: {e}") from e
|
||||
|
||||
# Make sure all pending txs from before the block are now confirmed
|
||||
for i, (env, txs) in enumerate(zip(self.environments, pending_txs)):
|
||||
try:
|
||||
await self.full_node.check_transactions_confirmed(env.wallet_state_manager, txs)
|
||||
except TimeoutError: # pragma: no cover
|
||||
unconfirmed: list[
|
||||
LightTransactionRecord
|
||||
] = await env.wallet_state_manager.tx_store.get_all_unconfirmed()
|
||||
raise TimeoutError(
|
||||
f"ENV-{i} TXs not confirmed: {[tx.to_json_dict() for tx in unconfirmed if tx in txs]}"
|
||||
)
|
||||
|
||||
if reorg_status == "before":
|
||||
height = self.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await self.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
|
||||
# Finally, check that the number of puzzle hashes did or did not increase by the specified amount
|
||||
|
||||
@@ -1616,12 +1616,12 @@ async def test_farmer_to_pool_protocol(
|
||||
path = str(URL(url).path).rstrip("/")
|
||||
if path.endswith("/auth"):
|
||||
assert method == "GET"
|
||||
yield DummyAuthResponse(ok=True) # noqa: RUF075
|
||||
yield DummyAuthResponse(ok=True) # ruff: ignore[fallible-context-manager]
|
||||
if path.endswith("/farmer"):
|
||||
if method == "POST":
|
||||
yield DummyPostFarmerResponse(ok=True) # noqa: RUF075
|
||||
yield DummyPostFarmerResponse(ok=True) # ruff: ignore[fallible-context-manager]
|
||||
if method == "PUT":
|
||||
yield DummyPutFarmerResponse(ok=True) # noqa: RUF075
|
||||
yield DummyPutFarmerResponse(ok=True) # ruff: ignore[fallible-context-manager]
|
||||
if method == "GET":
|
||||
yield DummyGetFarmerResponse(ok=True)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ruff: noqa: E501
|
||||
# ruff: file-ignore[line-too-long]
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
@@ -76,6 +76,7 @@ class StateUrlCase:
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -229,6 +230,7 @@ async def test_plotnft_cli_create_errors(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -309,6 +311,7 @@ async def test_plotnft_cli_show(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -381,6 +384,7 @@ async def test_plotnft_cli_show_with_farmer(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -472,6 +476,7 @@ async def test_plotnft_cli_leave(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -704,6 +709,7 @@ async def test_plotnft_cli_join(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -829,6 +835,7 @@ async def test_plotnft_cli_claim(wallet_environments: WalletTestFramework, versi
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reuse_puzhash": False,
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -918,6 +925,7 @@ async def test_plotnft_cli_inspect(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -990,6 +998,7 @@ async def test_plotnft_cli_change_payout(
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
|
||||
@@ -1171,6 +1171,7 @@ class TestPoolWalletRpc:
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -1359,6 +1360,7 @@ class TestPoolWalletRpc:
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [10],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -1401,6 +1403,7 @@ class TestPoolWalletRpc:
|
||||
"blocks_needed": [10],
|
||||
"trusted": True,
|
||||
"reuse_puzhash": False,
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
|
||||
@@ -208,7 +208,7 @@ def output_benchmark(
|
||||
else:
|
||||
output.write("# Benchmark Metrics\n\n")
|
||||
|
||||
output.write("| Test | 🍿 | Mean | Max | 3σ | Limit | Percent |\n") # noqa: RUF001
|
||||
output.write("| Test | 🍿 | Mean | Max | 3σ | Limit | Percent |\n") # ruff: ignore[ambiguous-unicode-character-string]
|
||||
output.write("| --- | --- | --- | --- | --- | --- | --- |\n")
|
||||
for result in sorted(results):
|
||||
link_url = result.link(prefix=link_prefix, line_separator=link_line_separator)
|
||||
@@ -285,7 +285,7 @@ def output_time_out_assert(
|
||||
else:
|
||||
output.write("# Time Out Assert Metrics\n\n")
|
||||
|
||||
output.write("| Test | 🍿 | Mean | Max | 3σ | Limit | Percent |\n") # noqa: RUF001
|
||||
output.write("| Test | 🍿 | Mean | Max | 3σ | Limit | Percent |\n") # ruff: ignore[ambiguous-unicode-character-string]
|
||||
output.write("| --- | --- | --- | --- | --- | --- | --- |\n")
|
||||
for result in sorted(results):
|
||||
link_url = result.link(prefix=link_prefix, line_separator=link_line_separator)
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
from chia_rs import BlockRecord, FullBlock, SubEpochSummary, UnfinishedBlock, is_overflow_block
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint64, uint128
|
||||
from chia_rs.sized_ints import uint64, uint128
|
||||
|
||||
from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block
|
||||
from chia._tests.conftest import ConsensusMode
|
||||
@@ -279,9 +279,9 @@ class TestNewPeak:
|
||||
block_1.foliage_transaction_block,
|
||||
block_1.transactions_info,
|
||||
block_1.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
block_1.transactions_generator_ref_list,
|
||||
block_1.transactions_generator_buffer,
|
||||
block_1.version,
|
||||
)
|
||||
await full_node.add_unfinished_block(block_1_unf, None)
|
||||
unf: UnfinishedBlock = full_node.full_node_store.get_unfinished_block(block_1_unf.partial_hash)
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import pickle # noqa: S403 # TODO: use explicit serialization instead of pickle
|
||||
import pickle # ruff: ignore[suspicious-pickle-import] # TODO: use explicit serialization instead of pickle
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
|
||||
@@ -74,7 +74,7 @@ def persistent_blocks(
|
||||
try:
|
||||
bytes_list = file_path.read_bytes()
|
||||
# TODO: use explicit serialization instead of pickle
|
||||
block_bytes_list: list[bytes] = pickle.loads(bytes_list) # noqa: S301
|
||||
block_bytes_list: list[bytes] = pickle.loads(bytes_list) # ruff: ignore[suspicious-pickle-usage]
|
||||
blocks: list[FullBlock] = []
|
||||
for block_bytes in block_bytes_list:
|
||||
blocks.append(FullBlock.from_bytes_unchecked(block_bytes))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ruff: noqa: F405
|
||||
# ruff: file-ignore[undefined-local-with-import-star-usage]
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
@@ -10,7 +10,7 @@ from typing import Any
|
||||
|
||||
from chia_rs.sized_ints import uint32
|
||||
|
||||
from chia._tests.util.network_protocol_data import * # noqa: F403
|
||||
from chia._tests.util.network_protocol_data import * # ruff: ignore[undefined-local-with-import-star]
|
||||
|
||||
version = "1.0"
|
||||
|
||||
@@ -196,7 +196,7 @@ def get_protocol_bytes() -> bytes:
|
||||
|
||||
|
||||
def build_protocol_test() -> str:
|
||||
result = """# ruff: noqa: F405
|
||||
result = """# ruff: file-ignore[undefined-local-with-import-star-usage]
|
||||
# this file is generated by build_network_protocol_files.py
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -204,8 +204,8 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
from chia._tests.util.build_network_protocol_files import get_network_protocol_filename
|
||||
from chia._tests.util.network_protocol_data import * # noqa: F403
|
||||
from chia._tests.util.protocol_messages_json import * # noqa: F403
|
||||
from chia._tests.util.network_protocol_data import * # ruff: ignore[undefined-local-with-import-star]
|
||||
from chia._tests.util.protocol_messages_json import * # ruff: ignore[undefined-local-with-import-star]
|
||||
|
||||
|
||||
def parse_blob(input_bytes: bytes) -> tuple[bytes, bytes]:
|
||||
@@ -246,7 +246,7 @@ def test_protocol_bytes() -> None:
|
||||
|
||||
|
||||
def get_protocol_json() -> str:
|
||||
result = """# ruff: noqa: E501
|
||||
result = """# ruff: file-ignore[line-too-long]
|
||||
# this file is generated by build_network_protocol_files.py
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -266,13 +266,13 @@ from typing import Any
|
||||
|
||||
|
||||
def build_json_test() -> str:
|
||||
result = """# ruff: noqa: F405
|
||||
result = """# ruff: file-ignore[undefined-local-with-import-star-usage]
|
||||
# this file is generated by build_network_protocol_files.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from chia._tests.util.network_protocol_data import * # noqa: F403
|
||||
from chia._tests.util.protocol_messages_json import * # noqa: F403
|
||||
from chia._tests.util.network_protocol_data import * # ruff: ignore[undefined-local-with-import-star]
|
||||
from chia._tests.util.protocol_messages_json import * # ruff: ignore[undefined-local-with-import-star]
|
||||
|
||||
|
||||
def test_protocol_json() -> None:
|
||||
|
||||
@@ -5,6 +5,7 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32
|
||||
|
||||
from chia._tests.util.get_name_puzzle_conditions import get_name_puzzle_conditions
|
||||
from chia.consensus.block_generator_info import get_transactions_generator_program
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.consensus.generator_tools import tx_removals_and_additions
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
@@ -26,9 +27,10 @@ def run_and_get_removals_and_additions(
|
||||
if not block.is_transaction_block():
|
||||
return [], []
|
||||
|
||||
if block.transactions_generator is not None:
|
||||
generator = get_transactions_generator_program(block)
|
||||
if generator is not None:
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
BlockGenerator(block.transactions_generator, []),
|
||||
BlockGenerator(generator, []),
|
||||
max_cost,
|
||||
mempool_mode=mempool_mode,
|
||||
height=height,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Persistent cache for expensive plot operations: get_qualities_for_challenge(),
|
||||
get_full_proof(), and solve_proof().
|
||||
|
||||
The cache is stored in the plots directory as `.plot_cache.pickle` and is keyed
|
||||
The cache is stored in the plots directory as `plot_cache.pickle` and is keyed
|
||||
by (plot_id, challenge) for qualities and full proofs, and by
|
||||
(partial_proof_bytes, plot_id) for solve_proof.
|
||||
|
||||
@@ -14,7 +14,7 @@ from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
import pickle # noqa: S403
|
||||
import pickle # ruff: ignore[suspicious-pickle-import]
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -27,7 +27,7 @@ from chia.plotting.prover import QualityProtocol, V1Prover, V1Quality, V2Prover,
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CACHE_FILENAME = ".plot_cache.pickle"
|
||||
CACHE_FILENAME = "plot_cache.pickle"
|
||||
|
||||
# (plot_id, challenge) -> list of serialized quality data
|
||||
# V1: each entry is 32 bytes (quality bytes32)
|
||||
@@ -50,7 +50,7 @@ def _load_from_disk(path: Path) -> tuple[dict[Any, Any], dict[Any, Any], dict[An
|
||||
if not path.exists():
|
||||
return {}, {}, {}
|
||||
try:
|
||||
data = pickle.loads(path.read_bytes()) # noqa: S301
|
||||
data = pickle.loads(path.read_bytes()) # ruff: ignore[suspicious-pickle-usage]
|
||||
if isinstance(data, tuple) and len(data) == 3:
|
||||
return data[0], data[1], data[2]
|
||||
except Exception:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ruff: noqa: E501
|
||||
# ruff: file-ignore[line-too-long]
|
||||
# this file is generated by build_network_protocol_files.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import platform
|
||||
import random
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Callable, Iterator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from chia_rs import (
|
||||
@@ -309,22 +310,27 @@ def get_full_blocks(shard: int) -> Iterator[FullBlock]:
|
||||
for finished_sub_slots in get_finished_sub_slots():
|
||||
for refs_list in get_ref_list():
|
||||
for gen in [None, generator]:
|
||||
yield FullBlock(
|
||||
finished_sub_slots,
|
||||
reward_chain_block,
|
||||
challenge_chain_sp_proof,
|
||||
vdf_proof(), # challenge_chain_ip_proof
|
||||
reward_chain_sp_proof,
|
||||
vdf_proof(), # reward_chain_ip_proof
|
||||
infused_challenge_chain_ip_proof,
|
||||
foliage,
|
||||
foliage_transaction_block,
|
||||
transactions_info,
|
||||
gen, # transactions_generator
|
||||
refs_list, # transactions_generator_ref_list
|
||||
None,
|
||||
uint8(0),
|
||||
)
|
||||
for version in [0, 1]:
|
||||
yield FullBlock(
|
||||
finished_sub_slots,
|
||||
reward_chain_block,
|
||||
challenge_chain_sp_proof,
|
||||
vdf_proof(), # challenge_chain_ip_proof
|
||||
reward_chain_sp_proof,
|
||||
vdf_proof(), # reward_chain_ip_proof
|
||||
infused_challenge_chain_ip_proof,
|
||||
foliage,
|
||||
foliage_transaction_block,
|
||||
transactions_info,
|
||||
gen if version == 0 and gen else None, # transactions_generator
|
||||
refs_list
|
||||
if version == 0
|
||||
else [], # transactions_generator_ref_list
|
||||
bytes(gen)
|
||||
if version == 1 and gen
|
||||
else None, # transactions_generator_buffer
|
||||
uint8(version),
|
||||
)
|
||||
|
||||
|
||||
def test_block_info_from_block_rejects_refs_count_exceeding_remaining_buffer() -> None:
|
||||
@@ -343,6 +349,73 @@ def test_block_info_from_block_rejects_short_refs_count_prefix() -> None:
|
||||
block_info_from_block(memoryview(block_bytes))
|
||||
|
||||
|
||||
def _version1_block_bytes_with_generator_buffer() -> tuple[bytearray, int]:
|
||||
for block in get_full_blocks(0):
|
||||
if block.version == 1 and block.transactions_generator_buffer is not None:
|
||||
return bytearray(bytes(block)), len(block.transactions_generator_buffer)
|
||||
raise AssertionError("expected a version-1 block with transactions_generator_buffer")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parse", [generator_from_block, block_info_from_block])
|
||||
def test_cheap_parser_rejects_short_generator_buffer_length_prefix(parse: Callable[[memoryview], Any]) -> None:
|
||||
block_bytes, buf_len = _version1_block_bytes_with_generator_buffer()
|
||||
# Trailing layout: discriminant (1) + length (4) + buffer. Leave discriminant + 1 length byte.
|
||||
del block_bytes[-(buf_len + 3) :]
|
||||
|
||||
with pytest.raises(ValueError, match="generator_buffer length prefix requires 4 bytes, remaining buffer 1"):
|
||||
parse(memoryview(block_bytes))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parse", [generator_from_block, block_info_from_block])
|
||||
def test_cheap_parser_rejects_generator_buffer_length_exceeding_remaining_buffer(
|
||||
parse: Callable[[memoryview], Any],
|
||||
) -> None:
|
||||
block_bytes, buf_len = _version1_block_bytes_with_generator_buffer()
|
||||
length_offset = len(block_bytes) - buf_len - 4
|
||||
block_bytes[length_offset : length_offset + 4] = (buf_len + 1).to_bytes(4, "big")
|
||||
|
||||
with pytest.raises(ValueError, match=f"generator_buffer length: {buf_len + 1}, exceeds remaining buffer {buf_len}"):
|
||||
parse(memoryview(block_bytes))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parse", [generator_from_block, block_info_from_block])
|
||||
def test_cheap_parser_rejects_invalid_generator_version(parse: Callable[[memoryview], Any]) -> None:
|
||||
block_bytes, buf_len = _version1_block_bytes_with_generator_buffer()
|
||||
# Present bit set with version 2 (unsupported).
|
||||
block_bytes[-(buf_len + 4 + 1)] = (2 << 1) | 1
|
||||
|
||||
with pytest.raises(ValueError, match="invalid FullBlock generator version: 2"):
|
||||
parse(memoryview(block_bytes))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", [0, 1])
|
||||
def test_cheap_parser_reads_present_generator_for_supported_versions(version: int) -> None:
|
||||
for block in get_full_blocks(0):
|
||||
if block.version != version:
|
||||
continue
|
||||
if version == 0 and block.transactions_generator is None:
|
||||
continue
|
||||
if version == 1 and block.transactions_generator_buffer is None:
|
||||
continue
|
||||
|
||||
block_bytes = memoryview(bytes(block))
|
||||
gen = generator_from_block(block_bytes)
|
||||
assert gen is not None
|
||||
if version == 0:
|
||||
assert Program.from_bytes(gen) == block.transactions_generator
|
||||
else:
|
||||
assert gen == block.transactions_generator_buffer
|
||||
|
||||
block_info = block_info_from_block(block_bytes)
|
||||
assert block_info.version == version
|
||||
assert block_info.transactions_generator == block.transactions_generator
|
||||
assert block_info.transactions_generator_buffer == block.transactions_generator_buffer
|
||||
assert block_info.transactions_generator_ref_list == block.transactions_generator_ref_list
|
||||
return
|
||||
|
||||
raise AssertionError(f"expected a version-{version} block with a present generator")
|
||||
|
||||
|
||||
def test_cheap_parser_matches_round_tripped_block() -> None:
|
||||
block = next(get_full_blocks(0))
|
||||
block_bytes = memoryview(bytes(block))
|
||||
@@ -378,21 +451,37 @@ async def test_parser(shard: int) -> None:
|
||||
gen = generator_from_block(block_bytes)
|
||||
if gen is None:
|
||||
assert block.transactions_generator is None
|
||||
else:
|
||||
assert block.transactions_generator_buffer is None
|
||||
elif block.version == 0:
|
||||
assert block.transactions_generator is not None
|
||||
assert Program.from_bytes(gen) == block.transactions_generator
|
||||
else:
|
||||
assert block.version == 1
|
||||
assert block.transactions_generator_buffer is not None
|
||||
assert gen == block.transactions_generator_buffer
|
||||
bi = block_info_from_block(block_bytes)
|
||||
if block.transactions_generator is None:
|
||||
assert bi.transactions_generator is None
|
||||
else:
|
||||
assert block.transactions_generator == bi.transactions_generator
|
||||
if block.transactions_generator_buffer is None:
|
||||
assert bi.transactions_generator_buffer is None
|
||||
else:
|
||||
assert block.transactions_generator_buffer == bi.transactions_generator_buffer
|
||||
assert block.prev_header_hash == bi.prev_header_hash
|
||||
assert block.transactions_generator_ref_list == bi.transactions_generator_ref_list
|
||||
# this doubles the run-time of this test, with questionable utility
|
||||
round_trip = FullBlock.from_bytes(block_bytes)
|
||||
if gen is None:
|
||||
assert FullBlock.from_bytes(block_bytes).transactions_generator is None
|
||||
assert round_trip.transactions_generator is None
|
||||
assert round_trip.transactions_generator_buffer is None
|
||||
elif round_trip.version == 0:
|
||||
assert round_trip.transactions_generator_buffer is None
|
||||
assert Program.from_bytes(gen) == round_trip.transactions_generator
|
||||
else:
|
||||
assert Program.from_bytes(gen) == FullBlock.from_bytes(block_bytes).transactions_generator
|
||||
assert round_trip.version == 1
|
||||
assert round_trip.transactions_generator is None
|
||||
assert gen == round_trip.transactions_generator_buffer
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ruff: noqa: F405
|
||||
# ruff: file-ignore[undefined-local-with-import-star-usage]
|
||||
# this file is generated by build_network_protocol_files.py
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -6,8 +6,8 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
from chia._tests.util.build_network_protocol_files import get_network_protocol_filename
|
||||
from chia._tests.util.network_protocol_data import * # noqa: F403
|
||||
from chia._tests.util.protocol_messages_json import * # noqa: F403
|
||||
from chia._tests.util.network_protocol_data import * # ruff: ignore[undefined-local-with-import-star]
|
||||
from chia._tests.util.protocol_messages_json import * # ruff: ignore[undefined-local-with-import-star]
|
||||
|
||||
|
||||
def parse_blob(input_bytes: bytes) -> tuple[bytes, bytes]:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# ruff: noqa: F405
|
||||
# ruff: file-ignore[undefined-local-with-import-star-usage]
|
||||
# this file is generated by build_network_protocol_files.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from chia._tests.util.network_protocol_data import * # noqa: F403
|
||||
from chia._tests.util.protocol_messages_json import * # noqa: F403
|
||||
from chia._tests.util.network_protocol_data import * # ruff: ignore[undefined-local-with-import-star]
|
||||
from chia._tests.util.protocol_messages_json import * # ruff: ignore[undefined-local-with-import-star]
|
||||
|
||||
|
||||
def test_protocol_json() -> None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pickle # noqa: S403
|
||||
import pickle # ruff: ignore[suspicious-pickle-import]
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
@@ -1320,7 +1320,25 @@ async def test_cat_hint(wallet_environments: WalletTestFramework, wallet_type: t
|
||||
}
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
post_reorg_balance_differences=[
|
||||
WalletStateTransition(),
|
||||
WalletStateTransition()
|
||||
if autodiscovery
|
||||
# after the reorg we'll already have discovered that these CATs belong to us
|
||||
else WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"cat": {
|
||||
"confirmed_wallet_balance": 60,
|
||||
"unconfirmed_wallet_balance": 60,
|
||||
"spendable_balance": 60,
|
||||
"max_send_amount": 60,
|
||||
"unspent_coin_count": 1,
|
||||
}
|
||||
},
|
||||
post_block_balance_updates={}, # the "init" means the balances will be overidden
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
cat_wallet_2 = wallet_node_2.wallet_state_manager.wallets[uint32(2)]
|
||||
@@ -1522,7 +1540,21 @@ async def test_cat_change_detection(wallet_environments: WalletTestFramework, wa
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
],
|
||||
bundles_to_repush=[eve_spend],
|
||||
# We're going to remember we received the CAT through the reorg
|
||||
post_reorg_balance_differences=[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"cat": {
|
||||
"unconfirmed_wallet_balance": 5,
|
||||
"pending_change": 5,
|
||||
"pending_coin_removal_count": 1,
|
||||
}
|
||||
},
|
||||
post_block_balance_updates={}, # the "init" means the balances will be overidden
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
assert not full_node_api.full_node.subscriptions.has_puzzle_subscription(puzzlehash_unhardened)
|
||||
@@ -1638,7 +1670,8 @@ async def test_cat_melt_balance(wallet_environments: WalletTestFramework) -> Non
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
],
|
||||
bundles_to_repush=[spend_to_wallet],
|
||||
)
|
||||
|
||||
cat_wallet = env.wallet_state_manager.wallets[uint32(2)]
|
||||
@@ -1688,7 +1721,8 @@ async def test_cat_melt_balance(wallet_environments: WalletTestFramework) -> Non
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
],
|
||||
bundles_to_repush=[signed_spend],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -63,42 +63,94 @@ async def get_trade_and_status(trade_manager: TradeManager, trade: TradeRecord)
|
||||
"wallet_environments,credential_restricted,active_softfork_height",
|
||||
[
|
||||
(
|
||||
{"num_environments": 2, "trusted": True, "blocks_needed": [1, 1], "reuse_puzhash": True},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": True,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": True,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
True,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
(
|
||||
{"num_environments": 2, "trusted": True, "blocks_needed": [1, 1], "reuse_puzhash": True},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": True,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": True,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
False,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
(
|
||||
{"num_environments": 2, "trusted": True, "blocks_needed": [1, 1], "reuse_puzhash": False},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": True,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": False,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
True,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
(
|
||||
{"num_environments": 2, "trusted": False, "blocks_needed": [1, 1], "reuse_puzhash": True},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": False,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": True,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
True,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
(
|
||||
{"num_environments": 2, "trusted": False, "blocks_needed": [1, 1], "reuse_puzhash": False},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": False,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": False,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
False,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
(
|
||||
{"num_environments": 2, "trusted": False, "blocks_needed": [1, 1], "reuse_puzhash": True},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": False,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": True,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
False,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
(
|
||||
{"num_environments": 2, "trusted": False, "blocks_needed": [1, 1], "reuse_puzhash": False},
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": False,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": False,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
True,
|
||||
SOFTFORK_HEIGHTS[0],
|
||||
),
|
||||
*(
|
||||
({"num_environments": 2, "trusted": True, "blocks_needed": [1, 1], "reuse_puzhash": False}, False, height)
|
||||
(
|
||||
{
|
||||
"num_environments": 2,
|
||||
"trusted": True,
|
||||
"blocks_needed": [1, 1],
|
||||
"reuse_puzhash": False,
|
||||
"reorg_exempt": True,
|
||||
},
|
||||
False,
|
||||
height,
|
||||
)
|
||||
for height in SOFTFORK_HEIGHTS
|
||||
),
|
||||
],
|
||||
@@ -1651,6 +1703,7 @@ async def test_cat_trades(
|
||||
{
|
||||
"num_environments": 2,
|
||||
"blocks_needed": [2, 1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -1963,6 +2016,7 @@ async def test_trade_cancellation(wallet_environments: WalletTestFramework, wall
|
||||
{
|
||||
"num_environments": 3,
|
||||
"blocks_needed": [2, 1, 1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -2160,6 +2214,7 @@ async def test_trade_conflict(wallet_environments: WalletTestFramework, wallet_t
|
||||
{
|
||||
"num_environments": 2,
|
||||
"blocks_needed": [1, 1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -2286,6 +2341,7 @@ async def test_trade_bad_spend(
|
||||
{
|
||||
"num_environments": 2,
|
||||
"blocks_needed": [1, 1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -2433,6 +2489,7 @@ async def test_trade_high_fee(wallet_environments: WalletTestFramework, wallet_t
|
||||
{
|
||||
"num_environments": 2,
|
||||
"blocks_needed": [1, 1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
|
||||
@@ -22,9 +22,11 @@ from chia_rs.sized_ints import uint32, uint64, uint128
|
||||
from chia._tests.environments.wallet import NewPuzzleHashError, WalletEnvironment, WalletState, WalletTestFramework
|
||||
from chia._tests.util.setup_nodes import setup_simulators_and_wallets_service
|
||||
from chia._tests.wallet.wallet_block_tools import WalletBlockTools
|
||||
from chia.consensus.block_generator_info import get_transactions_generator_bytes
|
||||
from chia.full_node.full_node import FullNode
|
||||
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
|
||||
from chia.types.peer_info import PeerInfo
|
||||
from chia.util.errors import Err
|
||||
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig
|
||||
from chia.wallet.wallet_node import Balance
|
||||
from chia.wallet.wallet_rpc_client import WalletRpcClient
|
||||
@@ -84,8 +86,9 @@ async def ignore_block_validation(
|
||||
|
||||
def run_block(
|
||||
block: FullBlock, prev_generators: list[bytes], prev_tx_height: uint32, constants: ConsensusConstants
|
||||
) -> tuple[int | None, str | None, SpendBundleConditions | None]:
|
||||
assert block.transactions_generator is not None
|
||||
) -> tuple[Err | None, str | None, SpendBundleConditions | None]:
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
assert generator_bytes is not None
|
||||
assert block.transactions_info is not None
|
||||
flags = get_flags_for_height_and_constants(prev_tx_height, constants) | DONT_VALIDATE_SIGNATURE
|
||||
if block.height >= constants.HARD_FORK_HEIGHT:
|
||||
@@ -93,7 +96,7 @@ async def ignore_block_validation(
|
||||
else:
|
||||
run_block = run_block_generator
|
||||
err, err_msg, conds = run_block(
|
||||
bytes(block.transactions_generator),
|
||||
generator_bytes,
|
||||
prev_generators,
|
||||
block.transactions_info.cost,
|
||||
flags,
|
||||
@@ -104,11 +107,12 @@ async def ignore_block_validation(
|
||||
# pretend that the signatures are OK
|
||||
if conds is not None:
|
||||
conds = conds.replace(validated_signature=True)
|
||||
return err, err_msg, conds
|
||||
return None if err is None else Err(err), err_msg, conds
|
||||
|
||||
monkeypatch.setattr("chia.simulator.block_tools.BlockTools", WalletBlockTools)
|
||||
monkeypatch.setattr(FullNode, "create", create_wrapper(FullNode.create))
|
||||
monkeypatch.setattr("chia.consensus.blockchain.validate_block_body", validate_block_body)
|
||||
monkeypatch.setattr("chia.consensus.multiprocess_validation.validate_generator_ref_list", lambda *_, **__: None)
|
||||
monkeypatch.setattr("chia.consensus.multiprocess_validation._run_block", run_block)
|
||||
monkeypatch.setattr(
|
||||
"chia.consensus.block_header_validation.validate_unfinished_header_block", lambda *_, **__: (uint64(1), None)
|
||||
@@ -277,4 +281,5 @@ async def wallet_environments(
|
||||
for service, rpc_client, wallet_state in zip(wallet_services, wallet_rpc_clients, wallet_states)
|
||||
],
|
||||
tx_config,
|
||||
request.param.get("reorg_exempt", False),
|
||||
)
|
||||
|
||||
@@ -39,7 +39,9 @@ def get_parent_branch(value: bytes32, proof: tuple[int, list[bytes32]]) -> tuple
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [2, 2]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [2, 2], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_dl_offers(wallet_environments: WalletTestFramework) -> None:
|
||||
env_maker = wallet_environments.environments[0]
|
||||
@@ -353,7 +355,9 @@ async def test_dl_offers(wallet_environments: WalletTestFramework) -> None:
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [3]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 1, "blocks_needed": [3], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_dl_offer_cancellation(wallet_environments: WalletTestFramework) -> None:
|
||||
env_maker = wallet_environments.environments[0]
|
||||
@@ -579,7 +583,9 @@ async def test_dl_offer_cancellation(wallet_environments: WalletTestFramework) -
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [3, 3]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [3, 3], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_multiple_dl_offers(wallet_environments: WalletTestFramework) -> None:
|
||||
env_maker = wallet_environments.environments[0]
|
||||
|
||||
@@ -469,7 +469,14 @@ async def test_did_find_lost_did(wallet_environments: WalletTestFramework, capsy
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
# Due to an ephemeral spend, sync will pick up an extra TX here and thus bump the pending coin removal count
|
||||
post_reorg_balance_differences=[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={"did_found": {"pending_coin_removal_count": 1}},
|
||||
post_block_balance_updates={"did_found": {"pending_coin_removal_count": -1}},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
coin = await did_wallet.get_coin()
|
||||
@@ -587,7 +594,10 @@ async def test_did_transfer(wallet_environments: WalletTestFramework, capsys: py
|
||||
"did": {"init": True, "confirmed_wallet_balance": 101, "set_remainder": True},
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
# TODO: there's a bug here where the user store autoincrement means this has a new ID after deleted
|
||||
# Instead of 2, it becomes 3 because there was a 2 at some point (is my best guess)
|
||||
reorg_exempt=True,
|
||||
)
|
||||
|
||||
did_wallets = list(
|
||||
@@ -1046,7 +1056,17 @@ async def test_update_metadata(wallet_environments: WalletTestFramework, capsys:
|
||||
"did": {"confirmed_wallet_balance": 0, "set_remainder": True},
|
||||
},
|
||||
),
|
||||
],
|
||||
post_reorg_balance_differences=[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={"did": {"pending_coin_removal_count": 1}},
|
||||
post_block_balance_updates={}, # set_remainder takes care of it
|
||||
),
|
||||
WalletStateTransition(),
|
||||
]
|
||||
# TODO: figure out why the reuse_puzhash being off is the only reason this happens
|
||||
if not wallet_environments.tx_config.reuse_puzhash
|
||||
else [],
|
||||
)
|
||||
|
||||
assert get_parent_num(did_wallet_1) == parent_num + 2
|
||||
|
||||
@@ -29,7 +29,9 @@ async def get_nft_count(wallet: NFTWallet) -> int:
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("zero_royalties", [True, False])
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_sell_nft(wallet_environments: WalletTestFramework, zero_royalties: bool) -> None:
|
||||
@@ -255,7 +257,9 @@ async def test_nft_offer_sell_nft(wallet_environments: WalletTestFramework, zero
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("zero_royalties", [True, False])
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_request_nft(wallet_environments: WalletTestFramework, zero_royalties: bool) -> None:
|
||||
@@ -485,7 +489,9 @@ async def test_nft_offer_request_nft(wallet_environments: WalletTestFramework, z
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("zero_royalties", [True, False])
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_sell_did_to_did(wallet_environments: WalletTestFramework, zero_royalties: bool) -> None:
|
||||
@@ -777,7 +783,9 @@ async def test_nft_offer_sell_did_to_did(wallet_environments: WalletTestFramewor
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("zero_royalties", [True, False])
|
||||
@pytest.mark.parametrize("wallet_type", [CATWallet, RCATWallet])
|
||||
@pytest.mark.anyio
|
||||
@@ -1101,7 +1109,9 @@ async def test_nft_offer_sell_nft_for_cat(
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("test_change", [True, False])
|
||||
@pytest.mark.parametrize("wallet_type", [CATWallet, RCATWallet])
|
||||
@pytest.mark.anyio
|
||||
@@ -1460,7 +1470,9 @@ async def test_nft_offer_request_nft_for_cat(
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [2]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 1, "blocks_needed": [2], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_sell_cancel(wallet_environments: WalletTestFramework) -> None:
|
||||
env_maker = wallet_environments.environments[0]
|
||||
@@ -1652,7 +1664,14 @@ async def test_nft_offer_sell_cancel(wallet_environments: WalletTestFramework) -
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments",
|
||||
[{"num_environments": 2, "blocks_needed": [3, 3], "config_overrides": {"automatically_add_unknown_cats": True}}],
|
||||
[
|
||||
{
|
||||
"num_environments": 2,
|
||||
"blocks_needed": [3, 3],
|
||||
"config_overrides": {"automatically_add_unknown_cats": True},
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.parametrize("wallet_type", [CATWallet, RCATWallet])
|
||||
|
||||
@@ -26,7 +26,9 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: # type: ig
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_with_fee(wallet_environments: WalletTestFramework) -> None:
|
||||
env_0 = wallet_environments.environments[0]
|
||||
@@ -308,7 +310,9 @@ async def test_nft_offer_with_fee(wallet_environments: WalletTestFramework) -> N
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 1, "blocks_needed": [1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 1, "blocks_needed": [1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_cancellations(wallet_environments: WalletTestFramework) -> None:
|
||||
env_0 = wallet_environments.environments[0]
|
||||
@@ -461,7 +465,9 @@ async def test_nft_offer_cancellations(wallet_environments: WalletTestFramework)
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_with_metadata_update(wallet_environments: WalletTestFramework) -> None:
|
||||
env_0 = wallet_environments.environments[0]
|
||||
@@ -696,7 +702,9 @@ async def test_nft_offer_with_metadata_update(wallet_environments: WalletTestFra
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("wallet_type", [CATWallet, RCATWallet])
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_nft_for_cat(wallet_environments: WalletTestFramework, wallet_type: type[CATWallet]) -> None:
|
||||
@@ -1093,7 +1101,9 @@ async def test_nft_offer_nft_for_cat(wallet_environments: WalletTestFramework, w
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_nft_for_nft(wallet_environments: WalletTestFramework) -> None:
|
||||
env_0 = wallet_environments.environments[0]
|
||||
@@ -1333,7 +1343,9 @@ async def test_nft_offer_nft_for_nft(wallet_environments: WalletTestFramework) -
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1]}], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments", [{"num_environments": 2, "blocks_needed": [1, 1], "reorg_exempt": True}], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("wallet_type", [CATWallet, RCATWallet])
|
||||
@pytest.mark.anyio
|
||||
async def test_nft_offer_nft0_and_xch_for_cat(
|
||||
|
||||
@@ -1595,7 +1595,10 @@ async def test_nft_transfer_nft_with_did(wallet_environments: WalletTestFramewor
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
# TODO: there's a bug here where the user store autoincrement means this has a new ID after deleted
|
||||
# Instead of 2, it becomes 3 because there was a 2 at some point (is my best guess)
|
||||
reorg_exempt=True,
|
||||
)
|
||||
|
||||
# Transfer NFT, wallet will be deleted
|
||||
|
||||
@@ -90,43 +90,6 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
uint32(env.wallet_aliases["plotnft"]), required_type=PlotNFT2Wallet
|
||||
)
|
||||
|
||||
# Reorg (creation)
|
||||
height = wallet_environments.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await wallet_environments.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": creation_fee + 1,
|
||||
"<=#spendable_balance": creation_fee + 1,
|
||||
"<=#max_send_amount": creation_fee + 1,
|
||||
">=#pending_change": 0,
|
||||
">=#pending_coin_removal_count": 1,
|
||||
">=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {"unspent_coin_count": -1},
|
||||
},
|
||||
post_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": -(creation_fee + 1),
|
||||
">=#spendable_balance": 1,
|
||||
">=#max_send_amount": 1,
|
||||
"<=#pending_change": 0,
|
||||
"<=#pending_coin_removal_count": -1,
|
||||
"<=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {"unspent_coin_count": 1},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# (check an error)
|
||||
with pytest.raises(ValueError, match=re.escape("`leave_pool` called on a non-pooling or exiting PlotNFT")):
|
||||
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
||||
@@ -244,49 +207,6 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
):
|
||||
await plotnft_wallet.claim_rewards(action_scope=action_scope)
|
||||
|
||||
# Reorg (claim rewards)
|
||||
height = wallet_environments.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await wallet_environments.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": -amount_to_succeed_in_claiming,
|
||||
"unconfirmed_wallet_balance": -amount_to_succeed_in_claiming,
|
||||
"spendable_balance": -amount_to_succeed_in_claiming,
|
||||
"max_send_amount": -amount_to_succeed_in_claiming,
|
||||
"unspent_coin_count": -1,
|
||||
},
|
||||
"plotnft": {
|
||||
"confirmed_wallet_balance": REWARDS_GAINED,
|
||||
"pending_coin_removal_count": NUM_REWARDS_FARMED + 1,
|
||||
"unspent_coin_count": NUM_REWARDS_FARMED,
|
||||
},
|
||||
},
|
||||
post_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": amount_to_succeed_in_claiming,
|
||||
"unconfirmed_wallet_balance": amount_to_succeed_in_claiming,
|
||||
"spendable_balance": amount_to_succeed_in_claiming,
|
||||
"max_send_amount": amount_to_succeed_in_claiming,
|
||||
"unspent_coin_count": 1,
|
||||
},
|
||||
"plotnft": {
|
||||
"confirmed_wallet_balance": -REWARDS_GAINED,
|
||||
"pending_coin_removal_count": -NUM_REWARDS_FARMED - 1,
|
||||
"unspent_coin_count": -NUM_REWARDS_FARMED,
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# JOIN POOL
|
||||
joining_fee = uint64(1_000)
|
||||
|
||||
@@ -332,43 +252,6 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
== "https://daurl.com"
|
||||
)
|
||||
|
||||
# Reorg (join pool)
|
||||
height = wallet_environments.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await wallet_environments.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": joining_fee,
|
||||
"<=#spendable_balance": -1,
|
||||
"<=#max_send_amount": -1,
|
||||
">=#pending_change": 0,
|
||||
">=#pending_coin_removal_count": 1,
|
||||
">=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {"pending_coin_removal_count": 1},
|
||||
},
|
||||
post_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": -joining_fee,
|
||||
">=#spendable_balance": 1,
|
||||
">=#max_send_amount": 1,
|
||||
"<=#pending_change": 0,
|
||||
"<=#pending_coin_removal_count": -1,
|
||||
"<=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {"pending_coin_removal_count": -1},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# RECEIVE REWARDS (while pooling)
|
||||
EXTRA_POOLING_REWARDS = 2
|
||||
await wallet_environments.full_node.farm_blocks_to_puzzlehash(
|
||||
@@ -429,7 +312,15 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
],
|
||||
# when we reorg, we'll still remember that we saw an attempt to spend our plotnft
|
||||
post_reorg_balance_differences=[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={"plotnft": {"pending_coin_removal_count": 2}},
|
||||
post_block_balance_updates={"plotnft": {"pending_coin_removal_count": -2}},
|
||||
)
|
||||
],
|
||||
bundles_to_repush=[WalletSpendBundle(coin_spends, G2Element())],
|
||||
)
|
||||
|
||||
# LEAVE POOL (to another)
|
||||
@@ -469,33 +360,6 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
||||
await plotnft_wallet.leave_pool(action_scope=action_scope)
|
||||
|
||||
# Reorg (leave pool to another)
|
||||
height = wallet_environments.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await wallet_environments.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"xch": {},
|
||||
"plotnft": {
|
||||
"pending_coin_removal_count": 1,
|
||||
},
|
||||
},
|
||||
post_block_balance_updates={
|
||||
"xch": {},
|
||||
"plotnft": {
|
||||
"pending_coin_removal_count": -1,
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# FINISH LEAVING (to new pool)
|
||||
plotnft = await plotnft_wallet.get_current_plotnft()
|
||||
await wallet_environments.full_node.farm_blocks_to_puzzlehash(
|
||||
@@ -581,47 +445,6 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
):
|
||||
await plotnft_wallet.claim_rewards(action_scope=action_scope)
|
||||
|
||||
# Reorg (leave pool)
|
||||
height = wallet_environments.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await wallet_environments.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": leave_fee,
|
||||
"<=#spendable_balance": -1,
|
||||
"<=#max_send_amount": -1,
|
||||
">=#pending_change": 0,
|
||||
">=#pending_coin_removal_count": 1,
|
||||
">=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {
|
||||
"pending_coin_removal_count": 1,
|
||||
},
|
||||
},
|
||||
post_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": -leave_fee,
|
||||
">=#spendable_balance": 1,
|
||||
">=#max_send_amount": 1,
|
||||
"<=#pending_change": 0,
|
||||
"<=#pending_coin_removal_count": -1,
|
||||
"<=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {
|
||||
"pending_coin_removal_count": -1,
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# LOSE REWARDS (while leaving)
|
||||
plotnft = await plotnft_wallet.get_current_plotnft()
|
||||
[pool_reward] = await env.wallet_state_manager.plotnft2_store.get_pool_rewards(plotnft_id=plotnft_wallet.plotnft_id)
|
||||
@@ -642,7 +465,8 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
],
|
||||
bundles_to_repush=[WalletSpendBundle(coin_spends, G2Element())],
|
||||
)
|
||||
|
||||
# FINISH LEAVING
|
||||
@@ -684,47 +508,6 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
]
|
||||
)
|
||||
|
||||
# Reorg (finish leaving)
|
||||
height = wallet_environments.full_node.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await wallet_environments.full_node.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": finish_leaving_fee,
|
||||
"<=#spendable_balance": 0,
|
||||
"<=#max_send_amount": 0,
|
||||
">=#pending_change": 0,
|
||||
">=#pending_coin_removal_count": 1,
|
||||
">=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {
|
||||
"pending_coin_removal_count": 1,
|
||||
},
|
||||
},
|
||||
post_block_balance_updates={
|
||||
"xch": {
|
||||
"confirmed_wallet_balance": -finish_leaving_fee,
|
||||
">=#spendable_balance": 0,
|
||||
">=#max_send_amount": 0,
|
||||
"<=#pending_change": 0,
|
||||
"<=#pending_coin_removal_count": -1,
|
||||
"<=#unspent_coin_count": 0,
|
||||
},
|
||||
"plotnft": {
|
||||
"pending_coin_removal_count": -1,
|
||||
},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Resync start
|
||||
env.node._close()
|
||||
await env.node._await_closed()
|
||||
@@ -762,6 +545,7 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
{
|
||||
"num_environments": 1,
|
||||
"blocks_needed": [1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from operator import attrgetter
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
@@ -46,10 +47,26 @@ from chia._tests.wallet.test_wallet_coin_store import (
|
||||
record_8,
|
||||
record_9,
|
||||
)
|
||||
from chia.cmds.cmd_classes import ChiaCliContext
|
||||
from chia.cmds.cmd_helpers import NeedsWalletRPC, WalletClientInfo
|
||||
from chia.cmds.coins import CombineCMD, SplitCMD
|
||||
from chia.cmds.param_types import CliAmount
|
||||
from chia.cmds.wallet import DidGetDidCMD, DidSetWalletNameCMD
|
||||
from chia.cmds.param_types import CliAddress, CliAmount
|
||||
from chia.cmds.wallet import (
|
||||
AddTokenCMD,
|
||||
CancelOfferCMD,
|
||||
CheckWalletCMD,
|
||||
DeleteUnconfirmedTransactionsCMD,
|
||||
DidGetDidCMD,
|
||||
DidSetWalletNameCMD,
|
||||
GetAddressCMD,
|
||||
GetOffersCMD,
|
||||
GetTransactionCMD,
|
||||
GetTransactionsCMD,
|
||||
MakeOfferCMD,
|
||||
ShowCMD,
|
||||
SignMessageCMD,
|
||||
TakeOfferCMD,
|
||||
)
|
||||
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
|
||||
from chia.pools.pool_wallet_info import NewPoolWalletInitialTargetState
|
||||
from chia.protocols.fee_estimate import FeeEstimate, FeeEstimateGroup
|
||||
@@ -114,7 +131,6 @@ from chia.wallet.wallet_request_types import (
|
||||
CATAssetIDToName,
|
||||
CATGetAssetID,
|
||||
CATGetName,
|
||||
CATSetName,
|
||||
CATSpend,
|
||||
CheckDeleteKey,
|
||||
CheckOfferValidity,
|
||||
@@ -127,7 +143,6 @@ from chia.wallet.wallet_request_types import (
|
||||
DefaultCAT,
|
||||
DeleteKey,
|
||||
DeleteNotifications,
|
||||
DeleteUnconfirmedTransactions,
|
||||
DIDCreateBackupFile,
|
||||
DIDGetMetadata,
|
||||
DIDGetPubkey,
|
||||
@@ -177,10 +192,8 @@ from chia.wallet.wallet_request_types import (
|
||||
SendTransaction,
|
||||
SendTransactionMulti,
|
||||
SetWalletResyncOnStartup,
|
||||
SignMessageByAddress,
|
||||
SpendClawbackCoins,
|
||||
SplitCoins,
|
||||
TakeOffer,
|
||||
TransactionEndpointRequest,
|
||||
TransactionEndpointResponse,
|
||||
VCSpend,
|
||||
@@ -415,7 +428,7 @@ async def test_send_transaction(wallet_environments: WalletTestFramework) -> Non
|
||||
)
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.anyio
|
||||
async def test_push_transactions(wallet_environments: WalletTestFramework) -> None:
|
||||
async def test_push_transactions(wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
|
||||
wallet: Wallet = env.xch_wallet
|
||||
@@ -458,7 +471,14 @@ async def test_push_transactions(wallet_environments: WalletTestFramework) -> No
|
||||
await farm_transaction(full_node_api, wallet_node, spend_bundle)
|
||||
|
||||
for tx in resp_client.transactions:
|
||||
assert (await client.get_transaction(GetTransaction(transaction_id=tx.name))).transaction.confirmed
|
||||
capsys.readouterr()
|
||||
await GetTransactionCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
||||
wallet_id=1,
|
||||
tx_id=tx.name.hex(),
|
||||
verbose=0,
|
||||
).run()
|
||||
assert "Status: Confirmed" in capsys.readouterr().out
|
||||
|
||||
# Just testing NOT failure here really (parsing)
|
||||
await client.push_tx(PushTX(spend_bundle=spend_bundle))
|
||||
@@ -1059,7 +1079,7 @@ async def test_send_transaction_multi(wallet_environments: WalletTestFramework)
|
||||
)
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.anyio
|
||||
async def test_get_transactions(wallet_environments: WalletTestFramework) -> None:
|
||||
async def test_get_transactions(wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
|
||||
wallet: Wallet = env.xch_wallet
|
||||
@@ -1073,15 +1093,35 @@ async def test_get_transactions(wallet_environments: WalletTestFramework) -> Non
|
||||
expected_initial_txs_count = initially_farmed_blocks * 2
|
||||
unconfirmed_txs_count = 0
|
||||
assert len(all_transactions) == expected_initial_txs_count
|
||||
# Test transaction pagination
|
||||
some_transactions = (
|
||||
await client.get_transactions(GetTransactions(wallet_id=uint32(1), start=uint32(0), end=uint32(5)))
|
||||
).transactions
|
||||
some_transactions_2 = (
|
||||
await client.get_transactions(GetTransactions(wallet_id=uint32(1), start=uint32(5), end=uint32(10)))
|
||||
).transactions
|
||||
assert some_transactions == all_transactions[0:5]
|
||||
assert some_transactions_2 == all_transactions[5:10]
|
||||
# Test transaction pagination via command
|
||||
capsys.readouterr()
|
||||
await GetTransactionsCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
||||
wallet_id=1,
|
||||
offset=0,
|
||||
limit=5,
|
||||
verbose=0,
|
||||
paginate=False,
|
||||
sort_key=SortKey.RELEVANCE,
|
||||
sort_by_height=True,
|
||||
reverse=False,
|
||||
clawback=False,
|
||||
).run()
|
||||
assert capsys.readouterr().out.count("Transaction ") == 5
|
||||
capsys.readouterr()
|
||||
await GetTransactionsCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
||||
wallet_id=1,
|
||||
offset=5,
|
||||
limit=5,
|
||||
verbose=0,
|
||||
paginate=False,
|
||||
sort_key=SortKey.RELEVANCE,
|
||||
sort_by_height=True,
|
||||
reverse=False,
|
||||
clawback=False,
|
||||
).run()
|
||||
assert capsys.readouterr().out.count("Transaction ") == 1 # 6 total farmed txs initially
|
||||
|
||||
# Testing sorts
|
||||
# Test the default sort (CONFIRMED_AT_HEIGHT)
|
||||
@@ -1279,7 +1319,11 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
|
||||
assert (
|
||||
await env_0.rpc_client.get_cat_name(CATGetName(wallet_id=cat_0_id))
|
||||
).name == wallet_type.default_wallet_name_for_unknown_cat(asset_id)
|
||||
await env_0.rpc_client.set_cat_name(CATSetName(wallet_id=cat_0_id, name="My cat"))
|
||||
await AddTokenCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env_0)["rpc_info"],
|
||||
asset_id=asset_id,
|
||||
token_name="My cat",
|
||||
).run()
|
||||
assert (await env_0.rpc_client.get_cat_name(CATGetName(wallet_id=cat_0_id))).name == "My cat"
|
||||
asset_to_name_response = await env_0.rpc_client.cat_asset_id_to_name(CATAssetIDToName(asset_id=asset_id))
|
||||
assert asset_to_name_response.wallet_id == cat_0_id
|
||||
@@ -1294,17 +1338,14 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
|
||||
assert asset_to_name_response.name == next(iter(DEFAULT_CATS.items()))[1]["name"]
|
||||
|
||||
# Creates a second wallet with the same CAT
|
||||
create_wallet_res = await env_1.rpc_client.create_new_wallet(
|
||||
CreateNewWallet(
|
||||
wallet_type=CreateNewWalletType.CAT_WALLET,
|
||||
mode=WalletCreationMode.EXISTING,
|
||||
asset_id=asset_id,
|
||||
push=True,
|
||||
),
|
||||
tx_config=wallet_environments.tx_config,
|
||||
)
|
||||
cat_1_id = create_wallet_res.wallet_id
|
||||
cat_1_asset_id = create_wallet_res.asset_id
|
||||
await AddTokenCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env_1)["rpc_info"],
|
||||
asset_id=asset_id,
|
||||
token_name="My cat",
|
||||
).run()
|
||||
cat_1_id = (await env_1.rpc_client.cat_asset_id_to_name(CATAssetIDToName(asset_id=asset_id))).wallet_id
|
||||
assert cat_1_id is not None
|
||||
cat_1_asset_id = asset_id
|
||||
assert cat_1_asset_id == asset_id
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
@@ -1563,6 +1604,7 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
|
||||
{
|
||||
"num_environments": 2,
|
||||
"blocks_needed": [1, 1],
|
||||
"reorg_exempt": True,
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
@@ -1570,7 +1612,13 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.parametrize("wallet_type", [CATWallet, RCATWallet])
|
||||
@pytest.mark.anyio
|
||||
async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_type: type[CATWallet]) -> None:
|
||||
async def test_offer_endpoints(
|
||||
wallet_environments: WalletTestFramework,
|
||||
wallet_type: type[CATWallet],
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
env_1 = wallet_environments.environments[0]
|
||||
env_2 = wallet_environments.environments[1]
|
||||
|
||||
@@ -1734,36 +1782,54 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
|
||||
assert offer_validity_response.id == offer.name()
|
||||
assert offer_validity_response.valid
|
||||
|
||||
all_offers = (await env_1.rpc_client.get_all_offers(GetAllOffers(file_contents=True))).trade_records
|
||||
assert len(all_offers) == 1
|
||||
assert TradeStatus(all_offers[0].status) == TradeStatus.PENDING_ACCEPT
|
||||
assert all_offers[0].offer == bytes(offer)
|
||||
monkeypatch.setattr("chia.cmds.wallet_funcs.cli_confirm", lambda *args, **kwargs: None)
|
||||
|
||||
offer_file = tmp_path / "pending.offer"
|
||||
capsys.readouterr()
|
||||
await GetOffersCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env_1)["rpc_info"],
|
||||
offer_id=offer.name(),
|
||||
filepath=str(offer_file),
|
||||
exclude_my_offers=False,
|
||||
exclude_taken_offers=False,
|
||||
include_completed=False,
|
||||
summaries=False,
|
||||
sort_by_relevance=True,
|
||||
reverse=False,
|
||||
).run()
|
||||
assert "PENDING_ACCEPT" in capsys.readouterr().out
|
||||
assert Offer.from_bech32(offer_file.read_text()).name() == offer.name()
|
||||
|
||||
offer_count = await env_1.rpc_client.get_offers_count()
|
||||
assert offer_count.total == 1
|
||||
assert offer_count.my_offers_count == 1
|
||||
assert offer_count.taken_offers_count == 0
|
||||
|
||||
trade_record = (
|
||||
await env_2.rpc_client.take_offer(
|
||||
TakeOffer(
|
||||
offer=offer.to_bech32(),
|
||||
fee=uint64(1),
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
)
|
||||
).trade_record
|
||||
capsys.readouterr()
|
||||
await TakeOfferCMD(
|
||||
**{
|
||||
**wallet_environments.cmd_tx_endpoint_args(env_2),
|
||||
"path_or_hex": offer.to_bech32(),
|
||||
"examine_only": False,
|
||||
"fee": uint64(1),
|
||||
"push": True,
|
||||
}
|
||||
).run()
|
||||
take_output = capsys.readouterr().out
|
||||
assert "Accepted offer with ID" in take_output
|
||||
taken_trade_id = bytes32.from_hexstr(take_output.split("Accepted offer with ID ")[1].split()[0])
|
||||
trade_record = (await env_2.rpc_client.get_offer(GetOffer(trade_id=taken_trade_id))).trade_record
|
||||
assert TradeStatus(trade_record.status) == TradeStatus.PENDING_CONFIRM
|
||||
|
||||
await env_1.rpc_client.cancel_offer(
|
||||
CancelOffer(
|
||||
trade_id=offer.name(),
|
||||
secure=False,
|
||||
push=True,
|
||||
),
|
||||
tx_config=wallet_environments.tx_config,
|
||||
)
|
||||
await CancelOfferCMD(
|
||||
**{
|
||||
**wallet_environments.cmd_tx_endpoint_args(env_1),
|
||||
"offer_id": offer.name(),
|
||||
"insecure": True,
|
||||
"fee": uint64(0),
|
||||
"push": True,
|
||||
}
|
||||
).run()
|
||||
|
||||
trade_record = (await env_1.rpc_client.get_offer(GetOffer(trade_id=offer.name(), file_contents=True))).trade_record
|
||||
assert trade_record.offer == bytes(offer)
|
||||
@@ -1777,17 +1843,26 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
|
||||
trade_record = (await env_1.rpc_client.get_offer(GetOffer(trade_id=offer.name()))).trade_record
|
||||
assert TradeStatus(trade_record.status) == TradeStatus.PENDING_CANCEL
|
||||
|
||||
create_res = await env_1.rpc_client.create_offer_for_ids(
|
||||
CreateOfferForIDs(offer={str(1): "-5", str(cat_wallet_id): "1"}, fee=uint64(1)),
|
||||
tx_config=wallet_environments.tx_config,
|
||||
)
|
||||
offer_file_2 = tmp_path / "second.offer"
|
||||
await MakeOfferCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env_1)["rpc_info"],
|
||||
offers=("1:0.000000000005",),
|
||||
requests=(f"{cat_wallet_id}:0.001",),
|
||||
filepath=offer_file_2,
|
||||
fee=uint64(1),
|
||||
reuse=bool(wallet_environments.tx_config.reuse_puzhash),
|
||||
override=False,
|
||||
valid_at=None,
|
||||
expires_at=None,
|
||||
).run()
|
||||
second_offer = Offer.from_bech32(offer_file_2.read_text())
|
||||
all_offers = (await env_1.rpc_client.get_all_offers(GetAllOffers())).trade_records
|
||||
assert len(all_offers) == 2
|
||||
offer_count = await env_1.rpc_client.get_offers_count()
|
||||
assert offer_count.total == 2
|
||||
assert offer_count.my_offers_count == 2
|
||||
assert offer_count.taken_offers_count == 0
|
||||
new_trade_record = create_res.trade_record
|
||||
new_trade_record = (await env_1.rpc_client.get_offer(GetOffer(trade_id=second_offer.name()))).trade_record
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -2299,7 +2374,11 @@ async def test_did_endpoints(wallet_environments: WalletTestFramework, capsys: p
|
||||
},
|
||||
),
|
||||
WalletStateTransition(),
|
||||
]
|
||||
],
|
||||
post_reorg_balance_differences=[
|
||||
WalletStateTransition({"did": {"set_remainder": True}}),
|
||||
WalletStateTransition(),
|
||||
],
|
||||
)
|
||||
|
||||
# Transfer DID
|
||||
@@ -2328,7 +2407,10 @@ async def test_did_endpoints(wallet_environments: WalletTestFramework, capsys: p
|
||||
"did": {"init": True, "set_remainder": True},
|
||||
}
|
||||
),
|
||||
]
|
||||
],
|
||||
# TODO: there's a bug here where the user store autoincrement means this has a new ID after deleted
|
||||
# Instead of 2, it becomes 3 because there was a 2 at some point (is my best guess)
|
||||
reorg_exempt=True,
|
||||
)
|
||||
|
||||
async def num_wallets() -> int:
|
||||
@@ -2708,14 +2790,19 @@ async def test_get_full_node_peer_count(wallet_environments: WalletTestFramework
|
||||
)
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.anyio
|
||||
async def test_key_and_address_endpoints(wallet_environments: WalletTestFramework) -> None:
|
||||
async def test_key_and_address_endpoints(
|
||||
wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
|
||||
wallet: Wallet = env.xch_wallet
|
||||
wallet_node: WalletNode = env.node
|
||||
client: WalletRpcClient = env.rpc_client
|
||||
rpc_info = wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"]
|
||||
|
||||
address = (await client.get_next_address(GetNextAddress(wallet_id=uint32(1), new_address=True))).address
|
||||
capsys.readouterr()
|
||||
await GetAddressCMD(rpc_info=rpc_info, wallet_id=1, new_address=True).run()
|
||||
address = capsys.readouterr().out.strip()
|
||||
assert len(address) > 10
|
||||
|
||||
pks = (await client.get_public_keys()).pk_fingerprints
|
||||
@@ -2725,6 +2812,24 @@ async def test_key_and_address_endpoints(wallet_environments: WalletTestFramewor
|
||||
assert height_info.height > 0
|
||||
assert (height_info.is_transaction_block is None) == (height_info.prev_transaction_block_height is None)
|
||||
|
||||
capsys.readouterr()
|
||||
show_config = env.node.config
|
||||
if "wallet" not in show_config:
|
||||
show_config = {**show_config, "wallet": show_config}
|
||||
await ShowCMD(
|
||||
rpc_info=NeedsWalletRPC(
|
||||
client_info=WalletClientInfo(
|
||||
env.rpc_client,
|
||||
env.wallet_state_manager.root_pubkey.get_fingerprint(),
|
||||
show_config,
|
||||
)
|
||||
),
|
||||
wallet_type=None,
|
||||
).run()
|
||||
show_output = capsys.readouterr().out
|
||||
assert "Sync status: Synced" in show_output
|
||||
assert f"Wallet height: {height_info.height}" in show_output
|
||||
|
||||
async with wallet.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
||||
ph = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(wallet_node)
|
||||
@@ -2739,9 +2844,19 @@ async def test_key_and_address_endpoints(wallet_environments: WalletTestFramewor
|
||||
|
||||
await time_out_assert(20, tx_in_mempool, True, client, created_tx.name)
|
||||
assert len(await wallet.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(1)) == 1
|
||||
await client.delete_unconfirmed_transactions(DeleteUnconfirmedTransactions(wallet_id=uint32(1)))
|
||||
await DeleteUnconfirmedTransactionsCMD(rpc_info=rpc_info, wallet_id=1).run()
|
||||
assert len(await wallet.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(1)) == 0
|
||||
|
||||
db_path = get_wallet_db_path(wallet_node.root_path, wallet_node.config, str(pks[0]))
|
||||
assert db_path.exists()
|
||||
capsys.readouterr()
|
||||
await CheckWalletCMD(
|
||||
context=ChiaCliContext(root_path=wallet_node.root_path),
|
||||
verbose=False,
|
||||
db_path=str(db_path),
|
||||
).run()
|
||||
assert f"Reading {db_path}" in capsys.readouterr().out
|
||||
|
||||
sk_resp = await client.get_private_key(GetPrivateKey(fingerprint=pks[0]))
|
||||
assert sk_resp.private_key.fingerprint == pks[0]
|
||||
assert sk_resp.private_key.seed is not None
|
||||
@@ -3435,21 +3550,33 @@ async def test_verify_signature(
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
async def test_sign_message_by_address(wallet_environments: WalletTestFramework) -> None:
|
||||
client: WalletRpcClient = wallet_environments.environments[0].rpc_client
|
||||
async def test_sign_message_by_address(
|
||||
wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
rpc_info = wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"]
|
||||
|
||||
message = "foo"
|
||||
address = await client.get_next_address(GetNextAddress(wallet_id=uint32(1)))
|
||||
signed_message = await client.sign_message_by_address(
|
||||
SignMessageByAddress(address=address.address, message=message)
|
||||
)
|
||||
capsys.readouterr()
|
||||
await GetAddressCMD(rpc_info=rpc_info, wallet_id=1, new_address=False).run()
|
||||
address = capsys.readouterr().out.strip()
|
||||
capsys.readouterr()
|
||||
await SignMessageCMD(
|
||||
rpc_info=rpc_info,
|
||||
address=CliAddress(decode_puzzle_hash(address), address, AddressType.XCH),
|
||||
hex_message=message,
|
||||
).run()
|
||||
output = capsys.readouterr().out
|
||||
pubkey = G1Element.from_bytes(bytes.fromhex(output.split("Public Key:")[1].split("\n")[0].strip()))
|
||||
signature = G2Element.from_bytes(bytes.fromhex(output.split("Signature:")[1].split("\n")[0].strip()))
|
||||
signing_mode = output.split("Signing Mode:")[1].split("\n")[0].strip()
|
||||
|
||||
await wallet_environments.environments[0].rpc_client.verify_signature(
|
||||
await env.rpc_client.verify_signature(
|
||||
VerifySignature(
|
||||
message=message,
|
||||
pubkey=signed_message.pubkey,
|
||||
signature=signed_message.signature,
|
||||
signing_mode=signed_message.signing_mode,
|
||||
pubkey=pubkey,
|
||||
signature=signature,
|
||||
signing_mode=signing_mode,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -214,5 +214,5 @@ pks: [<G1Element aa1a1c26055a329817a5759d877a2795f9499b97d6056edde0eea39512f24e8
|
||||
msgs: ['0000000000000000000000000000000000000000000000000000000000000000']
|
||||
add_data: ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb
|
||||
signature: a458f8f379a4bec00f36cd38e36790210f0b7c4ee772417c0cd20513d75c7505a0532d220ab03a471e9e255e2c54472a03abd31a9feb56d551bf482e31e48747b04fe90f4e3c2af98a2de0d11f869cde360f93b8efa37ec1c28950aa52bfbacd
|
||||
""" # noqa: E501
|
||||
""" # ruff: ignore[line-too-long]
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -263,14 +263,11 @@ async def test_p2dohp_wallet_signer_protocol(wallet_environments: WalletTestFram
|
||||
)
|
||||
).signed_transactions
|
||||
await wallet_rpc.submit_transactions(SubmitTransactions(signed_transactions=signed_txs))
|
||||
await wallet_environments.full_node.wait_bundle_ids_in_mempool(
|
||||
[
|
||||
WalletSpendBundle(
|
||||
[spend.as_coin_spend() for tx in signed_txs for spend in tx.transaction_info.spends],
|
||||
G2Element.from_bytes(signing_responses[0].signature),
|
||||
).name()
|
||||
]
|
||||
bundle = WalletSpendBundle(
|
||||
[spend.as_coin_spend() for tx in signed_txs for spend in tx.transaction_info.spends],
|
||||
G2Element.from_bytes(signing_responses[0].signature),
|
||||
)
|
||||
await wallet_environments.full_node.wait_bundle_ids_in_mempool([bundle.name()])
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -287,7 +284,8 @@ async def test_p2dohp_wallet_signer_protocol(wallet_environments: WalletTestFram
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
bundles_to_repush=[bundle],
|
||||
)
|
||||
|
||||
# And test that we can get compressed versions if we want
|
||||
|
||||
@@ -52,6 +52,81 @@ def test_compute_spend_hints_and_additions() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_offer_rejects_spend_exceeding_max_cost() -> None:
|
||||
from chia_rs import G2Element
|
||||
|
||||
from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
|
||||
|
||||
coin_generator = CoinGenerator()
|
||||
parent_coin = coin_generator.get()
|
||||
expensive_spend = make_spend(
|
||||
parent_coin.coin,
|
||||
Program.to(1),
|
||||
Program.to([[51, bytes32.zeros, 0] for _ in range(10000)]),
|
||||
)
|
||||
with pytest.raises(ValidationError):
|
||||
Offer({}, WalletSpendBundle([expensive_spend], G2Element()), {})
|
||||
|
||||
|
||||
def test_offer_enforces_remaining_max_cost_across_spends() -> None:
|
||||
"""Two spends each under the full block cost, but over when summed.
|
||||
|
||||
With max_cost= remaining passed through, the second spend fails inside
|
||||
compute_spend_hints_and_additions. Without it, both succeed and Offer only
|
||||
fails afterward on the residual max_cost < 0 check.
|
||||
"""
|
||||
from chia_rs import G2Element
|
||||
|
||||
from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
|
||||
|
||||
coin_generator = CoinGenerator()
|
||||
# ~6.3e9 each; MAX_BLOCK_COST_CLVM is 11e9, so one fits and two do not.
|
||||
spends = [
|
||||
make_spend(
|
||||
coin_generator.get().coin,
|
||||
Program.to(1),
|
||||
Program.to([[51, bytes32.zeros, 1] for _ in range(3500)]),
|
||||
)
|
||||
for _ in range(2)
|
||||
]
|
||||
with pytest.raises(ValidationError, match="compute_spend_hints_and_additions"):
|
||||
Offer({}, WalletSpendBundle(spends, G2Element()), {})
|
||||
|
||||
|
||||
def test_offer_maps_clvm_cost_exceeded_value_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""After prior spends leave almost no CLVM budget, run_with_cost raises ValueError.
|
||||
|
||||
Offer remaps that to ValidationError. Lower MAX_BLOCK_COST_CLVM so a cheap
|
||||
first spend can deplete the budget without needing huge CREATE_COIN lists.
|
||||
"""
|
||||
from chia_rs import G2Element
|
||||
from chia_rs.sized_ints import uint64
|
||||
|
||||
import chia.wallet.trading.offer as offer_mod
|
||||
from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
|
||||
|
||||
coin_generator = CoinGenerator()
|
||||
# Minimal first spend (~44 cost). Leave only 10 for the second so CLVM aborts
|
||||
# inside run_with_cost with ValueError("cost exceeded or below zero").
|
||||
first = make_spend(coin_generator.get().coin, Program.to(1), Program.to([]))
|
||||
_, first_cost = compute_spend_hints_and_additions(first)
|
||||
second = make_spend(
|
||||
coin_generator.get().coin,
|
||||
Program.to(1),
|
||||
Program.to([[51, bytes32.zeros, 1]]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
offer_mod,
|
||||
"DEFAULT_CONSTANTS",
|
||||
DEFAULT_CONSTANTS.replace(MAX_BLOCK_COST_CLVM=uint64(first_cost + 10)),
|
||||
)
|
||||
with pytest.raises(ValidationError, match="compute_additions for CoinSpend"):
|
||||
Offer({}, WalletSpendBundle([first, second], G2Element()), {})
|
||||
|
||||
|
||||
def test_cs_config() -> None:
|
||||
default_cs_config = DEFAULT_COIN_SELECTION_CONFIG.to_json_dict()
|
||||
assert (
|
||||
|
||||
+383
-354
@@ -1,17 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from chia_rs import AugSchemeMPL, Coin, CoinSpend
|
||||
from chia_rs import AugSchemeMPL, Coin, CoinSpend, G1Element, G2Element
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
|
||||
|
||||
from chia._tests.cmds.test_cmd_framework import check_click_parsing
|
||||
from chia._tests.conftest import ConsensusMode
|
||||
from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework
|
||||
from chia._tests.environments.wallet import (
|
||||
STANDARD_TX_ENDPOINT_ARGS,
|
||||
WalletEnvironment,
|
||||
WalletStateTransition,
|
||||
WalletTestFramework,
|
||||
)
|
||||
from chia._tests.util.time_out_assert import time_out_assert
|
||||
from chia.cmds.cmd_classes import ChiaCliContext
|
||||
from chia.cmds.cmd_helpers import NeedsWalletRPC
|
||||
from chia.cmds.param_types import CliAddress, CliAmount
|
||||
from chia.cmds.wallet import (
|
||||
AddTokenCMD,
|
||||
CancelOfferCMD,
|
||||
CheckWalletCMD,
|
||||
ClawbackCMD,
|
||||
DeleteUnconfirmedTransactionsCMD,
|
||||
GetAddressCMD,
|
||||
GetDerivationIndexCMD,
|
||||
GetOffersCMD,
|
||||
GetTransactionCMD,
|
||||
GetTransactionsCMD,
|
||||
MakeOfferCMD,
|
||||
SendCMD,
|
||||
ShowCMD,
|
||||
SignMessageCMD,
|
||||
TakeOfferCMD,
|
||||
UpdateDerivationIndexCMD,
|
||||
)
|
||||
from chia.server.server import ChiaServer
|
||||
from chia.simulator.block_tools import BlockTools
|
||||
from chia.simulator.full_node_simulator import FullNodeSimulator
|
||||
@@ -25,6 +53,9 @@ from chia.util.errors import Err
|
||||
from chia.wallet.derive_keys import master_sk_to_wallet_sk
|
||||
from chia.wallet.estimate_fees import estimate_fees
|
||||
from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.transaction_sorting import SortKey
|
||||
from chia.wallet.util.address_type import AddressType
|
||||
from chia.wallet.util.compute_additions import compute_additions
|
||||
from chia.wallet.util.query_filter import TransactionTypeFilter
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
@@ -32,16 +63,78 @@ from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
|
||||
from chia.wallet.util.wallet_types import CoinType, WalletType
|
||||
from chia.wallet.wallet_node import WalletNode, get_wallet_db_path
|
||||
from chia.wallet.wallet_request_types import (
|
||||
ClawbackPuzzleDecoratorOverride,
|
||||
GetCoinRecords,
|
||||
GetTransactionMemo,
|
||||
GetTransactions,
|
||||
SendTransaction,
|
||||
SignMessageByAddress,
|
||||
SpendClawbackCoins,
|
||||
)
|
||||
|
||||
|
||||
async def run_send_cmd(
|
||||
wallet_environments: WalletTestFramework,
|
||||
env: WalletEnvironment,
|
||||
*,
|
||||
amount: uint64,
|
||||
puzzle_hash: bytes32,
|
||||
clawback_time: int = 0,
|
||||
memo: str | None = None,
|
||||
) -> list[TransactionRecord]:
|
||||
wallet_id = int(env.xch_wallet.id())
|
||||
previous_transaction_ids = {
|
||||
transaction.name for transaction in await env.wallet_state_manager.get_all_transactions(wallet_id)
|
||||
}
|
||||
await SendCMD(
|
||||
**{
|
||||
**wallet_environments.cmd_tx_endpoint_args(env),
|
||||
"wallet_id": wallet_id,
|
||||
"amount": CliAmount(mojos=True, amount=amount),
|
||||
"memo": memo,
|
||||
"address": CliAddress(
|
||||
puzzle_hash=puzzle_hash,
|
||||
original_address=env.wallet_state_manager.encode_puzzle_hash(puzzle_hash),
|
||||
address_type=AddressType.XCH,
|
||||
),
|
||||
"override": False,
|
||||
"clawback_time": clawback_time,
|
||||
"push": True,
|
||||
}
|
||||
).run()
|
||||
return [
|
||||
transaction
|
||||
for transaction in await env.wallet_state_manager.get_all_transactions(wallet_id)
|
||||
if transaction.name not in previous_transaction_ids and transaction.spend_bundle is not None
|
||||
]
|
||||
|
||||
|
||||
async def run_clawback_cmd(
|
||||
wallet_environments: WalletTestFramework,
|
||||
env: WalletEnvironment,
|
||||
*,
|
||||
coin_ids: list[bytes32],
|
||||
fee: uint64 = uint64(0),
|
||||
) -> list[TransactionRecord]:
|
||||
wallet_id = int(env.xch_wallet.id())
|
||||
previous_transaction_ids = {
|
||||
transaction.name for transaction in await env.wallet_state_manager.get_all_transactions(wallet_id)
|
||||
}
|
||||
await ClawbackCMD(
|
||||
**{
|
||||
**wallet_environments.cmd_tx_endpoint_args(env),
|
||||
"wallet_id": wallet_id,
|
||||
"tx_ids": ",".join(coin_id.hex() for coin_id in coin_ids),
|
||||
"fee": fee,
|
||||
"force": False,
|
||||
"push": True,
|
||||
}
|
||||
).run()
|
||||
return [
|
||||
transaction
|
||||
for transaction in await env.wallet_state_manager.get_all_transactions(wallet_id)
|
||||
if transaction.name not in previous_transaction_ids and transaction.spend_bundle is not None
|
||||
]
|
||||
|
||||
|
||||
class TestWalletSimulator:
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments",
|
||||
@@ -78,17 +171,25 @@ class TestWalletSimulator:
|
||||
@pytest.mark.anyio
|
||||
async def test_wallet_make_transaction(self, wallet_environments: WalletTestFramework) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
wallet = env.xch_wallet
|
||||
|
||||
tx_amount = 10
|
||||
|
||||
async with wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
|
||||
await wallet.generate_signed_transaction(
|
||||
[uint64(tx_amount)],
|
||||
[bytes32.zeros],
|
||||
action_scope,
|
||||
uint64(0),
|
||||
)
|
||||
await SendCMD(
|
||||
**{
|
||||
**wallet_environments.cmd_tx_endpoint_args(env),
|
||||
"wallet_id": 1,
|
||||
"amount": CliAmount(mojos=True, amount=uint64(tx_amount)),
|
||||
"memo": None,
|
||||
"address": CliAddress(
|
||||
puzzle_hash=bytes32.zeros,
|
||||
original_address=encode_puzzle_hash(bytes32.zeros, "txch"),
|
||||
address_type=AddressType.XCH,
|
||||
),
|
||||
"override": False,
|
||||
"clawback_time": 0,
|
||||
"push": True,
|
||||
}
|
||||
).run()
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -116,11 +217,9 @@ class TestWalletSimulator:
|
||||
)
|
||||
|
||||
# Test match_hinted_coin
|
||||
async with wallet.wallet_state_manager.new_action_scope(
|
||||
wallet_environments.tx_config, push=False
|
||||
) as action_scope:
|
||||
selected_coin = next(iter(await wallet.select_coins(uint64(0), action_scope)))
|
||||
assert await wallet.match_hinted_coin(selected_coin, selected_coin.puzzle_hash)
|
||||
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=False) as action_scope:
|
||||
selected_coin = next(iter(await env.xch_wallet.select_coins(uint64(0), action_scope)))
|
||||
assert await env.xch_wallet.match_hinted_coin(selected_coin, selected_coin.puzzle_hash)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments",
|
||||
@@ -194,17 +293,14 @@ class TestWalletSimulator:
|
||||
) as action_scope:
|
||||
wallet_2_puzhash = await action_scope.get_puzzle_hash(env_2.wallet_state_manager)
|
||||
|
||||
send_response = await env_1.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=uint32(1),
|
||||
amount=uint64(500),
|
||||
address=encode_puzzle_hash(wallet_2_puzhash, "txch"),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
tx_config=wallet_environments.tx_config,
|
||||
[send_transaction] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env_1,
|
||||
amount=uint64(500),
|
||||
puzzle_hash=wallet_2_puzhash,
|
||||
clawback_time=5,
|
||||
)
|
||||
clawback_coin_id = send_response.transactions[0].additions[0].name()
|
||||
clawback_coin_id = send_transaction.additions[0].name()
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -268,9 +364,7 @@ class TestWalletSimulator:
|
||||
|
||||
# reset and claim the coin
|
||||
await env_2.wallet_state_manager.coin_store.add_coin_record(coin_record)
|
||||
resp = await env_2.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(coin_ids=[clawback_coin_id], push=True), tx_config=wallet_environments.tx_config
|
||||
)
|
||||
await run_clawback_cmd(wallet_environments, env_2, coin_ids=[clawback_coin_id])
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
@@ -312,17 +406,12 @@ class TestWalletSimulator:
|
||||
|
||||
# Transfer to normal wallet
|
||||
for _ in range(number_of_coins):
|
||||
await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[
|
||||
ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(10))
|
||||
],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount),
|
||||
puzzle_hash=normal_puzhash,
|
||||
clawback_time=10,
|
||||
)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
@@ -359,17 +448,13 @@ class TestWalletSimulator:
|
||||
20, wsm_1.coin_store.count_small_unspent, number_of_coins, tx_amount * 2, CoinType.CLAWBACK
|
||||
)
|
||||
|
||||
bad_send = await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(10))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx_bad] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount),
|
||||
puzzle_hash=normal_puzhash,
|
||||
clawback_time=10,
|
||||
)
|
||||
[tx_bad] = bad_send.transactions
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -427,7 +512,8 @@ class TestWalletSimulator:
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
reorg_exempt=True,
|
||||
)
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -468,16 +554,13 @@ class TestWalletSimulator:
|
||||
normal_puzhash = await action_scope.get_puzzle_hash(wsm_2)
|
||||
|
||||
# Transfer to normal wallet
|
||||
send_response = await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(500))],
|
||||
memos=["Test"],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount),
|
||||
puzzle_hash=normal_puzhash,
|
||||
clawback_time=500,
|
||||
memo="Test",
|
||||
)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
@@ -521,7 +604,6 @@ class TestWalletSimulator:
|
||||
),
|
||||
)
|
||||
# clawback merkle coin
|
||||
[tx] = send_response.transactions
|
||||
merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1]
|
||||
interested_coins = await wsm_2.interested_store.get_interested_coin_ids()
|
||||
assert merkle_coin.name() in set(interested_coins)
|
||||
@@ -532,15 +614,13 @@ class TestWalletSimulator:
|
||||
assert txs_response.transactions[0].metadata.coin_id == merkle_coin.name()
|
||||
|
||||
test_fee = 10
|
||||
resp = await env.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(
|
||||
coin_ids=[merkle_coin.name()],
|
||||
fee=uint64(test_fee),
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
clawback_transactions = await run_clawback_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
coin_ids=[merkle_coin.name()],
|
||||
fee=uint64(test_fee),
|
||||
)
|
||||
assert len(resp.transaction_ids) == 1
|
||||
assert len(clawback_transactions) == 1
|
||||
# just test that an extra submission doesn't do anything
|
||||
await env.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(
|
||||
@@ -633,16 +713,13 @@ class TestWalletSimulator:
|
||||
async with wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
|
||||
normal_puzhash = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
||||
|
||||
send_response = await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
memos=["Test"],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount),
|
||||
puzzle_hash=normal_puzhash,
|
||||
clawback_time=5,
|
||||
memo="Test",
|
||||
)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
@@ -677,18 +754,15 @@ class TestWalletSimulator:
|
||||
# Check merkle coins
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
# Claim merkle coin
|
||||
[tx] = send_response.transactions
|
||||
merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1]
|
||||
test_fee = 10
|
||||
resp = await env.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(
|
||||
coin_ids=[merkle_coin.name()],
|
||||
fee=uint64(test_fee),
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
clawback_transactions = await run_clawback_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
coin_ids=[merkle_coin.name()],
|
||||
fee=uint64(test_fee),
|
||||
)
|
||||
assert len(resp.transaction_ids) == 1
|
||||
assert len(clawback_transactions) == 1
|
||||
# Wait mempool update
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -758,25 +832,19 @@ class TestWalletSimulator:
|
||||
async with wsm_2.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
|
||||
normal_puzhash = await action_scope.get_puzzle_hash(wsm_2)
|
||||
# Transfer to normal wallet
|
||||
send_response_1 = await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount_1),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx_1] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount_1),
|
||||
puzzle_hash=normal_puzhash,
|
||||
clawback_time=5,
|
||||
)
|
||||
send_response_2 = await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount_2),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx_2] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount_2),
|
||||
puzzle_hash=normal_puzhash,
|
||||
clawback_time=5,
|
||||
)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
@@ -827,20 +895,16 @@ class TestWalletSimulator:
|
||||
)
|
||||
|
||||
# Claim merkle coin
|
||||
[tx_1] = send_response_1.transactions
|
||||
[tx_2] = send_response_2.transactions
|
||||
merkle_coin_1 = tx_1.additions[0] if tx_1.additions[0].amount == tx_amount_1 else tx_1.additions[1]
|
||||
merkle_coin_2 = tx_2.additions[0] if tx_2.additions[0].amount == tx_amount_2 else tx_2.additions[1]
|
||||
test_fee = 10
|
||||
resp = await env_2.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(
|
||||
coin_ids=[merkle_coin_1.name(), merkle_coin_2.name()],
|
||||
fee=uint64(test_fee),
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
clawback_transactions = await run_clawback_cmd(
|
||||
wallet_environments,
|
||||
env_2,
|
||||
coin_ids=[merkle_coin_1.name(), merkle_coin_2.name()],
|
||||
fee=uint64(test_fee),
|
||||
)
|
||||
assert len(resp.transaction_ids) == 1
|
||||
assert len(clawback_transactions) == 1
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -890,194 +954,6 @@ class TestWalletSimulator:
|
||||
assert txs_response.transactions[0].confirmed
|
||||
assert txs_response.transactions[1].confirmed
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments",
|
||||
[{"num_environments": 2, "blocks_needed": [1, 1], "reuse_puzhash": True}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.anyio
|
||||
async def test_wallet_clawback_reorg(self, wallet_environments: WalletTestFramework) -> None:
|
||||
full_node_api = wallet_environments.full_node
|
||||
env = wallet_environments.environments[0]
|
||||
env_2 = wallet_environments.environments[1]
|
||||
wsm = env.wallet_state_manager
|
||||
wsm_2 = env_2.wallet_state_manager
|
||||
|
||||
tx_amount = 500
|
||||
async with wsm_2.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
|
||||
normal_puzhash = await action_scope.get_puzzle_hash(wsm_2)
|
||||
# Transfer to normal wallet
|
||||
await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(normal_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
1: {
|
||||
"unconfirmed_wallet_balance": -1 * tx_amount,
|
||||
"<=#spendable_balance": -1 * tx_amount,
|
||||
"<=#max_send_amount": -1 * tx_amount,
|
||||
">=#pending_change": 1, # any amount increase
|
||||
"pending_coin_removal_count": 1,
|
||||
}
|
||||
},
|
||||
post_block_balance_updates={
|
||||
1: {
|
||||
"confirmed_wallet_balance": -1 * tx_amount,
|
||||
">=#spendable_balance": 1, # any amount increase
|
||||
">=#max_send_amount": 1, # any amount increase
|
||||
"<=#pending_change": -1, # any amount decrease
|
||||
"pending_coin_removal_count": -1,
|
||||
}
|
||||
},
|
||||
),
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={},
|
||||
post_block_balance_updates={},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# Check merkle coins
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
# Reorg before claim
|
||||
# Test Reorg mint
|
||||
height = full_node_api.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await full_node_api.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 2), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 0, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 0, 1000, CoinType.CLAWBACK)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
1: {
|
||||
"confirmed_wallet_balance": tx_amount, # confirmed balance comes back
|
||||
# clawback transaction is now outstanding
|
||||
"<=#spendable_balance": -1 * tx_amount,
|
||||
"<=#max_send_amount": -1 * tx_amount,
|
||||
">=#pending_change": 1, # any amount increase
|
||||
"pending_coin_removal_count": 1,
|
||||
}
|
||||
},
|
||||
post_block_balance_updates={
|
||||
1: {
|
||||
"confirmed_wallet_balance": -1 * tx_amount,
|
||||
">=#spendable_balance": 1, # any amount increase
|
||||
">=#max_send_amount": 1, # any amount increase
|
||||
"<=#pending_change": -1, # any amount decrease
|
||||
"pending_coin_removal_count": -1,
|
||||
}
|
||||
},
|
||||
),
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={},
|
||||
post_block_balance_updates={},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
|
||||
# Claim merkle coin
|
||||
await env_2.rpc_client.set_auto_claim(AutoClaimSettings(enabled=True))
|
||||
# clawback merkle coin
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(),
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={},
|
||||
# After auto claim is set, the next block will trigger submission of clawback claims
|
||||
post_block_balance_updates={
|
||||
1: {
|
||||
"unconfirmed_wallet_balance": tx_amount,
|
||||
"pending_change": tx_amount, # This is a little weird but I think intentional and correct
|
||||
"pending_coin_removal_count": 1,
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(),
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={},
|
||||
post_block_balance_updates={
|
||||
1: {
|
||||
"confirmed_wallet_balance": tx_amount,
|
||||
"spendable_balance": tx_amount,
|
||||
"max_send_amount": tx_amount,
|
||||
"unspent_coin_count": 1,
|
||||
"pending_change": -1 * tx_amount,
|
||||
"pending_coin_removal_count": -1,
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 0, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 0, 1000, CoinType.CLAWBACK)
|
||||
# Reorg after claim
|
||||
height = full_node_api.full_node.blockchain.get_peak_height()
|
||||
assert height is not None
|
||||
await full_node_api.reorg_from_index_to_new_index(
|
||||
ReorgProtocol(uint32(height - 1), uint32(height + 1), bytes32.zeros, None)
|
||||
)
|
||||
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={},
|
||||
post_block_balance_updates={},
|
||||
),
|
||||
WalletStateTransition(
|
||||
pre_block_balance_updates={
|
||||
1: {
|
||||
"confirmed_wallet_balance": -1 * tx_amount,
|
||||
"spendable_balance": -1 * tx_amount,
|
||||
"max_send_amount": -1 * tx_amount,
|
||||
"unspent_coin_count": -1,
|
||||
"pending_change": tx_amount,
|
||||
"pending_coin_removal_count": 1,
|
||||
}
|
||||
},
|
||||
post_block_balance_updates={
|
||||
1: {
|
||||
"confirmed_wallet_balance": tx_amount,
|
||||
"spendable_balance": tx_amount,
|
||||
"max_send_amount": tx_amount,
|
||||
"unspent_coin_count": 1,
|
||||
"pending_change": -1 * tx_amount,
|
||||
"pending_coin_removal_count": -1,
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 0, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 0, 1000, CoinType.CLAWBACK)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"wallet_environments",
|
||||
[{"num_environments": 1, "blocks_needed": [1], "trusted": True, "reuse_puzhash": True}],
|
||||
@@ -1090,15 +966,12 @@ class TestWalletSimulator:
|
||||
wsm = env.wallet_state_manager
|
||||
|
||||
tx_amount = 500
|
||||
send_response = await env.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env.wallet_state_manager.encode_puzzle_hash(bytes32.zeros),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env,
|
||||
amount=uint64(tx_amount),
|
||||
puzzle_hash=bytes32.zeros,
|
||||
clawback_time=5,
|
||||
)
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
@@ -1133,7 +1006,6 @@ class TestWalletSimulator:
|
||||
# Check merkle coins
|
||||
await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
# clawback merkle coin
|
||||
[tx] = send_response.transactions
|
||||
merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1]
|
||||
resp = await env.rpc_client.get_coin_records(GetCoinRecords(wallet_id=uint32(1), coin_type=uint8(1)))
|
||||
assert len(resp.coin_records) == 1
|
||||
@@ -1162,18 +1034,14 @@ class TestWalletSimulator:
|
||||
|
||||
tx_amount = 500
|
||||
# Transfer to normal wallet
|
||||
send_response = await env_1.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env_1.xch_wallet.id(),
|
||||
amount=uint64(tx_amount),
|
||||
address=env_1.wallet_state_manager.encode_puzzle_hash(wallet_2_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx1] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env_1,
|
||||
amount=uint64(tx_amount),
|
||||
puzzle_hash=wallet_2_puzhash,
|
||||
clawback_time=5,
|
||||
)
|
||||
|
||||
[tx1] = send_response.transactions
|
||||
clawback_coin_id_1 = tx1.additions[0].name()
|
||||
assert tx1.spend_bundle is not None
|
||||
|
||||
@@ -1211,17 +1079,13 @@ class TestWalletSimulator:
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
|
||||
tx_amount2 = 700
|
||||
send_response = await env_1.rpc_client.send_transaction(
|
||||
SendTransaction(
|
||||
wallet_id=env_1.xch_wallet.id(),
|
||||
amount=uint64(tx_amount2),
|
||||
address=env_1.wallet_state_manager.encode_puzzle_hash(wallet_1_puzhash),
|
||||
puzzle_decorator=[ClawbackPuzzleDecoratorOverride(decorator="CLAWBACK", clawback_timelock=uint64(5))],
|
||||
push=True,
|
||||
),
|
||||
wallet_environments.tx_config,
|
||||
[tx2] = await run_send_cmd(
|
||||
wallet_environments,
|
||||
env_1,
|
||||
amount=uint64(tx_amount2),
|
||||
puzzle_hash=wallet_1_puzhash,
|
||||
clawback_time=5,
|
||||
)
|
||||
[tx2] = send_response.transactions
|
||||
clawback_coin_id_2 = tx2.additions[0].name()
|
||||
assert tx2.spend_bundle is not None
|
||||
|
||||
@@ -1258,14 +1122,8 @@ class TestWalletSimulator:
|
||||
await time_out_assert(20, wsm_1.coin_store.count_small_unspent, 2, 1000, CoinType.CLAWBACK)
|
||||
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
|
||||
# clawback merkle coin
|
||||
resp = await env_1.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(coin_ids=[clawback_coin_id_1], push=True), wallet_environments.tx_config
|
||||
)
|
||||
assert len(resp.transaction_ids) == 1
|
||||
resp = await env_1.rpc_client.spend_clawback_coins(
|
||||
SpendClawbackCoins(coin_ids=[clawback_coin_id_2], push=True), wallet_environments.tx_config
|
||||
)
|
||||
assert len(resp.transaction_ids) == 1
|
||||
assert len(await run_clawback_cmd(wallet_environments, env_1, coin_ids=[clawback_coin_id_1])) == 1
|
||||
assert len(await run_clawback_cmd(wallet_environments, env_1, coin_ids=[clawback_coin_id_2])) == 1
|
||||
|
||||
await wallet_environments.process_pending_states(
|
||||
[
|
||||
@@ -2154,7 +2012,9 @@ class TestWalletSimulator:
|
||||
)
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.anyio
|
||||
async def test_sign_message(self, wallet_environments: WalletTestFramework) -> None:
|
||||
async def test_sign_message(
|
||||
self, wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
api_0 = env.rpc_api
|
||||
|
||||
@@ -2163,20 +2023,31 @@ class TestWalletSimulator:
|
||||
|
||||
async with env.wallet_state_manager.new_action_scope(wallet_environments.tx_config, push=True) as action_scope:
|
||||
ph = await action_scope.get_puzzle_hash(env.wallet_state_manager)
|
||||
response = await api_0.sign_message_by_address(
|
||||
SignMessageByAddress(address=encode_puzzle_hash(ph, "xch"), message=message)
|
||||
)
|
||||
address = encode_puzzle_hash(ph, "xch")
|
||||
capsys.readouterr()
|
||||
await SignMessageCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
||||
address=CliAddress(ph, address, AddressType.XCH),
|
||||
hex_message=message,
|
||||
).run()
|
||||
output = capsys.readouterr().out
|
||||
assert f"Message: {message}" in output
|
||||
assert "Public Key:" in output
|
||||
assert "Signature:" in output
|
||||
|
||||
pubkey = G1Element.from_bytes(bytes.fromhex(output.split("Public Key:")[1].split("\n")[0].strip()))
|
||||
signature = G2Element.from_bytes(bytes.fromhex(output.split("Signature:")[1].split("\n")[0].strip()))
|
||||
puzzle: Program = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, message))
|
||||
|
||||
assert AugSchemeMPL.verify(
|
||||
response.pubkey,
|
||||
pubkey,
|
||||
puzzle.get_tree_hash(),
|
||||
response.signature,
|
||||
signature,
|
||||
)
|
||||
# Test hex string
|
||||
message = "0123456789ABCDEF"
|
||||
response = await api_0.sign_message_by_address(
|
||||
SignMessageByAddress(address=encode_puzzle_hash(ph, "xch"), message=message, is_hex=True)
|
||||
SignMessageByAddress(address=address, message=message, is_hex=True)
|
||||
)
|
||||
puzzle = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, bytes.fromhex(message)))
|
||||
|
||||
@@ -2188,7 +2059,7 @@ class TestWalletSimulator:
|
||||
# Test informal input
|
||||
message = "0123456789ABCDEF"
|
||||
response = await api_0.sign_message_by_address(
|
||||
SignMessageByAddress(address=encode_puzzle_hash(ph, "xch"), message=message, is_hex=True, safe_mode=True)
|
||||
SignMessageByAddress(address=address, message=message, is_hex=True, safe_mode=True)
|
||||
)
|
||||
puzzle = Program.to((CHIP_0002_SIGN_MESSAGE_PREFIX, bytes.fromhex(message)))
|
||||
|
||||
@@ -2200,7 +2071,7 @@ class TestWalletSimulator:
|
||||
# Test BLS sign string
|
||||
message = "Hello World"
|
||||
response = await api_0.sign_message_by_address(
|
||||
SignMessageByAddress(address=encode_puzzle_hash(ph, "xch"), message=message, is_hex=False, safe_mode=False)
|
||||
SignMessageByAddress(address=address, message=message, is_hex=False, safe_mode=False)
|
||||
)
|
||||
|
||||
assert AugSchemeMPL.verify(
|
||||
@@ -2211,7 +2082,7 @@ class TestWalletSimulator:
|
||||
# Test BLS sign hex
|
||||
message = "0123456789ABCDEF"
|
||||
response = await api_0.sign_message_by_address(
|
||||
SignMessageByAddress(address=encode_puzzle_hash(ph, "xch"), message=message, is_hex=True, safe_mode=False)
|
||||
SignMessageByAddress(address=address, message=message, is_hex=True, safe_mode=False)
|
||||
)
|
||||
|
||||
assert AugSchemeMPL.verify(
|
||||
@@ -2322,6 +2193,164 @@ class TestWalletSimulator:
|
||||
await action_scope.get_puzzle_hash(wallet.wallet_state_manager, override_reuse_puzhash_with=False)
|
||||
|
||||
|
||||
def test_wallet_command_parsing() -> None:
|
||||
bare_rpc = NeedsWalletRPC(client_info=None, wallet_rpc_port=None, fingerprint=None)
|
||||
tx_args = {**STANDARD_TX_ENDPOINT_ARGS, "rpc_info": bare_rpc}
|
||||
puzzle_hash = bytes32([1] * 32)
|
||||
address = encode_puzzle_hash(puzzle_hash, "txch")
|
||||
cli_address = CliAddress(puzzle_hash=puzzle_hash, original_address=address, address_type=AddressType.XCH)
|
||||
identifier = bytes32([2] * 32)
|
||||
|
||||
check_click_parsing(
|
||||
GetTransactionCMD(rpc_info=bare_rpc, wallet_id=1, tx_id=identifier.hex(), verbose=1),
|
||||
"-tx",
|
||||
identifier.hex(),
|
||||
"-v",
|
||||
)
|
||||
check_click_parsing(
|
||||
GetTransactionsCMD(
|
||||
rpc_info=bare_rpc,
|
||||
wallet_id=2,
|
||||
offset=3,
|
||||
limit=4,
|
||||
verbose=1,
|
||||
paginate=False,
|
||||
sort_key=SortKey.RELEVANCE,
|
||||
sort_by_height=True,
|
||||
reverse=True,
|
||||
clawback=True,
|
||||
),
|
||||
"-i",
|
||||
"2",
|
||||
"-o",
|
||||
"3",
|
||||
"-l",
|
||||
"4",
|
||||
"-v",
|
||||
"--no-paginate",
|
||||
"--sort-by-height",
|
||||
"--reverse",
|
||||
"--clawback",
|
||||
)
|
||||
check_click_parsing(
|
||||
SendCMD(
|
||||
**{
|
||||
**tx_args,
|
||||
"wallet_id": 2,
|
||||
"amount": CliAmount(mojos=False, amount=Decimal("1")),
|
||||
"memo": "memo",
|
||||
"address": cli_address,
|
||||
"override": True,
|
||||
"clawback_time": 5,
|
||||
}
|
||||
),
|
||||
"-i",
|
||||
"2",
|
||||
"-a",
|
||||
"1",
|
||||
"-e",
|
||||
"memo",
|
||||
"-t",
|
||||
address,
|
||||
"-o",
|
||||
"--clawback_time",
|
||||
"5",
|
||||
context=ChiaCliContext(expected_prefix="txch"),
|
||||
)
|
||||
check_click_parsing(
|
||||
ShowCMD(rpc_info=bare_rpc, wallet_type=WalletType.STANDARD_WALLET.name.lower()), "-w", "standard_wallet"
|
||||
)
|
||||
check_click_parsing(GetAddressCMD(rpc_info=bare_rpc, wallet_id=2, new_address=True), "-i", "2", "-n")
|
||||
check_click_parsing(
|
||||
ClawbackCMD(**{**tx_args, "wallet_id": 1, "tx_ids": identifier.hex(), "force": True}),
|
||||
"-ids",
|
||||
identifier.hex(),
|
||||
"--force",
|
||||
)
|
||||
check_click_parsing(DeleteUnconfirmedTransactionsCMD(rpc_info=bare_rpc, wallet_id=2), "-i", "2")
|
||||
check_click_parsing(GetDerivationIndexCMD(rpc_info=bare_rpc))
|
||||
check_click_parsing(
|
||||
SignMessageCMD(rpc_info=bare_rpc, address=cli_address, hex_message="abcd"),
|
||||
"-a",
|
||||
address,
|
||||
"-m",
|
||||
"abcd",
|
||||
context=ChiaCliContext(expected_prefix="txch"),
|
||||
)
|
||||
check_click_parsing(UpdateDerivationIndexCMD(rpc_info=bare_rpc, index=100), "-i", "100")
|
||||
check_click_parsing(
|
||||
AddTokenCMD(rpc_info=bare_rpc, asset_id=identifier, token_name="token"),
|
||||
"-id",
|
||||
identifier.hex(),
|
||||
"-n",
|
||||
"token",
|
||||
)
|
||||
check_click_parsing(
|
||||
MakeOfferCMD(
|
||||
rpc_info=bare_rpc,
|
||||
offers=("1:1",),
|
||||
requests=("2:2",),
|
||||
filepath=Path("offer.txt"),
|
||||
fee=uint64(0),
|
||||
reuse=True,
|
||||
override=False,
|
||||
valid_at=100,
|
||||
expires_at=150,
|
||||
),
|
||||
"-o",
|
||||
"1:1",
|
||||
"-r",
|
||||
"2:2",
|
||||
"-p",
|
||||
"offer.txt",
|
||||
"--reuse",
|
||||
"--valid-at",
|
||||
"100",
|
||||
"--expires-at",
|
||||
"150",
|
||||
)
|
||||
check_click_parsing(
|
||||
GetOffersCMD(
|
||||
rpc_info=bare_rpc,
|
||||
offer_id=identifier,
|
||||
filepath="offer.txt",
|
||||
exclude_my_offers=True,
|
||||
exclude_taken_offers=True,
|
||||
include_completed=True,
|
||||
summaries=True,
|
||||
sort_by_relevance=False,
|
||||
reverse=True,
|
||||
),
|
||||
"-id",
|
||||
identifier.hex(),
|
||||
"-p",
|
||||
"offer.txt",
|
||||
"-em",
|
||||
"-et",
|
||||
"-ic",
|
||||
"-s",
|
||||
"--sort-by-confirmed-height",
|
||||
"-r",
|
||||
)
|
||||
check_click_parsing(
|
||||
TakeOfferCMD(**{**tx_args, "path_or_hex": "offer-data", "examine_only": True}),
|
||||
"offer-data",
|
||||
"-e",
|
||||
)
|
||||
check_click_parsing(
|
||||
CancelOfferCMD(**{**tx_args, "offer_id": identifier, "insecure": True}),
|
||||
"-id",
|
||||
identifier.hex(),
|
||||
"--insecure",
|
||||
)
|
||||
check_click_parsing(
|
||||
CheckWalletCMD(context=ChiaCliContext(), verbose=True, db_path="wallet.sqlite"),
|
||||
"-v",
|
||||
"--db-path",
|
||||
"wallet.sqlite",
|
||||
)
|
||||
|
||||
|
||||
def test_get_wallet_db_path_v2_r1() -> None:
|
||||
root_path: Path = Path("/x/y/z/.chia/mainnet").resolve()
|
||||
config: dict[str, Any] = {
|
||||
|
||||
@@ -44,6 +44,7 @@ from chia.wallet.util.peer_request_cache import PeerRequestCache
|
||||
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
|
||||
from chia.wallet.util.wallet_sync_utils import PeerRequestException
|
||||
from chia.wallet.wallet_node import Balance, WalletNode, request_and_validate_header_block
|
||||
from chia.wallet.wallet_node_api import WalletNodeAPI
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -624,6 +625,75 @@ async def test_get_timestamp_for_height_from_peer_rejects_wrong_response_height(
|
||||
add_to_blocks.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("peak_response", [None, []])
|
||||
async def test_get_timestamp_for_height_from_peer_rejects_missing_peak_response(
|
||||
root_path_populated_with_config: Path, monkeypatch: pytest.MonkeyPatch, peak_response: list[object] | None
|
||||
) -> None:
|
||||
config = load_config(root_path_populated_with_config, "config.yaml", "wallet")
|
||||
wallet_node = WalletNode(config, root_path_populated_with_config, test_constants)
|
||||
expected_hash = bytes32(b"\x01" * 32)
|
||||
peer = cast(WSChiaConnection, types.SimpleNamespace(get_peer_info=lambda: "peer"))
|
||||
add_to_blocks = Mock()
|
||||
cache = types.SimpleNamespace(
|
||||
get_height_timestamp=Mock(return_value=None),
|
||||
get_block=Mock(return_value=None),
|
||||
add_to_blocks=add_to_blocks,
|
||||
)
|
||||
requested_heights: list[int] = []
|
||||
|
||||
async def missing_response(peer: WSChiaConnection, start_height: uint32, end_height: uint32) -> list[object] | None:
|
||||
requested_heights.append(int(start_height))
|
||||
return peak_response
|
||||
|
||||
monkeypatch.setattr(wallet_node, "get_cache_for_peer", lambda peer: cache)
|
||||
monkeypatch.setattr("chia.wallet.wallet_node.request_header_blocks", missing_response)
|
||||
|
||||
# An anchored lookup whose peak height returns no block (empty or timeout) must not
|
||||
# backtrack with a stale anchor; it returns None after the single failed peak request.
|
||||
assert await wallet_node.get_timestamp_for_height_from_peer(uint32(100), peer, expected_hash) is None
|
||||
assert requested_heights == [100]
|
||||
add_to_blocks.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_timestamp_for_height_from_peer_unanchored_backtracks_past_missing_peak(
|
||||
root_path_populated_with_config: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
config = load_config(root_path_populated_with_config, "config.yaml", "wallet")
|
||||
wallet_node = WalletNode(config, root_path_populated_with_config, test_constants)
|
||||
timestamp = uint64(12_345)
|
||||
peer = cast(WSChiaConnection, types.SimpleNamespace(get_peer_info=lambda: "peer"))
|
||||
add_to_blocks = Mock()
|
||||
cache = types.SimpleNamespace(
|
||||
get_height_timestamp=Mock(return_value=None),
|
||||
get_block=Mock(return_value=None),
|
||||
add_to_blocks=add_to_blocks,
|
||||
)
|
||||
requested_heights: list[int] = []
|
||||
|
||||
async def response(peer: WSChiaConnection, start_height: uint32, end_height: uint32) -> list[object]:
|
||||
requested_heights.append(int(start_height))
|
||||
if int(start_height) == 100:
|
||||
return []
|
||||
return [
|
||||
types.SimpleNamespace(
|
||||
height=uint32(start_height),
|
||||
header_hash=bytes32(b"\x05" * 32),
|
||||
prev_header_hash=bytes32(b"\x06" * 32),
|
||||
foliage_transaction_block=types.SimpleNamespace(timestamp=timestamp),
|
||||
)
|
||||
]
|
||||
|
||||
monkeypatch.setattr(wallet_node, "get_cache_for_peer", lambda peer: cache)
|
||||
monkeypatch.setattr("chia.wallet.wallet_node.request_header_blocks", response)
|
||||
|
||||
# Without an anchor an empty peak response is not fatal: the lookup keeps backtracking
|
||||
# toward an older transaction block, so the anchored short-circuit must not apply here.
|
||||
assert await wallet_node.get_timestamp_for_height_from_peer(uint32(100), peer) == timestamp
|
||||
assert requested_heights == [100, 99]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_timestamp_for_height_from_peer_rejects_multiple_response_blocks(
|
||||
root_path_populated_with_config: Path, monkeypatch: pytest.MonkeyPatch
|
||||
@@ -2183,3 +2253,102 @@ async def test_collect_valid_states(
|
||||
)
|
||||
assert [cs.coin.name() for cs in valid_states] == [good_coin_state.coin.name()]
|
||||
assert f"Failed to validate coin_state {bad_coin_state}" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.HARD_FORK_2_0])
|
||||
@pytest.mark.anyio
|
||||
async def test_state_update_received_rejects_oversized_update(
|
||||
simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
[full_node_api], [(wallet_node, wallet_server)], _ = simulator_and_wallet
|
||||
await wallet_server.start_client(PeerInfo(self_hostname, full_node_api.server.get_port()), None)
|
||||
peer = next(iter(wallet_server.all_connections.values()))
|
||||
|
||||
wallet_node.config["max_coin_state_update_items"] = 2
|
||||
coin_generator = CoinGenerator()
|
||||
items = [CoinState(coin_generator.get().coin, None, None) for _ in range(10)]
|
||||
update = wallet_protocol.CoinStateUpdate(uint32(1), uint32(0), bytes32(b"\0" * 32), items)
|
||||
api = WalletNodeAPI(wallet_node)
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
await wallet_node.state_update_received(update, peer)
|
||||
assert "coin state update with too many items" in caplog.text
|
||||
caplog.clear()
|
||||
|
||||
await api.coin_state_update(bytes(update), peer)
|
||||
await time_out_assert(10, lambda: "coin state update with too many items" in caplog.text)
|
||||
|
||||
|
||||
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.HARD_FORK_2_0])
|
||||
@pytest.mark.anyio
|
||||
async def test_state_update_received_accepts_update_within_limit(
|
||||
simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
[full_node_api], [(wallet_node, wallet_server)], _ = simulator_and_wallet
|
||||
await wallet_server.start_client(PeerInfo(self_hostname, full_node_api.server.get_port()), None)
|
||||
peer = next(iter(wallet_server.all_connections.values()))
|
||||
|
||||
wallet_node.config["max_coin_state_update_items"] = 5
|
||||
update = wallet_protocol.CoinStateUpdate(uint32(1), uint32(0), bytes32(b"\0" * 32), [])
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
await wallet_node.state_update_received(update, peer)
|
||||
|
||||
assert not peer.closed
|
||||
assert peer.peer_info.host not in wallet_server.banned_peers
|
||||
assert "Received coin state update" in caplog.text
|
||||
assert "0 items" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize(
|
||||
"subscription_kind",
|
||||
["puzzle_hash", "coin_id"],
|
||||
)
|
||||
async def test_process_new_subscriptions_continues_after_peer_error(
|
||||
setup_two_nodes_and_wallet: OldSimulatorsAndWallets,
|
||||
self_hostname: str,
|
||||
subscription_kind: str,
|
||||
) -> None:
|
||||
"""One failing peer must not skip subscription registration on later peers."""
|
||||
[bad_fn_api, good_fn_api], [(wallet_node, wallet_server)], _ = setup_two_nodes_and_wallet
|
||||
# Keep both peers; do not prefer a trusted peer that would disconnect the other.
|
||||
wallet_node.config["trusted_peers"] = {}
|
||||
|
||||
assert await wallet_server.start_client(PeerInfo(self_hostname, bad_fn_api.server.get_port()), None)
|
||||
assert await wallet_server.start_client(PeerInfo(self_hostname, good_fn_api.server.get_port()), None)
|
||||
await time_out_assert(10, lambda: len(wallet_server.get_connections(NodeType.FULL_NODE)) == 2)
|
||||
|
||||
target = bytes32.random()
|
||||
|
||||
async def register_for_ph_updates(
|
||||
self: object, request: wallet_protocol.RegisterForPhUpdates, peer: WSChiaConnection
|
||||
) -> None:
|
||||
raise RuntimeError("simulated peer failure")
|
||||
|
||||
async def register_for_coin_updates(
|
||||
self: object, request: wallet_protocol.RegisterForCoinUpdates, peer: WSChiaConnection
|
||||
) -> None:
|
||||
raise RuntimeError("simulated peer failure")
|
||||
|
||||
if subscription_kind == "puzzle_hash":
|
||||
handler: Any = register_for_ph_updates
|
||||
|
||||
def good_peer_subscribed() -> bool:
|
||||
return target in good_fn_api.full_node.subscriptions.puzzle_subscriptions(wallet_server.node_id)
|
||||
|
||||
else:
|
||||
handler = register_for_coin_updates
|
||||
|
||||
def good_peer_subscribed() -> bool:
|
||||
return target in good_fn_api.full_node.subscriptions.coin_subscriptions(wallet_server.node_id)
|
||||
|
||||
with patch_request_handler(api=bad_fn_api, handler=handler):
|
||||
if subscription_kind == "puzzle_hash":
|
||||
await wallet_node.new_peak_queue.subscribe_to_puzzle_hashes([target])
|
||||
else:
|
||||
await wallet_node.new_peak_queue.subscribe_to_coin_ids([target])
|
||||
await time_out_assert(10, good_peer_subscribed)
|
||||
await time_out_assert(10, lambda: bad_fn_api.server.node_id not in wallet_server.all_connections)
|
||||
|
||||
assert good_fn_api.server.node_id in wallet_server.all_connections
|
||||
|
||||
@@ -18,6 +18,7 @@ from chia._tests.conftest import ConsensusMode
|
||||
from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework
|
||||
from chia._tests.util.setup_nodes import OldSimulatorsAndWallets
|
||||
from chia._tests.util.time_out_assert import time_out_assert
|
||||
from chia.cmds.wallet import GetDerivationIndexCMD, UpdateDerivationIndexCMD
|
||||
from chia.data_layer.data_layer_wallet import DataLayerWallet
|
||||
from chia.protocols.outbound_message import NodeType
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
@@ -477,8 +478,11 @@ class PuzzleHashState:
|
||||
)
|
||||
@pytest.mark.limit_consensus_modes(reason="irrelevant")
|
||||
@pytest.mark.anyio
|
||||
async def test_puzzle_hash_requests(wallet_environments: WalletTestFramework) -> None:
|
||||
wsm = wallet_environments.environments[0].wallet_state_manager
|
||||
async def test_puzzle_hash_requests(
|
||||
wallet_environments: WalletTestFramework, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
env = wallet_environments.environments[0]
|
||||
wsm = env.wallet_state_manager
|
||||
|
||||
async def get_puzzle_hash_state() -> PuzzleHashState:
|
||||
last_index = await wsm.puzzle_store.get_last_derivation_path_for_wallet(wsm.main_wallet.id())
|
||||
@@ -490,10 +494,10 @@ async def test_puzzle_hash_requests(wallet_environments: WalletTestFramework) ->
|
||||
|
||||
expected_state = await get_puzzle_hash_state()
|
||||
|
||||
# Quick test of this RPC
|
||||
assert (
|
||||
await wallet_environments.environments[0].rpc_client.get_current_derivation_index()
|
||||
).index == expected_state.highest_index
|
||||
# Quick test of this command
|
||||
capsys.readouterr()
|
||||
await GetDerivationIndexCMD(rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"]).run()
|
||||
assert f"Last derivation index: {expected_state.highest_index}" in capsys.readouterr().out
|
||||
|
||||
# `create_more_puzzle_hashes`
|
||||
# No-op
|
||||
@@ -649,9 +653,12 @@ async def test_puzzle_hash_requests(wallet_environments: WalletTestFramework) ->
|
||||
)
|
||||
|
||||
# Test the actual functionality
|
||||
assert (
|
||||
await rpc_client.extend_derivation_index(ExtendDerivationIndex(index=uint32(expected_state.highest_index + 5)))
|
||||
).index == expected_state.highest_index + 5
|
||||
capsys.readouterr()
|
||||
await UpdateDerivationIndexCMD(
|
||||
rpc_info=wallet_environments.cmd_tx_endpoint_args(env)["rpc_info"],
|
||||
index=expected_state.highest_index + 5,
|
||||
).run()
|
||||
assert f"Updated derivation index: {expected_state.highest_index + 5}" in capsys.readouterr().out
|
||||
expected_state = PuzzleHashState(expected_state.highest_index + 5, expected_state.used_up_to_index)
|
||||
assert await get_puzzle_hash_state() == expected_state
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from chia_rs.sized_ints import int16
|
||||
|
||||
from chia.protocols.shared_protocol import Error
|
||||
from chia.wallet.util.wallet_sync_utils import _coin_states_from_subscribe_response
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"response, match",
|
||||
[
|
||||
(None, "None response from peer peer.example for register_for_ph_updates"),
|
||||
(
|
||||
Error(int16(1), "rejected", None),
|
||||
"Error response from peer peer.example for register_for_ph_updates",
|
||||
),
|
||||
(object(), "Unexpected response from peer peer.example for register_for_ph_updates: object"),
|
||||
],
|
||||
ids=["none", "protocol_error", "unexpected_type"],
|
||||
)
|
||||
def test_coin_states_from_subscribe_response_rejects_invalid(response: object, match: str) -> None:
|
||||
with pytest.raises(ValueError, match=match):
|
||||
_coin_states_from_subscribe_response(response, "peer.example", "register_for_ph_updates")
|
||||
@@ -204,9 +204,9 @@ def finish_block(
|
||||
unfinished_block.foliage_transaction_block,
|
||||
unfinished_block.transactions_info,
|
||||
unfinished_block.transactions_generator,
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
unfinished_block.transactions_generator_ref_list,
|
||||
unfinished_block.transactions_generator_buffer,
|
||||
unfinished_block.version,
|
||||
)
|
||||
|
||||
block_record = block_to_block_record(
|
||||
@@ -323,6 +323,7 @@ def get_full_block_and_block_record(
|
||||
foliage_transaction_block.get_hash(),
|
||||
G2Element(),
|
||||
)
|
||||
new_format = height >= constants.HARD_FORK2_HEIGHT
|
||||
unfinished_block = UnfinishedBlock(
|
||||
[],
|
||||
RewardChainBlockUnfinished(
|
||||
@@ -340,10 +341,10 @@ def get_full_block_and_block_record(
|
||||
foliage,
|
||||
foliage_transaction_block,
|
||||
transactions_info,
|
||||
block_generator.program if block_generator else None,
|
||||
None if new_format else (block_generator.program if block_generator else None),
|
||||
[],
|
||||
None,
|
||||
uint8(0),
|
||||
bytes(block_generator.program) if block_generator and new_format else None,
|
||||
uint8(1) if new_format else uint8(0),
|
||||
)
|
||||
|
||||
full_block, block_record = finish_block(constants, unfinished_block, prev_block, blocks)
|
||||
|
||||
@@ -132,7 +132,11 @@ class WalletNodeApiStub(ApiProtocol, Protocol):
|
||||
"""Handle reject block headers from full node."""
|
||||
...
|
||||
|
||||
@metadata.request(peer_required=True, execute_task=True)
|
||||
@metadata.request(
|
||||
peer_required=True,
|
||||
execute_task=True,
|
||||
list_limits=lambda self, peer: {"items": self.wallet_node.max_coin_state_update_items() + 1},
|
||||
)
|
||||
async def coin_state_update(self, request: CoinStateUpdate, peer: WSChiaConnection) -> None:
|
||||
"""Handle coin state update from full node."""
|
||||
...
|
||||
|
||||
@@ -207,7 +207,7 @@ def print_min_max_derivation_for_wallets(derivation_paths: list[DerivationPath])
|
||||
class WalletDBReader:
|
||||
db_wrapper: DBWrapper2 # TODO: Remove db_wrapper member
|
||||
# TODO: make this a dataclass and make this an instance attribute instead of a mutable classvar
|
||||
config = {"db_readers": 1} # noqa: RUF012
|
||||
config = {"db_readers": 1} # ruff: ignore[mutable-class-default]
|
||||
sql_log_path: Path | None = None
|
||||
verbose = False
|
||||
|
||||
|
||||
@@ -53,6 +53,18 @@ def option(*param_decls: str, **kwargs: Any) -> Any:
|
||||
)
|
||||
|
||||
|
||||
def argument(*param_decls: str, **kwargs: Any) -> Any:
|
||||
return field(
|
||||
metadata=dict(
|
||||
argument_args=dict(
|
||||
param_decls=tuple(param_decls),
|
||||
**kwargs,
|
||||
),
|
||||
),
|
||||
default=kwargs.get("default", MISSING),
|
||||
)
|
||||
|
||||
|
||||
@final
|
||||
@dataclasses.dataclass
|
||||
class ChiaCliContext:
|
||||
@@ -111,6 +123,7 @@ _CLASS_TYPES_TO_CLICK_TYPES = {
|
||||
class _CommandParsingStage:
|
||||
my_dataclass: type[ChiaCommand]
|
||||
my_option_decorators: list[Callable[[SyncCmd], SyncCmd]]
|
||||
my_argument_decorators: list[Callable[[SyncCmd], SyncCmd]]
|
||||
my_members: dict[str, _CommandParsingStage]
|
||||
my_kwarg_names: list[str]
|
||||
_needs_context: bool
|
||||
@@ -152,6 +165,9 @@ class _CommandParsingStage:
|
||||
|
||||
for decorator in self.get_all_option_decorators():
|
||||
cmd_to_return = decorator(cmd_to_return)
|
||||
# Positional arguments are currently only supported directly on commands.
|
||||
for decorator in self.my_argument_decorators:
|
||||
cmd_to_return = decorator(cmd_to_return)
|
||||
|
||||
return cmd_to_return
|
||||
|
||||
@@ -167,6 +183,7 @@ class _CommandParsingStage:
|
||||
|
||||
def _generate_command_parser(cls: type[ChiaCommand]) -> _CommandParsingStage:
|
||||
option_decorators: list[Callable[[SyncCmd], SyncCmd]] = []
|
||||
argument_decorators: list[Callable[[SyncCmd], SyncCmd]] = []
|
||||
kwarg_names: list[str] = []
|
||||
members: dict[str, _CommandParsingStage] = {}
|
||||
needs_context: bool = False
|
||||
@@ -234,10 +251,20 @@ def _generate_command_parser(cls: type[ChiaCommand]) -> _CommandParsingStage:
|
||||
**{k: v for k, v in option_args.items() if k not in {"param_decls", "type"}},
|
||||
)
|
||||
)
|
||||
elif "argument_args" in cls_field.metadata:
|
||||
argument_args = cls_field.metadata["argument_args"]
|
||||
kwarg_names.append(field_name)
|
||||
argument_decorators.append(
|
||||
click.argument(
|
||||
*argument_args["param_decls"],
|
||||
**{k: v for k, v in argument_args.items() if k != "param_decls"},
|
||||
)
|
||||
)
|
||||
|
||||
return _CommandParsingStage(
|
||||
my_dataclass=cls,
|
||||
my_option_decorators=option_decorators,
|
||||
my_argument_decorators=argument_decorators,
|
||||
my_members=members,
|
||||
my_kwarg_names=kwarg_names,
|
||||
_needs_context=needs_context,
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ from chia.wallet.wallet_spend_bundle import WalletSpendBundle
|
||||
def _clear_screen() -> None:
|
||||
# Cross-platform screen clear
|
||||
# TODO: consider if not-nt anyways could avoid os.system()
|
||||
os.system("cls" if os.name == "nt" else "clear") # noqa: S605
|
||||
os.system("cls" if os.name == "nt" else "clear") # ruff: ignore[start-process-with-a-shell]
|
||||
|
||||
|
||||
@click.group("signer", help="Get information for an external signer")
|
||||
|
||||
+501
-664
File diff suppressed because it is too large
Load Diff
+612
-654
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ from chia_rs import BlockRecord, FullBlock
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32
|
||||
|
||||
from chia.consensus.block_generator_info import get_transactions_generator_bytes
|
||||
from chia.consensus.blockchain_interface import BlocksProtocol, MMRManagerProtocol
|
||||
from chia.util.errors import Err
|
||||
|
||||
@@ -130,9 +131,10 @@ class AugmentedBlockchain:
|
||||
while curr is not None:
|
||||
b = curr[0]
|
||||
if b.height in generator_refs:
|
||||
if b.transactions_generator is None:
|
||||
gen = get_transactions_generator_bytes(b)
|
||||
if gen is None:
|
||||
raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR)
|
||||
generators[b.height] = bytes(b.transactions_generator)
|
||||
generators[b.height] = gen
|
||||
to_remove.append(b.height)
|
||||
header_hash = b.prev_header_hash
|
||||
curr = self._extra_blocks.get(header_hash)
|
||||
|
||||
@@ -20,15 +20,20 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
from chiabip158 import PyBIP158
|
||||
|
||||
from chia.consensus.block_generator_info import (
|
||||
block_has_transactions_generator,
|
||||
get_transactions_generator_bytes,
|
||||
)
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
|
||||
from chia.consensus.blockchain_interface import BlockRecordsProtocol
|
||||
from chia.consensus.coinbase import create_farmer_coin, create_pool_coin
|
||||
from chia.consensus.generator_validation import validate_generator_ref_list, validate_tx_generator
|
||||
from chia.consensus.get_block_challenge import pre_sp_tx_block_height
|
||||
from chia.types.blockchain_format.coin import Coin, hash_coin_ids
|
||||
from chia.util.errors import Err
|
||||
from chia.util.hash import std_hash
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# peak-> o
|
||||
# main |
|
||||
# chain o o <- peak_height \ additions and removals
|
||||
@@ -214,17 +219,14 @@ async def validate_block_body(
|
||||
assert height == block.height
|
||||
prev_transaction_block_height: uint32 = uint32(0)
|
||||
prev_transaction_block_timestamp: uint64 = uint64(0)
|
||||
|
||||
# 0. Validate block version. Version 1 (which omits transaction_generator_ref_list
|
||||
# and encodes the generator as a plain buffer) is only valid after the 3.0 hard fork.
|
||||
# TODO: allow version 1 after the hard fork activates
|
||||
# if block.version == 1:
|
||||
# if height < constants.HARD_FORK2_HEIGHT:
|
||||
# return Err.INVALID_BLOCK_VERSION
|
||||
# elif block.version != 0:
|
||||
# return Err.INVALID_BLOCK_VERSION
|
||||
if block.version != 0:
|
||||
return Err.INVALID_BLOCK_VERSION
|
||||
# Version is an SP-time gate (matches unfinished-header validation).
|
||||
pre_sp_tx_height = pre_sp_tx_block_height(
|
||||
constants=constants,
|
||||
blocks=records,
|
||||
prev_b_hash=block.prev_header_hash,
|
||||
sp_index=block.reward_chain_block.signage_point_index,
|
||||
finished_sub_slots=len(block.finished_sub_slots),
|
||||
)
|
||||
|
||||
# 1. For non transaction-blocs: foliage block, transaction filter, transactions info, and generator must
|
||||
# be empty. If it is a block but not a transaction block, there is no body to validate. Check that all fields are
|
||||
@@ -233,8 +235,7 @@ async def validate_block_body(
|
||||
if (
|
||||
block.foliage_transaction_block is not None
|
||||
or block.transactions_info is not None
|
||||
or block.transactions_generator is not None
|
||||
or block.transactions_generator_buffer is not None
|
||||
or block_has_transactions_generator(block)
|
||||
):
|
||||
return Err.NOT_BLOCK_BUT_HAS_DATA
|
||||
|
||||
@@ -250,6 +251,11 @@ async def validate_block_body(
|
||||
assert fork_info.peak_height == height - 1
|
||||
|
||||
assert conds is None
|
||||
|
||||
version_error = validate_tx_generator(constants, block, pre_sp_tx_height)
|
||||
if version_error:
|
||||
return version_error
|
||||
|
||||
# This means the block is valid
|
||||
return None
|
||||
|
||||
@@ -271,7 +277,8 @@ async def validate_block_body(
|
||||
return Err.INVALID_FOLIAGE_BLOCK_HASH
|
||||
|
||||
# 5. The reward claims must be valid for the previous blocks, and current block fees
|
||||
# If height == 0, expected_reward_coins will be left empty
|
||||
# If height == 0, expected_reward_coins will be left empty and prev_transaction_block_height
|
||||
# stays 0 (no previous transaction block).
|
||||
if height > 0:
|
||||
# Add reward claims for all blocks from the prev prev block, until the prev block (including the latter)
|
||||
prev_transaction_block = records.block_record(block.foliage_transaction_block.prev_transaction_block_hash)
|
||||
@@ -280,6 +287,7 @@ async def validate_block_body(
|
||||
assert prev_transaction_block.timestamp
|
||||
prev_transaction_block_timestamp = prev_transaction_block.timestamp
|
||||
assert prev_transaction_block.fees is not None
|
||||
|
||||
pool_coin = create_pool_coin(
|
||||
prev_transaction_block_height,
|
||||
prev_transaction_block.pool_puzzle_hash,
|
||||
@@ -320,6 +328,10 @@ async def validate_block_body(
|
||||
curr_b = records.block_record(curr_b.prev_hash)
|
||||
assert curr_b is not None
|
||||
|
||||
version_error = validate_tx_generator(constants, block, pre_sp_tx_height)
|
||||
if version_error:
|
||||
return version_error
|
||||
|
||||
if set(block.transactions_info.reward_claims_incorporated) != expected_reward_coins:
|
||||
return Err.INVALID_REWARD_COINS
|
||||
|
||||
@@ -342,8 +354,9 @@ async def validate_block_body(
|
||||
|
||||
# 7a. The generator root must be the hash of the serialized bytes of
|
||||
# the generator for this block (or zeroes if no generator)
|
||||
if block.transactions_generator is not None:
|
||||
if std_hash(bytes(block.transactions_generator)) != block.transactions_info.generator_root:
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
if generator_bytes is not None:
|
||||
if std_hash(generator_bytes) != block.transactions_info.generator_root:
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_HASH
|
||||
elif block.transactions_info.generator_root != bytes([0] * 32):
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_HASH
|
||||
@@ -352,28 +365,11 @@ async def validate_block_body(
|
||||
# the generator ref list for this block (or 'one' bytes [0x01] if no generator)
|
||||
# 8b. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries
|
||||
# 8c. The generator ref list must not point to a height >= this block's height
|
||||
if block.transactions_generator_ref_list == []:
|
||||
if block.transactions_info.generator_refs_root != bytes([1] * 32):
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
|
||||
else:
|
||||
# With hard fork 2 we ban transactions_generator_ref_list.
|
||||
if prev_transaction_block_height >= constants.SOFT_FORK9_HEIGHT:
|
||||
return Err.TOO_MANY_GENERATOR_REFS
|
||||
generator_ref_error = validate_generator_ref_list(constants, block, height, prev_transaction_block_height)
|
||||
if generator_ref_error is not None:
|
||||
return generator_ref_error
|
||||
|
||||
# If we have a generator reference list, we must have a generator
|
||||
if block.transactions_generator is None:
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
|
||||
|
||||
# The generator_refs_root must be the hash of the concatenation of the list[uint32]
|
||||
generator_refs_hash = std_hash(b"".join([i.stream_to_bytes() for i in block.transactions_generator_ref_list]))
|
||||
if block.transactions_info.generator_refs_root != generator_refs_hash:
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
|
||||
if len(block.transactions_generator_ref_list) > constants.MAX_GENERATOR_REF_LIST_SIZE:
|
||||
return Err.TOO_MANY_GENERATOR_REFS
|
||||
if any([index >= height for index in block.transactions_generator_ref_list]):
|
||||
return Err.FUTURE_GENERATOR_REFS
|
||||
|
||||
if block.transactions_generator is not None:
|
||||
if generator_bytes is not None:
|
||||
# Get List of names removed, puzzles hashes for removed coins and conditions created
|
||||
|
||||
cost = uint64(0 if conds is None else conds.cost)
|
||||
@@ -391,7 +387,7 @@ async def validate_block_body(
|
||||
assert conds.validated_signature
|
||||
|
||||
if prev_transaction_block_height >= constants.SOFT_FORK9_HEIGHT:
|
||||
if not is_canonical_serialization(bytes(block.transactions_generator)):
|
||||
if not is_canonical_serialization(generator_bytes):
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_ENCODING
|
||||
|
||||
for spend in conds.spends:
|
||||
|
||||
@@ -299,6 +299,7 @@ def create_unfinished_block(
|
||||
signage_point: SignagePoint,
|
||||
timestamp: uint64,
|
||||
blocks: BlockRecordsProtocol,
|
||||
pre_sp_tx_height: uint32,
|
||||
seed: bytes = b"",
|
||||
new_block_gen: NewBlockGenerator | None = None,
|
||||
prev_block: BlockRecord | None = None,
|
||||
@@ -324,6 +325,7 @@ def create_unfinished_block(
|
||||
get_pool_signature: function that returns signature corresponding to pool public key
|
||||
signage_point: signage point information (VDFs)
|
||||
timestamp: timestamp to add to the foliage block, if created
|
||||
pre_sp_tx_height: height of the latest tx block infused before this block's signage point
|
||||
seed: seed to randomize chain
|
||||
new_block_gen: transactions to add to the foliage block, if created, including aggregate signature
|
||||
prev_block: previous block (already in chain) from the signage point
|
||||
@@ -395,6 +397,22 @@ def create_unfinished_block(
|
||||
seed,
|
||||
compute_fees,
|
||||
)
|
||||
# Version / generator format is an SP-time decision keyed on pre_sp_tx_height
|
||||
# (latest tx block infused before this block's signage point).
|
||||
if pre_sp_tx_height >= constants.HARD_FORK2_HEIGHT:
|
||||
# Post hard fork 2: serialize the generator as a plain buffer (version 1).
|
||||
version = uint8(1)
|
||||
if new_block_gen is not None:
|
||||
generator_buffer: bytes | None = bytes(new_block_gen.program)
|
||||
else:
|
||||
generator_buffer = None
|
||||
generator = None
|
||||
generator_refs: list[uint32] = []
|
||||
else:
|
||||
version = uint8(0)
|
||||
generator = new_block_gen.program if new_block_gen else None
|
||||
generator_buffer = None
|
||||
generator_refs = new_block_gen.block_refs if new_block_gen else []
|
||||
return UnfinishedBlock(
|
||||
finished_sub_slots,
|
||||
rc_block,
|
||||
@@ -403,10 +421,10 @@ def create_unfinished_block(
|
||||
foliage,
|
||||
foliage_transaction_block,
|
||||
transactions_info,
|
||||
new_block_gen.program if new_block_gen else None,
|
||||
new_block_gen.block_refs if new_block_gen else [],
|
||||
None,
|
||||
uint8(0),
|
||||
generator,
|
||||
generator_refs,
|
||||
generator_buffer,
|
||||
version,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from chia.types.block_protocol import BlockInfo
|
||||
from chia.types.blockchain_format.serialized_program import SerializedProgram
|
||||
|
||||
|
||||
def block_has_transactions_generator(block: BlockInfo) -> bool:
|
||||
# these fields are mutually exclusive
|
||||
assert block.transactions_generator is None or block.transactions_generator_buffer is None
|
||||
return block.transactions_generator is not None or block.transactions_generator_buffer is not None
|
||||
|
||||
|
||||
def get_transactions_generator_bytes(block: BlockInfo) -> bytes | None:
|
||||
if block.transactions_generator is not None:
|
||||
return bytes(block.transactions_generator)
|
||||
return block.transactions_generator_buffer
|
||||
|
||||
|
||||
def get_transactions_generator_program(block: BlockInfo) -> SerializedProgram | None:
|
||||
if block.transactions_generator is not None:
|
||||
return block.transactions_generator
|
||||
if block.transactions_generator_buffer is not None:
|
||||
return SerializedProgram.from_bytes(block.transactions_generator_buffer)
|
||||
return None
|
||||
@@ -26,6 +26,10 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint16, uint32, uint64, uint128
|
||||
|
||||
from chia.consensus.block_body_validation import ForkInfo, validate_block_body
|
||||
from chia.consensus.block_generator_info import (
|
||||
block_has_transactions_generator,
|
||||
get_transactions_generator_bytes,
|
||||
)
|
||||
from chia.consensus.block_header_validation import validate_unfinished_header_block
|
||||
from chia.consensus.block_height_map import BlockHeightMap
|
||||
from chia.consensus.block_store_protocol import BlockStoreProtocol
|
||||
@@ -36,6 +40,7 @@ from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_dif
|
||||
from chia.consensus.find_fork_point import lookup_fork_chain
|
||||
from chia.consensus.full_block_to_block_record import block_to_block_record
|
||||
from chia.consensus.generator_tools import get_block_header
|
||||
from chia.consensus.generator_validation import validate_tx_generator
|
||||
from chia.consensus.get_block_challenge import pre_sp_tx_block_height
|
||||
from chia.consensus.get_block_generator import get_block_generator
|
||||
from chia.consensus.multiprocess_validation import PreValidationResult
|
||||
@@ -273,7 +278,8 @@ class Blockchain:
|
||||
|
||||
additions: list[tuple[Coin, bytes | None]] = []
|
||||
removals: list[tuple[bytes32, Coin]] = []
|
||||
if block.transactions_generator is not None:
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
if generator_bytes is not None:
|
||||
block_generator: BlockGenerator | None = await get_block_generator(self.lookup_block_generators, block)
|
||||
assert block_generator is not None
|
||||
assert block.transactions_info is not None
|
||||
@@ -287,7 +293,7 @@ class Blockchain:
|
||||
)
|
||||
flags = get_flags_for_height_and_constants(prev_tx_height, self.constants)
|
||||
additions, removals = additions_and_removals(
|
||||
bytes(block.transactions_generator),
|
||||
generator_bytes,
|
||||
block_generator.generator_refs,
|
||||
flags,
|
||||
self.constants,
|
||||
@@ -392,7 +398,7 @@ class Blockchain:
|
||||
assert fork_info.peak_height == block.height - 1
|
||||
assert block.height == 0 or fork_info.peak_hash == block.prev_header_hash
|
||||
|
||||
assert block.transactions_generator is None or pre_validation_result.validated_signature
|
||||
assert not block_has_transactions_generator(block) or pre_validation_result.validated_signature
|
||||
error_code = await validate_block_body(
|
||||
self.constants,
|
||||
self,
|
||||
@@ -725,16 +731,6 @@ class Blockchain:
|
||||
if prev_b is None and block.prev_header_hash != self.constants.GENESIS_CHALLENGE:
|
||||
return None, Err.INVALID_PREV_BLOCK_HASH
|
||||
|
||||
# TODO: todo_v2_plots allow version 1 after the hard fork activates
|
||||
# block_height = uint32(0) if prev_b is None else uint32(prev_b.height + 1)
|
||||
# if block.version == 1:
|
||||
# if block_height < self.constants.HARD_FORK2_HEIGHT:
|
||||
# return None, Err.INVALID_BLOCK_VERSION
|
||||
# elif block.version != 0:
|
||||
# return None, Err.INVALID_BLOCK_VERSION
|
||||
if block.version != 0:
|
||||
return None, Err.INVALID_BLOCK_VERSION
|
||||
|
||||
prev_tx_height = pre_sp_tx_block_height(
|
||||
constants=self.constants,
|
||||
blocks=self,
|
||||
@@ -743,13 +739,18 @@ class Blockchain:
|
||||
finished_sub_slots=len(block.finished_sub_slots),
|
||||
)
|
||||
|
||||
# With hard fork 2 we ban transactions_generator_ref_list.
|
||||
version_error = validate_tx_generator(self.constants, block, prev_tx_height)
|
||||
if version_error:
|
||||
return None, version_error
|
||||
|
||||
# With soft fork 9 we ban transactions_generator_ref_list.
|
||||
if prev_tx_height >= self.constants.SOFT_FORK9_HEIGHT and block.transactions_generator_ref_list != []:
|
||||
return None, Err.TOO_MANY_GENERATOR_REFS
|
||||
|
||||
if block.transactions_info is not None:
|
||||
if block.transactions_generator is not None:
|
||||
if std_hash(bytes(block.transactions_generator)) != block.transactions_info.generator_root:
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
if generator_bytes is not None:
|
||||
if std_hash(generator_bytes) != block.transactions_info.generator_root:
|
||||
return None, Err.INVALID_TRANSACTIONS_GENERATOR_HASH
|
||||
elif block.transactions_info.generator_root != bytes([0] * 32):
|
||||
return None, Err.INVALID_TRANSACTIONS_GENERATOR_HASH
|
||||
@@ -761,7 +762,7 @@ class Blockchain:
|
||||
return None, Err.INVALID_TRANSACTIONS_INFO_HASH
|
||||
else:
|
||||
# make sure non-tx blocks don't have these fields
|
||||
if block.transactions_generator is not None:
|
||||
if block_has_transactions_generator(block):
|
||||
return None, Err.INVALID_TRANSACTIONS_GENERATOR_HASH
|
||||
if block.foliage_transaction_block is not None:
|
||||
return None, Err.INVALID_TRANSACTIONS_INFO_HASH
|
||||
@@ -937,7 +938,7 @@ class Blockchain:
|
||||
raise ValueError(f"Block at {block.header_hash} is no longer in the blockchain (it's in a fork)")
|
||||
if tx_filter is False:
|
||||
header = get_block_header(block)
|
||||
elif block.transactions_generator is not None:
|
||||
elif block_has_transactions_generator(block):
|
||||
added_coins_records, removed_coins_records = await asyncio.gather(
|
||||
self.coin_store.get_coins_added_at_height(block.height),
|
||||
self.coin_store.get_coins_removed_at_height(block.height),
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from chia_rs import ConsensusConstants, FullBlock, UnfinishedBlock
|
||||
from chia_rs.sized_ints import uint32
|
||||
|
||||
from chia.consensus.block_generator_info import block_has_transactions_generator
|
||||
from chia.types.block_protocol import BlockInfo
|
||||
from chia.util.errors import Err
|
||||
from chia.util.hash import std_hash
|
||||
|
||||
|
||||
def validate_generator_ref_list(
|
||||
constants: ConsensusConstants,
|
||||
block: FullBlock | UnfinishedBlock,
|
||||
height: uint32,
|
||||
prev_transaction_block_height: uint32,
|
||||
) -> Err | None:
|
||||
assert block.transactions_info is not None
|
||||
|
||||
generator_refs = block.transactions_generator_ref_list
|
||||
if generator_refs == []:
|
||||
if block.transactions_info.generator_refs_root != bytes([1] * 32):
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
|
||||
return None
|
||||
|
||||
if prev_transaction_block_height >= constants.SOFT_FORK9_HEIGHT:
|
||||
return Err.TOO_MANY_GENERATOR_REFS
|
||||
if not block_has_transactions_generator(block):
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
|
||||
if len(generator_refs) > constants.MAX_GENERATOR_REF_LIST_SIZE:
|
||||
return Err.TOO_MANY_GENERATOR_REFS
|
||||
generator_refs_hash = std_hash(b"".join(i.stream_to_bytes() for i in generator_refs))
|
||||
if block.transactions_info.generator_refs_root != generator_refs_hash:
|
||||
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
|
||||
if any(index >= height for index in generator_refs):
|
||||
return Err.FUTURE_GENERATOR_REFS
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_tx_generator(
|
||||
constants: ConsensusConstants,
|
||||
block: BlockInfo,
|
||||
pre_sp_tx_height: uint32,
|
||||
) -> Err | None:
|
||||
# Validate block version. Version 1 (plain generator buffer, no ref list) is required
|
||||
# after the 3.0 hard fork, and version 0 is required before it. Activation is based on
|
||||
# pre_sp_tx_block_height (0 for genesis), matching unfinished-header validation.
|
||||
if block.version == 1:
|
||||
if pre_sp_tx_height < constants.HARD_FORK2_HEIGHT:
|
||||
return Err.INVALID_BLOCK_VERSION
|
||||
elif block.version == 0:
|
||||
if pre_sp_tx_height >= constants.HARD_FORK2_HEIGHT:
|
||||
return Err.INVALID_BLOCK_VERSION
|
||||
else:
|
||||
return Err.INVALID_BLOCK_VERSION
|
||||
return None
|
||||
@@ -5,6 +5,7 @@ from collections.abc import Awaitable, Callable
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32
|
||||
|
||||
from chia.consensus.block_generator_info import get_transactions_generator_program
|
||||
from chia.types.block_protocol import BlockInfo
|
||||
from chia.types.generator_types import BlockGenerator
|
||||
|
||||
@@ -14,14 +15,15 @@ async def get_block_generator(
|
||||
block: BlockInfo,
|
||||
) -> BlockGenerator | None:
|
||||
ref_list = block.transactions_generator_ref_list
|
||||
if block.transactions_generator is None:
|
||||
program = get_transactions_generator_program(block)
|
||||
if program is None:
|
||||
assert len(ref_list) == 0
|
||||
return None
|
||||
if len(ref_list) == 0:
|
||||
return BlockGenerator(block.transactions_generator, [])
|
||||
return BlockGenerator(program, [])
|
||||
|
||||
generator_refs = set(ref_list)
|
||||
generators: dict[uint32, bytes] = await lookup_block_generators(block.prev_header_hash, generator_refs)
|
||||
|
||||
result = [generators[height] for height in block.transactions_generator_ref_list]
|
||||
return BlockGenerator(block.transactions_generator, result)
|
||||
return BlockGenerator(program, result)
|
||||
|
||||
@@ -13,6 +13,7 @@ from chia_rs import (
|
||||
SpendBundleConditions,
|
||||
SubEpochSummary,
|
||||
get_flags_for_height_and_constants,
|
||||
is_canonical_serialization,
|
||||
run_block_generator,
|
||||
run_block_generator2,
|
||||
)
|
||||
@@ -20,11 +21,16 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint16, uint32, uint64
|
||||
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_generator_info import (
|
||||
block_has_transactions_generator,
|
||||
get_transactions_generator_bytes,
|
||||
)
|
||||
from chia.consensus.block_header_validation import validate_finished_header_block
|
||||
from chia.consensus.blockchain_interface import BlockRecordsProtocol
|
||||
from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty
|
||||
from chia.consensus.full_block_to_block_record import block_to_block_record
|
||||
from chia.consensus.generator_tools import get_block_header, tx_removals_and_additions
|
||||
from chia.consensus.generator_validation import validate_generator_ref_list
|
||||
from chia.consensus.get_block_challenge import (
|
||||
get_block_challenge,
|
||||
get_filter_challenge_from_chain,
|
||||
@@ -39,6 +45,7 @@ from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.generator_types import BlockGenerator
|
||||
from chia.types.validation_state import ValidationState
|
||||
from chia.util.errors import Err
|
||||
from chia.util.hash import std_hash
|
||||
from chia.util.priority_thread_pool_executor import Executor, _SupportsLessThan
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
|
||||
@@ -64,16 +71,17 @@ class PreValidationResult(Streamable):
|
||||
# this layer of abstraction is here to let wallet tests monkeypatch it
|
||||
def _run_block(
|
||||
block: FullBlock, prev_generators: list[bytes], prev_tx_height: uint32, constants: ConsensusConstants
|
||||
) -> tuple[int | None, str | None, SpendBundleConditions | None]:
|
||||
assert block.transactions_generator is not None
|
||||
) -> tuple[Err | None, str | None, SpendBundleConditions | None]:
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
assert generator_bytes is not None
|
||||
assert block.transactions_info is not None
|
||||
flags = get_flags_for_height_and_constants(prev_tx_height, constants)
|
||||
if block.height >= constants.HARD_FORK_HEIGHT:
|
||||
run_block = run_block_generator2
|
||||
else:
|
||||
run_block = run_block_generator
|
||||
return run_block(
|
||||
bytes(block.transactions_generator),
|
||||
error, error_msg, conds = run_block(
|
||||
generator_bytes,
|
||||
prev_generators,
|
||||
block.transactions_info.cost,
|
||||
flags,
|
||||
@@ -81,6 +89,7 @@ def _run_block(
|
||||
None,
|
||||
constants,
|
||||
)
|
||||
return None if error is None else Err(error), error_msg, conds
|
||||
|
||||
|
||||
def _pre_validate_block(
|
||||
@@ -89,6 +98,7 @@ def _pre_validate_block(
|
||||
block: FullBlock,
|
||||
prev_generators: list[bytes] | None,
|
||||
conds: SpendBundleConditions | None,
|
||||
prev_tx_height: uint32,
|
||||
expected_vs: ValidationState,
|
||||
*,
|
||||
skip_commitment_validation: bool = False,
|
||||
@@ -100,42 +110,57 @@ def _pre_validate_block(
|
||||
block:
|
||||
prev_generators:
|
||||
conds:
|
||||
prev_tx_height:
|
||||
expected_vs: The validation state that we calculate for the next block
|
||||
if it's validated.
|
||||
skip_commitment_validation: If True, skips validation of MMR roots (for weight proofs without full history).
|
||||
Challenge merkle tree validation is gated by HARD_FORK2_HEIGHT, not this flag.
|
||||
"""
|
||||
|
||||
validation_start = time.monotonic()
|
||||
|
||||
def error_result(error: Err, error_msg: str | None = None) -> PreValidationResult:
|
||||
validation_time = time.monotonic() - validation_start
|
||||
return PreValidationResult(uint16(error.value), error_msg, None, None, uint32(validation_time * 1000))
|
||||
|
||||
try:
|
||||
validation_start = time.monotonic()
|
||||
removals_and_additions: tuple[Collection[bytes32], Collection[Coin]] | None = None
|
||||
if conds is not None:
|
||||
assert conds.validated_signature is True
|
||||
assert block.transactions_generator is not None
|
||||
assert block_has_transactions_generator(block)
|
||||
removals_and_additions = tx_removals_and_additions(conds)
|
||||
elif block.transactions_generator is not None:
|
||||
elif block_has_transactions_generator(block):
|
||||
assert prev_generators is not None
|
||||
assert block.transactions_info is not None
|
||||
|
||||
if block.transactions_info.cost > constants.MAX_BLOCK_COST_CLVM:
|
||||
validation_time = time.monotonic() - validation_start
|
||||
return PreValidationResult(
|
||||
uint16(Err.BLOCK_COST_EXCEEDS_MAX.value), None, None, None, uint32(validation_time * 1000)
|
||||
)
|
||||
return error_result(Err.BLOCK_COST_EXCEEDS_MAX)
|
||||
|
||||
if block.foliage_transaction_block is None:
|
||||
return error_result(Err.INVALID_TRANSACTIONS_INFO_HASH)
|
||||
|
||||
# Fast-fail: prove the generator is the committed one before
|
||||
# executing it. A peer can keep all farmed/signed header fields
|
||||
# intact and swap only transactions_generator; these cheap hash
|
||||
# checks reject that before the expensive CLVM run.
|
||||
generator_bytes = get_transactions_generator_bytes(block)
|
||||
assert generator_bytes is not None
|
||||
if std_hash(generator_bytes) != block.transactions_info.generator_root:
|
||||
return error_result(Err.INVALID_TRANSACTIONS_GENERATOR_HASH)
|
||||
if block.foliage_transaction_block.transactions_info_hash != block.transactions_info.get_hash():
|
||||
return error_result(Err.INVALID_TRANSACTIONS_INFO_HASH)
|
||||
if block.foliage.foliage_transaction_block_hash != block.foliage_transaction_block.get_hash():
|
||||
return error_result(Err.INVALID_FOLIAGE_BLOCK_HASH)
|
||||
|
||||
if prev_tx_height >= constants.SOFT_FORK9_HEIGHT:
|
||||
if not is_canonical_serialization(generator_bytes):
|
||||
return error_result(Err.INVALID_TRANSACTIONS_GENERATOR_ENCODING)
|
||||
|
||||
prev_tx_height = pre_sp_tx_block_height(
|
||||
constants=constants,
|
||||
blocks=blockchain,
|
||||
prev_b_hash=block.prev_header_hash,
|
||||
sp_index=block.reward_chain_block.signage_point_index,
|
||||
finished_sub_slots=len(block.finished_sub_slots),
|
||||
)
|
||||
err, err_msg, conds = _run_block(block, prev_generators, prev_tx_height, constants)
|
||||
|
||||
assert (err is None) != (conds is None)
|
||||
if err is not None:
|
||||
validation_time = time.monotonic() - validation_start
|
||||
return PreValidationResult(uint16(err), err_msg, None, None, uint32(validation_time * 1000))
|
||||
return error_result(err, err_msg)
|
||||
assert conds is not None
|
||||
assert conds.validated_signature is True
|
||||
removals_and_additions = tx_removals_and_additions(conds)
|
||||
@@ -152,9 +177,7 @@ def _pre_validate_block(
|
||||
expected_vs,
|
||||
skip_commitment_validation=skip_commitment_validation,
|
||||
)
|
||||
error_int: uint16 | None = None
|
||||
if error is not None:
|
||||
error_int = uint16(error.code.value)
|
||||
error_int = None if error is None else uint16(error.code.value)
|
||||
|
||||
validation_time = time.monotonic() - validation_start
|
||||
return PreValidationResult(
|
||||
@@ -167,8 +190,7 @@ def _pre_validate_block(
|
||||
except Exception:
|
||||
error_stack = traceback.format_exc()
|
||||
log.error(f"Exception: {error_stack}")
|
||||
validation_time = time.monotonic() - validation_start
|
||||
return PreValidationResult(uint16(Err.UNKNOWN.value), None, None, None, uint32(validation_time * 1000))
|
||||
return error_result(Err.UNKNOWN)
|
||||
|
||||
|
||||
async def pre_validate_block(
|
||||
@@ -248,6 +270,13 @@ async def pre_validate_block(
|
||||
block.reward_chain_block.signage_point_index,
|
||||
)
|
||||
|
||||
prev_tx_height = pre_sp_tx_block_height(
|
||||
constants=constants,
|
||||
blocks=blockchain,
|
||||
prev_b_hash=block.prev_header_hash,
|
||||
sp_index=block.reward_chain_block.signage_point_index,
|
||||
finished_sub_slots=len(block.finished_sub_slots),
|
||||
)
|
||||
required_iters = validate_pospace_and_get_required_iters(
|
||||
constants,
|
||||
block.reward_chain_block.proof_of_space,
|
||||
@@ -255,19 +284,26 @@ async def pre_validate_block(
|
||||
cc_sp_hash,
|
||||
block.height,
|
||||
expected_vs.difficulty,
|
||||
pre_sp_tx_block_height(
|
||||
constants=constants,
|
||||
blocks=blockchain,
|
||||
prev_b_hash=block.prev_header_hash,
|
||||
sp_index=block.reward_chain_block.signage_point_index,
|
||||
finished_sub_slots=len(block.finished_sub_slots),
|
||||
),
|
||||
prev_tx_height,
|
||||
filter_challenge=filter_challenge,
|
||||
signage_point_index=block.reward_chain_block.signage_point_index,
|
||||
)
|
||||
if required_iters is None:
|
||||
return return_error(Err.INVALID_POSPACE)
|
||||
|
||||
if block.transactions_info is None:
|
||||
if block_has_transactions_generator(block) or block.transactions_generator_ref_list != []:
|
||||
error = (
|
||||
Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA
|
||||
if block.foliage.foliage_transaction_block_hash is not None
|
||||
else Err.NOT_BLOCK_BUT_HAS_DATA
|
||||
)
|
||||
return return_error(error)
|
||||
else:
|
||||
generator_ref_error = validate_generator_ref_list(constants, block, block.height, prev_tx_height)
|
||||
if generator_ref_error is not None:
|
||||
return return_error(generator_ref_error)
|
||||
|
||||
try:
|
||||
block_rec = block_to_block_record(
|
||||
constants,
|
||||
@@ -287,9 +323,6 @@ async def pre_validate_block(
|
||||
log.error("sub_epoch_summary does not match wp sub_epoch_summary list")
|
||||
return return_error(Err.INVALID_SUB_EPOCH_SUMMARY)
|
||||
|
||||
blockchain.add_extra_block(block, block_rec) # Temporarily add block to chain
|
||||
prev_b = block_rec
|
||||
|
||||
previous_generators: list[bytes] | None = None
|
||||
|
||||
try:
|
||||
@@ -299,6 +332,7 @@ async def pre_validate_block(
|
||||
except ValueError:
|
||||
return return_error(Err.FAILED_GETTING_GENERATOR_MULTIPROCESSING)
|
||||
|
||||
blockchain.add_extra_block(block, block_rec) # Temporarily add block to chain
|
||||
readonly_blockchain = blockchain.read_only_snapshot()
|
||||
|
||||
future = pool.run_in_loop(
|
||||
@@ -308,6 +342,7 @@ async def pre_validate_block(
|
||||
block,
|
||||
previous_generators,
|
||||
conds,
|
||||
prev_tx_height,
|
||||
expected_vs,
|
||||
skip_commitment_validation=skip_commitment_validation,
|
||||
nice=nice,
|
||||
|
||||
@@ -11,12 +11,7 @@ def get_prev_transaction_block(
|
||||
blocks: BlockRecordsProtocol,
|
||||
total_iters_sp: uint128,
|
||||
) -> tuple[bool, BlockRecord]:
|
||||
prev_transaction_block = curr
|
||||
while not curr.is_transaction_block:
|
||||
curr = blocks.block_record(curr.prev_hash)
|
||||
if total_iters_sp > curr.total_iters:
|
||||
prev_transaction_block = curr
|
||||
is_transaction_block = True
|
||||
else:
|
||||
is_transaction_block = False
|
||||
return is_transaction_block, prev_transaction_block
|
||||
is_transaction_block = total_iters_sp > curr.total_iters
|
||||
return is_transaction_block, curr
|
||||
|
||||
@@ -401,7 +401,7 @@ class WebSocketServer:
|
||||
show_traceback=False,
|
||||
):
|
||||
await connection.ping()
|
||||
except: # noqa E722
|
||||
except: # ruff: ignore[bare-except] E722
|
||||
self.remove_connection(connection)
|
||||
await connection.close()
|
||||
|
||||
|
||||
@@ -616,8 +616,28 @@ class DataLayer:
|
||||
|
||||
await self._update_confirmation_status(store_id=store_id)
|
||||
|
||||
if not await self.data_store.store_id_exists(store_id=store_id):
|
||||
await self.data_store.create_tree(store_id=store_id, status=Status.COMMITTED)
|
||||
root = None
|
||||
if await self.data_store.store_id_exists(store_id=store_id):
|
||||
root = await self.data_store.get_tree_root(store_id=store_id)
|
||||
|
||||
if root is None:
|
||||
await self.data_store.reset_store_to_empty_root(store_id)
|
||||
elif (
|
||||
root.node_hash is not None
|
||||
and root.generation <= singleton_record.generation
|
||||
and not self.data_store.merkle_blob_available(store_id, root.node_hash)
|
||||
):
|
||||
if self.data_store.merkle_blobs_path.is_dir():
|
||||
self.log.warning(
|
||||
f"Detected committed root with missing blob for {store_id} at generation "
|
||||
f"{root.generation}; resetting and resyncing."
|
||||
)
|
||||
await self.data_store.reset_store_to_empty_root(store_id)
|
||||
else:
|
||||
self.log.warning(
|
||||
f"Merkle blobs path {self.data_store.merkle_blobs_path} is missing; skipping "
|
||||
f"self-heal for {store_id} - check the storage volume."
|
||||
)
|
||||
|
||||
timestamp = int(time.time())
|
||||
servers_info = await self.data_store.get_available_servers_for_store(store_id, timestamp)
|
||||
|
||||
@@ -33,6 +33,7 @@ from chia.wallet.db_wallet.db_wallet_puzzles import (
|
||||
ACS_MU,
|
||||
ACS_MU_PH,
|
||||
GRAFTROOT_DL_OFFERS,
|
||||
MIRROR_PUZZLE_HASH,
|
||||
create_graftroot_offer_puz,
|
||||
create_host_fullpuz,
|
||||
create_host_layer_puzzle,
|
||||
@@ -52,7 +53,7 @@ from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.compute_additions import compute_additions
|
||||
from chia.wallet.util.merkle_utils import _simplify_merkle_proof
|
||||
from chia.wallet.util.wallet_sync_utils import fetch_coin_spend, fetch_coin_spend_for_coin_state
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.util.wallet_types import WalletIdentifier, WalletType
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_action_scope import WalletActionScope
|
||||
from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
@@ -894,6 +895,19 @@ class DataLayerWallet:
|
||||
raise e # raise the error if we've tried all peers
|
||||
continue # try some other peers, maybe someone has it
|
||||
|
||||
@classmethod
|
||||
async def identify(cls, wallet_state_manager: WalletStateManager, coin_state: CoinState) -> WalletIdentifier | None:
|
||||
try:
|
||||
dl_wallet = await wallet_state_manager.get_dl_wallet()
|
||||
except ValueError:
|
||||
return None
|
||||
if (
|
||||
coin_state.coin.puzzle_hash == MIRROR_PUZZLE_HASH
|
||||
or await dl_wallet.get_singleton_record(coin_state.coin.name()) is not None
|
||||
):
|
||||
return WalletIdentifier.create(dl_wallet)
|
||||
return None
|
||||
|
||||
###########
|
||||
# UTILITY #
|
||||
###########
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user