mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-09-26 17:20:25 -04:00
Merge branch 'long_lived/atari' into fc.get_keys
This commit is contained in:
@@ -29,6 +29,7 @@ jobs:
|
||||
python-version: [ 3.9 ]
|
||||
env:
|
||||
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
|
||||
BLOCKS_AND_PLOTS_VERSION: 0.29.0
|
||||
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
@@ -62,7 +63,7 @@ jobs:
|
||||
with:
|
||||
repository: 'Chia-Network/test-cache'
|
||||
path: '.chia'
|
||||
ref: '0.29.0'
|
||||
ref: ${{ env.BLOCKS_AND_PLOTS_VERSION }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run install script
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
#
|
||||
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
|
||||
#
|
||||
name: MacOS wallet-db_wallet Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'long_lived/**'
|
||||
- main
|
||||
- 'release/**'
|
||||
tags:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
concurrency:
|
||||
# SHA is added to the end if on `main` to let all main workflows run
|
||||
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: MacOS wallet-db_wallet Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ['3.9', '3.10']
|
||||
os: [macOS-latest]
|
||||
env:
|
||||
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
|
||||
JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-db_wallet
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python environment
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Create keychain for CI use
|
||||
run: |
|
||||
security create-keychain -p foo chiachain
|
||||
security default-keychain -s chiachain
|
||||
security unlock-keychain -p foo chiachain
|
||||
security set-keychain-settings -t 7200 -u chiachain
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# Note that new runners may break this https://github.com/actions/cache/issues/292
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache test blocks and plots
|
||||
uses: actions/cache@v2
|
||||
id: test-blocks-plots
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/.chia/blocks
|
||||
${{ github.workspace }}/.chia/test-plots
|
||||
key: 0.29.0
|
||||
|
||||
- name: Checkout test blocks and plots
|
||||
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
|
||||
mkdir ${{ github.workspace }}/.chia
|
||||
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
|
||||
|
||||
- name: Run install script
|
||||
env:
|
||||
INSTALL_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
run: |
|
||||
brew install boost
|
||||
sh install.sh -d
|
||||
|
||||
# Omitted installing Timelord
|
||||
|
||||
- name: Test wallet-db_wallet code with pytest
|
||||
run: |
|
||||
. ./activate
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/db_wallet/test_db_graftroot.py tests/wallet/db_wallet/test_dl_wallet.py
|
||||
|
||||
- name: Process coverage data
|
||||
run: |
|
||||
venv/bin/coverage combine --rcfile=.coveragerc .coverage.*
|
||||
venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml
|
||||
mkdir coverage_reports
|
||||
cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}"
|
||||
cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml"
|
||||
venv/bin/coverage report --rcfile=.coveragerc --show-missing
|
||||
|
||||
- name: Publish coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage_reports/*
|
||||
if-no-files-found: error
|
||||
#
|
||||
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
|
||||
#
|
||||
@@ -0,0 +1,119 @@
|
||||
#
|
||||
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
|
||||
#
|
||||
name: Ubuntu wallet-db_wallet Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'long_lived/**'
|
||||
- main
|
||||
- 'release/**'
|
||||
tags:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
concurrency:
|
||||
# SHA is added to the end if on `main` to let all main workflows run
|
||||
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Ubuntu wallet-db_wallet Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||
os: [ubuntu-latest]
|
||||
env:
|
||||
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
|
||||
JOB_FILE_NAME: tests_${{ matrix.os }}_python-${{ matrix.python-version }}_wallet-db_wallet
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python environment
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(pip cache dir)"
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache test blocks and plots
|
||||
uses: actions/cache@v2
|
||||
id: test-blocks-plots
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/.chia/blocks
|
||||
${{ github.workspace }}/.chia/test-plots
|
||||
key: 0.29.0
|
||||
|
||||
- name: Checkout test blocks and plots
|
||||
if: steps.test-blocks-plots.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
|
||||
mkdir ${{ github.workspace }}/.chia
|
||||
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
|
||||
|
||||
- name: Run install script
|
||||
env:
|
||||
INSTALL_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
run: |
|
||||
sh install.sh -d
|
||||
|
||||
# Omitted installing Timelord
|
||||
|
||||
- name: Test wallet-db_wallet code with pytest
|
||||
run: |
|
||||
. ./activate
|
||||
venv/bin/coverage run --rcfile=.coveragerc --module pytest --durations=10 -n 0 -m "not benchmark" tests/wallet/db_wallet/test_db_graftroot.py tests/wallet/db_wallet/test_dl_wallet.py
|
||||
|
||||
- name: Process coverage data
|
||||
run: |
|
||||
venv/bin/coverage combine --rcfile=.coveragerc .coverage.*
|
||||
venv/bin/coverage xml --rcfile=.coveragerc -o coverage.xml
|
||||
mkdir coverage_reports
|
||||
cp .coverage "coverage_reports/.coverage.${{ env.JOB_FILE_NAME }}"
|
||||
cp coverage.xml "coverage_reports/coverage.${{ env.JOB_FILE_NAME }}.xml"
|
||||
venv/bin/coverage report --rcfile=.coveragerc --show-missing
|
||||
|
||||
- name: Publish coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage_reports/*
|
||||
if-no-files-found: error
|
||||
|
||||
# Omitted resource usage check
|
||||
|
||||
#
|
||||
# THIS FILE IS GENERATED. SEE https://github.com/Chia-Network/chia-blockchain/tree/main/tests#readme
|
||||
#
|
||||
@@ -97,6 +97,7 @@ jobs:
|
||||
env:
|
||||
CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
|
||||
JOB_FILE_NAME: tests_${{ matrix.os.file_name }}_python-${{ matrix.python.file_name }}_${{ matrix.configuration.name }}
|
||||
BLOCKS_AND_PLOTS_VERSION: 0.29.0
|
||||
|
||||
steps:
|
||||
- name: Configure git
|
||||
@@ -152,21 +153,21 @@ jobs:
|
||||
path: |
|
||||
${{ github.workspace }}/.chia/blocks
|
||||
${{ github.workspace }}/.chia/test-plots
|
||||
key: 0.29.0
|
||||
key: ${{ env.BLOCKS_AND_PLOTS_VERSION }}
|
||||
|
||||
- name: Checkout test blocks and plots (macOS, Ubuntu)
|
||||
if: steps.test-blocks-plots.outputs.cache-hit != 'true' && (matrix.os.matrix == 'ubuntu' || matrix.os.matrix == 'macos')
|
||||
run: |
|
||||
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.tar.gz | tar xzf -
|
||||
wget -qO- https://github.com/Chia-Network/test-cache/archive/refs/tags/${{ env.BLOCKS_AND_PLOTS_VERSION }}.tar.gz | tar xzf -
|
||||
mkdir ${{ github.workspace }}/.chia
|
||||
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
|
||||
mv ${{ github.workspace }}/test-cache-${{ env.BLOCKS_AND_PLOTS_VERSION }}/* ${{ github.workspace }}/.chia
|
||||
|
||||
- name: Checkout test blocks and plots (Windows)
|
||||
if: steps.test-blocks-plots.outputs.cache-hit != 'true' && matrix.os.matrix == 'windows'
|
||||
run: |
|
||||
Invoke-WebRequest -OutFile blocks_and_plots.zip https://github.com/Chia-Network/test-cache/archive/refs/tags/0.29.0.zip; Expand-Archive blocks_and_plots.zip -DestinationPath .
|
||||
Invoke-WebRequest -OutFile blocks_and_plots.zip https://github.com/Chia-Network/test-cache/archive/refs/tags/${{ env.BLOCKS_AND_PLOTS_VERSION }}.zip; Expand-Archive blocks_and_plots.zip -DestinationPath .
|
||||
mkdir ${{ github.workspace }}/.chia
|
||||
mv ${{ github.workspace }}/test-cache-0.29.0/* ${{ github.workspace }}/.chia
|
||||
mv ${{ github.workspace }}/test-cache-${{ env.BLOCKS_AND_PLOTS_VERSION }}/* ${{ github.workspace }}/.chia
|
||||
|
||||
- name: Install boost (macOS)
|
||||
if: matrix.os.matrix == 'macos'
|
||||
|
||||
@@ -117,7 +117,6 @@ extend_skip=
|
||||
chia/wallet/did_wallet/did_info.py
|
||||
chia/wallet/did_wallet/did_wallet_puzzles.py
|
||||
chia/wallet/did_wallet/did_wallet.py
|
||||
chia/wallet/dlo_wallet/dlo_wallet.py
|
||||
chia/wallet/lineage_proof.py
|
||||
chia/wallet/payment.py
|
||||
chia/wallet/puzzles/load_clvm.py
|
||||
|
||||
+26
-18
@@ -5194,9 +5194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -5385,14 +5385,22 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
@@ -6216,9 +6224,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
@@ -12155,9 +12163,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -12298,9 +12306,9 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -12928,9 +12936,9 @@
|
||||
}
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
|
||||
+26
-18
@@ -5036,9 +5036,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -5227,14 +5227,22 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
@@ -6058,9 +6066,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
@@ -11851,9 +11859,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -11994,9 +12002,9 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -12624,9 +12632,9 @@
|
||||
}
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
|
||||
+26
-18
@@ -6158,9 +6158,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -6372,14 +6372,22 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
@@ -7311,9 +7319,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
@@ -14054,9 +14062,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -14220,9 +14228,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -14931,9 +14939,9 @@
|
||||
}
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
|
||||
+26
-18
@@ -6133,9 +6133,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -6344,14 +6344,22 @@
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
@@ -7279,9 +7287,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
@@ -13989,9 +13997,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -14152,9 +14160,9 @@
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -14859,9 +14867,9 @@
|
||||
}
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
|
||||
+26
-18
@@ -4974,9 +4974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -5165,14 +5165,22 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
@@ -5996,9 +6004,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
@@ -11722,9 +11730,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"minimist-options": {
|
||||
"version": "4.1.0",
|
||||
@@ -11865,9 +11873,9 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -12495,9 +12503,9 @@
|
||||
}
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
|
||||
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
|
||||
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.5.1",
|
||||
"xmlbuilder": "^9.0.7"
|
||||
|
||||
+37
-20
@@ -1,12 +1,14 @@
|
||||
import aiosqlite
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List, Dict, Tuple, Any
|
||||
from typing import Optional, List, Dict, Tuple, Any, Type, TypeVar
|
||||
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program, SerializedProgram
|
||||
from chia.types.mempool_item import MempoolItem
|
||||
from chia.util.ints import uint64, uint32
|
||||
from chia.util.hash import std_hash
|
||||
from chia.util.errors import Err, ValidationError
|
||||
@@ -45,6 +47,9 @@ class SimFullBlock(Streamable):
|
||||
height: uint32 # Note that height is not on a regular FullBlock
|
||||
|
||||
|
||||
_T_SimBlockRecord = TypeVar("_T_SimBlockRecord", bound="SimBlockRecord")
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class SimBlockRecord(Streamable):
|
||||
@@ -57,7 +62,7 @@ class SimBlockRecord(Streamable):
|
||||
prev_transaction_block_hash: bytes32
|
||||
|
||||
@classmethod
|
||||
def create(cls, rci: List[Coin], height: uint32, timestamp: uint64):
|
||||
def create(cls: Type[_T_SimBlockRecord], rci: List[Coin], height: uint32, timestamp: uint64) -> _T_SimBlockRecord:
|
||||
return cls(
|
||||
rci,
|
||||
height,
|
||||
@@ -78,6 +83,9 @@ class SimStore(Streamable):
|
||||
blocks: List[SimFullBlock]
|
||||
|
||||
|
||||
_T_SpendSim = TypeVar("_T_SpendSim", bound="SpendSim")
|
||||
|
||||
|
||||
class SpendSim:
|
||||
|
||||
db_wrapper: DBWrapper2
|
||||
@@ -89,7 +97,9 @@ class SpendSim:
|
||||
defaults: ConsensusConstants
|
||||
|
||||
@classmethod
|
||||
async def create(cls, db_path=None, defaults=DEFAULT_CONSTANTS):
|
||||
async def create(
|
||||
cls: Type[_T_SpendSim], db_path: Optional[Path] = None, defaults: ConsensusConstants = DEFAULT_CONSTANTS
|
||||
) -> _T_SpendSim:
|
||||
self = cls()
|
||||
if db_path is None:
|
||||
uri = f"file:db_{random.randint(0, 99999999)}?mode=memory&cache=shared"
|
||||
@@ -114,15 +124,16 @@ class SpendSim:
|
||||
self.block_height = store_data.block_height
|
||||
self.block_records = store_data.block_records
|
||||
self.blocks = store_data.blocks
|
||||
self.mempool_manager.peak = self.block_records[-1]
|
||||
# Create a protocol to make BlockRecord and SimBlockRecord interchangeable.
|
||||
self.mempool_manager.peak = self.block_records[-1] # type: ignore[assignment]
|
||||
else:
|
||||
self.timestamp = 1
|
||||
self.block_height = 0
|
||||
self.timestamp = uint64(1)
|
||||
self.block_height = uint32(0)
|
||||
self.block_records = []
|
||||
self.blocks = []
|
||||
return self
|
||||
|
||||
async def close(self):
|
||||
async def close(self) -> None:
|
||||
async with self.db_wrapper.write_db() as conn:
|
||||
c = await conn.execute("DELETE FROM block_data")
|
||||
await c.close()
|
||||
@@ -133,10 +144,11 @@ class SpendSim:
|
||||
await c.close()
|
||||
await self.db_wrapper.close()
|
||||
|
||||
async def new_peak(self):
|
||||
await self.mempool_manager.new_peak(self.block_records[-1], None)
|
||||
async def new_peak(self) -> None:
|
||||
# Create a protocol to make BlockRecord and SimBlockRecord interchangeable.
|
||||
await self.mempool_manager.new_peak(self.block_records[-1], None) # type: ignore[arg-type]
|
||||
|
||||
def new_coin_record(self, coin: Coin, coinbase=False) -> CoinRecord:
|
||||
def new_coin_record(self, coin: Coin, coinbase: bool = False) -> CoinRecord:
|
||||
return CoinRecord(
|
||||
coin,
|
||||
uint32(self.block_height + 1),
|
||||
@@ -164,7 +176,7 @@ class SpendSim:
|
||||
return None
|
||||
return simple_solution_generator(bundle)
|
||||
|
||||
async def farm_block(self, puzzle_hash: bytes32 = bytes32(b"0" * 32)):
|
||||
async def farm_block(self, puzzle_hash: bytes32 = bytes32(b"0" * 32)) -> Tuple[List[Coin], List[Coin]]:
|
||||
# Fees get calculated
|
||||
fees = uint64(0)
|
||||
if self.mempool_manager.mempool.spends:
|
||||
@@ -234,13 +246,13 @@ class SpendSim:
|
||||
def get_height(self) -> uint32:
|
||||
return self.block_height
|
||||
|
||||
def pass_time(self, time: uint64):
|
||||
def pass_time(self, time: uint64) -> None:
|
||||
self.timestamp = uint64(self.timestamp + time)
|
||||
|
||||
def pass_blocks(self, blocks: uint32):
|
||||
def pass_blocks(self, blocks: uint32) -> None:
|
||||
self.block_height = uint32(self.block_height + blocks)
|
||||
|
||||
async def rewind(self, block_height: uint32):
|
||||
async def rewind(self, block_height: uint32) -> None:
|
||||
new_br_list = list(filter(lambda br: br.height <= block_height, self.block_records))
|
||||
new_block_list = list(filter(lambda block: block.height <= block_height, self.blocks))
|
||||
self.block_records = new_br_list
|
||||
@@ -255,7 +267,7 @@ class SpendSim:
|
||||
|
||||
|
||||
class SimClient:
|
||||
def __init__(self, service):
|
||||
def __init__(self, service: SpendSim) -> None:
|
||||
self.service = service
|
||||
|
||||
async def push_tx(self, spend_bundle: SpendBundle) -> Tuple[MempoolInclusionStatus, Optional[Err]]:
|
||||
@@ -270,7 +282,7 @@ class SimClient:
|
||||
)
|
||||
return status, error
|
||||
|
||||
async def get_coin_record_by_name(self, name: bytes32) -> CoinRecord:
|
||||
async def get_coin_record_by_name(self, name: bytes32) -> Optional[CoinRecord]:
|
||||
return await self.service.mempool_manager.coin_store.get_coin_record(name)
|
||||
|
||||
async def get_coin_records_by_names(
|
||||
@@ -363,8 +375,13 @@ class SimClient:
|
||||
return additions, removals
|
||||
|
||||
async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> Optional[CoinSpend]:
|
||||
generator = list(filter(lambda block: block.height == height, self.service.blocks))[0].transactions_generator
|
||||
coin_record = await self.service.mempool_manager.coin_store.get_coin_record(coin_id)
|
||||
filtered_generators = list(filter(lambda block: block.height == height, self.service.blocks))
|
||||
# real consideration should be made for the None cases instead of just hint ignoring
|
||||
generator: BlockGenerator = filtered_generators[0].transactions_generator # type: ignore[assignment]
|
||||
coin_record: CoinRecord
|
||||
coin_record = await self.service.mempool_manager.coin_store.get_coin_record( # type: ignore[assignment]
|
||||
coin_id,
|
||||
)
|
||||
error, puzzle, solution = get_puzzle_and_solution_for_coin(
|
||||
generator,
|
||||
coin_id,
|
||||
@@ -380,13 +397,13 @@ class SimClient:
|
||||
async def get_all_mempool_tx_ids(self) -> List[bytes32]:
|
||||
return list(self.service.mempool_manager.mempool.spends.keys())
|
||||
|
||||
async def get_all_mempool_items(self) -> Dict[bytes32, Dict]:
|
||||
async def get_all_mempool_items(self) -> Dict[bytes32, MempoolItem]:
|
||||
spends = {}
|
||||
for tx_id, item in self.service.mempool_manager.mempool.spends.items():
|
||||
spends[tx_id] = item
|
||||
return spends
|
||||
|
||||
async def get_mempool_item_by_tx_id(self, tx_id: bytes32) -> Optional[Dict]:
|
||||
async def get_mempool_item_by_tx_id(self, tx_id: bytes32) -> Optional[Dict[str, Any]]:
|
||||
item = self.service.mempool_manager.get_mempool_item(tx_id)
|
||||
if item is None:
|
||||
return None
|
||||
|
||||
@@ -519,9 +519,13 @@ def chia_init(
|
||||
db_path_replaced = config["database_path"].replace("CHALLENGE", config["selected_network"])
|
||||
db_path = path_from_root(root_path, db_path_replaced)
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with sqlite3.connect(db_path) as connection:
|
||||
set_db_version(connection, 2)
|
||||
try:
|
||||
# create new v2 db file
|
||||
with sqlite3.connect(db_path) as connection:
|
||||
set_db_version(connection, 2)
|
||||
except sqlite3.OperationalError:
|
||||
# db already exists, so we're good
|
||||
pass
|
||||
|
||||
print("")
|
||||
print("To see your keys, run 'chia keys show --show-mnemonic-seed'")
|
||||
|
||||
@@ -12,9 +12,9 @@ def passphrase_cmd():
|
||||
|
||||
@passphrase_cmd.command(
|
||||
"set",
|
||||
help="""Sets or updates the keyring passphrase. If --passphrase-file and/or --current-passphrase-file options are provided,
|
||||
the passphrases will be read from the specified files. Otherwise, a prompt will be provided to enter the
|
||||
passphrase.""",
|
||||
help="""Sets or updates the keyring passphrase. If --passphrase-file and/or --current-passphrase-file options are
|
||||
provided, the passphrases will be read from the specified files. Otherwise, a prompt will be provided to
|
||||
enter the passphrase.""",
|
||||
short_help="Set or update the keyring passphrase",
|
||||
)
|
||||
@click.option("--passphrase-file", type=click.File("r"), help="File or descriptor to read the passphrase from")
|
||||
@@ -70,8 +70,8 @@ def set_cmd(
|
||||
|
||||
@passphrase_cmd.command(
|
||||
"remove",
|
||||
help="""Remove the keyring passphrase. If the --current-passphrase-file option is provided, the passphrase will be read from
|
||||
the specified file. Otherwise, a prompt will be provided to enter the passphrase.""",
|
||||
help="""Remove the keyring passphrase. If the --current-passphrase-file option is provided, the passphrase will be
|
||||
read from the specified file. Otherwise, a prompt will be provided to enter the passphrase.""",
|
||||
short_help="Remove the keyring passphrase",
|
||||
)
|
||||
@click.option(
|
||||
|
||||
@@ -71,7 +71,7 @@ class BlockchainInterface:
|
||||
return None
|
||||
|
||||
async def persist_sub_epoch_challenge_segments(
|
||||
self, sub_epoch_summary_height: bytes32, segments: List[SubEpochChallengeSegment]
|
||||
self, sub_epoch_summary_hash: bytes32, segments: List[SubEpochChallengeSegment]
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@@ -244,7 +244,6 @@ class WebSocketServer:
|
||||
self.log.error(f"Unexpected exception trying to send to websocket: {e} {tb}")
|
||||
self.remove_connection(socket)
|
||||
await socket.close()
|
||||
break
|
||||
else:
|
||||
service_name = "Unknown"
|
||||
if ws in self.remote_address_map:
|
||||
|
||||
@@ -12,6 +12,8 @@ from blspy import G2Element
|
||||
from chia.consensus.block_record import BlockRecord
|
||||
from chia.protocols.wallet_protocol import PuzzleSolutionResponse, CoinState
|
||||
from chia.wallet.db_wallet.db_wallet_puzzles import (
|
||||
ACS_MU,
|
||||
ACS_MU_PH,
|
||||
create_host_fullpuz,
|
||||
SINGLETON_LAUNCHER,
|
||||
create_host_layer_puzzle,
|
||||
@@ -148,7 +150,8 @@ class DataLayerWallet:
|
||||
|
||||
# Now let's check that the full puzzle is an odd data layer singleton
|
||||
if (
|
||||
full_puzhash != create_host_fullpuz(inner_puzhash, root, launcher_spend.coin.name()).get_tree_hash()
|
||||
full_puzhash
|
||||
!= create_host_fullpuz(inner_puzhash, root, launcher_spend.coin.name()).get_tree_hash(inner_puzhash)
|
||||
or amount % 2 == 0
|
||||
):
|
||||
return False, None
|
||||
@@ -264,7 +267,7 @@ class DataLayerWallet:
|
||||
timestamp=timestamp,
|
||||
lineage_proof=LineageProof(
|
||||
launcher_id,
|
||||
create_host_layer_puzzle(inner_puzhash, root).get_tree_hash(),
|
||||
create_host_layer_puzzle(inner_puzhash, root).get_tree_hash(inner_puzhash),
|
||||
amount,
|
||||
),
|
||||
generation=uint32(0),
|
||||
@@ -308,7 +311,7 @@ class DataLayerWallet:
|
||||
launcher_coin: Coin = Coin(launcher_parent.name(), SINGLETON_LAUNCHER.get_tree_hash(), uint64(1))
|
||||
|
||||
inner_puzzle: Program = await self.standard_wallet.get_new_puzzle()
|
||||
full_puzzle: Program = create_host_fullpuz(inner_puzzle.get_tree_hash(), initial_root, launcher_coin.name())
|
||||
full_puzzle: Program = create_host_fullpuz(inner_puzzle, initial_root, launcher_coin.name())
|
||||
|
||||
genesis_launcher_solution: Program = Program.to(
|
||||
[full_puzzle.get_tree_hash(), 1, [initial_root, inner_puzzle.get_tree_hash()]]
|
||||
@@ -365,7 +368,7 @@ class DataLayerWallet:
|
||||
timestamp=uint64(0),
|
||||
lineage_proof=LineageProof(
|
||||
launcher_coin.name(),
|
||||
create_host_layer_puzzle(inner_puzzle.get_tree_hash(), initial_root).get_tree_hash(),
|
||||
create_host_layer_puzzle(inner_puzzle, initial_root).get_tree_hash(),
|
||||
uint64(1),
|
||||
),
|
||||
generation=uint32(0),
|
||||
@@ -398,12 +401,20 @@ class DataLayerWallet:
|
||||
async def create_update_state_spend(
|
||||
self,
|
||||
launcher_id: bytes32,
|
||||
root_hash: bytes32,
|
||||
root_hash: Optional[bytes32],
|
||||
new_puz_hash: Optional[bytes32] = None,
|
||||
new_amount: Optional[uint64] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
coin_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
puzzle_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
sign: bool = True,
|
||||
in_transaction: bool = False,
|
||||
) -> List[TransactionRecord]:
|
||||
singleton_record, parent_lineage = await self.get_spendable_singleton_info(launcher_id)
|
||||
|
||||
if root_hash is None:
|
||||
root_hash = singleton_record.root
|
||||
|
||||
inner_puzzle_derivation: Optional[
|
||||
DerivationRecord
|
||||
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
|
||||
@@ -413,14 +424,17 @@ class DataLayerWallet:
|
||||
raise ValueError(f"DL Wallet does not have permission to update Singleton with launcher ID {launcher_id}")
|
||||
|
||||
# Make the child's puzzles
|
||||
next_inner_puzzle: Program = await self.standard_wallet.get_new_puzzle(in_transaction=in_transaction)
|
||||
next_db_layer_puzzle: Program = create_host_layer_puzzle(next_inner_puzzle.get_tree_hash(), root_hash)
|
||||
next_full_puz = create_host_fullpuz(next_inner_puzzle.get_tree_hash(), root_hash, launcher_id)
|
||||
if new_puz_hash is None:
|
||||
new_puz_hash = (await self.standard_wallet.get_new_puzzle(in_transaction=in_transaction)).get_tree_hash()
|
||||
assert new_puz_hash is not None
|
||||
next_full_puz_hash: bytes32 = create_host_fullpuz(new_puz_hash, root_hash, launcher_id).get_tree_hash(
|
||||
new_puz_hash
|
||||
)
|
||||
|
||||
# Construct the current puzzles
|
||||
current_inner_puzzle: Program = self.standard_wallet.puzzle_for_pk(inner_puzzle_derivation.pubkey)
|
||||
current_full_puz = create_host_fullpuz(
|
||||
current_inner_puzzle.get_tree_hash(),
|
||||
current_inner_puzzle,
|
||||
singleton_record.root,
|
||||
launcher_id,
|
||||
)
|
||||
@@ -430,16 +444,26 @@ class DataLayerWallet:
|
||||
assert singleton_record.lineage_proof.amount is not None
|
||||
primaries: List[AmountWithPuzzlehash] = [
|
||||
{
|
||||
"puzzlehash": next_db_layer_puzzle.get_tree_hash(),
|
||||
"amount": singleton_record.lineage_proof.amount,
|
||||
"memos": [launcher_id, root_hash, next_inner_puzzle.get_tree_hash()],
|
||||
"puzzlehash": new_puz_hash,
|
||||
"amount": singleton_record.lineage_proof.amount if new_amount is None else new_amount,
|
||||
"memos": [launcher_id, root_hash, new_puz_hash],
|
||||
}
|
||||
]
|
||||
inner_sol: Program = self.standard_wallet.make_solution(
|
||||
primaries=primaries,
|
||||
coin_announcements={b"$"} if fee > 0 else None,
|
||||
coin_announcements_to_assert={a.name() for a in coin_announcements_to_consume}
|
||||
if coin_announcements_to_consume is not None
|
||||
else None,
|
||||
puzzle_announcements_to_assert={a.name() for a in puzzle_announcements_to_consume}
|
||||
if puzzle_announcements_to_consume is not None
|
||||
else None,
|
||||
)
|
||||
db_layer_sol = Program.to([0, inner_sol, current_inner_puzzle])
|
||||
if root_hash != singleton_record.root:
|
||||
magic_condition = Program.to([-24, ACS_MU, [[Program.to((root_hash, None)), ACS_MU_PH], None]])
|
||||
# TODO: This line is a hack, make_solution should allow us to pass extra conditions to it
|
||||
inner_sol = Program.to([[], (1, magic_condition.cons(inner_sol.at("rfr"))), []])
|
||||
db_layer_sol = Program.to([inner_sol])
|
||||
full_sol = Program.to(
|
||||
[
|
||||
parent_lineage.to_program(),
|
||||
@@ -460,12 +484,16 @@ class DataLayerWallet:
|
||||
SerializedProgram.from_program(full_sol),
|
||||
)
|
||||
await self.standard_wallet.hack_populate_secret_key_for_puzzle_hash(current_inner_puzzle.get_tree_hash())
|
||||
spend_bundle = await self.sign(coin_spend)
|
||||
|
||||
if sign:
|
||||
spend_bundle = await self.sign(coin_spend)
|
||||
else:
|
||||
spend_bundle = SpendBundle([coin_spend], G2Element())
|
||||
|
||||
dl_tx = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=next_inner_puzzle.get_tree_hash(),
|
||||
to_puzzle_hash=new_puz_hash,
|
||||
amount=uint64(singleton_record.lineage_proof.amount),
|
||||
fee_amount=fee,
|
||||
confirmed=False,
|
||||
@@ -491,18 +519,16 @@ class DataLayerWallet:
|
||||
else:
|
||||
txs = [dl_tx]
|
||||
new_singleton_record = SingletonRecord(
|
||||
coin_id=Coin(
|
||||
current_coin.name(), next_full_puz.get_tree_hash(), singleton_record.lineage_proof.amount
|
||||
).name(),
|
||||
coin_id=Coin(current_coin.name(), next_full_puz_hash, singleton_record.lineage_proof.amount).name(),
|
||||
launcher_id=launcher_id,
|
||||
root=root_hash,
|
||||
inner_puzzle_hash=next_inner_puzzle.get_tree_hash(),
|
||||
inner_puzzle_hash=new_puz_hash,
|
||||
confirmed=False,
|
||||
confirmed_at_height=uint32(0),
|
||||
timestamp=uint64(0),
|
||||
lineage_proof=LineageProof(
|
||||
singleton_record.coin_id,
|
||||
next_inner_puzzle.get_tree_hash(),
|
||||
new_puz_hash,
|
||||
singleton_record.lineage_proof.amount,
|
||||
),
|
||||
generation=uint32(singleton_record.generation + 1),
|
||||
@@ -513,95 +539,48 @@ class DataLayerWallet:
|
||||
)
|
||||
return txs
|
||||
|
||||
async def create_report_spend(
|
||||
async def generate_signed_transaction(
|
||||
self,
|
||||
launcher_id: bytes32,
|
||||
amounts: List[uint64],
|
||||
puzzle_hashes: List[bytes32],
|
||||
fee: uint64 = uint64(0),
|
||||
) -> Tuple[List[TransactionRecord], Announcement]:
|
||||
singleton_record, parent_lineage = await self.get_spendable_singleton_info(launcher_id)
|
||||
|
||||
# Create the puzzle
|
||||
current_full_puz = create_host_fullpuz(
|
||||
singleton_record.inner_puzzle_hash,
|
||||
singleton_record.root,
|
||||
launcher_id,
|
||||
)
|
||||
|
||||
# Create the solution
|
||||
assert singleton_record.lineage_proof.parent_name is not None
|
||||
assert singleton_record.lineage_proof.amount is not None
|
||||
db_layer_sol = Program.to([1, singleton_record.lineage_proof.amount, []])
|
||||
full_sol = Program.to(
|
||||
[
|
||||
parent_lineage.to_program(),
|
||||
singleton_record.lineage_proof.amount,
|
||||
db_layer_sol,
|
||||
]
|
||||
)
|
||||
|
||||
# Create the spend
|
||||
current_coin = Coin(
|
||||
singleton_record.lineage_proof.parent_name,
|
||||
current_full_puz.get_tree_hash(),
|
||||
singleton_record.lineage_proof.amount,
|
||||
)
|
||||
coin_spend = CoinSpend(
|
||||
current_coin,
|
||||
SerializedProgram.from_program(current_full_puz),
|
||||
SerializedProgram.from_program(full_sol),
|
||||
)
|
||||
spend_bundle = SpendBundle([coin_spend], G2Element())
|
||||
expected_announcement = Announcement(current_full_puz.get_tree_hash(), singleton_record.root)
|
||||
|
||||
# Create the relevant records
|
||||
dl_tx = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=singleton_record.inner_puzzle_hash,
|
||||
amount=uint64(singleton_record.lineage_proof.amount),
|
||||
fee_amount=uint64(0),
|
||||
confirmed=False,
|
||||
sent=uint32(10),
|
||||
spend_bundle=spend_bundle,
|
||||
additions=spend_bundle.additions(),
|
||||
removals=spend_bundle.removals(),
|
||||
memos=list(compute_memos(spend_bundle).items()),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=singleton_record.coin_id,
|
||||
)
|
||||
if fee > 0:
|
||||
chia_tx = await self.create_tandem_xch_tx(fee, expected_announcement, coin_announcement=False)
|
||||
aggregate_bundle = SpendBundle.aggregate([dl_tx.spend_bundle, chia_tx.spend_bundle])
|
||||
dl_tx = dataclasses.replace(dl_tx, spend_bundle=aggregate_bundle)
|
||||
chia_tx = dataclasses.replace(chia_tx, spend_bundle=None)
|
||||
txs: List[TransactionRecord] = [dl_tx, chia_tx]
|
||||
coins: Set[Coin] = set(),
|
||||
memos: Optional[List[List[bytes]]] = None, # ignored
|
||||
coin_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
puzzle_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
ignore_max_send_amount: bool = False, # ignored
|
||||
# This wallet only
|
||||
launcher_id: Optional[bytes32] = None,
|
||||
new_root_hash: Optional[bytes32] = None,
|
||||
sign: bool = True, # This only prevent signing of THIS wallet's part of the tx (fee will still be signed)
|
||||
) -> List[TransactionRecord]:
|
||||
# Figure out the launcher ID
|
||||
if len(coins) == 0:
|
||||
if launcher_id is None:
|
||||
raise ValueError("Not enough info to know which DL coin to send")
|
||||
else:
|
||||
txs = [dl_tx]
|
||||
new_singleton_record = SingletonRecord(
|
||||
coin_id=Coin(
|
||||
current_coin.name(), current_full_puz.get_tree_hash(), singleton_record.lineage_proof.amount
|
||||
).name(),
|
||||
launcher_id=launcher_id,
|
||||
root=singleton_record.root,
|
||||
confirmed=False,
|
||||
confirmed_at_height=uint32(0),
|
||||
timestamp=uint64(0),
|
||||
inner_puzzle_hash=singleton_record.inner_puzzle_hash,
|
||||
lineage_proof=LineageProof(
|
||||
singleton_record.coin_id,
|
||||
create_host_layer_puzzle(
|
||||
singleton_record.inner_puzzle_hash,
|
||||
singleton_record.root,
|
||||
).get_tree_hash(),
|
||||
singleton_record.lineage_proof.amount,
|
||||
),
|
||||
generation=uint32(singleton_record.generation + 1),
|
||||
if len(coins) != 1:
|
||||
raise ValueError("The wallet can only send one DL coin at a time")
|
||||
else:
|
||||
record = await self.wallet_state_manager.dl_store.get_singleton_record(next(iter(coins)).name())
|
||||
if record is None:
|
||||
raise ValueError("The specified coin is not a tracked DL")
|
||||
else:
|
||||
launcher_id = record.launcher_id
|
||||
|
||||
if len(amounts) != 1 or len(puzzle_hashes) != 1:
|
||||
raise ValueError("The wallet can only send one DL coin to one place at a time")
|
||||
|
||||
return await self.create_update_state_spend(
|
||||
launcher_id,
|
||||
new_root_hash,
|
||||
puzzle_hashes[0],
|
||||
amounts[0],
|
||||
fee,
|
||||
coin_announcements_to_consume,
|
||||
puzzle_announcements_to_consume,
|
||||
sign,
|
||||
)
|
||||
await self.wallet_state_manager.dl_store.add_singleton_record(new_singleton_record, False)
|
||||
return txs, expected_announcement
|
||||
|
||||
async def get_spendable_singleton_info(self, launcher_id: bytes32) -> Tuple[SingletonRecord, LineageProof]:
|
||||
# First, let's make sure this is a singleton that we track and that we can spend
|
||||
@@ -666,7 +645,7 @@ class DataLayerWallet:
|
||||
puzzle = parent_spend.puzzle_reveal
|
||||
solution = parent_spend.solution
|
||||
|
||||
matched, curried_args = match_dl_singleton(puzzle.to_program())
|
||||
matched, _ = match_dl_singleton(puzzle.to_program())
|
||||
if matched:
|
||||
self.log.info(f"DL singleton removed: {parent_spend.coin}")
|
||||
singleton_record: Optional[SingletonRecord] = await self.wallet_state_manager.dl_store.get_singleton_record(
|
||||
@@ -676,13 +655,6 @@ class DataLayerWallet:
|
||||
self.log.warning(f"DL wallet received coin it does not have parent for. Expected parent {parent_name}.")
|
||||
return
|
||||
|
||||
# First let's create the singleton's full puz to check if it's the same (report spend)
|
||||
current_full_puz: Program = create_host_fullpuz(
|
||||
singleton_record.inner_puzzle_hash,
|
||||
singleton_record.root,
|
||||
singleton_record.launcher_id,
|
||||
)
|
||||
|
||||
# Information we need to create the singleton record
|
||||
full_puzzle_hash: bytes32
|
||||
amount: uint64
|
||||
@@ -697,19 +669,15 @@ class DataLayerWallet:
|
||||
if condition[0] == ConditionOpcode.CREATE_COIN and int.from_bytes(condition[2], "big") % 2 == 1:
|
||||
full_puzzle_hash = bytes32(condition[1])
|
||||
amount = uint64(int.from_bytes(condition[2], "big"))
|
||||
if current_full_puz.get_tree_hash() == full_puzzle_hash:
|
||||
root = singleton_record.root
|
||||
inner_puzzle_hash = singleton_record.inner_puzzle_hash
|
||||
else:
|
||||
try:
|
||||
root = bytes32(condition[3][1])
|
||||
inner_puzzle_hash = bytes32(condition[3][2])
|
||||
except IndexError:
|
||||
self.log.warning(
|
||||
f"Parent {parent_name} with launcher {singleton_record.launcher_id} "
|
||||
"did not hint its child properly"
|
||||
)
|
||||
return
|
||||
try:
|
||||
root = bytes32(condition[3][1])
|
||||
inner_puzzle_hash = bytes32(condition[3][2])
|
||||
except IndexError:
|
||||
self.log.warning(
|
||||
f"Parent {parent_name} with launcher {singleton_record.launcher_id} "
|
||||
"did not hint its child properly"
|
||||
)
|
||||
return
|
||||
found_singleton = True
|
||||
break
|
||||
|
||||
@@ -730,7 +698,7 @@ class DataLayerWallet:
|
||||
timestamp=timestamp,
|
||||
lineage_proof=LineageProof(
|
||||
parent_name,
|
||||
create_host_layer_puzzle(inner_puzzle_hash, root).get_tree_hash(),
|
||||
create_host_layer_puzzle(inner_puzzle_hash, root).get_tree_hash(inner_puzzle_hash),
|
||||
amount,
|
||||
),
|
||||
generation=uint32(singleton_record.generation + 1),
|
||||
@@ -825,7 +793,7 @@ class DataLayerWallet:
|
||||
await self.create_update_state_spend(
|
||||
launcher_id,
|
||||
singleton.root,
|
||||
fee,
|
||||
fee=fee,
|
||||
in_transaction=in_transaction,
|
||||
)
|
||||
)
|
||||
@@ -841,27 +809,6 @@ class DataLayerWallet:
|
||||
await self.wallet_state_manager.dl_store.delete_singleton_records_by_launcher_id(launcher_id)
|
||||
await self.wallet_state_manager.dl_store.delete_launcher(launcher_id)
|
||||
|
||||
#############
|
||||
# DL OFFERS #
|
||||
#############
|
||||
|
||||
async def get_info_for_offer_claim(
|
||||
self,
|
||||
launcher_id: bytes32,
|
||||
) -> Tuple[Program, bytes32, bytes32]:
|
||||
singleton_record: Optional[SingletonRecord] = await self.get_latest_singleton(launcher_id)
|
||||
if singleton_record is None:
|
||||
raise ValueError(f"Singleton with launcher ID {launcher_id} is not tracked by DL Wallet")
|
||||
elif not singleton_record.confirmed:
|
||||
raise ValueError(f"Singleton with launcher ID {launcher_id} is in an unconfirmed state")
|
||||
|
||||
current_full_puz = create_host_fullpuz(
|
||||
singleton_record.inner_puzzle_hash,
|
||||
singleton_record.root,
|
||||
launcher_id,
|
||||
)
|
||||
return current_full_puz, singleton_record.inner_puzzle_hash, singleton_record.root
|
||||
|
||||
###########
|
||||
# UTILITY #
|
||||
###########
|
||||
|
||||
@@ -180,7 +180,7 @@ class PoolWallet:
|
||||
raise ValueError(f"Invalid internal Pool State: {err}: {initial_target_state}")
|
||||
|
||||
async def get_spend_history(self) -> List[Tuple[uint32, CoinSpend]]:
|
||||
return self.wallet_state_manager.pool_store.get_spends_for_wallet(self.wallet_id)
|
||||
return await self.wallet_state_manager.pool_store.get_spends_for_wallet(self.wallet_id)
|
||||
|
||||
async def get_current_state(self) -> PoolWalletInfo:
|
||||
history: List[Tuple[uint32, CoinSpend]] = await self.get_spend_history()
|
||||
@@ -228,7 +228,7 @@ class PoolWallet:
|
||||
return await self.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(self.wallet_id)
|
||||
|
||||
async def get_tip(self) -> Tuple[uint32, CoinSpend]:
|
||||
return self.wallet_state_manager.pool_store.get_spends_for_wallet(self.wallet_id)[-1]
|
||||
return (await self.wallet_state_manager.pool_store.get_spends_for_wallet(self.wallet_id))[-1]
|
||||
|
||||
async def update_pool_config(self) -> None:
|
||||
current_state: PoolWalletInfo = await self.get_current_state()
|
||||
@@ -286,7 +286,9 @@ class PoolWallet:
|
||||
self.log.info(f"New PoolWallet singleton tip_coin: {tip_spend} farmed at height {block_height}")
|
||||
|
||||
# If we have reached the target state, resets it to None. Loops back to get current state
|
||||
for _, added_spend in reversed(self.wallet_state_manager.pool_store.get_spends_for_wallet(self.wallet_id)):
|
||||
for _, added_spend in reversed(
|
||||
await self.wallet_state_manager.pool_store.get_spends_for_wallet(self.wallet_id)
|
||||
):
|
||||
latest_state: Optional[PoolState] = solution_to_pool_state(added_spend)
|
||||
if latest_state is not None:
|
||||
if self.target_state == latest_state:
|
||||
@@ -303,9 +305,9 @@ class PoolWallet:
|
||||
Returns True if the wallet should be removed.
|
||||
"""
|
||||
try:
|
||||
history: List[Tuple[uint32, CoinSpend]] = self.wallet_state_manager.pool_store.get_spends_for_wallet(
|
||||
history: List[Tuple[uint32, CoinSpend]] = await self.wallet_state_manager.pool_store.get_spends_for_wallet(
|
||||
self.wallet_id
|
||||
).copy()
|
||||
)
|
||||
prev_state: PoolWalletInfo = await self.get_current_state()
|
||||
await self.wallet_state_manager.pool_store.rollback(block_height, self.wallet_id, in_transaction)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ipaddress
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
from chia.seeder.crawler import Crawler
|
||||
from chia.util.ws_message import WsRpcMessage, create_payload_dict
|
||||
|
||||
@@ -28,7 +28,7 @@ class CrawlerRpcApi:
|
||||
|
||||
return payloads
|
||||
|
||||
async def get_peer_counts(self, _request: Dict) -> Dict[str, Any]:
|
||||
async def get_peer_counts(self, _request: Dict) -> EndpointResult:
|
||||
ipv6_addresses_count = 0
|
||||
for host in self.service.best_timestamp_per_peer.keys():
|
||||
try:
|
||||
@@ -52,7 +52,7 @@ class CrawlerRpcApi:
|
||||
}
|
||||
return data
|
||||
|
||||
async def get_ips_after_timestamp(self, _request: Dict) -> Dict[str, Any]:
|
||||
async def get_ips_after_timestamp(self, _request: Dict) -> EndpointResult:
|
||||
after = _request.get("after", None)
|
||||
if after is None:
|
||||
raise ValueError("`after` is required and must be a unix timestamp")
|
||||
|
||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
|
||||
from chia.data_layer.data_layer import DataLayer
|
||||
from chia.data_layer.data_layer_util import Side, Subscription
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
@@ -74,20 +74,20 @@ class DataLayerRpcApi:
|
||||
"/add_missing_files": self.add_missing_files,
|
||||
}
|
||||
|
||||
async def create_data_store(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def create_data_store(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
if self.service is None:
|
||||
raise Exception("Data layer not created")
|
||||
fee = get_fee(self.service.config, request)
|
||||
txs, value = await self.service.create_store(uint64(fee))
|
||||
return {"txs": txs, "id": value.hex()}
|
||||
|
||||
async def get_owned_stores(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_owned_stores(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
if self.service is None:
|
||||
raise Exception("Data layer not created")
|
||||
singleton_records = await self.service.get_owned_stores()
|
||||
return {"store_ids": [singleton.launcher_id.hex() for singleton in singleton_records]}
|
||||
|
||||
async def get_value(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_value(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
store_id = bytes32.from_hexstr(request["id"])
|
||||
key = hexstr_to_bytes(request["key"])
|
||||
if self.service is None:
|
||||
@@ -98,14 +98,14 @@ class DataLayerRpcApi:
|
||||
hex = value.hex()
|
||||
return {"value": hex}
|
||||
|
||||
async def get_keys(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_keys(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
store_id = bytes32(hexstr_to_bytes(request["id"]))
|
||||
if self.service is None:
|
||||
raise Exception("Data layer not created")
|
||||
keys = await self.service.get_keys(store_id)
|
||||
return {"keys": keys}
|
||||
|
||||
async def get_keys_values(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_keys_values(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
store_id = bytes32(hexstr_to_bytes(request["id"]))
|
||||
root_hash = request.get("root_hash")
|
||||
if root_hash is not None:
|
||||
@@ -119,7 +119,7 @@ class DataLayerRpcApi:
|
||||
json_nodes.append(json)
|
||||
return {"keys_values": json_nodes}
|
||||
|
||||
async def get_ancestors(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_ancestors(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
store_id = bytes32(hexstr_to_bytes(request["id"]))
|
||||
node_hash = bytes32.from_hexstr(request["hash"])
|
||||
if self.service is None:
|
||||
@@ -127,7 +127,7 @@ class DataLayerRpcApi:
|
||||
value = await self.service.get_ancestors(node_hash, store_id)
|
||||
return {"ancestors": value}
|
||||
|
||||
async def batch_update(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def batch_update(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
id - the id of the store we are operating on
|
||||
changelist - a list of changes to apply on store
|
||||
@@ -143,7 +143,7 @@ class DataLayerRpcApi:
|
||||
raise Exception(f"Batch update failed for: {store_id}")
|
||||
return {"tx_id": transaction_record.name}
|
||||
|
||||
async def insert(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def insert(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
rows_to_add a list of clvm objects as bytes to add to talbe
|
||||
rows_to_remove a list of row hashes to remove
|
||||
@@ -159,7 +159,7 @@ class DataLayerRpcApi:
|
||||
transaction_record = await self.service.batch_update(store_id, changelist, uint64(fee))
|
||||
return {"tx_id": transaction_record.name}
|
||||
|
||||
async def delete_key(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def delete_key(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
rows_to_add a list of clvm objects as bytes to add to talbe
|
||||
rows_to_remove a list of row hashes to remove
|
||||
@@ -174,7 +174,7 @@ class DataLayerRpcApi:
|
||||
transaction_record = await self.service.batch_update(store_id, changelist, uint64(fee))
|
||||
return {"tx_id": transaction_record.name}
|
||||
|
||||
async def get_root(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_root(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""get hash of latest tree root"""
|
||||
store_id = bytes32(hexstr_to_bytes(request["id"]))
|
||||
# todo input checks
|
||||
@@ -185,7 +185,7 @@ class DataLayerRpcApi:
|
||||
raise Exception(f"Failed to get root for {store_id.hex()}")
|
||||
return {"hash": rec.root, "confirmed": rec.confirmed, "timestamp": rec.timestamp}
|
||||
|
||||
async def get_local_root(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_local_root(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""get hash of latest tree root saved in our local datastore"""
|
||||
store_id = bytes32(hexstr_to_bytes(request["id"]))
|
||||
# todo input checks
|
||||
@@ -194,7 +194,7 @@ class DataLayerRpcApi:
|
||||
res = await self.service.get_local_root(store_id)
|
||||
return {"hash": res}
|
||||
|
||||
async def get_roots(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_roots(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
get state hashes for a list of roots
|
||||
"""
|
||||
@@ -210,7 +210,7 @@ class DataLayerRpcApi:
|
||||
roots.append({"id": id_bytes, "hash": rec.root, "confirmed": rec.confirmed, "timestamp": rec.timestamp})
|
||||
return {"root_hashes": roots}
|
||||
|
||||
async def subscribe(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def subscribe(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
subscribe to singleton
|
||||
"""
|
||||
@@ -225,7 +225,7 @@ class DataLayerRpcApi:
|
||||
await self.service.subscribe(store_id=store_id_bytes, urls=urls)
|
||||
return {}
|
||||
|
||||
async def unsubscribe(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def unsubscribe(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
unsubscribe from singleton
|
||||
"""
|
||||
@@ -238,7 +238,7 @@ class DataLayerRpcApi:
|
||||
await self.service.unsubscribe(store_id_bytes)
|
||||
return {}
|
||||
|
||||
async def subscriptions(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def subscriptions(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
List current subscriptions
|
||||
"""
|
||||
@@ -247,7 +247,7 @@ class DataLayerRpcApi:
|
||||
subscriptions: List[Subscription] = await self.service.get_subscriptions()
|
||||
return {"store_ids": [sub.tree_id.hex() for sub in subscriptions]}
|
||||
|
||||
async def add_missing_files(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def add_missing_files(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
complete the data server files.
|
||||
"""
|
||||
@@ -265,7 +265,7 @@ class DataLayerRpcApi:
|
||||
await self.service.add_missing_files(tree_id, override, foldername)
|
||||
return {}
|
||||
|
||||
async def get_root_history(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_root_history(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
get history of state hashes for a store
|
||||
"""
|
||||
@@ -279,7 +279,7 @@ class DataLayerRpcApi:
|
||||
res.insert(0, {"root_hash": rec.root, "confirmed": rec.confirmed, "timestamp": rec.timestamp})
|
||||
return {"root_history": res}
|
||||
|
||||
async def get_kv_diff(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def get_kv_diff(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
get kv diff between two root hashes
|
||||
"""
|
||||
|
||||
+14
-14
@@ -7,7 +7,7 @@ from typing_extensions import Protocol
|
||||
from chia.farmer.farmer import Farmer
|
||||
from chia.plot_sync.receiver import Receiver
|
||||
from chia.protocols.harvester_protocol import Plot
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.ints import uint32
|
||||
@@ -161,7 +161,7 @@ class FarmerRpcApi:
|
||||
|
||||
return payloads
|
||||
|
||||
async def get_signage_point(self, request: Dict) -> Dict:
|
||||
async def get_signage_point(self, request: Dict) -> EndpointResult:
|
||||
sp_hash = hexstr_to_bytes(request["sp_hash"])
|
||||
for _, sps in self.service.sps.items():
|
||||
for sp in sps:
|
||||
@@ -180,7 +180,7 @@ class FarmerRpcApi:
|
||||
}
|
||||
raise ValueError(f"Signage point {sp_hash.hex()} not found")
|
||||
|
||||
async def get_signage_points(self, _: Dict) -> Dict[str, Any]:
|
||||
async def get_signage_points(self, _: Dict) -> EndpointResult:
|
||||
result: List[Dict[str, Any]] = []
|
||||
for sps in self.service.sps.values():
|
||||
for sp in sps:
|
||||
@@ -200,12 +200,12 @@ class FarmerRpcApi:
|
||||
)
|
||||
return {"signage_points": result}
|
||||
|
||||
async def get_reward_targets(self, request: Dict) -> Dict:
|
||||
async def get_reward_targets(self, request: Dict) -> EndpointResult:
|
||||
search_for_private_key = request["search_for_private_key"]
|
||||
max_ph_to_search = request.get("max_ph_to_search", 500)
|
||||
return await self.service.get_reward_targets(search_for_private_key, max_ph_to_search)
|
||||
|
||||
async def set_reward_targets(self, request: Dict) -> Dict:
|
||||
async def set_reward_targets(self, request: Dict) -> EndpointResult:
|
||||
farmer_target, pool_target = None, None
|
||||
if "farmer_target" in request:
|
||||
farmer_target = request["farmer_target"]
|
||||
@@ -223,7 +223,7 @@ class FarmerRpcApi:
|
||||
)
|
||||
return plot_count
|
||||
|
||||
async def get_pool_state(self, _: Dict) -> Dict:
|
||||
async def get_pool_state(self, _: Dict) -> EndpointResult:
|
||||
pools_list = []
|
||||
for p2_singleton_puzzle_hash, pool_dict in self.service.pool_state.items():
|
||||
pool_state = pool_dict.copy()
|
||||
@@ -232,18 +232,18 @@ class FarmerRpcApi:
|
||||
pools_list.append(pool_state)
|
||||
return {"pool_state": pools_list}
|
||||
|
||||
async def set_payout_instructions(self, request: Dict) -> Dict:
|
||||
async def set_payout_instructions(self, request: Dict) -> EndpointResult:
|
||||
launcher_id: bytes32 = bytes32.from_hexstr(request["launcher_id"])
|
||||
await self.service.set_payout_instructions(launcher_id, request["payout_instructions"])
|
||||
return {}
|
||||
|
||||
async def get_harvesters(self, _: Dict):
|
||||
async def get_harvesters(self, _: Dict) -> EndpointResult:
|
||||
return await self.service.get_harvesters(False)
|
||||
|
||||
async def get_harvesters_summary(self, _: Dict[str, object]) -> Dict[str, object]:
|
||||
async def get_harvesters_summary(self, _: Dict[str, object]) -> EndpointResult:
|
||||
return await self.service.get_harvesters(True)
|
||||
|
||||
async def get_harvester_plots_valid(self, request_dict: Dict[str, object]) -> Dict[str, object]:
|
||||
async def get_harvester_plots_valid(self, request_dict: Dict[str, object]) -> EndpointResult:
|
||||
# TODO: Consider having a extra List[PlotInfo] in Receiver to avoid rebuilding the list for each call
|
||||
request = PlotInfoRequestData.from_json_dict(request_dict)
|
||||
plot_list = list(self.service.get_receiver(request.node_id).plots().values())
|
||||
@@ -271,16 +271,16 @@ class FarmerRpcApi:
|
||||
source = sorted(source, reverse=request.reverse)
|
||||
return paginated_plot_request(source, request)
|
||||
|
||||
async def get_harvester_plots_invalid(self, request_dict: Dict[str, object]) -> Dict[str, object]:
|
||||
async def get_harvester_plots_invalid(self, request_dict: Dict[str, object]) -> EndpointResult:
|
||||
return self.paginated_plot_path_request(Receiver.invalid, request_dict)
|
||||
|
||||
async def get_harvester_plots_keys_missing(self, request_dict: Dict[str, object]) -> Dict[str, object]:
|
||||
async def get_harvester_plots_keys_missing(self, request_dict: Dict[str, object]) -> EndpointResult:
|
||||
return self.paginated_plot_path_request(Receiver.keys_missing, request_dict)
|
||||
|
||||
async def get_harvester_plots_duplicates(self, request_dict: Dict[str, object]) -> Dict[str, object]:
|
||||
async def get_harvester_plots_duplicates(self, request_dict: Dict[str, object]) -> EndpointResult:
|
||||
return self.paginated_plot_path_request(Receiver.duplicates, request_dict)
|
||||
|
||||
async def get_pool_login_link(self, request: Dict) -> Dict:
|
||||
async def get_pool_login_link(self, request: Dict) -> EndpointResult:
|
||||
launcher_id: bytes32 = bytes32(hexstr_to_bytes(request["launcher_id"]))
|
||||
login_link: Optional[str] = await self.service.generate_login_link(launcher_id)
|
||||
if login_link is None:
|
||||
|
||||
@@ -4,7 +4,7 @@ from chia.consensus.block_record import BlockRecord
|
||||
from chia.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR
|
||||
from chia.full_node.full_node import FullNode
|
||||
from chia.full_node.mempool_check_conditions import get_puzzle_and_solution_for_coin
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
from chia.types.blockchain_format.program import Program, SerializedProgram
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_record import CoinRecord
|
||||
@@ -96,11 +96,11 @@ class FullNodeRpcApi:
|
||||
|
||||
# this function is just here for backwards-compatibility. It will probably
|
||||
# be removed in the future
|
||||
async def get_initial_freeze_period(self, _: Dict):
|
||||
async def get_initial_freeze_period(self, _: Dict) -> EndpointResult:
|
||||
# Mon May 03 2021 17:00:00 GMT+0000
|
||||
return {"INITIAL_FREEZE_END_TIMESTAMP": 1620061200}
|
||||
|
||||
async def get_blockchain_state(self, _request: Dict):
|
||||
async def get_blockchain_state(self, _request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Returns a summary of the node's view of the blockchain.
|
||||
"""
|
||||
@@ -214,12 +214,12 @@ class FullNodeRpcApi:
|
||||
self.cached_blockchain_state = dict(response["blockchain_state"])
|
||||
return response
|
||||
|
||||
async def get_network_info(self, request: Dict):
|
||||
async def get_network_info(self, request: Dict) -> EndpointResult:
|
||||
network_name = self.service.config["selected_network"]
|
||||
address_prefix = self.service.config["network_overrides"]["config"][network_name]["address_prefix"]
|
||||
return {"network_name": network_name, "network_prefix": address_prefix}
|
||||
|
||||
async def get_recent_signage_point_or_eos(self, request: Dict):
|
||||
async def get_recent_signage_point_or_eos(self, request: Dict) -> EndpointResult:
|
||||
if "sp_hash" not in request:
|
||||
challenge_hash: bytes32 = bytes32.from_hexstr(request["challenge_hash"])
|
||||
# This is the case of getting an end of slot
|
||||
@@ -308,7 +308,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"signage_point": sp, "time_received": time_received, "reverted": True}
|
||||
|
||||
async def get_block(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_block(self, request: Dict) -> EndpointResult:
|
||||
if "header_hash" not in request:
|
||||
raise ValueError("No header_hash in request")
|
||||
header_hash = bytes32.from_hexstr(request["header_hash"])
|
||||
@@ -319,7 +319,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"block": block}
|
||||
|
||||
async def get_blocks(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_blocks(self, request: Dict) -> EndpointResult:
|
||||
if "start" not in request:
|
||||
raise ValueError("No start in request")
|
||||
if "end" not in request:
|
||||
@@ -349,7 +349,7 @@ class FullNodeRpcApi:
|
||||
json_blocks.append(json)
|
||||
return {"blocks": json_blocks}
|
||||
|
||||
async def get_block_count_metrics(self, request: Dict):
|
||||
async def get_block_count_metrics(self, request: Dict) -> EndpointResult:
|
||||
compact_blocks = 0
|
||||
uncompact_blocks = 0
|
||||
with log_exceptions(self.service.log, consume=True):
|
||||
@@ -369,7 +369,7 @@ class FullNodeRpcApi:
|
||||
}
|
||||
}
|
||||
|
||||
async def get_block_records(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_block_records(self, request: Dict) -> EndpointResult:
|
||||
if "start" not in request:
|
||||
raise ValueError("No start in request")
|
||||
if "end" not in request:
|
||||
@@ -399,7 +399,7 @@ class FullNodeRpcApi:
|
||||
records.append(record)
|
||||
return {"block_records": records}
|
||||
|
||||
async def get_block_record_by_height(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_block_record_by_height(self, request: Dict) -> EndpointResult:
|
||||
if "height" not in request:
|
||||
raise ValueError("No height in request")
|
||||
height = request["height"]
|
||||
@@ -418,7 +418,7 @@ class FullNodeRpcApi:
|
||||
raise ValueError(f"Block {header_hash} does not exist")
|
||||
return {"block_record": record}
|
||||
|
||||
async def get_block_record(self, request: Dict):
|
||||
async def get_block_record(self, request: Dict) -> EndpointResult:
|
||||
if "header_hash" not in request:
|
||||
raise ValueError("header_hash not in request")
|
||||
header_hash_str = request["header_hash"]
|
||||
@@ -432,7 +432,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"block_record": record}
|
||||
|
||||
async def get_unfinished_block_headers(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_unfinished_block_headers(self, request: Dict) -> EndpointResult:
|
||||
|
||||
peak: Optional[BlockRecord] = self.service.blockchain.get_peak()
|
||||
if peak is None:
|
||||
@@ -453,7 +453,7 @@ class FullNodeRpcApi:
|
||||
response_headers.append(unfinished_header_block)
|
||||
return {"headers": response_headers}
|
||||
|
||||
async def get_network_space(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_network_space(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves an estimate of total space validating the chain
|
||||
between two block header hashes.
|
||||
@@ -493,7 +493,7 @@ class FullNodeRpcApi:
|
||||
)
|
||||
return {"space": uint128(int(network_space_bytes_estimate))}
|
||||
|
||||
async def get_coin_records_by_puzzle_hash(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_coin_records_by_puzzle_hash(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves the coins for a given puzzlehash, by default returns unspent coins.
|
||||
"""
|
||||
@@ -512,7 +512,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"coin_records": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in coin_records]}
|
||||
|
||||
async def get_coin_records_by_puzzle_hashes(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_coin_records_by_puzzle_hashes(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves the coins for a given puzzlehash, by default returns unspent coins.
|
||||
"""
|
||||
@@ -534,7 +534,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"coin_records": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in coin_records]}
|
||||
|
||||
async def get_coin_record_by_name(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_coin_record_by_name(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves a coin record by it's name.
|
||||
"""
|
||||
@@ -548,7 +548,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"coin_record": coin_record_dict_backwards_compat(coin_record.to_json_dict())}
|
||||
|
||||
async def get_coin_records_by_names(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_coin_records_by_names(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves the coins for given coin IDs, by default returns unspent coins.
|
||||
"""
|
||||
@@ -570,7 +570,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"coin_records": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in coin_records]}
|
||||
|
||||
async def get_coin_records_by_parent_ids(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_coin_records_by_parent_ids(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves the coins for given parent coin IDs, by default returns unspent coins.
|
||||
"""
|
||||
@@ -592,7 +592,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"coin_records": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in coin_records]}
|
||||
|
||||
async def get_coin_records_by_hint(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_coin_records_by_hint(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Retrieves coins by hint, by default returns unspent coins.
|
||||
"""
|
||||
@@ -621,7 +621,7 @@ class FullNodeRpcApi:
|
||||
|
||||
return {"coin_records": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in coin_records]}
|
||||
|
||||
async def push_tx(self, request: Dict) -> Dict[str, object]:
|
||||
async def push_tx(self, request: Dict) -> EndpointResult:
|
||||
if "spend_bundle" not in request:
|
||||
raise ValueError("Spend bundle not in request")
|
||||
|
||||
@@ -646,7 +646,7 @@ class FullNodeRpcApi:
|
||||
"status": status.name,
|
||||
}
|
||||
|
||||
async def get_puzzle_and_solution(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_puzzle_and_solution(self, request: Dict) -> EndpointResult:
|
||||
coin_name: bytes32 = bytes32.from_hexstr(request["coin_id"])
|
||||
height = request["height"]
|
||||
coin_record = await self.service.coin_store.get_coin_record(coin_name)
|
||||
@@ -672,7 +672,7 @@ class FullNodeRpcApi:
|
||||
solution_ser: SerializedProgram = SerializedProgram.from_program(Program.to(solution))
|
||||
return {"coin_solution": CoinSpend(coin_record.coin, puzzle_ser, solution_ser)}
|
||||
|
||||
async def get_additions_and_removals(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_additions_and_removals(self, request: Dict) -> EndpointResult:
|
||||
if "header_hash" not in request:
|
||||
raise ValueError("No header_hash in request")
|
||||
header_hash = bytes32.from_hexstr(request["header_hash"])
|
||||
@@ -692,17 +692,17 @@ class FullNodeRpcApi:
|
||||
"removals": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in removals],
|
||||
}
|
||||
|
||||
async def get_all_mempool_tx_ids(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_all_mempool_tx_ids(self, request: Dict) -> EndpointResult:
|
||||
ids = list(self.service.mempool_manager.mempool.spends.keys())
|
||||
return {"tx_ids": ids}
|
||||
|
||||
async def get_all_mempool_items(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_all_mempool_items(self, request: Dict) -> EndpointResult:
|
||||
spends = {}
|
||||
for tx_id, item in self.service.mempool_manager.mempool.spends.items():
|
||||
spends[tx_id.hex()] = item
|
||||
return {"mempool_items": spends}
|
||||
|
||||
async def get_mempool_item_by_tx_id(self, request: Dict) -> Dict[str, object]:
|
||||
async def get_mempool_item_by_tx_id(self, request: Dict) -> EndpointResult:
|
||||
if "tx_id" not in request:
|
||||
raise ValueError("No tx_id in request")
|
||||
tx_id: bytes32 = bytes32.from_hexstr(request["tx_id"])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from chia.harvester.harvester import Harvester
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
from chia.util.ws_message import WsRpcMessage, create_payload_dict
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class HarvesterRpcApi:
|
||||
|
||||
return payloads
|
||||
|
||||
async def get_plots(self, request: Dict) -> Dict:
|
||||
async def get_plots(self, request: Dict) -> EndpointResult:
|
||||
plots, failed_to_open, not_found = self.service.get_plots()
|
||||
return {
|
||||
"plots": plots,
|
||||
@@ -44,27 +44,27 @@ class HarvesterRpcApi:
|
||||
"not_found_filenames": not_found,
|
||||
}
|
||||
|
||||
async def refresh_plots(self, request: Dict) -> Dict:
|
||||
async def refresh_plots(self, request: Dict) -> EndpointResult:
|
||||
self.service.plot_manager.trigger_refresh()
|
||||
return {}
|
||||
|
||||
async def delete_plot(self, request: Dict) -> Dict:
|
||||
async def delete_plot(self, request: Dict) -> EndpointResult:
|
||||
filename = request["filename"]
|
||||
if self.service.delete_plot(filename):
|
||||
return {}
|
||||
raise ValueError(f"Not able to delete file {filename}")
|
||||
|
||||
async def add_plot_directory(self, request: Dict) -> Dict:
|
||||
async def add_plot_directory(self, request: Dict) -> EndpointResult:
|
||||
directory_name = request["dirname"]
|
||||
if await self.service.add_plot_directory(directory_name):
|
||||
return {}
|
||||
raise ValueError(f"Did not add plot directory {directory_name}")
|
||||
|
||||
async def get_plot_directories(self, request: Dict) -> Dict:
|
||||
async def get_plot_directories(self, request: Dict) -> EndpointResult:
|
||||
plot_dirs = await self.service.get_plot_directories()
|
||||
return {"directories": plot_dirs}
|
||||
|
||||
async def remove_plot_directory(self, request: Dict) -> Dict:
|
||||
async def remove_plot_directory(self, request: Dict) -> EndpointResult:
|
||||
directory_name = request["dirname"]
|
||||
if await self.service.remove_plot_directory(directory_name):
|
||||
return {}
|
||||
|
||||
+10
-9
@@ -26,7 +26,8 @@ log = logging.getLogger(__name__)
|
||||
max_message_size = 50 * 1024 * 1024 # 50MB
|
||||
|
||||
|
||||
Endpoint = Callable[[Dict[str, object]], Awaitable[Dict[str, object]]]
|
||||
EndpointResult = Dict[str, Any]
|
||||
Endpoint = Callable[[Dict[str, object]], Awaitable[EndpointResult]]
|
||||
|
||||
|
||||
class RpcApiProtocol(Protocol):
|
||||
@@ -112,13 +113,13 @@ class RpcServer:
|
||||
"/healthz": self.healthz,
|
||||
}
|
||||
|
||||
async def _get_routes(self, request: Dict[str, Any]) -> Dict[str, object]:
|
||||
async def _get_routes(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
return {
|
||||
"success": "true",
|
||||
"routes": list(self.get_routes().keys()),
|
||||
}
|
||||
|
||||
async def get_connections(self, request: Dict[str, Any]) -> Dict[str, object]:
|
||||
async def get_connections(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
request_node_type: Optional[NodeType] = None
|
||||
if "node_type" in request:
|
||||
request_node_type = NodeType(request["node_type"])
|
||||
@@ -174,7 +175,7 @@ class RpcServer:
|
||||
]
|
||||
return {"connections": con_info}
|
||||
|
||||
async def open_connection(self, request: Dict[str, Any]) -> Dict[str, object]:
|
||||
async def open_connection(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
host = request["host"]
|
||||
port = request["port"]
|
||||
target_node: PeerInfo = PeerInfo(host, uint16(int(port)))
|
||||
@@ -187,7 +188,7 @@ class RpcServer:
|
||||
raise ValueError("Start client failed, or server is not set")
|
||||
return {}
|
||||
|
||||
async def close_connection(self, request: Dict[str, Any]) -> Dict[str, object]:
|
||||
async def close_connection(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
node_id = hexstr_to_bytes(request["node_id"])
|
||||
if self.rpc_api.service.server is None:
|
||||
raise web.HTTPInternalServerError()
|
||||
@@ -198,7 +199,7 @@ class RpcServer:
|
||||
await connection.close()
|
||||
return {}
|
||||
|
||||
async def stop_node(self, request: Dict[str, Any]) -> Dict[str, object]:
|
||||
async def stop_node(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
"""
|
||||
Shuts down the node.
|
||||
"""
|
||||
@@ -206,19 +207,19 @@ class RpcServer:
|
||||
self.stop_cb()
|
||||
return {}
|
||||
|
||||
async def healthz(self, request: Dict[str, Any]) -> Dict[str, object]:
|
||||
async def healthz(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
return {
|
||||
"success": "true",
|
||||
}
|
||||
|
||||
async def ws_api(self, message: WsRpcMessage) -> Dict[str, object]:
|
||||
async def ws_api(self, message: WsRpcMessage) -> Optional[Dict[str, object]]:
|
||||
"""
|
||||
This function gets called when new message is received via websocket.
|
||||
"""
|
||||
|
||||
command = message["command"]
|
||||
if message["ack"]:
|
||||
return {}
|
||||
return None
|
||||
|
||||
data: Dict[str, object] = {}
|
||||
if "data" in message:
|
||||
|
||||
+109
-114
@@ -3,7 +3,7 @@ import dataclasses
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from blspy import G1Element, G2Element, PrivateKey
|
||||
|
||||
@@ -13,7 +13,7 @@ from chia.pools.pool_wallet import PoolWallet
|
||||
from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
|
||||
from chia.protocols.protocol_message_types import ProtocolMessageTypes
|
||||
from chia.protocols.wallet_protocol import CoinState
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
from chia.server.outbound_message import NodeType, make_msg
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.types.announcement import Announcement
|
||||
@@ -206,7 +206,7 @@ class WalletRpcApi:
|
||||
# Key management
|
||||
##########################################################################################
|
||||
|
||||
async def log_in(self, request):
|
||||
async def log_in(self, request) -> EndpointResult:
|
||||
"""
|
||||
Logs in the wallet with a specific key.
|
||||
"""
|
||||
@@ -223,10 +223,10 @@ class WalletRpcApi:
|
||||
|
||||
return {"success": False, "error": "Unknown Error"}
|
||||
|
||||
async def get_logged_in_fingerprint(self, request: Dict):
|
||||
async def get_logged_in_fingerprint(self, request: Dict) -> EndpointResult:
|
||||
return {"fingerprint": self.service.logged_in_fingerprint}
|
||||
|
||||
async def get_public_keys(self, request: Dict):
|
||||
async def get_public_keys(self, request: Dict) -> EndpointResult:
|
||||
try:
|
||||
fingerprints = [
|
||||
sk.get_g1().get_fingerprint() for (sk, seed) in await self.service.keychain_proxy.get_all_private_keys()
|
||||
@@ -248,7 +248,7 @@ class WalletRpcApi:
|
||||
log.error(f"Failed to get private key by fingerprint: {e}")
|
||||
return None, None
|
||||
|
||||
async def get_private_key(self, request):
|
||||
async def get_private_key(self, request) -> EndpointResult:
|
||||
fingerprint = request["fingerprint"]
|
||||
sk, seed = await self._get_private_key(fingerprint)
|
||||
if sk is not None:
|
||||
@@ -265,10 +265,10 @@ class WalletRpcApi:
|
||||
}
|
||||
return {"success": False, "private_key": {"fingerprint": fingerprint}}
|
||||
|
||||
async def generate_mnemonic(self, request: Dict):
|
||||
async def generate_mnemonic(self, request: Dict) -> EndpointResult:
|
||||
return {"mnemonic": generate_mnemonic().split(" ")}
|
||||
|
||||
async def add_key(self, request):
|
||||
async def add_key(self, request) -> EndpointResult:
|
||||
if "mnemonic" not in request:
|
||||
raise ValueError("Mnemonic not in request")
|
||||
|
||||
@@ -300,7 +300,7 @@ class WalletRpcApi:
|
||||
return {"fingerprint": fingerprint}
|
||||
raise ValueError("Failed to start")
|
||||
|
||||
async def delete_key(self, request):
|
||||
async def delete_key(self, request) -> EndpointResult:
|
||||
await self._stop_wallet()
|
||||
fingerprint = request["fingerprint"]
|
||||
try:
|
||||
@@ -340,7 +340,7 @@ class WalletRpcApi:
|
||||
|
||||
return found_farmer, found_pool
|
||||
|
||||
async def check_delete_key(self, request):
|
||||
async def check_delete_key(self, request) -> EndpointResult:
|
||||
"""Check the key use prior to possible deletion
|
||||
checks whether key is used for either farm or pool rewards
|
||||
checks if any wallets have a non-zero balance
|
||||
@@ -379,7 +379,7 @@ class WalletRpcApi:
|
||||
"wallet_balance": walletBalance,
|
||||
}
|
||||
|
||||
async def delete_all_keys(self, request: Dict):
|
||||
async def delete_all_keys(self, request: Dict) -> EndpointResult:
|
||||
await self._stop_wallet()
|
||||
try:
|
||||
await self.service.keychain_proxy.delete_all_keys()
|
||||
@@ -395,28 +395,28 @@ class WalletRpcApi:
|
||||
# Wallet Node
|
||||
##########################################################################################
|
||||
|
||||
async def get_sync_status(self, request: Dict):
|
||||
async def get_sync_status(self, request: Dict) -> EndpointResult:
|
||||
syncing = self.service.wallet_state_manager.sync_mode
|
||||
synced = await self.service.wallet_state_manager.synced()
|
||||
return {"synced": synced, "syncing": syncing, "genesis_initialized": True}
|
||||
|
||||
async def get_height_info(self, request: Dict):
|
||||
async def get_height_info(self, request: Dict) -> EndpointResult:
|
||||
height = await self.service.wallet_state_manager.blockchain.get_finished_sync_up_to()
|
||||
return {"height": height}
|
||||
|
||||
async def get_network_info(self, request: Dict):
|
||||
async def get_network_info(self, request: Dict) -> EndpointResult:
|
||||
network_name = self.service.config["selected_network"]
|
||||
address_prefix = self.service.config["network_overrides"]["config"][network_name]["address_prefix"]
|
||||
return {"network_name": network_name, "network_prefix": address_prefix}
|
||||
|
||||
async def push_tx(self, request: Dict):
|
||||
async def push_tx(self, request: Dict) -> EndpointResult:
|
||||
nodes = self.service.server.get_full_node_connections()
|
||||
if len(nodes) == 0:
|
||||
raise ValueError("Wallet is not currently connected to any full node peers")
|
||||
await self.service.push_tx(SpendBundle.from_bytes(hexstr_to_bytes(request["spend_bundle"])))
|
||||
return {}
|
||||
|
||||
async def farm_block(self, request):
|
||||
async def farm_block(self, request) -> EndpointResult:
|
||||
raw_puzzle_hash = decode_puzzle_hash(request["address"])
|
||||
request = FarmNewBlockProtocol(raw_puzzle_hash)
|
||||
msg = make_msg(ProtocolMessageTypes.farm_new_block, request)
|
||||
@@ -428,7 +428,7 @@ class WalletRpcApi:
|
||||
# Wallet Management
|
||||
##########################################################################################
|
||||
|
||||
async def get_wallets(self, request: Dict):
|
||||
async def get_wallets(self, request: Dict) -> EndpointResult:
|
||||
include_data: bool = request.get("include_data", True)
|
||||
wallet_type: Optional[WalletType] = None
|
||||
if "type" in request:
|
||||
@@ -442,7 +442,7 @@ class WalletRpcApi:
|
||||
wallets = result
|
||||
return {"wallets": wallets}
|
||||
|
||||
async def create_new_wallet(self, request: Dict):
|
||||
async def create_new_wallet(self, request: Dict) -> EndpointResult:
|
||||
wallet_state_manager = self.service.wallet_state_manager
|
||||
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
@@ -652,13 +652,14 @@ class WalletRpcApi:
|
||||
else: # undefined wallet_type
|
||||
pass
|
||||
|
||||
return None
|
||||
# TODO: rework this function to report detailed errors for each error case
|
||||
return {"success": False, "error": "invalid request"}
|
||||
|
||||
##########################################################################################
|
||||
# Wallet
|
||||
##########################################################################################
|
||||
|
||||
async def get_wallet_balance(self, request: Dict) -> Dict:
|
||||
async def get_wallet_balance(self, request: Dict) -> EndpointResult:
|
||||
wallet_id = uint32(int(request["wallet_id"]))
|
||||
wallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
|
||||
@@ -710,7 +711,7 @@ class WalletRpcApi:
|
||||
|
||||
return {"wallet_balance": wallet_balance}
|
||||
|
||||
async def get_transaction(self, request: Dict) -> Dict:
|
||||
async def get_transaction(self, request: Dict) -> EndpointResult:
|
||||
transaction_id: bytes32 = bytes32(hexstr_to_bytes(request["transaction_id"]))
|
||||
tr: Optional[TransactionRecord] = await self.service.wallet_state_manager.get_transaction(transaction_id)
|
||||
if tr is None:
|
||||
@@ -721,7 +722,7 @@ class WalletRpcApi:
|
||||
"transaction_id": tr.name,
|
||||
}
|
||||
|
||||
async def get_transactions(self, request: Dict) -> Dict:
|
||||
async def get_transactions(self, request: Dict) -> EndpointResult:
|
||||
wallet_id = int(request["wallet_id"])
|
||||
|
||||
start = request.get("start", 0)
|
||||
@@ -745,7 +746,7 @@ class WalletRpcApi:
|
||||
"wallet_id": wallet_id,
|
||||
}
|
||||
|
||||
async def get_transaction_count(self, request: Dict) -> Dict:
|
||||
async def get_transaction_count(self, request: Dict) -> EndpointResult:
|
||||
wallet_id = int(request["wallet_id"])
|
||||
count = await self.service.wallet_state_manager.tx_store.get_transaction_count_for_wallet(wallet_id)
|
||||
return {
|
||||
@@ -755,11 +756,11 @@ class WalletRpcApi:
|
||||
|
||||
# this function is just here for backwards-compatibility. It will probably
|
||||
# be removed in the future
|
||||
async def get_initial_freeze_period(self, _: Dict):
|
||||
async def get_initial_freeze_period(self, _: Dict) -> EndpointResult:
|
||||
# Mon May 03 2021 17:00:00 GMT+0000
|
||||
return {"INITIAL_FREEZE_END_TIMESTAMP": 1620061200}
|
||||
|
||||
async def get_next_address(self, request: Dict) -> Dict:
|
||||
async def get_next_address(self, request: Dict) -> EndpointResult:
|
||||
"""
|
||||
Returns a new address
|
||||
"""
|
||||
@@ -785,11 +786,11 @@ class WalletRpcApi:
|
||||
"address": address,
|
||||
}
|
||||
|
||||
async def send_transaction(self, request):
|
||||
async def send_transaction(self, request) -> EndpointResult:
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
raise ValueError("Wallet needs to be fully synced before sending transactions")
|
||||
|
||||
wallet_id = int(request["wallet_id"])
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
|
||||
if wallet.type() == WalletType.CAT:
|
||||
@@ -823,7 +824,7 @@ class WalletRpcApi:
|
||||
"transaction_id": tx.name,
|
||||
}
|
||||
|
||||
async def send_transaction_multi(self, request) -> Dict:
|
||||
async def send_transaction_multi(self, request) -> EndpointResult:
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
raise ValueError("Wallet needs to be fully synced before sending transactions")
|
||||
|
||||
@@ -838,7 +839,7 @@ class WalletRpcApi:
|
||||
# Transaction may not have been included in the mempool yet. Use get_transaction to check.
|
||||
return {"transaction": transaction, "transaction_id": tr.name}
|
||||
|
||||
async def delete_unconfirmed_transactions(self, request):
|
||||
async def delete_unconfirmed_transactions(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
if wallet_id not in self.service.wallet_state_manager.wallets:
|
||||
raise ValueError(f"Wallet id {wallet_id} does not exist")
|
||||
@@ -852,11 +853,9 @@ class WalletRpcApi:
|
||||
if self.service.wallet_state_manager.wallets[wallet_id].type() == WalletType.POOLING_WALLET.value:
|
||||
self.service.wallet_state_manager.wallets[wallet_id].target_state = None
|
||||
await self.service.wallet_state_manager.tx_store.db_wrapper.commit_transaction()
|
||||
# Update the cache
|
||||
await self.service.wallet_state_manager.tx_store.rebuild_tx_cache()
|
||||
return {}
|
||||
|
||||
async def select_coins(self, request) -> Dict[str, object]:
|
||||
async def select_coins(self, request) -> EndpointResult:
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
raise ValueError("Wallet needs to be fully synced before selecting coins")
|
||||
|
||||
@@ -873,22 +872,22 @@ class WalletRpcApi:
|
||||
# CATs and Trading
|
||||
##########################################################################################
|
||||
|
||||
async def get_cat_list(self, request):
|
||||
async def get_cat_list(self, request) -> EndpointResult:
|
||||
return {"cat_list": list(DEFAULT_CATS.values())}
|
||||
|
||||
async def cat_set_name(self, request):
|
||||
wallet_id = int(request["wallet_id"])
|
||||
async def cat_set_name(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: CATWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
await wallet.set_name(str(request["name"]))
|
||||
return {"wallet_id": wallet_id}
|
||||
|
||||
async def cat_get_name(self, request):
|
||||
wallet_id = int(request["wallet_id"])
|
||||
async def cat_get_name(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: CATWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
name: str = await wallet.get_name()
|
||||
return {"wallet_id": wallet_id, "name": name}
|
||||
|
||||
async def get_stray_cats(self, request):
|
||||
async def get_stray_cats(self, request) -> EndpointResult:
|
||||
"""
|
||||
Get a list of all unacknowledged CATs
|
||||
:param request: RPC request
|
||||
@@ -897,10 +896,10 @@ class WalletRpcApi:
|
||||
cats = await self.service.wallet_state_manager.interested_store.get_unacknowledged_tokens()
|
||||
return {"stray_cats": cats}
|
||||
|
||||
async def cat_spend(self, request):
|
||||
async def cat_spend(self, request) -> EndpointResult:
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
raise ValueError("Wallet needs to be fully synced.")
|
||||
wallet_id = int(request["wallet_id"])
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: CATWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
|
||||
puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"])
|
||||
@@ -916,7 +915,7 @@ class WalletRpcApi:
|
||||
else:
|
||||
fee = uint64(0)
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
txs: TransactionRecord = await wallet.generate_signed_transaction(
|
||||
txs: List[TransactionRecord] = await wallet.generate_signed_transaction(
|
||||
[amount], [puzzle_hash], fee, memos=[memos]
|
||||
)
|
||||
for tx in txs:
|
||||
@@ -927,13 +926,13 @@ class WalletRpcApi:
|
||||
"transaction_id": tx.name,
|
||||
}
|
||||
|
||||
async def cat_get_asset_id(self, request):
|
||||
wallet_id = int(request["wallet_id"])
|
||||
async def cat_get_asset_id(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: CATWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
asset_id: str = wallet.get_asset_id()
|
||||
return {"asset_id": asset_id, "wallet_id": wallet_id}
|
||||
|
||||
async def cat_asset_id_to_name(self, request):
|
||||
async def cat_asset_id_to_name(self, request) -> EndpointResult:
|
||||
wallet = await self.service.wallet_state_manager.get_wallet_for_asset_id(request["asset_id"])
|
||||
if wallet is None:
|
||||
if request["asset_id"] in DEFAULT_CATS:
|
||||
@@ -943,7 +942,7 @@ class WalletRpcApi:
|
||||
else:
|
||||
return {"wallet_id": wallet.id(), "name": (await wallet.get_name())}
|
||||
|
||||
async def create_offer_for_ids(self, request):
|
||||
async def create_offer_for_ids(self, request) -> EndpointResult:
|
||||
offer: Dict[str, int] = request["offer"]
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
validate_only: bool = request.get("validate_only", False)
|
||||
@@ -964,7 +963,7 @@ class WalletRpcApi:
|
||||
for key, value in driver_dict_str.items():
|
||||
driver_dict[bytes32.from_hexstr(key)] = PuzzleInfo(value)
|
||||
|
||||
modified_offer = {}
|
||||
modified_offer: Dict[Union[int, bytes32], int] = {}
|
||||
for key in offer:
|
||||
try:
|
||||
modified_offer[bytes32.from_hexstr(key)] = offer[key]
|
||||
@@ -972,49 +971,43 @@ class WalletRpcApi:
|
||||
modified_offer[int(key)] = offer[key]
|
||||
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
(
|
||||
success,
|
||||
trade_record,
|
||||
error,
|
||||
) = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids(
|
||||
result = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids(
|
||||
modified_offer, driver_dict, fee=fee, validate_only=validate_only
|
||||
)
|
||||
if success:
|
||||
if result[0]:
|
||||
success, trade_record, error = result
|
||||
return {
|
||||
"offer": Offer.from_bytes(trade_record.offer).to_bech32(),
|
||||
"trade_record": trade_record.to_json_dict_convenience(),
|
||||
}
|
||||
raise ValueError(error)
|
||||
raise ValueError(result[2])
|
||||
|
||||
async def get_offer_summary(self, request):
|
||||
async def get_offer_summary(self, request) -> EndpointResult:
|
||||
offer_hex: str = request["offer"]
|
||||
offer = Offer.from_bech32(offer_hex)
|
||||
offered, requested, infos = offer.summary()
|
||||
|
||||
return {"summary": {"offered": offered, "requested": requested, "fees": offer.bundle.fees(), "infos": infos}}
|
||||
|
||||
async def check_offer_validity(self, request):
|
||||
async def check_offer_validity(self, request) -> EndpointResult:
|
||||
offer_hex: str = request["offer"]
|
||||
offer = Offer.from_bech32(offer_hex)
|
||||
|
||||
return {"valid": (await self.service.wallet_state_manager.trade_manager.check_offer_validity(offer))}
|
||||
|
||||
async def take_offer(self, request):
|
||||
async def take_offer(self, request) -> EndpointResult:
|
||||
offer_hex: str = request["offer"]
|
||||
offer = Offer.from_bech32(offer_hex)
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
(
|
||||
success,
|
||||
trade_record,
|
||||
error,
|
||||
) = await self.service.wallet_state_manager.trade_manager.respond_to_offer(offer, fee=fee)
|
||||
if not success:
|
||||
raise ValueError(error)
|
||||
result = await self.service.wallet_state_manager.trade_manager.respond_to_offer(offer, fee=fee)
|
||||
if not result[0]:
|
||||
raise ValueError(result[2])
|
||||
success, trade_record, error = result
|
||||
return {"trade_record": trade_record.to_json_dict_convenience()}
|
||||
|
||||
async def get_offer(self, request: Dict):
|
||||
async def get_offer(self, request: Dict) -> EndpointResult:
|
||||
trade_mgr = self.service.wallet_state_manager.trade_manager
|
||||
|
||||
trade_id = bytes32.from_hexstr(request["trade_id"])
|
||||
@@ -1027,7 +1020,7 @@ class WalletRpcApi:
|
||||
offer_value: Optional[str] = Offer.from_bytes(offer_to_return).to_bech32() if file_contents else None
|
||||
return {"trade_record": trade_record.to_json_dict_convenience(), "offer": offer_value}
|
||||
|
||||
async def get_all_offers(self, request: Dict):
|
||||
async def get_all_offers(self, request: Dict) -> EndpointResult:
|
||||
trade_mgr = self.service.wallet_state_manager.trade_manager
|
||||
|
||||
start: int = request.get("start", 0)
|
||||
@@ -1058,14 +1051,14 @@ class WalletRpcApi:
|
||||
|
||||
return {"trade_records": result, "offers": offer_values}
|
||||
|
||||
async def get_offers_count(self, request: Dict):
|
||||
async def get_offers_count(self, request: Dict) -> EndpointResult:
|
||||
trade_mgr = self.service.wallet_state_manager.trade_manager
|
||||
|
||||
(total, my_offers_count, taken_offers_count) = await trade_mgr.trade_store.get_trades_count()
|
||||
|
||||
return {"total": total, "my_offers_count": my_offers_count, "taken_offers_count": taken_offers_count}
|
||||
|
||||
async def cancel_offer(self, request: Dict):
|
||||
async def cancel_offer(self, request: Dict) -> EndpointResult:
|
||||
wsm = self.service.wallet_state_manager
|
||||
secure = request["secure"]
|
||||
trade_id = bytes32.from_hexstr(request["trade_id"])
|
||||
@@ -1082,7 +1075,7 @@ class WalletRpcApi:
|
||||
# Distributed Identities
|
||||
##########################################################################################
|
||||
|
||||
async def did_set_wallet_name(self, request):
|
||||
async def did_set_wallet_name(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
if wallet.type() == WalletType.DECENTRALIZED_ID:
|
||||
@@ -1091,13 +1084,13 @@ class WalletRpcApi:
|
||||
else:
|
||||
return {"success": False, "error": f"Wallet id {wallet_id} is not a DID wallet"}
|
||||
|
||||
async def did_get_wallet_name(self, request):
|
||||
async def did_get_wallet_name(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
name: str = await wallet.get_name()
|
||||
return {"success": True, "wallet_id": wallet_id, "name": name}
|
||||
|
||||
async def did_update_recovery_ids(self, request):
|
||||
async def did_update_recovery_ids(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
recovery_list = []
|
||||
@@ -1107,7 +1100,7 @@ class WalletRpcApi:
|
||||
if "num_verifications_required" in request:
|
||||
new_amount_verifications_required = uint64(request["num_verifications_required"])
|
||||
else:
|
||||
new_amount_verifications_required = len(recovery_list)
|
||||
new_amount_verifications_required = uint64(len(recovery_list))
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required)
|
||||
# Update coin with new ID info
|
||||
@@ -1117,7 +1110,7 @@ class WalletRpcApi:
|
||||
success = True
|
||||
return {"success": success}
|
||||
|
||||
async def did_update_metadata(self, request):
|
||||
async def did_update_metadata(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
if wallet.type() != WalletType.DECENTRALIZED_ID.value:
|
||||
@@ -1137,7 +1130,7 @@ class WalletRpcApi:
|
||||
else:
|
||||
return {"success": False, "error": f"Couldn't update metadata with input: {metadata}"}
|
||||
|
||||
async def did_get_did(self, request):
|
||||
async def did_get_did(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
my_did: str = encode_puzzle_hash(bytes32.fromhex(wallet.get_my_DID()), DID_HRP)
|
||||
@@ -1149,7 +1142,7 @@ class WalletRpcApi:
|
||||
coin = coins.pop()
|
||||
return {"success": True, "wallet_id": wallet_id, "my_did": my_did, "coin_id": coin.name()}
|
||||
|
||||
async def did_get_recovery_list(self, request):
|
||||
async def did_get_recovery_list(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
recovery_list = wallet.did_info.backup_ids
|
||||
@@ -1163,7 +1156,7 @@ class WalletRpcApi:
|
||||
"num_required": wallet.did_info.num_of_backup_ids_needed,
|
||||
}
|
||||
|
||||
async def did_get_metadata(self, request):
|
||||
async def did_get_metadata(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
metadata = json.loads(wallet.did_info.metadata)
|
||||
@@ -1173,7 +1166,7 @@ class WalletRpcApi:
|
||||
"metadata": metadata,
|
||||
}
|
||||
|
||||
async def did_recovery_spend(self, request):
|
||||
async def did_recovery_spend(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed:
|
||||
@@ -1192,13 +1185,14 @@ class WalletRpcApi:
|
||||
pubkey = wallet.did_info.temp_pubkey
|
||||
|
||||
if "puzhash" in request:
|
||||
puzhash = hexstr_to_bytes(request["puzhash"])
|
||||
puzhash = bytes32.from_hexstr(request["puzhash"])
|
||||
else:
|
||||
assert wallet.did_info.temp_puzhash is not None
|
||||
puzhash = wallet.did_info.temp_puzhash
|
||||
|
||||
# TODO: this ignore should be dealt with
|
||||
spend_bundle = await wallet.recovery_spend(
|
||||
wallet.did_info.temp_coin,
|
||||
wallet.did_info.temp_coin, # type: ignore[arg-type]
|
||||
puzhash,
|
||||
info_list,
|
||||
pubkey,
|
||||
@@ -1209,13 +1203,13 @@ class WalletRpcApi:
|
||||
else:
|
||||
return {"success": False}
|
||||
|
||||
async def did_get_pubkey(self, request):
|
||||
async def did_get_pubkey(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
pubkey = bytes((await wallet.wallet_state_manager.get_unused_derivation_record(wallet_id)).pubkey).hex()
|
||||
return {"success": True, "pubkey": pubkey}
|
||||
|
||||
async def did_create_attest(self, request):
|
||||
async def did_create_attest(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
@@ -1237,11 +1231,12 @@ class WalletRpcApi:
|
||||
else:
|
||||
return {"success": False}
|
||||
|
||||
async def did_get_information_needed_for_recovery(self, request):
|
||||
async def did_get_information_needed_for_recovery(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
my_did = encode_puzzle_hash(bytes32.from_hexstr(did_wallet.get_my_DID()), DID_HRP)
|
||||
coin_name = did_wallet.did_info.temp_coin.name().hex()
|
||||
# TODO: this ignore should be dealt with
|
||||
coin_name = did_wallet.did_info.temp_coin.name().hex() # type: ignore[union-attr]
|
||||
return {
|
||||
"success": True,
|
||||
"wallet_id": wallet_id,
|
||||
@@ -1252,7 +1247,7 @@ class WalletRpcApi:
|
||||
"backup_dids": did_wallet.did_info.backup_ids,
|
||||
}
|
||||
|
||||
async def did_get_current_coin_info(self, request):
|
||||
async def did_get_current_coin_info(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
my_did = encode_puzzle_hash(bytes32.from_hexstr(did_wallet.get_my_DID()), DID_HRP)
|
||||
@@ -1268,12 +1263,12 @@ class WalletRpcApi:
|
||||
"did_amount": did_coin_threeple[2],
|
||||
}
|
||||
|
||||
async def did_create_backup_file(self, request):
|
||||
async def did_create_backup_file(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
did_wallet: DIDWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
return {"wallet_id": wallet_id, "success": True, "backup_data": did_wallet.create_backup()}
|
||||
|
||||
async def did_transfer_did(self, request):
|
||||
async def did_transfer_did(self, request) -> EndpointResult:
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
raise ValueError("Wallet needs to be fully synced.")
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
@@ -1294,7 +1289,7 @@ class WalletRpcApi:
|
||||
# NFT Wallet
|
||||
##########################################################################################
|
||||
|
||||
async def nft_mint_nft(self, request) -> Dict:
|
||||
async def nft_mint_nft(self, request) -> EndpointResult:
|
||||
log.debug("Got minting RPC request: %s", request)
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
assert self.service.wallet_state_manager
|
||||
@@ -1353,7 +1348,7 @@ class WalletRpcApi:
|
||||
)
|
||||
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
|
||||
|
||||
async def nft_get_nfts(self, request) -> Dict:
|
||||
async def nft_get_nfts(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
nfts = nft_wallet.get_current_nfts()
|
||||
@@ -1381,7 +1376,7 @@ class WalletRpcApi:
|
||||
log.exception(f"Failed to set DID on NFT: {e}")
|
||||
return {"success": False, "error": f"Failed to set DID on NFT: {e}"}
|
||||
|
||||
async def nft_get_by_did(self, request) -> Dict:
|
||||
async def nft_get_by_did(self, request) -> EndpointResult:
|
||||
did_id: Optional[bytes32] = None
|
||||
if "did_id" in request:
|
||||
did_id = decode_puzzle_hash(request["did_id"])
|
||||
@@ -1390,7 +1385,7 @@ class WalletRpcApi:
|
||||
return {"wallet_id": wallet.wallet_id, "success": True}
|
||||
return {"error": f"Cannot find a NFT wallet DID = {did_id}", "success": False}
|
||||
|
||||
async def nft_get_wallet_did(self, request) -> Dict:
|
||||
async def nft_get_wallet_did(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
if nft_wallet is not None:
|
||||
@@ -1403,7 +1398,7 @@ class WalletRpcApi:
|
||||
return {"success": True, "did_id": None if len(did_id) == 0 else did_id}
|
||||
return {"success": False, "error": f"Wallet {wallet_id} not found"}
|
||||
|
||||
async def nft_get_wallets_with_dids(self, request) -> Dict:
|
||||
async def nft_get_wallets_with_dids(self, request) -> EndpointResult:
|
||||
all_wallets = self.service.wallet_state_manager.wallets.values()
|
||||
did_wallets_by_did_id: Dict[bytes32, uint32] = {
|
||||
wallet.did_info.origin_coin.name(): wallet.id()
|
||||
@@ -1428,7 +1423,7 @@ class WalletRpcApi:
|
||||
)
|
||||
return {"success": True, "nft_wallets": did_nft_wallets}
|
||||
|
||||
async def nft_set_nft_status(self, request) -> Dict:
|
||||
async def nft_set_nft_status(self, request) -> EndpointResult:
|
||||
try:
|
||||
wallet_id: uint32 = uint32(request["wallet_id"])
|
||||
coin_id: bytes32 = bytes32.from_hexstr(request["coin_id"])
|
||||
@@ -1442,7 +1437,7 @@ class WalletRpcApi:
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"Cannot change the status of the NFT.{e}"}
|
||||
|
||||
async def nft_transfer_nft(self, request):
|
||||
async def nft_transfer_nft(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
address = request["target_address"]
|
||||
if isinstance(address, str):
|
||||
@@ -1477,7 +1472,7 @@ class WalletRpcApi:
|
||||
log.exception(f"Failed to transfer NFT: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
async def nft_get_info(self, request: Dict) -> Dict[str, object]:
|
||||
async def nft_get_info(self, request: Dict) -> EndpointResult:
|
||||
if "coin_id" not in request:
|
||||
return {"success": False, "error": "Coin ID is required."}
|
||||
coin_id = request["coin_id"]
|
||||
@@ -1485,13 +1480,8 @@ class WalletRpcApi:
|
||||
coin_id = decode_puzzle_hash(coin_id)
|
||||
else:
|
||||
coin_id = bytes32.from_hexstr(coin_id)
|
||||
peer = self.service.wallet_state_manager.wallet_node.get_full_node_peer()
|
||||
if peer is None:
|
||||
return {"success": False, "error": "Cannot find a full node peer."}
|
||||
# Get coin state
|
||||
coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[coin_id], peer=peer
|
||||
)
|
||||
coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state([coin_id])
|
||||
if coin_state_list is None or len(coin_state_list) < 1:
|
||||
return {"success": False, "error": f"Coin record 0x{coin_id.hex()} not found"}
|
||||
coin_state: CoinState = coin_state_list[0]
|
||||
@@ -1499,7 +1489,7 @@ class WalletRpcApi:
|
||||
# Find the unspent coin
|
||||
while coin_state.spent_height is not None:
|
||||
coin_state_list = await self.service.wallet_state_manager.wallet_node.fetch_children(
|
||||
peer, coin_state.coin.name()
|
||||
coin_state.coin.name()
|
||||
)
|
||||
odd_coin = 0
|
||||
for coin in coin_state_list:
|
||||
@@ -1512,7 +1502,7 @@ class WalletRpcApi:
|
||||
coin_state = coin_state_list[0]
|
||||
# Get parent coin
|
||||
parent_coin_state_list: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[coin_state.coin.parent_coin_info], peer=peer
|
||||
[coin_state.coin.parent_coin_info]
|
||||
)
|
||||
if parent_coin_state_list is None or len(parent_coin_state_list) < 1:
|
||||
return {
|
||||
@@ -1521,7 +1511,7 @@ class WalletRpcApi:
|
||||
}
|
||||
parent_coin_state: CoinState = parent_coin_state_list[0]
|
||||
coin_spend: CoinSpend = await self.service.wallet_state_manager.wallet_node.fetch_puzzle_solution(
|
||||
peer, parent_coin_state.spent_height, parent_coin_state.coin
|
||||
parent_coin_state.spent_height, parent_coin_state.coin
|
||||
)
|
||||
# convert to NFTInfo
|
||||
try:
|
||||
@@ -1548,7 +1538,7 @@ class WalletRpcApi:
|
||||
|
||||
# Get launcher coin
|
||||
launcher_coin: List[CoinState] = await self.service.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[uncurried_nft.singleton_launcher_id], peer=peer
|
||||
[uncurried_nft.singleton_launcher_id]
|
||||
)
|
||||
if launcher_coin is None or len(launcher_coin) < 1 or launcher_coin[0].spent_height is None:
|
||||
return {
|
||||
@@ -1569,7 +1559,7 @@ class WalletRpcApi:
|
||||
else:
|
||||
return {"success": True, "nft_info": nft_info}
|
||||
|
||||
async def nft_add_uri(self, request) -> Dict:
|
||||
async def nft_add_uri(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
# Note metadata updater can only add one uri for one field per spend.
|
||||
# If you want to add multiple uris for one field, you need to spend multiple times.
|
||||
@@ -1594,7 +1584,7 @@ class WalletRpcApi:
|
||||
# Rate Limited Wallet
|
||||
##########################################################################################
|
||||
|
||||
async def rl_set_user_info(self, request):
|
||||
async def rl_set_user_info(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(int(request["wallet_id"]))
|
||||
rl_user = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
origin = request["origin"]
|
||||
@@ -1609,8 +1599,8 @@ class WalletRpcApi:
|
||||
)
|
||||
return {}
|
||||
|
||||
async def send_clawback_transaction(self, request):
|
||||
wallet_id = int(request["wallet_id"])
|
||||
async def send_clawback_transaction(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: RLWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
|
||||
fee = int(request["fee"])
|
||||
@@ -1624,7 +1614,7 @@ class WalletRpcApi:
|
||||
"transaction_id": tx.name,
|
||||
}
|
||||
|
||||
async def add_rate_limited_funds(self, request):
|
||||
async def add_rate_limited_funds(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: RLWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
puzzle_hash = wallet.rl_get_aggregation_puzzlehash(wallet.rl_info.rl_puzzle_hash)
|
||||
@@ -1632,7 +1622,7 @@ class WalletRpcApi:
|
||||
await wallet.rl_add_funds(request["amount"], puzzle_hash, request["fee"])
|
||||
return {"status": "SUCCESS"}
|
||||
|
||||
async def get_farmed_amount(self, request):
|
||||
async def get_farmed_amount(self, request) -> EndpointResult:
|
||||
tx_records: List[TransactionRecord] = await self.service.wallet_state_manager.tx_store.get_farming_rewards()
|
||||
amount = 0
|
||||
pool_reward_amount = 0
|
||||
@@ -1648,6 +1638,11 @@ class WalletRpcApi:
|
||||
continue
|
||||
pool_reward_amount += record.amount
|
||||
height = record.height_farmed(self.service.constants.GENESIS_CHALLENGE)
|
||||
# .get_farming_rewards() above queries for only confirmed records. This
|
||||
# could be hinted by making TransactionRecord generic but streamable can't
|
||||
# handle that presently. Existing code would have raised an exception
|
||||
# anyways if this were to fail and we already have an assert below.
|
||||
assert height is not None
|
||||
if record.type == TransactionType.FEE_REWARD:
|
||||
fee_amount += record.amount - calculate_base_farmer_reward(height)
|
||||
farmer_reward_amount += calculate_base_farmer_reward(height)
|
||||
@@ -1664,7 +1659,7 @@ class WalletRpcApi:
|
||||
"last_height_farmed": last_height_farmed,
|
||||
}
|
||||
|
||||
async def create_signed_transaction(self, request, hold_lock=True) -> Dict:
|
||||
async def create_signed_transaction(self, request, hold_lock=True) -> EndpointResult:
|
||||
if "additions" not in request or len(request["additions"]) < 1:
|
||||
raise ValueError("Specify additions list")
|
||||
|
||||
@@ -1760,7 +1755,7 @@ class WalletRpcApi:
|
||||
##########################################################################################
|
||||
# Pool Wallet
|
||||
##########################################################################################
|
||||
async def pw_join_pool(self, request) -> Dict:
|
||||
async def pw_join_pool(self, request) -> EndpointResult:
|
||||
fee = uint64(request.get("fee", 0))
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: PoolWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
@@ -1788,7 +1783,7 @@ class WalletRpcApi:
|
||||
total_fee, tx, fee_tx = await wallet.join_pool(new_target_state, fee)
|
||||
return {"total_fee": total_fee, "transaction": tx, "fee_transaction": fee_tx}
|
||||
|
||||
async def pw_self_pool(self, request) -> Dict:
|
||||
async def pw_self_pool(self, request) -> EndpointResult:
|
||||
# Leaving a pool requires two state transitions.
|
||||
# First we transition to PoolSingletonState.LEAVING_POOL
|
||||
# Then we transition to FARMING_TO_POOL or SELF_POOLING
|
||||
@@ -1805,7 +1800,7 @@ class WalletRpcApi:
|
||||
total_fee, tx, fee_tx = await wallet.self_pool(fee)
|
||||
return {"total_fee": total_fee, "transaction": tx, "fee_transaction": fee_tx}
|
||||
|
||||
async def pw_absorb_rewards(self, request) -> Dict:
|
||||
async def pw_absorb_rewards(self, request) -> EndpointResult:
|
||||
"""Perform a sweep of the p2_singleton rewards controlled by the pool wallet singleton"""
|
||||
if await self.service.wallet_state_manager.synced() is False:
|
||||
raise ValueError("Wallet needs to be fully synced before collecting rewards")
|
||||
@@ -1821,7 +1816,7 @@ class WalletRpcApi:
|
||||
state: PoolWalletInfo = await wallet.get_current_state()
|
||||
return {"state": state.to_json_dict(), "transaction": transaction, "fee_transaction": fee_tx}
|
||||
|
||||
async def pw_status(self, request) -> Dict:
|
||||
async def pw_status(self, request) -> EndpointResult:
|
||||
"""Return the complete state of the Pool wallet with id `request["wallet_id"]`"""
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
wallet: PoolWallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
|
||||
@@ -16,7 +16,7 @@ from chia.util.default_root import DEFAULT_ROOT_PATH
|
||||
# See: https://bugs.python.org/issue29288
|
||||
"".encode("idna")
|
||||
|
||||
SERVICE_NAME = "full_node"
|
||||
SERVICE_NAME = "seeder"
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ def service_kwargs_for_full_node_crawler(
|
||||
|
||||
|
||||
def main():
|
||||
config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", "seeder")
|
||||
config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME)
|
||||
overrides = config["network_overrides"]["constants"][config["selected_network"]]
|
||||
updated_constants = DEFAULT_CONSTANTS.replace_str_to_bytes(**overrides)
|
||||
kwargs = service_kwargs_for_full_node_crawler(DEFAULT_ROOT_PATH, config, updated_constants)
|
||||
|
||||
@@ -53,8 +53,6 @@ class Service:
|
||||
rpc_info: Optional[Tuple[type, int]] = None,
|
||||
parse_cli_args=True,
|
||||
connect_to_daemon=True,
|
||||
running_new_process=True,
|
||||
service_name_prefix="",
|
||||
max_request_body_size: Optional[int] = None,
|
||||
override_capabilities: Optional[List[Tuple[uint16, str]]] = None,
|
||||
) -> None:
|
||||
@@ -71,24 +69,13 @@ class Service:
|
||||
self._rpc_close_task: Optional[asyncio.Task] = None
|
||||
self._network_id: str = network_id
|
||||
self.max_request_body_size = max_request_body_size
|
||||
self._running_new_process = running_new_process
|
||||
|
||||
# when we start this service as a component of an existing process,
|
||||
# don't change its proctitle
|
||||
if running_new_process:
|
||||
proctitle_name = f"chia_{service_name_prefix}{service_name}"
|
||||
setproctitle(proctitle_name)
|
||||
|
||||
self._log = logging.getLogger(service_name)
|
||||
|
||||
if parse_cli_args:
|
||||
service_config = load_config_cli(root_path, "config.yaml", service_name)
|
||||
self.service_config = load_config_cli(root_path, "config.yaml", service_name)
|
||||
else:
|
||||
service_config = load_config(root_path, "config.yaml", service_name)
|
||||
|
||||
# only initialize logging once per process
|
||||
if running_new_process:
|
||||
initialize_logging(service_name, service_config["logging"], root_path)
|
||||
self.service_config = load_config(root_path, "config.yaml", service_name)
|
||||
|
||||
self._rpc_info = rpc_info
|
||||
private_ca_crt, private_ca_key = private_ssl_ca_paths(root_path, self.config)
|
||||
@@ -96,7 +83,7 @@ class Service:
|
||||
inbound_rlp = self.config.get("inbound_rate_limit_percent")
|
||||
outbound_rlp = self.config.get("outbound_rate_limit_percent")
|
||||
if node_type == NodeType.WALLET:
|
||||
inbound_rlp = service_config.get("inbound_rate_limit_percent", inbound_rlp)
|
||||
inbound_rlp = self.service_config.get("inbound_rate_limit_percent", inbound_rlp)
|
||||
outbound_rlp = 60
|
||||
capabilities_to_use: List[Tuple[uint16, str]] = capabilities
|
||||
if override_capabilities is not None:
|
||||
@@ -114,7 +101,7 @@ class Service:
|
||||
outbound_rlp,
|
||||
capabilities_to_use,
|
||||
root_path,
|
||||
service_config,
|
||||
self.service_config,
|
||||
(private_ca_crt, private_ca_key),
|
||||
(chia_ca_crt, chia_ca_key),
|
||||
name=f"{service_name}_server",
|
||||
@@ -154,9 +141,6 @@ class Service:
|
||||
|
||||
self._did_start = True
|
||||
|
||||
if self._running_new_process:
|
||||
self._enable_signals()
|
||||
|
||||
await self._node._start(**kwargs)
|
||||
self._node._shut_down = False
|
||||
|
||||
@@ -201,7 +185,12 @@ class Service:
|
||||
await self.start()
|
||||
await self.wait_closed()
|
||||
|
||||
def _enable_signals(self) -> None:
|
||||
async def setup_process_global_state(self) -> None:
|
||||
# Being async forces this to be run from within an active event loop as is
|
||||
# needed for the signal handler setup.
|
||||
proctitle_name = f"chia_{self._service_name}"
|
||||
setproctitle(proctitle_name)
|
||||
initialize_logging(self._service_name, self.service_config["logging"], self.root_path)
|
||||
|
||||
global main_pid
|
||||
main_pid = os.getpid()
|
||||
@@ -288,6 +277,7 @@ class Service:
|
||||
|
||||
async def async_run_service(*args, **kwargs) -> None:
|
||||
service = Service(*args, **kwargs)
|
||||
await service.setup_process_global_state()
|
||||
return await service.run()
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Dict
|
||||
|
||||
from chia.rpc.full_node_rpc_api import FullNodeRpcApi
|
||||
from chia.rpc.rpc_server import Endpoint
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.util.bech32m import decode_puzzle_hash
|
||||
|
||||
@@ -12,7 +12,7 @@ class SimulatorFullNodeRpcApi(FullNodeRpcApi):
|
||||
routes["/farm_tx_block"] = self.farm_tx_block
|
||||
return routes
|
||||
|
||||
async def farm_tx_block(self, _request: Dict[str, object]) -> Dict[str, object]:
|
||||
async def farm_tx_block(self, _request: Dict[str, object]) -> EndpointResult:
|
||||
request_address = str(_request["address"])
|
||||
ph = decode_puzzle_hash(request_address)
|
||||
req = FarmNewBlockProtocol(ph)
|
||||
|
||||
@@ -8,7 +8,7 @@ from blspy import AugSchemeMPL, G2Element
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.streamable import Streamable, dataclass_from_dict, recurse_jsonify, streamable
|
||||
from chia.util.streamable import Streamable, streamable_from_dict, recurse_jsonify, streamable
|
||||
from chia.wallet.util.debug_spend_bundle import debug_spend_bundle
|
||||
|
||||
from .coin_spend import CoinSpend
|
||||
@@ -95,7 +95,7 @@ class SpendBundle(Streamable):
|
||||
warnings.warn("`coin_solutions` is now `coin_spends` in `SpendBundle.from_json_dict`")
|
||||
else:
|
||||
raise ValueError("JSON contains both `coin_solutions` and `coin_spends`, just use `coin_spends`")
|
||||
return dataclass_from_dict(cls, json_dict)
|
||||
return streamable_from_dict(cls, json_dict)
|
||||
|
||||
def to_json_dict(self, include_legacy_keys: bool = True, exclude_modern_keys: bool = True):
|
||||
if include_legacy_keys is False and exclude_modern_keys is True:
|
||||
|
||||
@@ -50,7 +50,7 @@ def get_os_passphrase_store() -> Optional[OSPassphraseStore]:
|
||||
|
||||
|
||||
def check_legacy_keyring_keys_present(keyring: LegacyKeyring) -> bool:
|
||||
from keyring.credentials import SimpleCredential
|
||||
from keyring.credentials import Credential
|
||||
from chia.util.keychain import default_keychain_user, default_keychain_service, get_private_key_user, MAX_KEYS
|
||||
|
||||
keychain_user: str = default_keychain_user()
|
||||
@@ -58,7 +58,7 @@ def check_legacy_keyring_keys_present(keyring: LegacyKeyring) -> bool:
|
||||
|
||||
for index in range(0, MAX_KEYS):
|
||||
current_user: str = get_private_key_user(keychain_user, index)
|
||||
credential: Optional[SimpleCredential] = keyring.get_credential(keychain_service, current_user)
|
||||
credential: Optional[Credential] = keyring.get_credential(keychain_service, current_user)
|
||||
if credential is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
+6
-19
@@ -169,7 +169,7 @@ def convert_primitive(f_type: Type[Any], item: Any) -> Any:
|
||||
raise TypeError(f"Can't convert type {type(item).__name__} to {f_type.__name__}: {e}") from e
|
||||
|
||||
|
||||
def dataclass_from_dict(klass: Type[Any], item: Any) -> Any:
|
||||
def streamable_from_dict(klass: Type[_T_Streamable], item: Any) -> _T_Streamable:
|
||||
"""
|
||||
Converts a dictionary based on a dataclass, into an instance of that dataclass.
|
||||
Recursively goes through lists, optionals, and dictionaries.
|
||||
@@ -179,22 +179,12 @@ def dataclass_from_dict(klass: Type[Any], item: Any) -> Any:
|
||||
if not isinstance(item, dict):
|
||||
raise TypeError(f"expected: dict, actual: {type(item).__name__}")
|
||||
|
||||
if klass not in CONVERT_FUNCTIONS_FOR_STREAMABLE_CLASS:
|
||||
# For non-streamable dataclasses we can't populate the cache on startup, so we do it here for convert
|
||||
# functions only.
|
||||
fields = create_fields_cache(klass)
|
||||
convert_funcs = [function_to_convert_one_item(field.type) for field in fields]
|
||||
FIELDS_FOR_STREAMABLE_CLASS[klass] = fields
|
||||
CONVERT_FUNCTIONS_FOR_STREAMABLE_CLASS[klass] = convert_funcs
|
||||
else:
|
||||
fields = FIELDS_FOR_STREAMABLE_CLASS[klass]
|
||||
convert_funcs = CONVERT_FUNCTIONS_FOR_STREAMABLE_CLASS[klass]
|
||||
|
||||
fields = FIELDS_FOR_STREAMABLE_CLASS[klass]
|
||||
try:
|
||||
return klass(
|
||||
**{
|
||||
field.name: convert_func(item[field.name])
|
||||
for field, convert_func in zip(fields, convert_funcs)
|
||||
for field, convert_func in zip(fields, CONVERT_FUNCTIONS_FOR_STREAMABLE_CLASS[klass])
|
||||
if field.name in item
|
||||
}
|
||||
)
|
||||
@@ -224,9 +214,6 @@ def function_to_convert_one_item(f_type: Type[Any]) -> ConvertFunctionType:
|
||||
convert_inner_func = function_to_convert_one_item(inner_type)
|
||||
# Ignoring for now as the proper solution isn't obvious
|
||||
return lambda items: convert_list(convert_inner_func, items) # type: ignore[arg-type]
|
||||
elif dataclasses.is_dataclass(f_type):
|
||||
# Type is a dataclass, data is a dictionary
|
||||
return lambda item: dataclass_from_dict(f_type, item)
|
||||
elif hasattr(f_type, "from_json_dict"):
|
||||
return lambda item: f_type.from_json_dict(item)
|
||||
elif issubclass(f_type, bytes):
|
||||
@@ -645,7 +632,7 @@ class Streamable:
|
||||
stream_func(getattr(self, field.name), f)
|
||||
|
||||
def get_hash(self) -> bytes32:
|
||||
return bytes32(std_hash(bytes(self), skip_bytes_conversion=True))
|
||||
return std_hash(bytes(self), skip_bytes_conversion=True)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls: Any, blob: bytes) -> Any:
|
||||
@@ -670,5 +657,5 @@ class Streamable:
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def from_json_dict(cls: Any, json_dict: Dict[str, Any]) -> Any:
|
||||
return dataclass_from_dict(cls, json_dict)
|
||||
def from_json_dict(cls: Type[_T_Streamable], json_dict: Dict[str, Any]) -> _T_Streamable:
|
||||
return streamable_from_dict(cls, json_dict)
|
||||
|
||||
@@ -335,22 +335,15 @@ class CATWallet:
|
||||
lineage = await self.get_lineage_proof_for_coin(coin)
|
||||
|
||||
if lineage is None:
|
||||
for node_id, node in self.wallet_state_manager.wallet_node.server.all_connections.items():
|
||||
try:
|
||||
coin_state = await self.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[coin.parent_coin_info], None, node
|
||||
)
|
||||
# check for empty list and continue on to next node
|
||||
if not coin_state:
|
||||
continue
|
||||
assert coin_state[0].coin.name() == coin.parent_coin_info
|
||||
coin_spend = await self.wallet_state_manager.wallet_node.fetch_puzzle_solution(
|
||||
node, coin_state[0].spent_height, coin_state[0].coin
|
||||
)
|
||||
await self.puzzle_solution_received(coin_spend, parent_coin=coin_state[0].coin)
|
||||
break
|
||||
except Exception as e:
|
||||
self.log.debug(f"Exception: {e}, traceback: {traceback.format_exc()}")
|
||||
try:
|
||||
coin_state = await self.wallet_state_manager.wallet_node.get_coin_state([coin.parent_coin_info])
|
||||
assert coin_state[0].coin.name() == coin.parent_coin_info
|
||||
coin_spend = await self.wallet_state_manager.wallet_node.fetch_puzzle_solution(
|
||||
coin_state[0].spent_height, coin_state[0].coin
|
||||
)
|
||||
await self.puzzle_solution_received(coin_spend, parent_coin=coin_state[0].coin)
|
||||
except Exception as e:
|
||||
self.log.debug(f"Exception: {e}, traceback: {traceback.format_exc()}")
|
||||
|
||||
async def puzzle_solution_received(self, coin_spend: CoinSpend, parent_coin: Coin):
|
||||
coin_name = coin_spend.coin.name()
|
||||
|
||||
@@ -1,100 +1,35 @@
|
||||
from typing import Tuple, Iterator
|
||||
from typing import Union, Tuple, Iterator
|
||||
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.nft_wallet.nft_puzzles import create_nft_layer_puzzle_with_curry_params, NFT_STATE_LAYER_MOD
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
|
||||
# from chia.types.condition_opcodes import ConditionOpcode
|
||||
# from chia.wallet.util.merkle_tree import MerkleTree, TreeType
|
||||
|
||||
|
||||
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_atari_only.clvm")
|
||||
ACS_MU = Program.to(11) # returns the third argument a.k.a the full solution
|
||||
ACS_MU_PH = ACS_MU.get_tree_hash()
|
||||
SINGLETON_TOP_LAYER_MOD = load_clvm("singleton_top_layer_v1_1.clvm")
|
||||
# TODO: need new data layer specific clvm
|
||||
SINGLETON_LAUNCHER = load_clvm("singleton_launcher.clvm")
|
||||
DB_HOST_MOD = load_clvm("database_layer.clvm")
|
||||
DB_OFFER_MOD = load_clvm("database_offer.clvm")
|
||||
|
||||
DB_HOST_MOD_HASH = DB_HOST_MOD.get_tree_hash()
|
||||
|
||||
|
||||
def create_host_fullpuz(innerpuz_hash: bytes32, current_root: bytes32, genesis_id: bytes32) -> Program:
|
||||
db_layer = create_host_layer_puzzle(innerpuz_hash, current_root)
|
||||
def create_host_fullpuz(innerpuz: Union[Program, bytes32], current_root: bytes32, genesis_id: bytes32) -> Program:
|
||||
db_layer = create_host_layer_puzzle(innerpuz, current_root)
|
||||
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
|
||||
singleton_struct = Program.to((mod_hash, (genesis_id, SINGLETON_LAUNCHER.get_tree_hash())))
|
||||
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, db_layer)
|
||||
|
||||
|
||||
def create_host_layer_puzzle(innerpuz_hash: bytes32, current_root: bytes32) -> Program:
|
||||
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
||||
db_layer = DB_HOST_MOD.curry(DB_HOST_MOD_HASH, current_root, innerpuz_hash)
|
||||
return db_layer
|
||||
|
||||
|
||||
def solve_data_layer_to_report(amount: uint64) -> Program:
|
||||
# https://github.com/Chia-Network/clvm/pull/102
|
||||
# https://github.com/Chia-Network/clvm/pull/106
|
||||
return Program.to( # type: ignore[no-any-return]
|
||||
[
|
||||
1,
|
||||
amount,
|
||||
[],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def solve_data_layer_to_update(inner_puzzle: Program, inner_solution: Program) -> Program:
|
||||
# https://github.com/Chia-Network/clvm/pull/102
|
||||
# https://github.com/Chia-Network/clvm/pull/106
|
||||
return Program.to( # type: ignore[no-any-return]
|
||||
[
|
||||
0,
|
||||
inner_solution,
|
||||
inner_puzzle,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def create_offer_fullpuz(
|
||||
leaf_reveal: bytes,
|
||||
host_genesis_id: bytes32,
|
||||
claim_target: bytes32,
|
||||
recovery_target: bytes32,
|
||||
recovery_timelock: uint64,
|
||||
) -> Program:
|
||||
mod_hash = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
|
||||
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
||||
singleton_struct = Program.to((mod_hash, (host_genesis_id, SINGLETON_LAUNCHER.get_tree_hash())))
|
||||
full_puz = DB_OFFER_MOD.curry(
|
||||
DB_HOST_MOD_HASH, singleton_struct, leaf_reveal, claim_target, recovery_target, recovery_timelock
|
||||
)
|
||||
return full_puz
|
||||
|
||||
|
||||
def solve_dl_offer_for_claim(
|
||||
offer_amount: uint64, inner_puzzle_hash: bytes32, root: bytes32, proof_of_inclusion: Program
|
||||
) -> Program:
|
||||
# https://github.com/Chia-Network/clvm/pull/102
|
||||
# https://github.com/Chia-Network/clvm/pull/106
|
||||
return Program.to( # type: ignore[no-any-return]
|
||||
[
|
||||
1,
|
||||
offer_amount,
|
||||
inner_puzzle_hash,
|
||||
root,
|
||||
proof_of_inclusion,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def solve_dl_offer_for_recover(offer_amount: uint64) -> Program:
|
||||
# https://github.com/Chia-Network/clvm/pull/102
|
||||
# https://github.com/Chia-Network/clvm/pull/106
|
||||
return Program.to( # type: ignore[no-any-return]
|
||||
[
|
||||
0,
|
||||
offer_amount,
|
||||
]
|
||||
def create_host_layer_puzzle(innerpuz: Union[Program, bytes32], current_root: bytes32) -> Program:
|
||||
# some hard coded metadata formatting and metadata updater for now
|
||||
return create_nft_layer_puzzle_with_curry_params(
|
||||
Program.to((current_root, None)),
|
||||
ACS_MU_PH,
|
||||
# TODO: the nft driver doesn't like the Union yet, but changing that is out of scope for me rn - Quex
|
||||
innerpuz, # type: ignore
|
||||
)
|
||||
|
||||
|
||||
@@ -105,11 +40,11 @@ def match_dl_singleton(puzzle: Program) -> Tuple[bool, Iterator[Program]]:
|
||||
mod, singleton_curried_args = puzzle.uncurry()
|
||||
if mod == SINGLETON_TOP_LAYER_MOD:
|
||||
mod, dl_curried_args = singleton_curried_args.at("rf").uncurry()
|
||||
if mod == DB_HOST_MOD:
|
||||
if mod == NFT_STATE_LAYER_MOD and dl_curried_args.at("rrf") == ACS_MU_PH:
|
||||
launcher_id = singleton_curried_args.at("frf")
|
||||
root = dl_curried_args.at("rf")
|
||||
innerpuz_hash = dl_curried_args.at("rrf")
|
||||
return True, iter((innerpuz_hash, root, launcher_id))
|
||||
root = dl_curried_args.at("rff")
|
||||
innerpuz = dl_curried_args.at("rrrf")
|
||||
return True, iter((innerpuz, root, launcher_id))
|
||||
|
||||
return False, iter(())
|
||||
|
||||
|
||||
@@ -434,22 +434,21 @@ class DIDWallet:
|
||||
did_info.origin_coin.name(),
|
||||
did_wallet_puzzles.metadata_to_program(json.loads(self.did_info.metadata)),
|
||||
)
|
||||
node = self.wallet_state_manager.wallet_node.get_full_node_peer()
|
||||
children = await self.wallet_state_manager.wallet_node.fetch_children(node, did_info.origin_coin.name())
|
||||
wallet_node = self.wallet_state_manager.wallet_node
|
||||
parent_coin: Coin = did_info.origin_coin
|
||||
while True:
|
||||
children = await wallet_node.fetch_children(parent_coin.name())
|
||||
if len(children) == 0:
|
||||
break
|
||||
|
||||
children_state: CoinState = children[0]
|
||||
coin = children_state.coin
|
||||
name = coin.name()
|
||||
children = await self.wallet_state_manager.wallet_node.fetch_children(node, name)
|
||||
child_coin = children_state.coin
|
||||
future_parent = LineageProof(
|
||||
coin.parent_coin_info,
|
||||
child_coin.parent_coin_info,
|
||||
did_info.current_inner.get_tree_hash(),
|
||||
uint64(coin.amount),
|
||||
uint64(child_coin.amount),
|
||||
)
|
||||
await self.add_parent(coin.name(), future_parent, True)
|
||||
await self.add_parent(child_coin.name(), future_parent, True)
|
||||
if children_state.spent_height != children_state.created_height:
|
||||
did_info = DIDInfo(
|
||||
did_info.origin_coin,
|
||||
@@ -457,7 +456,7 @@ class DIDWallet:
|
||||
did_info.num_of_backup_ids_needed,
|
||||
self.did_info.parent_info,
|
||||
did_info.current_inner,
|
||||
coin,
|
||||
child_coin,
|
||||
new_did_inner_puzhash,
|
||||
new_pubkey,
|
||||
False,
|
||||
@@ -466,23 +465,19 @@ class DIDWallet:
|
||||
|
||||
await self.save_info(did_info, True)
|
||||
assert children_state.created_height
|
||||
puzzle_solution_request = wallet_protocol.RequestPuzzleSolution(
|
||||
coin.parent_coin_info, children_state.created_height
|
||||
parent_spend = await wallet_node.fetch_puzzle_solution(children_state.created_height, parent_coin)
|
||||
assert parent_spend is not None
|
||||
parent_innerpuz = did_wallet_puzzles.get_innerpuzzle_from_puzzle(
|
||||
parent_spend.puzzle_reveal.to_program()
|
||||
)
|
||||
parent_state: CoinState = (
|
||||
await self.wallet_state_manager.wallet_node.get_coin_state([coin.parent_coin_info])
|
||||
)[0]
|
||||
response = await node.request_puzzle_solution(puzzle_solution_request)
|
||||
req_puz_sol = response.response
|
||||
assert req_puz_sol.puzzle is not None
|
||||
parent_innerpuz = did_wallet_puzzles.get_innerpuzzle_from_puzzle(req_puz_sol.puzzle.to_program())
|
||||
assert parent_innerpuz is not None
|
||||
parent_info = LineageProof(
|
||||
parent_state.coin.parent_coin_info,
|
||||
parent_coin.parent_coin_info,
|
||||
parent_innerpuz.get_tree_hash(),
|
||||
uint64(parent_state.coin.amount),
|
||||
uint64(parent_coin.amount),
|
||||
)
|
||||
await self.add_parent(coin.parent_coin_info, parent_info, True)
|
||||
await self.add_parent(child_coin.parent_coin_info, parent_info, True)
|
||||
parent_coin = child_coin
|
||||
assert parent_info is not None
|
||||
|
||||
async def create_tandem_xch_tx(
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar
|
||||
|
||||
from blspy import AugSchemeMPL
|
||||
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.wallet.db_wallet.db_wallet_puzzles import create_offer_fullpuz
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.ints import uint8, uint32, uint64
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.compute_memos import compute_memos
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
from chia.wallet.wallet_info import WalletInfo
|
||||
from chia.wallet.wallet_state_manager import WalletStateManager
|
||||
|
||||
|
||||
_T_DLOWallet = TypeVar("_T_DLOWallet", bound="DLOWallet")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@streamable
|
||||
class DLOInfo(Streamable):
|
||||
leaf_reveal: Optional[bytes]
|
||||
host_genesis_id: Optional[bytes32]
|
||||
claim_target: Optional[bytes32]
|
||||
recovery_target: Optional[bytes32]
|
||||
recovery_timelock: Optional[uint64]
|
||||
active_offer: Optional[Coin]
|
||||
|
||||
|
||||
@dataclass
|
||||
class DLOWallet:
|
||||
wallet_state_manager: WalletStateManager
|
||||
log: logging.Logger
|
||||
wallet_id: uint32
|
||||
wallet_info: WalletInfo
|
||||
dlo_info: DLOInfo
|
||||
standard_wallet: Wallet
|
||||
base_puzzle_program: Optional[bytes]
|
||||
base_inner_puzzle_hash: Optional[bytes32]
|
||||
cost_of_single_tx: Optional[int]
|
||||
|
||||
@classmethod
|
||||
def type(cls) -> uint8:
|
||||
return uint8(WalletType.DATA_LAYER_OFFER)
|
||||
|
||||
@classmethod
|
||||
async def create_new_dlo_wallet(
|
||||
cls: Type[_T_DLOWallet],
|
||||
wallet_state_manager: Any,
|
||||
wallet: Wallet,
|
||||
) -> _T_DLOWallet:
|
||||
dlo_info = DLOInfo(None, None, None, None, None, None)
|
||||
info_as_string = bytes(dlo_info).hex()
|
||||
wallet_info = await wallet_state_manager.user_store.create_wallet(
|
||||
"DLO Wallet", WalletType.DATA_LAYER_OFFER.value, info_as_string
|
||||
)
|
||||
|
||||
self = cls(
|
||||
cost_of_single_tx=None,
|
||||
base_puzzle_program=None,
|
||||
base_inner_puzzle_hash=None,
|
||||
standard_wallet=wallet,
|
||||
log=logging.getLogger(__name__),
|
||||
wallet_state_manager=wallet_state_manager,
|
||||
dlo_info=dlo_info,
|
||||
wallet_info=wallet_info,
|
||||
wallet_id=wallet_info.id,
|
||||
)
|
||||
|
||||
await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id)
|
||||
return self
|
||||
|
||||
def puzzle_for_pk(self, pubkey: bytes) -> Program:
|
||||
if self.dlo_info.leaf_reveal is not None:
|
||||
return create_offer_fullpuz(
|
||||
self.dlo_info.leaf_reveal,
|
||||
self.dlo_info.host_genesis_id, # type: ignore[arg-type]
|
||||
self.dlo_info.claim_target, # type: ignore[arg-type]
|
||||
self.dlo_info.recovery_target, # type: ignore[arg-type]
|
||||
self.dlo_info.recovery_timelock, # type: ignore[arg-type]
|
||||
)
|
||||
return Program.to(pubkey) # type: ignore[no-any-return]
|
||||
|
||||
def id(self) -> uint32:
|
||||
return self.wallet_info.id
|
||||
|
||||
async def generate_datalayer_offer_spend(
|
||||
self,
|
||||
amount: uint64,
|
||||
leaf_reveal: bytes,
|
||||
host_genesis_id: bytes32,
|
||||
claim_target: bytes32,
|
||||
recovery_target: bytes32,
|
||||
recovery_timelock: uint64,
|
||||
) -> TransactionRecord:
|
||||
full_puzzle: Program = create_offer_fullpuz(
|
||||
leaf_reveal,
|
||||
host_genesis_id,
|
||||
claim_target,
|
||||
recovery_target,
|
||||
recovery_timelock,
|
||||
)
|
||||
tr: TransactionRecord = await self.standard_wallet.generate_signed_transaction(
|
||||
amount, full_puzzle.get_tree_hash()
|
||||
)
|
||||
await self.wallet_state_manager.add_interested_puzzle_hashes(
|
||||
[full_puzzle.get_tree_hash()], [self.wallet_id], True
|
||||
)
|
||||
|
||||
active_coin = None
|
||||
spend_bundle = tr.spend_bundle
|
||||
if spend_bundle is not None:
|
||||
for coin in spend_bundle.additions():
|
||||
if coin.puzzle_hash == full_puzzle.get_tree_hash():
|
||||
active_coin = coin
|
||||
if active_coin is None:
|
||||
raise ValueError("Unable to find created coin")
|
||||
|
||||
await self.standard_wallet.push_transaction(tr)
|
||||
dlo_info = DLOInfo(
|
||||
leaf_reveal,
|
||||
host_genesis_id,
|
||||
claim_target,
|
||||
recovery_target,
|
||||
recovery_timelock,
|
||||
active_coin,
|
||||
)
|
||||
await self.save_info(dlo_info, True)
|
||||
return tr
|
||||
|
||||
async def claim_dl_offer(
|
||||
self,
|
||||
offer_coin: Coin,
|
||||
offer_full_puzzle: Program,
|
||||
db_innerpuz_hash: bytes32,
|
||||
current_root: bytes32,
|
||||
inclusion_proof: Tuple[Optional[int], List[Optional[List[bytes32]]]],
|
||||
fee: uint64 = uint64(0),
|
||||
) -> SpendBundle:
|
||||
solution = Program.to([1, offer_coin.amount, db_innerpuz_hash, current_root, inclusion_proof])
|
||||
sb = SpendBundle([CoinSpend(offer_coin, offer_full_puzzle, solution)], AugSchemeMPL.aggregate([]))
|
||||
# ret = uncurry_offer_puzzle(offer_full_puzzle)
|
||||
# singleton_struct, leaf_reveal, claim_target, recovery_target, recovery_timelock = ret
|
||||
# tr = TransactionRecord(
|
||||
# confirmed_at_height=uint32(0),
|
||||
# created_at_time=uint64(int(time.time())),
|
||||
# to_puzzle_hash=claim_target,
|
||||
# amount=uint64(offer_coin.amount),
|
||||
# fee_amount=uint64(fee),
|
||||
# confirmed=False,
|
||||
# sent=uint32(0),
|
||||
# spend_bundle=sb,
|
||||
# additions=list(sb.additions()),
|
||||
# removals=list(sb.removals()),
|
||||
# wallet_id=self.id(),
|
||||
# sent_to=[],
|
||||
# trade_id=None,
|
||||
# type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
# name=sb.name(),
|
||||
# )
|
||||
# self.standard_wallet.push_transaction(tr)
|
||||
return sb
|
||||
|
||||
async def create_recover_dl_offer_spend(
|
||||
self,
|
||||
leaf_reveal: Optional[bytes] = None,
|
||||
host_genesis_id: Optional[bytes32] = None,
|
||||
claim_target: Optional[bytes32] = None,
|
||||
recovery_target: Optional[bytes32] = None,
|
||||
recovery_timelock: Optional[uint64] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
) -> TransactionRecord:
|
||||
coin = self.dlo_info.active_offer
|
||||
if coin is None:
|
||||
raise ValueError("Active offer coin unexpectedly None")
|
||||
|
||||
solution = Program.to([0, coin.amount])
|
||||
|
||||
if leaf_reveal is None:
|
||||
leaf_reveal = self.dlo_info.leaf_reveal
|
||||
host_genesis_id = self.dlo_info.host_genesis_id
|
||||
claim_target = self.dlo_info.claim_target
|
||||
recovery_target = self.dlo_info.recovery_target
|
||||
recovery_timelock = self.dlo_info.recovery_timelock
|
||||
full_puzzle: Program = create_offer_fullpuz(
|
||||
leaf_reveal, host_genesis_id, claim_target, recovery_target, recovery_timelock # type: ignore[arg-type]
|
||||
)
|
||||
coin_spend = CoinSpend(coin, full_puzzle, solution)
|
||||
sb = SpendBundle([coin_spend], AugSchemeMPL.aggregate([]))
|
||||
# TODO: fix optionality issue with to_puzzle_hash
|
||||
tr = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=recovery_target, # type: ignore[arg-type]
|
||||
amount=uint64(coin.amount),
|
||||
fee_amount=uint64(fee),
|
||||
confirmed=False,
|
||||
sent=uint32(0),
|
||||
spend_bundle=sb,
|
||||
additions=list(sb.additions()),
|
||||
removals=list(sb.removals()),
|
||||
memos=list(compute_memos(sb).items()),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
name=sb.name(),
|
||||
)
|
||||
await self.standard_wallet.push_transaction(tr)
|
||||
return tr
|
||||
|
||||
async def get_coin(self) -> Coin:
|
||||
try:
|
||||
coins = await self.select_coins(uint64(1))
|
||||
except ValueError:
|
||||
# TODO: not really right exactly the same since there are two cases of ValueError...
|
||||
coins = set()
|
||||
|
||||
if len(coins) > 1:
|
||||
return coins.pop()
|
||||
|
||||
if self.dlo_info.active_offer is None:
|
||||
# TODO: is this what we want here?
|
||||
raise ValueError("Unable to get a coin, no coins selected and no active offer.")
|
||||
|
||||
return self.dlo_info.active_offer
|
||||
|
||||
async def get_confirmed_balance(self, record_list: Optional[Set[WalletCoinRecord]] = None) -> uint64:
|
||||
if record_list is None:
|
||||
record_list = await self.wallet_state_manager.coin_store.get_unspent_coins_for_wallet(self.id())
|
||||
|
||||
amount: uint64 = uint64(0)
|
||||
for record in record_list:
|
||||
amount = uint64(amount + record.coin.amount)
|
||||
|
||||
self.log.info(f"Confirmed balance for dlo wallet is {amount}")
|
||||
return uint64(amount)
|
||||
|
||||
async def get_unconfirmed_balance(self, record_list: Optional[Set[WalletCoinRecord]] = None) -> uint64:
|
||||
# TODO: should the uint128 be changed?
|
||||
return await self.wallet_state_manager.get_unconfirmed_balance(self.id(), record_list) # type: ignore[return-value] # noqa: E501
|
||||
|
||||
async def get_spendable_balance(self, unspent_records: Optional[Set[WalletCoinRecord]] = None) -> uint64:
|
||||
spendable_am = await self.wallet_state_manager.get_confirmed_spendable_balance_for_wallet(
|
||||
self.wallet_info.id, unspent_records
|
||||
)
|
||||
# TODO: should the uint128 be changed?
|
||||
return spendable_am # type: ignore[return-value]
|
||||
|
||||
async def select_coins(self, amount: uint64, exclude: Optional[List[Coin]] = None) -> Set[Coin]:
|
||||
"""Returns a set of coins that can be used for generating a new transaction."""
|
||||
if exclude is None:
|
||||
exclude = []
|
||||
|
||||
spendable_amount = await self.get_spendable_balance()
|
||||
if amount > spendable_amount:
|
||||
error_msg = f"Can't select {amount}, from spendable {spendable_amount} for wallet id {self.id()}"
|
||||
self.log.warning(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
self.log.info(f"About to select coins for amount {amount}")
|
||||
unspent: List[WalletCoinRecord] = list(
|
||||
await self.wallet_state_manager.get_spendable_coins_for_wallet(self.wallet_info.id)
|
||||
)
|
||||
sum_value = 0
|
||||
used_coins: Set[Coin] = set()
|
||||
|
||||
# Use older coins first
|
||||
unspent.sort(key=lambda r: r.confirmed_block_height)
|
||||
|
||||
# Try to use coins from the store, if there isn't enough of "unused"
|
||||
# coins use change coins that are not confirmed yet
|
||||
unconfirmed_removals: Dict[bytes32, Coin] = await self.wallet_state_manager.unconfirmed_removals_for_wallet(
|
||||
self.wallet_info.id
|
||||
)
|
||||
for coinrecord in unspent:
|
||||
if sum_value >= amount and len(used_coins) > 0:
|
||||
break
|
||||
if coinrecord.coin.name() in unconfirmed_removals:
|
||||
continue
|
||||
if coinrecord.coin in exclude:
|
||||
continue
|
||||
sum_value += coinrecord.coin.amount
|
||||
used_coins.add(coinrecord.coin)
|
||||
|
||||
# This happens when we couldn't use one of the coins because it's already used
|
||||
# but unconfirmed, and we are waiting for the change. (unconfirmed_additions)
|
||||
if sum_value < amount:
|
||||
raise ValueError(
|
||||
"Can't make this transaction at the moment. Waiting for the change from the previous transaction."
|
||||
)
|
||||
|
||||
self.log.info(f"Successfully selected coins: {used_coins}")
|
||||
return used_coins
|
||||
|
||||
async def save_info(self, dlo_info: DLOInfo, in_transaction: bool) -> None:
|
||||
self.dlo_info = dlo_info
|
||||
current_info = self.wallet_info
|
||||
info_as_string = bytes(self.dlo_info).hex()
|
||||
wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, info_as_string)
|
||||
self.wallet_info = wallet_info
|
||||
await self.wallet_state_manager.user_store.update_wallet(wallet_info, in_transaction)
|
||||
await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_info.id)
|
||||
return
|
||||
@@ -35,19 +35,6 @@ def create_nft_layer_puzzle_with_curry_params(
|
||||
METADATA
|
||||
METADATA_UPDATER_PUZZLE_HASH
|
||||
INNER_PUZZLE"""
|
||||
log.debug(
|
||||
"Creating nft layer puzzle curry: mod_hash: %s, metadata: %r, metadata_hash: %s",
|
||||
NFT_STATE_LAYER_MOD_HASH,
|
||||
metadata,
|
||||
metadata_updater_hash,
|
||||
)
|
||||
log.debug(
|
||||
"Currying with: %s %s %s %s",
|
||||
NFT_STATE_LAYER_MOD_HASH,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
metadata_updater_hash,
|
||||
metadata.get_tree_hash(),
|
||||
)
|
||||
return NFT_STATE_LAYER_MOD.curry(NFT_STATE_LAYER_MOD_HASH, metadata, metadata_updater_hash, inner_puzzle)
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar
|
||||
from blspy import AugSchemeMPL, G2Element
|
||||
|
||||
from chia.protocols.wallet_protocol import CoinState
|
||||
from chia.server.outbound_message import NodeType
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.types.announcement import Announcement
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
@@ -158,22 +156,14 @@ class NFTWallet:
|
||||
if coin_info.coin == coin:
|
||||
return
|
||||
wallet_node = self.wallet_state_manager.wallet_node
|
||||
server = wallet_node.server
|
||||
full_nodes: Dict[bytes32, WSChiaConnection] = server.connection_by_type.get(NodeType.FULL_NODE, {})
|
||||
cs: Optional[CoinSpend] = None
|
||||
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
|
||||
[coin.parent_coin_info]
|
||||
)
|
||||
coin_states: Optional[List[CoinState]] = await wallet_node.get_coin_state([coin.parent_coin_info])
|
||||
if not coin_states:
|
||||
# farm coin
|
||||
return
|
||||
assert coin_states
|
||||
parent_coin = coin_states[0].coin
|
||||
for node_id in full_nodes:
|
||||
node = server.all_connections[node_id]
|
||||
cs = await wallet_node.fetch_puzzle_solution(node, height, parent_coin)
|
||||
if cs is not None:
|
||||
break
|
||||
cs = await wallet_node.fetch_puzzle_solution(height, parent_coin)
|
||||
assert cs is not None
|
||||
await self.puzzle_solution_received(cs, in_transaction=in_transaction)
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
(mod (
|
||||
DB_LAYER_MOD_HASH ; This mod file - used to update our state
|
||||
CURRENT_STATE ; 32 byte merkle root
|
||||
INNER_PUZZLE_HASH ; Our inner inner puzzle
|
||||
spend_type
|
||||
inner_solution ; if spend_type is 1 this is my_amount
|
||||
inner_puzzle ; Useless in the report case, should hash to the curried in hash if doing update
|
||||
)
|
||||
|
||||
(include condition_codes.clvm)
|
||||
(include curry-and-treehash.clinc)
|
||||
|
||||
(defmacro assert items
|
||||
(if (r items)
|
||||
(list if (f items) (c assert (r items)) (q . (x)))
|
||||
(f items)
|
||||
)
|
||||
)
|
||||
|
||||
(defun sha256tree1 (TREE)
|
||||
(if (l TREE)
|
||||
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
|
||||
(sha256 ONE TREE)))
|
||||
|
||||
(defun-inline current_puzzle_hash (DB_LAYER_MOD_HASH CURRENT_STATE INNER_PUZZLE_HASH)
|
||||
(puzzle-hash-of-curried-function DB_LAYER_MOD_HASH
|
||||
(sha256 1 INNER_PUZZLE_HASH)
|
||||
(sha256 1 CURRENT_STATE)
|
||||
(sha256 1 DB_LAYER_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
; spend_type 1 is REPORT current state
|
||||
; spend_type 0 is UPDATE the current state
|
||||
(if spend_type
|
||||
; REPORT
|
||||
(list
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT CURRENT_STATE)
|
||||
(list CREATE_COIN (current_puzzle_hash DB_LAYER_MOD_HASH CURRENT_STATE INNER_PUZZLE_HASH) inner_solution)
|
||||
(list ASSERT_MY_AMOUNT inner_solution)
|
||||
)
|
||||
; UPDATE
|
||||
(assert (= INNER_PUZZLE_HASH (sha256tree1 inner_puzzle)) (a inner_puzzle inner_solution))
|
||||
)
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
ff02ffff01ff02ffff03ff2fffff01ff04ffff04ff2cffff04ff0bff808080ffff04ffff04ff14ffff04ffff02ff2effff04ff02ffff04ff05ffff04ffff0bffff0101ff1780ffff04ffff0bffff0101ff0b80ffff04ffff0bffff0101ff0580ff80808080808080ffff04ff5fff80808080ffff04ffff04ff10ffff04ff5fff808080ff80808080ffff01ff02ffff03ffff09ff17ffff02ff3effff04ff02ffff04ff81bfff8080808080ffff01ff02ff81bfff5f80ffff01ff088080ff018080ff0180ffff04ffff01ffffff4902ff33ff3e04ffff01ff0102ffff02ffff03ff05ffff01ff02ff16ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ffff0bff3affff0bff12ff1880ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff16ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bff12ff058080ff0180ff018080
|
||||
@@ -1 +0,0 @@
|
||||
dff4698ae98ada214a7e7f3b37482fab7927532f90fabe02ab6f5229df284f8f
|
||||
@@ -1,70 +0,0 @@
|
||||
(mod (
|
||||
DB_LAYER_MOD_HASH
|
||||
DB_SINGLETON_STRUCT
|
||||
LEAF_REVEAL
|
||||
CLAIM_TARGET
|
||||
RECOVERY_TARGET
|
||||
RECOVERY_TIMELOCK
|
||||
spend_type ; 1 is claim, 0 is recover
|
||||
my_amount
|
||||
db_innerpuz_hash
|
||||
current_root
|
||||
inclusion_proof
|
||||
)
|
||||
; DB_SINGLETON_STRUCT is (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
||||
; current_root is a bytes32 of the top of the merkle tree in the db_host - also known as current_state
|
||||
; inclusion_proof is a list where first entry is a binary representation of path, and subsequent entries are corresponding merkle hashes
|
||||
(include condition_codes.clvm)
|
||||
(include curry-and-treehash.clinc)
|
||||
|
||||
(defun-inline tree_branch_hash (left right) (sha256 2 left right))
|
||||
|
||||
(defun calculate_merkle_root (path current_hash additional_steps)
|
||||
(if additional_steps
|
||||
(calculate_merkle_root
|
||||
(lsh path -1)
|
||||
(if (logand path 1)
|
||||
(tree_branch_hash
|
||||
(f additional_steps)
|
||||
current_hash
|
||||
)
|
||||
(tree_branch_hash
|
||||
current_hash
|
||||
(f additional_steps)
|
||||
)
|
||||
)
|
||||
(r additional_steps)
|
||||
)
|
||||
current_hash
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_merkle_root_for_merkle_proof (LEAF_REVEAL proof_of_inclusion)
|
||||
(calculate_merkle_root
|
||||
(f proof_of_inclusion)
|
||||
(sha256tree LEAF_REVEAL) ; should this be tree-hashed when curried in?
|
||||
(if (r proof_of_inclusion) (f (r proof_of_inclusion)) ())
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_db_current_puzzlehash (DB_SINGLETON_STRUCT DB_LAYER_MOD_HASH current_root db_innerpuz_hash)
|
||||
(puzzle-hash-of-curried-function (f DB_SINGLETON_STRUCT)
|
||||
(puzzle-hash-of-curried-function DB_LAYER_MOD_HASH
|
||||
(sha256 1 db_innerpuz_hash)
|
||||
(sha256 1 current_root)
|
||||
(sha256 1 DB_LAYER_MOD_HASH)
|
||||
)
|
||||
(sha256tree DB_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(if spend_type
|
||||
; CLAIM
|
||||
(if (= current_root (calculate_merkle_root_for_merkle_proof LEAF_REVEAL inclusion_proof)) ; current root is validated in assert_puzzle_announcement below
|
||||
(list (list CREATE_COIN CLAIM_TARGET my_amount) (list ASSERT_MY_AMOUNT my_amount) (list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 (calculate_db_current_puzzlehash DB_SINGLETON_STRUCT DB_LAYER_MOD_HASH current_root db_innerpuz_hash) current_root)))
|
||||
(x)
|
||||
)
|
||||
; RECOVER
|
||||
(list (list CREATE_COIN RECOVERY_TARGET my_amount) (list ASSERT_MY_AMOUNT my_amount) (list ASSERT_SECONDS_RELATIVE RECOVERY_TIMELOCK))
|
||||
)
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
ff02ffff01ff02ffff03ff82017fffff01ff02ffff03ffff09ff820bffffff02ff36ffff04ff02ffff04ff17ffff04ff8217ffff808080808080ffff01ff04ffff04ff34ffff04ff2fffff04ff8202ffff80808080ffff04ffff04ff10ffff04ff8202ffff808080ffff04ffff04ff28ffff04ffff0bffff02ff3affff04ff02ffff04ff0bffff04ff05ffff04ff820bffffff04ff8205ffff80808080808080ff820bff80ff808080ff80808080ffff01ff088080ff0180ffff01ff04ffff04ff34ffff04ff5fffff04ff8202ffff80808080ffff04ffff04ff10ffff04ff8202ffff808080ffff04ffff04ff38ffff04ff81bfff808080ff8080808080ff0180ffff04ffff01ffffff49ff3f50ffff0233ff0401ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff3cff2c80ffff0bff32ffff0bff32ffff0bff3cff2280ff0980ffff0bff32ff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ff02ff2effff04ff02ffff04ff09ffff04ffff02ff2effff04ff02ffff04ff0bffff04ffff0bffff0101ff2f80ffff04ffff0bffff0101ff1780ffff04ffff0bffff0101ff0b80ff80808080808080ffff04ffff02ff3effff04ff02ffff04ff05ff80808080ff808080808080ffffff02ffff03ff17ffff01ff02ff26ffff04ff02ffff04ffff17ff05ffff0181ff80ffff04ffff02ffff03ffff18ff05ffff010180ffff01ff0bffff0102ff27ff0b80ffff01ff0bffff0102ff0bff278080ff0180ffff04ff37ff808080808080ffff010b80ff0180ff02ff26ffff04ff02ffff04ff13ffff04ffff02ff3effff04ff02ffff04ff05ff80808080ffff04ffff02ffff03ff1bffff012bff8080ff0180ff808080808080ffff0bff32ffff0bff3cff2480ffff0bff32ffff0bff32ffff0bff3cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
||||
@@ -1 +0,0 @@
|
||||
6b5c5137fddf15424f97953a00111589838d42eb2bf314f1cc37e71144009881
|
||||
@@ -1,173 +0,0 @@
|
||||
(mod (SINGLETON_STRUCT INNER_PUZZLE lineage_proof my_amount inner_solution)
|
||||
|
||||
;; SINGLETON_STRUCT = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
||||
|
||||
; SINGLETON_STRUCT, INNER_PUZZLE are curried in by the wallet
|
||||
|
||||
; EXAMPLE SOLUTION '(0xfadeddab 0xdeadbeef 1 (0xdeadbeef 200) 50 ((51 0xfadeddab 100) (60 "trash") (51 deadbeef 0)))'
|
||||
|
||||
|
||||
; This puzzle is a wrapper around an inner smart puzzle which guarantees uniqueness.
|
||||
; It takes its singleton identity from a coin with a launcher puzzle which guarantees that it is unique.
|
||||
|
||||
(include condition_codes.clvm)
|
||||
(include curry-and-treehash.clinc) ; also imports the constant ONE == 1
|
||||
(include singleton_truths.clib)
|
||||
|
||||
; takes a lisp tree and returns the hash of it
|
||||
(defun sha256tree1 (TREE)
|
||||
(if (l TREE)
|
||||
(sha256 2 (sha256tree1 (f TREE)) (sha256tree1 (r TREE)))
|
||||
(sha256 ONE TREE)
|
||||
)
|
||||
)
|
||||
|
||||
; "assert" is a macro that wraps repeated instances of "if"
|
||||
; usage: (assert A0 A1 ... An R)
|
||||
; all of A0, A1, ... An must evaluate to non-null, or an exception is raised
|
||||
; return the value of R (if we get that far)
|
||||
|
||||
(defmacro assert items
|
||||
(if (r items)
|
||||
(list if (f items) (c assert (r items)) (q . (x)))
|
||||
(f items)
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline mod_hash_for_singleton_struct (SINGLETON_STRUCT) (f SINGLETON_STRUCT))
|
||||
(defun-inline launcher_id_for_singleton_struct (SINGLETON_STRUCT) (f (r SINGLETON_STRUCT)))
|
||||
(defun-inline launcher_puzzle_hash_for_singleton_struct (SINGLETON_STRUCT) (r (r SINGLETON_STRUCT)))
|
||||
|
||||
;; return the full puzzlehash for a singleton with the innerpuzzle curried in
|
||||
; puzzle-hash-of-curried-function is imported from curry-and-treehash.clinc
|
||||
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (mod_hash_for_singleton_struct SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree1 SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
; assembles information from the solution to create our own full ID including asserting our parent is a singleton
|
||||
(defun-inline create_my_ID (SINGLETON_STRUCT full_puzzle_hash parent_parent parent_inner_puzzle_hash parent_amount my_amount)
|
||||
(sha256 (sha256 parent_parent (calculate_full_puzzle_hash SINGLETON_STRUCT parent_inner_puzzle_hash) parent_amount)
|
||||
full_puzzle_hash
|
||||
my_amount)
|
||||
)
|
||||
|
||||
;; take a boolean and a non-empty list of conditions
|
||||
;; strip off the first condition if a boolean is set
|
||||
;; this is used to remove `(CREATE_COIN xxx -113)`
|
||||
;; pretty sneaky, eh?
|
||||
(defun strip_first_condition_if (boolean condition_list)
|
||||
(if boolean
|
||||
(r condition_list)
|
||||
condition_list
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline morph_condition (condition SINGLETON_STRUCT)
|
||||
(c (f condition) (c (calculate_full_puzzle_hash SINGLETON_STRUCT (f (r condition))) (r (r condition))))
|
||||
)
|
||||
|
||||
;; return the value of the coin created if this is a `CREATE_COIN` condition, or 0 otherwise
|
||||
(defun-inline created_coin_value_or_0 (condition)
|
||||
(if (= (f condition) CREATE_COIN)
|
||||
(f (r (r condition)))
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
;; Returns a (bool . bool)
|
||||
(defun odd_cons_m113 (output_amount)
|
||||
(c
|
||||
(= (logand output_amount ONE) ONE) ;; is it odd?
|
||||
(= output_amount -113) ;; is it the escape value?
|
||||
)
|
||||
)
|
||||
|
||||
; Assert exactly one output with odd value exists - ignore it if value is -113
|
||||
|
||||
;; this function iterates over the output conditions from the inner puzzle & solution
|
||||
;; and both checks that exactly one unique singleton child is created (with odd valued output),
|
||||
;; and wraps the inner puzzle with this same singleton wrapper puzzle
|
||||
;;
|
||||
;; The special case where the output value is -113 means a child singleton is intentionally
|
||||
;; *NOT* being created, thus forever ending this singleton's existence
|
||||
|
||||
(defun check_and_morph_conditions_for_singleton (SINGLETON_STRUCT conditions has_odd_output_been_found)
|
||||
(if conditions
|
||||
(morph_next_condition SINGLETON_STRUCT conditions has_odd_output_been_found (odd_cons_m113 (created_coin_value_or_0 (f conditions))))
|
||||
(if has_odd_output_been_found
|
||||
0
|
||||
(x) ;; no odd output found
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; a continuation of `check_and_morph_conditions_for_singleton` with booleans `is_output_odd` and `is_output_m113`
|
||||
;; precalculated
|
||||
(defun morph_next_condition (SINGLETON_STRUCT conditions has_odd_output_been_found (is_output_odd . is_output_m113))
|
||||
(assert
|
||||
(not (all is_output_odd has_odd_output_been_found))
|
||||
(strip_first_condition_if
|
||||
is_output_m113
|
||||
(c (if is_output_odd
|
||||
(morph_condition (f conditions) SINGLETON_STRUCT)
|
||||
(f conditions)
|
||||
)
|
||||
(check_and_morph_conditions_for_singleton SINGLETON_STRUCT (r conditions) (any is_output_odd has_odd_output_been_found))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; this final stager asserts our ID
|
||||
; it also runs the innerpuz with the innersolution with the "truths" added
|
||||
; it then passes that output conditions from the innerpuz to the morph conditions function
|
||||
(defun-inline stager_three (SINGLETON_STRUCT my_id INNER_PUZZLE inner_solution)
|
||||
(c (list ASSERT_MY_COIN_ID my_id) (check_and_morph_conditions_for_singleton SINGLETON_STRUCT (a INNER_PUZZLE inner_solution) 0))
|
||||
)
|
||||
|
||||
; this checks whether we are an eve spend or not and calculates our full coin ID appropriately and passes it on to the final stager
|
||||
; if we are the eve spend it also adds the additional checks that our parent's puzzle is the standard launcher format and that out parent ID is the same as our singleton ID
|
||||
|
||||
(defun-inline stager_two (SINGLETON_STRUCT lineage_proof full_puzhash my_amount INNER_PUZZLE inner_solution)
|
||||
(stager_three
|
||||
SINGLETON_STRUCT
|
||||
(if (is_not_eve_proof lineage_proof)
|
||||
(create_my_ID
|
||||
SINGLETON_STRUCT
|
||||
full_puzhash
|
||||
(parent_info_for_lineage_proof lineage_proof)
|
||||
(puzzle_hash_for_lineage_proof lineage_proof)
|
||||
(amount_for_lineage_proof lineage_proof)
|
||||
my_amount
|
||||
)
|
||||
(if (=
|
||||
(launcher_id_for_singleton_struct SINGLETON_STRUCT)
|
||||
(sha256 (parent_info_for_eve_proof lineage_proof) (launcher_puzzle_hash_for_singleton_struct SINGLETON_STRUCT) (amount_for_eve_proof lineage_proof))
|
||||
)
|
||||
(sha256 (launcher_id_for_singleton_struct SINGLETON_STRUCT) full_puzhash my_amount)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
INNER_PUZZLE
|
||||
inner_solution
|
||||
)
|
||||
)
|
||||
|
||||
; this calculates our current full puzzle hash and passes it to stager two
|
||||
(defun-inline stager_one (SINGLETON_STRUCT lineage_proof my_innerpuzhash my_amount INNER_PUZZLE inner_solution)
|
||||
(stager_two SINGLETON_STRUCT lineage_proof (calculate_full_puzzle_hash SINGLETON_STRUCT my_innerpuzhash) my_amount INNER_PUZZLE inner_solution)
|
||||
)
|
||||
|
||||
|
||||
; main
|
||||
|
||||
; if our value is not an odd amount then we are invalid
|
||||
; this calculates my_innerpuzhash and passes all values to stager_one
|
||||
(if (logand my_amount ONE)
|
||||
(stager_one SINGLETON_STRUCT lineage_proof (sha256tree1 INNER_PUZZLE) my_amount INNER_PUZZLE inner_solution)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff10ffff04ffff02ffff03ff77ffff01ff0bffff0bff27ffff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff81b780ffff02ff36ffff04ff02ffff04ff09ffff04ffff02ff2effff04ff02ffff04ff0bff80808080ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff2f80ffff01ff02ffff03ffff09ff15ffff0bff27ff1dff578080ffff01ff0bff15ffff02ff36ffff04ff02ffff04ff09ffff04ffff02ff2effff04ff02ffff04ff0bff80808080ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff2f80ffff01ff088080ff018080ff0180ff808080ffff02ff2affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff80808080808080ffff01ff088080ff0180ffff04ffff01ffffff46ff0233ffff0401ff0102ffffff02ffff03ff05ffff01ff02ff12ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ffff02ff26ffff04ff02ffff04ffff02ffff03ffff09ff23ff3880ffff0181b3ff8080ff0180ff80808080ff80808080808080ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ff02ffff03ffff20ffff22ff4fff178080ffff01ff02ff3effff04ff02ffff04ff6fffff04ffff04ffff02ffff03ff4fffff01ff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff011380ff0180ffff02ff2affff04ff02ffff04ff05ffff04ff1bffff04ffff21ff4fff1780ff80808080808080ff8080808080ffff01ff088080ff0180ffffff04ffff09ffff18ff05ff3480ff3480ffff09ff05ffff01818f8080ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff12ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff34ff058080ff0180ff02ffff03ff05ffff011bffff010b80ff0180ff018080
|
||||
@@ -1 +0,0 @@
|
||||
ffc0846eea5898e9aefea956b7d0e08df97a1c080910648270d979a4900d88aa
|
||||
@@ -4,6 +4,8 @@ import time
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from typing_extensions import Literal
|
||||
|
||||
from chia.protocols.wallet_protocol import CoinState
|
||||
from chia.types.blockchain_format.coin import Coin, coin_as_list
|
||||
from chia.types.blockchain_format.program import Program
|
||||
@@ -297,12 +299,14 @@ class TradeManager:
|
||||
driver_dict: Optional[Dict[bytes32, PuzzleInfo]] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
validate_only: bool = False,
|
||||
) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
|
||||
) -> Union[Tuple[Literal[True], TradeRecord, None], Tuple[Literal[False], None, str]]:
|
||||
if driver_dict is None:
|
||||
driver_dict = {}
|
||||
success, created_offer, error = await self._create_offer_for_ids(offer, driver_dict, fee=fee)
|
||||
if not success or created_offer is None:
|
||||
raise Exception(f"Error creating offer: {error}")
|
||||
result = await self._create_offer_for_ids(offer, driver_dict, fee=fee)
|
||||
if not result[0] or result[1] is None:
|
||||
raise Exception(f"Error creating offer: {result[2]}")
|
||||
|
||||
success, created_offer, error = result
|
||||
|
||||
now = uint64(int(time.time()))
|
||||
trade_offer: TradeRecord = TradeRecord(
|
||||
@@ -329,7 +333,7 @@ class TradeManager:
|
||||
offer_dict: Dict[Union[int, bytes32], int],
|
||||
driver_dict: Optional[Dict[bytes32, PuzzleInfo]] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
) -> Tuple[bool, Optional[Offer], Optional[str]]:
|
||||
) -> Union[Tuple[Literal[True], Offer, None], Tuple[Literal[False], None, str]]:
|
||||
"""
|
||||
Offer is dictionary of wallet ids and amount
|
||||
"""
|
||||
@@ -582,7 +586,11 @@ class TradeManager:
|
||||
|
||||
return txs
|
||||
|
||||
async def respond_to_offer(self, offer: Offer, fee=uint64(0)) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
|
||||
async def respond_to_offer(
|
||||
self,
|
||||
offer: Offer,
|
||||
fee=uint64(0),
|
||||
) -> Union[Tuple[Literal[True], TradeRecord, None], Tuple[Literal[False], None, str]]:
|
||||
take_offer_dict: Dict[Union[bytes32, int], int] = {}
|
||||
arbitrage: Dict[Optional[bytes32], int] = offer.arbitrage()
|
||||
|
||||
@@ -605,9 +613,11 @@ class TradeManager:
|
||||
valid: bool = await self.check_offer_validity(offer)
|
||||
if not valid:
|
||||
return False, None, "This offer is no longer valid"
|
||||
success, take_offer, error = await self._create_offer_for_ids(take_offer_dict, offer.driver_dict, fee=fee)
|
||||
if not success or take_offer is None:
|
||||
return False, None, error
|
||||
result = await self._create_offer_for_ids(take_offer_dict, offer.driver_dict, fee=fee)
|
||||
if not result[0] or result[1] is None:
|
||||
return False, None, result[2]
|
||||
|
||||
success, take_offer, error = result
|
||||
|
||||
complete_offer = Offer.aggregate([offer, take_offer])
|
||||
assert complete_offer.is_valid()
|
||||
|
||||
@@ -45,6 +45,8 @@ class TransactionRecord(Streamable):
|
||||
sent_to: List[Tuple[str, uint8, Optional[str]]]
|
||||
trade_id: Optional[bytes32]
|
||||
type: uint32 # TransactionType
|
||||
|
||||
# name is also called bundle_id and tx_id
|
||||
name: bytes32
|
||||
memos: List[Tuple[bytes32, List[bytes]]]
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import enum
|
||||
|
||||
|
||||
class SortKey(enum.Enum):
|
||||
CONFIRMED_AT_HEIGHT = "order by confirmed_at_height {ASC}"
|
||||
RELEVANCE = "order by confirmed {ASC}, confirmed_at_height {DESC}, created_at_time {DESC}"
|
||||
CONFIRMED_AT_HEIGHT = "ORDER BY confirmed_at_height {ASC}"
|
||||
RELEVANCE = "ORDER BY confirmed {ASC}, confirmed_at_height {DESC}, created_at_time {DESC}"
|
||||
|
||||
def ascending(self) -> str:
|
||||
return self.value.format(ASC="ASC", DESC="DESC")
|
||||
|
||||
@@ -23,6 +23,8 @@ from chia.protocols.wallet_protocol import (
|
||||
RespondToCoinUpdates,
|
||||
RespondHeaderBlocks,
|
||||
RequestHeaderBlocks,
|
||||
RejectHeaderBlocks,
|
||||
RejectBlockHeaders,
|
||||
)
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.types.blockchain_format.coin import hash_coin_ids, Coin
|
||||
@@ -325,7 +327,7 @@ async def request_header_blocks(
|
||||
response = await peer.request_block_headers(RequestBlockHeaders(start_height, end_height, False))
|
||||
else:
|
||||
response = await peer.request_header_blocks(RequestHeaderBlocks(start_height, end_height))
|
||||
if response is None:
|
||||
if response is None or isinstance(response, RejectBlockHeaders) or isinstance(response, RejectHeaderBlocks):
|
||||
return None
|
||||
return response.header_blocks
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Optional, Set
|
||||
from typing import List, Optional, Set
|
||||
|
||||
import aiosqlite
|
||||
import sqlite3
|
||||
@@ -17,10 +17,6 @@ class WalletCoinStore:
|
||||
"""
|
||||
|
||||
db_connection: aiosqlite.Connection
|
||||
# coin_record_cache keeps ALL coin records in memory. [record_name: record]
|
||||
coin_record_cache: Dict[bytes32, WalletCoinRecord]
|
||||
# unspent_coin_wallet_cache keeps ALL unspent coin records for wallet in memory [wallet_id: [record_name: record]]
|
||||
unspent_coin_wallet_cache: Dict[int, Dict[bytes32, WalletCoinRecord]]
|
||||
db_wrapper: DBWrapper
|
||||
|
||||
@classmethod
|
||||
@@ -61,9 +57,6 @@ class WalletCoinStore:
|
||||
await self.db_connection.execute("CREATE INDEX IF NOT EXISTS wallet_id on coin_record(wallet_id)")
|
||||
|
||||
await self.db_connection.commit()
|
||||
self.coin_record_cache = {}
|
||||
self.unspent_coin_wallet_cache = {}
|
||||
await self.rebuild_wallet_cache()
|
||||
return self
|
||||
|
||||
async def _clear_database(self):
|
||||
@@ -71,49 +64,23 @@ class WalletCoinStore:
|
||||
await cursor.close()
|
||||
await self.db_connection.commit()
|
||||
|
||||
async def rebuild_wallet_cache(self):
|
||||
# First update all coins that were reorged, then re-add coin_records
|
||||
all_coins = await self.get_all_coins()
|
||||
self.unspent_coin_wallet_cache = {}
|
||||
self.coin_record_cache = {}
|
||||
for coin_record in all_coins:
|
||||
name = coin_record.name()
|
||||
self.coin_record_cache[name] = coin_record
|
||||
if coin_record.spent is False:
|
||||
if coin_record.wallet_id not in self.unspent_coin_wallet_cache:
|
||||
self.unspent_coin_wallet_cache[coin_record.wallet_id] = {}
|
||||
self.unspent_coin_wallet_cache[coin_record.wallet_id][name] = coin_record
|
||||
|
||||
async def get_multiple_coin_records(self, coin_names: List[bytes32]) -> List[WalletCoinRecord]:
|
||||
"""Return WalletCoinRecord(s) that have a coin name in the specified list"""
|
||||
if set(coin_names).issubset(set(self.coin_record_cache.keys())):
|
||||
return list(filter(lambda cr: cr.coin.name() in coin_names, self.coin_record_cache.values()))
|
||||
else:
|
||||
as_hexes = [cn.hex() for cn in coin_names]
|
||||
cursor = await self.db_connection.execute(
|
||||
f'SELECT * from coin_record WHERE coin_name in ({"?," * (len(as_hexes) - 1)}?)', tuple(as_hexes)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
if len(coin_names) == 0:
|
||||
return []
|
||||
|
||||
return [self.coin_record_from_row(row) for row in rows]
|
||||
as_hexes = [cn.hex() for cn in coin_names]
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
f'SELECT * from coin_record WHERE coin_name in ({"?," * (len(as_hexes) - 1)}?)', tuple(as_hexes)
|
||||
)
|
||||
|
||||
return [self.coin_record_from_row(row) for row in rows]
|
||||
|
||||
# Store CoinRecord in DB and ram cache
|
||||
async def add_coin_record(self, record: WalletCoinRecord, name: Optional[bytes32] = None) -> None:
|
||||
# update wallet cache
|
||||
if name is None:
|
||||
name = record.name()
|
||||
self.coin_record_cache[name] = record
|
||||
if record.wallet_id in self.unspent_coin_wallet_cache:
|
||||
if record.spent and name in self.unspent_coin_wallet_cache[record.wallet_id]:
|
||||
self.unspent_coin_wallet_cache[record.wallet_id].pop(name)
|
||||
if not record.spent:
|
||||
self.unspent_coin_wallet_cache[record.wallet_id][name] = record
|
||||
else:
|
||||
if not record.spent:
|
||||
self.unspent_coin_wallet_cache[record.wallet_id] = {}
|
||||
self.unspent_coin_wallet_cache[record.wallet_id][name] = record
|
||||
|
||||
assert record.spent == (record.spent_block_height != 0)
|
||||
cursor = await self.db_connection.execute(
|
||||
"INSERT OR REPLACE INTO coin_record VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
@@ -133,13 +100,6 @@ class WalletCoinStore:
|
||||
|
||||
# Sometimes we realize that a coin is actually not interesting to us so we need to delete it
|
||||
async def delete_coin_record(self, coin_name: bytes32) -> None:
|
||||
if coin_name in self.coin_record_cache:
|
||||
coin_record = self.coin_record_cache.pop(coin_name)
|
||||
if coin_record.wallet_id in self.unspent_coin_wallet_cache:
|
||||
coin_cache = self.unspent_coin_wallet_cache[coin_record.wallet_id]
|
||||
if coin_name in coin_cache:
|
||||
coin_cache.pop(coin_record.coin.name())
|
||||
|
||||
c = await self.db_connection.execute("DELETE FROM coin_record WHERE coin_name=?", (coin_name.hex(),))
|
||||
await c.close()
|
||||
|
||||
@@ -170,108 +130,65 @@ class WalletCoinStore:
|
||||
|
||||
async def get_coin_record(self, coin_name: bytes32) -> Optional[WalletCoinRecord]:
|
||||
"""Returns CoinRecord with specified coin id."""
|
||||
if coin_name in self.coin_record_cache:
|
||||
return self.coin_record_cache[coin_name]
|
||||
cursor = await self.db_connection.execute("SELECT * from coin_record WHERE coin_name=?", (coin_name.hex(),))
|
||||
row = await cursor.fetchone()
|
||||
await cursor.close()
|
||||
rows = list(
|
||||
await self.db_connection.execute_fetchall("SELECT * from coin_record WHERE coin_name=?", (coin_name.hex(),))
|
||||
)
|
||||
|
||||
if row is None:
|
||||
if len(rows) == 0:
|
||||
return None
|
||||
return self.coin_record_from_row(row)
|
||||
return self.coin_record_from_row(rows[0])
|
||||
|
||||
async def get_first_coin_height(self) -> Optional[uint32]:
|
||||
"""Returns height of first confirmed coin"""
|
||||
cursor = await self.db_connection.execute("SELECT MIN(confirmed_height) FROM coin_record;")
|
||||
row = await cursor.fetchone()
|
||||
await cursor.close()
|
||||
rows = list(await self.db_connection.execute_fetchall("SELECT MIN(confirmed_height) FROM coin_record"))
|
||||
|
||||
if row is not None and row[0] is not None:
|
||||
return uint32(row[0])
|
||||
if len(rows) != 0 and rows[0][0] is not None:
|
||||
return uint32(rows[0][0])
|
||||
|
||||
return None
|
||||
|
||||
async def get_unspent_coins_for_wallet(self, wallet_id: int) -> Set[WalletCoinRecord]:
|
||||
"""Returns set of CoinRecords that have not been spent yet for a wallet."""
|
||||
if wallet_id in self.unspent_coin_wallet_cache:
|
||||
wallet_coins: Dict[bytes32, WalletCoinRecord] = self.unspent_coin_wallet_cache[wallet_id]
|
||||
return set(wallet_coins.values())
|
||||
else:
|
||||
return set()
|
||||
|
||||
async def get_all_coins(self) -> Set[WalletCoinRecord]:
|
||||
"""Returns set of all CoinRecords."""
|
||||
cursor = await self.db_connection.execute("SELECT * from coin_record")
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * FROM coin_record WHERE wallet_id=? AND spent_height=0", (wallet_id,)
|
||||
)
|
||||
return set(self.coin_record_from_row(row) for row in rows)
|
||||
|
||||
async def get_coins_to_check(self, check_height) -> Set[WalletCoinRecord]:
|
||||
"""Returns set of all CoinRecords."""
|
||||
cursor = await self.db_connection.execute(
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from coin_record where spent_height=0 or spent_height>? or confirmed_height>?",
|
||||
(
|
||||
check_height,
|
||||
check_height,
|
||||
),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
|
||||
return set(self.coin_record_from_row(row) for row in rows)
|
||||
|
||||
# Checks DB and DiffStores for CoinRecords with puzzle_hash and returns them
|
||||
async def get_coin_records_by_puzzle_hash(self, puzzle_hash: bytes32) -> List[WalletCoinRecord]:
|
||||
"""Returns a list of all coin records with the given puzzle hash"""
|
||||
cursor = await self.db_connection.execute("SELECT * from coin_record WHERE puzzle_hash=?", (puzzle_hash.hex(),))
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from coin_record WHERE puzzle_hash=?", (puzzle_hash.hex(),)
|
||||
)
|
||||
|
||||
return [self.coin_record_from_row(row) for row in rows]
|
||||
|
||||
# Checks DB and DiffStores for CoinRecords with parent_coin_info and returns them
|
||||
async def get_coin_records_by_parent_id(self, parent_coin_info: bytes32) -> List[WalletCoinRecord]:
|
||||
"""Returns a list of all coin records with the given parent id"""
|
||||
cursor = await self.db_connection.execute(
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from coin_record WHERE coin_parent=?", (parent_coin_info.hex(),)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
|
||||
return [self.coin_record_from_row(row) for row in rows]
|
||||
|
||||
async def rollback_to_block(self, height: int):
|
||||
"""
|
||||
Rolls back the blockchain to block_index. All blocks confirmed after this point
|
||||
are removed from the LCA. All coins confirmed after this point are removed.
|
||||
Rolls back the blockchain to block_index. All coins confirmed after this point are removed.
|
||||
All coins spent after this point are set to unspent. Can be -1 (rollback all)
|
||||
"""
|
||||
# Delete from storage
|
||||
delete_queue: List[WalletCoinRecord] = []
|
||||
for coin_name, coin_record in self.coin_record_cache.items():
|
||||
if coin_record.spent_block_height > height:
|
||||
new_record = WalletCoinRecord(
|
||||
coin_record.coin,
|
||||
coin_record.confirmed_block_height,
|
||||
uint32(0),
|
||||
False,
|
||||
coin_record.coinbase,
|
||||
coin_record.wallet_type,
|
||||
coin_record.wallet_id,
|
||||
)
|
||||
self.coin_record_cache[coin_record.coin.name()] = new_record
|
||||
if coin_record.wallet_id in self.unspent_coin_wallet_cache:
|
||||
self.unspent_coin_wallet_cache[coin_record.wallet_id][coin_record.coin.name()] = new_record
|
||||
if coin_record.confirmed_block_height > height:
|
||||
delete_queue.append(coin_record)
|
||||
|
||||
for coin_record in delete_queue:
|
||||
self.coin_record_cache.pop(coin_record.coin.name())
|
||||
if coin_record.wallet_id in self.unspent_coin_wallet_cache:
|
||||
coin_cache = self.unspent_coin_wallet_cache[coin_record.wallet_id]
|
||||
if coin_record.coin.name() in coin_cache:
|
||||
coin_cache.pop(coin_record.coin.name())
|
||||
|
||||
c1 = await self.db_connection.execute("DELETE FROM coin_record WHERE confirmed_height>?", (height,))
|
||||
await c1.close()
|
||||
|
||||
+148
-144
@@ -26,9 +26,7 @@ from chia.protocols.full_node_protocol import RequestProofOfWeight, RespondProof
|
||||
from chia.protocols.protocol_message_types import ProtocolMessageTypes
|
||||
from chia.protocols.wallet_protocol import (
|
||||
CoinState,
|
||||
RequestSESInfo,
|
||||
RespondBlockHeader,
|
||||
RespondSESInfo,
|
||||
RespondToCoinUpdates,
|
||||
RespondToPhUpdates,
|
||||
)
|
||||
@@ -44,7 +42,7 @@ from chia.types.coin_spend import CoinSpend
|
||||
from chia.types.header_block import HeaderBlock
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.types.peer_info import PeerInfo
|
||||
from chia.types.weight_proof import SubEpochData, WeightProof
|
||||
from chia.types.weight_proof import WeightProof
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.chunks import chunks
|
||||
from chia.util.config import WALLET_PEERS_PATH_KEY_DEPRECATED
|
||||
@@ -514,9 +512,6 @@ class WalletNode:
|
||||
tb = traceback.format_exc()
|
||||
self.log.error(f"Exception while perform_atomic_rollback: {e} {tb}")
|
||||
await self.wallet_state_manager.db_wrapper.rollback_transaction()
|
||||
await self.wallet_state_manager.coin_store.rebuild_wallet_cache()
|
||||
await self.wallet_state_manager.tx_store.rebuild_tx_cache()
|
||||
await self.wallet_state_manager.pool_store.rebuild_cache()
|
||||
raise
|
||||
else:
|
||||
await self.wallet_state_manager.blockchain.clean_block_records()
|
||||
@@ -717,9 +712,6 @@ class WalletNode:
|
||||
tb = traceback.format_exc()
|
||||
self.log.error(f"Exception while adding state: {e} {tb}")
|
||||
await self.wallet_state_manager.db_wrapper.rollback_transaction()
|
||||
await self.wallet_state_manager.coin_store.rebuild_wallet_cache()
|
||||
await self.wallet_state_manager.tx_store.rebuild_tx_cache()
|
||||
await self.wallet_state_manager.pool_store.rebuild_cache()
|
||||
else:
|
||||
await self.wallet_state_manager.blockchain.clean_block_records()
|
||||
|
||||
@@ -754,9 +746,6 @@ class WalletNode:
|
||||
await self.wallet_state_manager.db_wrapper.commit_transaction()
|
||||
except Exception as e:
|
||||
await self.wallet_state_manager.db_wrapper.rollback_transaction()
|
||||
await self.wallet_state_manager.coin_store.rebuild_wallet_cache()
|
||||
await self.wallet_state_manager.tx_store.rebuild_tx_cache()
|
||||
await self.wallet_state_manager.pool_store.rebuild_cache()
|
||||
tb = traceback.format_exc()
|
||||
self.log.error(f"Error adding states.. {e} {tb}")
|
||||
return False
|
||||
@@ -840,13 +829,27 @@ class WalletNode:
|
||||
request.peak_hash,
|
||||
)
|
||||
|
||||
def get_full_node_peer(self) -> Optional[WSChiaConnection]:
|
||||
def get_full_node_peer(self, synced_only: bool = False) -> Optional[WSChiaConnection]:
|
||||
"""
|
||||
Get a full node, preferring synced & trusted > synced & untrusted > unsynced & trusted > unsynced & untrusted
|
||||
"""
|
||||
if self._server is None:
|
||||
return None
|
||||
|
||||
nodes = self.server.get_full_node_connections()
|
||||
if len(nodes) > 0:
|
||||
return random.choice(nodes)
|
||||
synced_peers = set(node for node in nodes if node.peer_node_id in self.synced_peers)
|
||||
trusted_peers = set(node for node in nodes if self.is_trusted(node))
|
||||
if len(synced_peers & trusted_peers) > 0:
|
||||
return random.choice(list(synced_peers & trusted_peers))
|
||||
elif len(synced_peers) > 0:
|
||||
return random.choice(list(synced_peers))
|
||||
elif synced_only:
|
||||
return None
|
||||
elif len(trusted_peers) > 0:
|
||||
return random.choice(list(trusted_peers))
|
||||
else:
|
||||
return random.choice(list(nodes))
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1354,128 +1357,137 @@ class WalletNode:
|
||||
self.log.error("Failed validation 1")
|
||||
return False
|
||||
return True
|
||||
|
||||
# block is not included in wp recent chain
|
||||
start = block.height + 1
|
||||
compare_to_recent = False
|
||||
inserted: int = 0
|
||||
first_height_recent = weight_proof.recent_chain_data[0].height
|
||||
if start > first_height_recent - 1000:
|
||||
# compare up to weight_proof.recent_chain_data[0].height
|
||||
compare_to_recent = True
|
||||
end = first_height_recent
|
||||
else:
|
||||
start = block.height + 1
|
||||
compare_to_recent = False
|
||||
current_ses: Optional[SubEpochData] = None
|
||||
inserted: Optional[SubEpochData] = None
|
||||
first_height_recent = weight_proof.recent_chain_data[0].height
|
||||
if start > first_height_recent - 1000:
|
||||
compare_to_recent = True
|
||||
end = first_height_recent
|
||||
else:
|
||||
if block.height < self.constants.SUB_EPOCH_BLOCKS:
|
||||
inserted = weight_proof.sub_epochs[1]
|
||||
end = self.constants.SUB_EPOCH_BLOCKS + inserted.num_blocks_overflow
|
||||
else:
|
||||
request = RequestSESInfo(block.height, block.height + 32)
|
||||
res_ses: Optional[RespondSESInfo] = peer_request_cache.get_ses_request(block.height)
|
||||
if res_ses is None:
|
||||
res_ses = await peer.request_ses_hashes(request)
|
||||
peer_request_cache.add_to_ses_requests(block.height, res_ses)
|
||||
assert res_ses is not None
|
||||
|
||||
ses_0 = res_ses.reward_chain_hash[0]
|
||||
last_height = res_ses.heights[0][-1] # Last height in sub epoch
|
||||
end = last_height
|
||||
num_sub_epochs = len(weight_proof.sub_epochs)
|
||||
for idx, ses in enumerate(weight_proof.sub_epochs):
|
||||
if idx > num_sub_epochs - 3:
|
||||
break
|
||||
if ses.reward_chain_hash == ses_0:
|
||||
current_ses = ses
|
||||
inserted = weight_proof.sub_epochs[idx + 2]
|
||||
break
|
||||
if current_ses is None:
|
||||
self.log.error("Failed validation 2")
|
||||
return False
|
||||
|
||||
all_peers = self.server.get_full_node_connections()
|
||||
blocks: Optional[List[HeaderBlock]] = await fetch_header_blocks_in_range(
|
||||
start, end, peer_request_cache, all_peers
|
||||
)
|
||||
|
||||
if blocks is None:
|
||||
self.log.error(f"Error fetching blocks {start} {end}")
|
||||
return False
|
||||
|
||||
if compare_to_recent and weight_proof.recent_chain_data[0].header_hash != blocks[-1].header_hash:
|
||||
self.log.error("Failed validation 3")
|
||||
return False
|
||||
|
||||
if not compare_to_recent:
|
||||
last = blocks[-1].finished_sub_slots[-1].reward_chain.get_hash()
|
||||
if inserted is None or last != inserted.reward_chain_hash:
|
||||
self.log.error("Failed validation 4")
|
||||
return False
|
||||
pk_m_sig: List[Tuple[G1Element, bytes32, G2Element]] = []
|
||||
sigs_to_cache: List[HeaderBlock] = []
|
||||
blocks_to_cache: List[Tuple[bytes32, uint32]] = []
|
||||
|
||||
signatures_to_validate: int = 30
|
||||
for idx in range(len(blocks)):
|
||||
en_block = blocks[idx]
|
||||
if idx < signatures_to_validate and not peer_request_cache.in_block_signatures_validated(en_block):
|
||||
# Validate that the block is buried in the foliage by checking the signatures
|
||||
pk_m_sig.append(
|
||||
(
|
||||
en_block.reward_chain_block.proof_of_space.plot_public_key,
|
||||
en_block.foliage.foliage_block_data.get_hash(),
|
||||
en_block.foliage.foliage_block_data_signature,
|
||||
)
|
||||
)
|
||||
sigs_to_cache.append(en_block)
|
||||
|
||||
# This is the reward chain challenge. If this is in the cache, it means the prev block
|
||||
# has been validated. We must at least check the first block to ensure they are connected
|
||||
reward_chain_hash: bytes32 = en_block.reward_chain_block.reward_chain_ip_vdf.challenge
|
||||
if idx != 0 and peer_request_cache.in_blocks_validated(reward_chain_hash):
|
||||
# As soon as we see a block we have already concluded is in the chain, we can quit.
|
||||
if idx > signatures_to_validate:
|
||||
# get ses from wp
|
||||
start_height = block.height
|
||||
end_height = block.height + 32
|
||||
ses_start_height = 0
|
||||
end = 0
|
||||
for idx, ses in enumerate(weight_proof.sub_epochs):
|
||||
if idx == len(weight_proof.sub_epochs) - 1:
|
||||
break
|
||||
next_ses_height = (idx + 1) * self.constants.SUB_EPOCH_BLOCKS + weight_proof.sub_epochs[
|
||||
idx + 1
|
||||
].num_blocks_overflow
|
||||
# start_ses_hash
|
||||
if ses_start_height <= start_height < next_ses_height:
|
||||
inserted = idx + 1
|
||||
if ses_start_height < end_height < next_ses_height:
|
||||
end = next_ses_height
|
||||
break
|
||||
else:
|
||||
# Validate that the block is committed to by the weight proof
|
||||
if idx == 0:
|
||||
prev_block_rc_hash: bytes32 = block.reward_chain_block.get_hash()
|
||||
prev_hash = block.header_hash
|
||||
else:
|
||||
prev_block_rc_hash = blocks[idx - 1].reward_chain_block.get_hash()
|
||||
prev_hash = blocks[idx - 1].header_hash
|
||||
if idx > len(weight_proof.sub_epochs) - 3:
|
||||
break
|
||||
# else add extra ses as request start <-> end spans two ses
|
||||
end = (idx + 2) * self.constants.SUB_EPOCH_BLOCKS + weight_proof.sub_epochs[
|
||||
idx + 2
|
||||
].num_blocks_overflow
|
||||
inserted += 1
|
||||
break
|
||||
ses_start_height = next_ses_height
|
||||
|
||||
if not en_block.prev_header_hash == prev_hash:
|
||||
self.log.error("Failed validation 5")
|
||||
return False
|
||||
if end == 0:
|
||||
self.log.error("Error finding sub epoch")
|
||||
return False
|
||||
all_peers = self.server.get_full_node_connections()
|
||||
blocks: Optional[List[HeaderBlock]] = await fetch_header_blocks_in_range(
|
||||
start, end, peer_request_cache, all_peers
|
||||
)
|
||||
if blocks is None:
|
||||
self.log.error(f"Error fetching blocks {start} {end}")
|
||||
return False
|
||||
|
||||
if len(en_block.finished_sub_slots) > 0:
|
||||
reversed_slots = en_block.finished_sub_slots.copy()
|
||||
reversed_slots.reverse()
|
||||
for slot_idx, slot in enumerate(reversed_slots[:-1]):
|
||||
hash_val = reversed_slots[slot_idx + 1].reward_chain.get_hash()
|
||||
if not hash_val == slot.reward_chain.end_of_slot_vdf.challenge:
|
||||
self.log.error("Failed validation 6")
|
||||
return False
|
||||
if not prev_block_rc_hash == reversed_slots[-1].reward_chain.end_of_slot_vdf.challenge:
|
||||
self.log.error("Failed validation 7")
|
||||
return False
|
||||
else:
|
||||
if not prev_block_rc_hash == reward_chain_hash:
|
||||
self.log.error("Failed validation 8")
|
||||
return False
|
||||
blocks_to_cache.append((reward_chain_hash, en_block.height))
|
||||
if compare_to_recent and weight_proof.recent_chain_data[0].header_hash != blocks[-1].header_hash:
|
||||
self.log.error("Failed validation 3")
|
||||
return False
|
||||
|
||||
agg_sig: G2Element = AugSchemeMPL.aggregate([sig for (_, _, sig) in pk_m_sig])
|
||||
if not AugSchemeMPL.aggregate_verify(
|
||||
[pk for (pk, _, _) in pk_m_sig], [m for (_, m, _) in pk_m_sig], agg_sig
|
||||
):
|
||||
self.log.error("Failed signature validation")
|
||||
if not compare_to_recent:
|
||||
last = blocks[-1].finished_sub_slots[-1].reward_chain.get_hash()
|
||||
if last != weight_proof.sub_epochs[inserted].reward_chain_hash:
|
||||
self.log.error("Failed validation 4")
|
||||
return False
|
||||
for header_block in sigs_to_cache:
|
||||
peer_request_cache.add_to_block_signatures_validated(header_block)
|
||||
for reward_chain_hash, height in blocks_to_cache:
|
||||
peer_request_cache.add_to_blocks_validated(reward_chain_hash, height)
|
||||
return True
|
||||
pk_m_sig: List[Tuple[G1Element, bytes32, G2Element]] = []
|
||||
sigs_to_cache: List[HeaderBlock] = []
|
||||
blocks_to_cache: List[Tuple[bytes32, uint32]] = []
|
||||
|
||||
async def fetch_puzzle_solution(self, peer: WSChiaConnection, height: uint32, coin: Coin) -> CoinSpend:
|
||||
signatures_to_validate: int = 30
|
||||
for idx in range(len(blocks)):
|
||||
en_block = blocks[idx]
|
||||
if idx < signatures_to_validate and not peer_request_cache.in_block_signatures_validated(en_block):
|
||||
# Validate that the block is buried in the foliage by checking the signatures
|
||||
pk_m_sig.append(
|
||||
(
|
||||
en_block.reward_chain_block.proof_of_space.plot_public_key,
|
||||
en_block.foliage.foliage_block_data.get_hash(),
|
||||
en_block.foliage.foliage_block_data_signature,
|
||||
)
|
||||
)
|
||||
sigs_to_cache.append(en_block)
|
||||
|
||||
# This is the reward chain challenge. If this is in the cache, it means the prev block
|
||||
# has been validated. We must at least check the first block to ensure they are connected
|
||||
reward_chain_hash: bytes32 = en_block.reward_chain_block.reward_chain_ip_vdf.challenge
|
||||
if idx != 0 and peer_request_cache.in_blocks_validated(reward_chain_hash):
|
||||
# As soon as we see a block we have already concluded is in the chain, we can quit.
|
||||
if idx > signatures_to_validate:
|
||||
break
|
||||
else:
|
||||
# Validate that the block is committed to by the weight proof
|
||||
if idx == 0:
|
||||
prev_block_rc_hash: bytes32 = block.reward_chain_block.get_hash()
|
||||
prev_hash = block.header_hash
|
||||
else:
|
||||
prev_block_rc_hash = blocks[idx - 1].reward_chain_block.get_hash()
|
||||
prev_hash = blocks[idx - 1].header_hash
|
||||
|
||||
if not en_block.prev_header_hash == prev_hash:
|
||||
self.log.error("Failed validation 5")
|
||||
return False
|
||||
|
||||
if len(en_block.finished_sub_slots) > 0:
|
||||
reversed_slots = en_block.finished_sub_slots.copy()
|
||||
reversed_slots.reverse()
|
||||
for slot_idx, slot in enumerate(reversed_slots[:-1]):
|
||||
hash_val = reversed_slots[slot_idx + 1].reward_chain.get_hash()
|
||||
if not hash_val == slot.reward_chain.end_of_slot_vdf.challenge:
|
||||
self.log.error("Failed validation 6")
|
||||
return False
|
||||
if not prev_block_rc_hash == reversed_slots[-1].reward_chain.end_of_slot_vdf.challenge:
|
||||
self.log.error("Failed validation 7")
|
||||
return False
|
||||
else:
|
||||
if not prev_block_rc_hash == reward_chain_hash:
|
||||
self.log.error("Failed validation 8")
|
||||
return False
|
||||
blocks_to_cache.append((reward_chain_hash, en_block.height))
|
||||
|
||||
agg_sig: G2Element = AugSchemeMPL.aggregate([sig for (_, _, sig) in pk_m_sig])
|
||||
if not AugSchemeMPL.aggregate_verify([pk for (pk, _, _) in pk_m_sig], [m for (_, m, _) in pk_m_sig], agg_sig):
|
||||
self.log.error("Failed signature validation")
|
||||
return False
|
||||
for header_block in sigs_to_cache:
|
||||
peer_request_cache.add_to_block_signatures_validated(header_block)
|
||||
for reward_chain_hash, height in blocks_to_cache:
|
||||
peer_request_cache.add_to_blocks_validated(reward_chain_hash, height)
|
||||
return True
|
||||
|
||||
async def fetch_puzzle_solution(
|
||||
self, height: uint32, coin: Coin, peer: Optional[WSChiaConnection] = None
|
||||
) -> CoinSpend:
|
||||
if peer is None:
|
||||
peer = self.get_full_node_peer()
|
||||
if peer is None:
|
||||
raise ValueError("Could not find any peers to request puzzle and solution from")
|
||||
solution_response = await peer.request_puzzle_solution(
|
||||
wallet_protocol.RequestPuzzleSolution(coin.name(), height)
|
||||
)
|
||||
@@ -1493,23 +1505,11 @@ class WalletNode:
|
||||
async def get_coin_state(
|
||||
self, coin_names: List[bytes32], fork_height: Optional[uint32] = None, peer: Optional[WSChiaConnection] = None
|
||||
) -> List[CoinState]:
|
||||
all_nodes = self.server.connection_by_type[NodeType.FULL_NODE]
|
||||
if len(all_nodes.keys()) == 0:
|
||||
raise ValueError("Not connected to the full node")
|
||||
# Use supplied if provided, prioritize trusted otherwise
|
||||
synced_peers = [node for node in all_nodes.values() if node.peer_node_id in self.synced_peers]
|
||||
if peer is None:
|
||||
for node in synced_peers:
|
||||
if self.is_trusted(node):
|
||||
peer = node
|
||||
break
|
||||
if peer is None:
|
||||
if len(synced_peers) > 0:
|
||||
peer = synced_peers[0]
|
||||
else:
|
||||
peer = list(all_nodes.values())[0]
|
||||
peer = self.get_full_node_peer()
|
||||
if peer is None:
|
||||
raise ValueError("Could not find any peers to request puzzle and solution from")
|
||||
|
||||
assert peer is not None
|
||||
msg = wallet_protocol.RegisterForCoinUpdates(coin_names, uint32(0))
|
||||
coin_state: Optional[RespondToCoinUpdates] = await peer.register_interest_in_coin(msg)
|
||||
assert coin_state is not None
|
||||
@@ -1527,8 +1527,12 @@ class WalletNode:
|
||||
return coin_state.coin_states
|
||||
|
||||
async def fetch_children(
|
||||
self, peer: WSChiaConnection, coin_name: bytes32, fork_height: Optional[uint32] = None
|
||||
self, coin_name: bytes32, fork_height: Optional[uint32] = None, peer: Optional[WSChiaConnection] = None
|
||||
) -> List[CoinState]:
|
||||
if peer is None:
|
||||
peer = self.get_full_node_peer()
|
||||
if peer is None:
|
||||
raise ValueError("Could not find any peers to request puzzle and solution from")
|
||||
response: Optional[wallet_protocol.RespondChildren] = await peer.request_children(
|
||||
wallet_protocol.RequestChildren(coin_name)
|
||||
)
|
||||
|
||||
@@ -148,6 +148,10 @@ class WalletNodeAPI:
|
||||
async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks):
|
||||
self.log.warning(f"Reject header blocks: {request}")
|
||||
|
||||
@api_request
|
||||
async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders):
|
||||
pass
|
||||
|
||||
@execute_task
|
||||
@peer_required
|
||||
@api_request
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import List, Tuple, Dict, Optional
|
||||
from typing import List, Tuple
|
||||
|
||||
import aiosqlite
|
||||
|
||||
@@ -13,7 +13,6 @@ log = logging.getLogger(__name__)
|
||||
class WalletPoolStore:
|
||||
db_connection: aiosqlite.Connection
|
||||
db_wrapper: DBWrapper
|
||||
_state_transitions_cache: Dict[int, List[Tuple[uint32, CoinSpend]]]
|
||||
|
||||
@classmethod
|
||||
async def create(cls, wrapper: DBWrapper):
|
||||
@@ -23,11 +22,15 @@ class WalletPoolStore:
|
||||
self.db_wrapper = wrapper
|
||||
|
||||
await self.db_connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS pool_state_transitions(transition_index integer, wallet_id integer, "
|
||||
"height bigint, coin_spend blob, PRIMARY KEY(transition_index, wallet_id))"
|
||||
"CREATE TABLE IF NOT EXISTS pool_state_transitions("
|
||||
" transition_index integer,"
|
||||
" wallet_id integer,"
|
||||
" height bigint,"
|
||||
" coin_spend blob,"
|
||||
" PRIMARY KEY(transition_index, wallet_id))"
|
||||
)
|
||||
|
||||
await self.db_connection.commit()
|
||||
await self.rebuild_cache()
|
||||
return self
|
||||
|
||||
async def _clear_database(self):
|
||||
@@ -51,28 +54,48 @@ class WalletPoolStore:
|
||||
if not in_transaction:
|
||||
await self.db_wrapper.lock.acquire()
|
||||
try:
|
||||
if wallet_id not in self._state_transitions_cache:
|
||||
self._state_transitions_cache[wallet_id] = []
|
||||
all_state_transitions: List[Tuple[uint32, CoinSpend]] = self.get_spends_for_wallet(wallet_id)
|
||||
# find the most recent transition in wallet_id
|
||||
rows = list(
|
||||
await self.db_connection.execute_fetchall(
|
||||
"SELECT transition_index, height, coin_spend "
|
||||
"FROM pool_state_transitions "
|
||||
"WHERE wallet_id=? "
|
||||
"ORDER BY transition_index DESC "
|
||||
"LIMIT 1",
|
||||
(wallet_id,),
|
||||
)
|
||||
)
|
||||
serialized_spend = bytes(spend)
|
||||
if len(rows) == 0:
|
||||
transition_index = 0
|
||||
else:
|
||||
existing = list(
|
||||
await self.db_connection.execute_fetchall(
|
||||
"SELECT COUNT(*) "
|
||||
"FROM pool_state_transitions "
|
||||
"WHERE wallet_id=? AND height=? AND coin_spend=?",
|
||||
(wallet_id, height, serialized_spend),
|
||||
)
|
||||
)
|
||||
if existing[0][0] != 0:
|
||||
# we already have this transition in the DB
|
||||
return
|
||||
|
||||
if (height, spend) in all_state_transitions:
|
||||
return
|
||||
|
||||
if len(all_state_transitions) > 0:
|
||||
if height < all_state_transitions[-1][0]:
|
||||
row = rows[0]
|
||||
if height < row[1]:
|
||||
raise ValueError("Height cannot go down")
|
||||
if spend.coin.parent_coin_info != all_state_transitions[-1][1].coin.name():
|
||||
prev = CoinSpend.from_bytes(row[2])
|
||||
if spend.coin.parent_coin_info != prev.coin.name():
|
||||
raise ValueError("New spend does not extend")
|
||||
|
||||
all_state_transitions.append((height, spend))
|
||||
transition_index = row[0]
|
||||
|
||||
cursor = await self.db_connection.execute(
|
||||
"INSERT OR REPLACE INTO pool_state_transitions VALUES (?, ?, ?, ?)",
|
||||
"INSERT OR IGNORE INTO pool_state_transitions VALUES (?, ?, ?, ?)",
|
||||
(
|
||||
len(all_state_transitions) - 1,
|
||||
transition_index + 1,
|
||||
wallet_id,
|
||||
height,
|
||||
bytes(spend),
|
||||
serialized_spend,
|
||||
),
|
||||
)
|
||||
await cursor.close()
|
||||
@@ -81,27 +104,16 @@ class WalletPoolStore:
|
||||
await self.db_connection.commit()
|
||||
self.db_wrapper.lock.release()
|
||||
|
||||
def get_spends_for_wallet(self, wallet_id: int) -> List[Tuple[uint32, CoinSpend]]:
|
||||
async def get_spends_for_wallet(self, wallet_id: int) -> List[Tuple[uint32, CoinSpend]]:
|
||||
"""
|
||||
Retrieves all entries for a wallet ID from the cache, works even if commit is not called yet.
|
||||
Retrieves all entries for a wallet ID.
|
||||
"""
|
||||
return self._state_transitions_cache.get(wallet_id, [])
|
||||
|
||||
async def rebuild_cache(self) -> None:
|
||||
"""
|
||||
This resets the cache, and loads all entries from the DB. Any entries in the cache that were not committed
|
||||
are removed. This can happen if a state transition in wallet_blockchain fails.
|
||||
"""
|
||||
cursor = await self.db_connection.execute("SELECT * FROM pool_state_transitions ORDER BY transition_index")
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
self._state_transitions_cache = {}
|
||||
for row in rows:
|
||||
_, wallet_id, height, coin_spend_bytes = row
|
||||
coin_spend: CoinSpend = CoinSpend.from_bytes(coin_spend_bytes)
|
||||
if wallet_id not in self._state_transitions_cache:
|
||||
self._state_transitions_cache[wallet_id] = []
|
||||
self._state_transitions_cache[wallet_id].append((height, coin_spend))
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT height, coin_spend FROM pool_state_transitions WHERE wallet_id=? ORDER BY transition_index",
|
||||
(wallet_id,),
|
||||
)
|
||||
return [(uint32(row[0]), CoinSpend.from_bytes(row[1])) for row in rows]
|
||||
|
||||
async def rollback(self, height: int, wallet_id_arg: int, in_transaction: bool) -> None:
|
||||
"""
|
||||
@@ -113,14 +125,6 @@ class WalletPoolStore:
|
||||
if not in_transaction:
|
||||
await self.db_wrapper.lock.acquire()
|
||||
try:
|
||||
for wallet_id, items in self._state_transitions_cache.items():
|
||||
remove_index_start: Optional[int] = None
|
||||
for i, (item_block_height, _) in enumerate(items):
|
||||
if item_block_height > height and wallet_id == wallet_id_arg:
|
||||
remove_index_start = i
|
||||
break
|
||||
if remove_index_start is not None:
|
||||
del items[remove_index_start:]
|
||||
cursor = await self.db_connection.execute(
|
||||
"DELETE FROM pool_state_transitions WHERE height>? AND wallet_id=?", (height, wallet_id_arg)
|
||||
)
|
||||
|
||||
@@ -119,7 +119,6 @@ class WalletStateManager:
|
||||
blockchain: WalletBlockchain
|
||||
coin_store: WalletCoinStore
|
||||
sync_store: WalletSyncStore
|
||||
finished_sync_up_to: uint32
|
||||
interested_store: WalletInterestedStore
|
||||
multiprocessing_context: multiprocessing.context.BaseContext
|
||||
weight_proof_handler: WalletWeightProofHandler
|
||||
@@ -187,7 +186,6 @@ class WalletStateManager:
|
||||
self.wallet_node = wallet_node
|
||||
self.sync_mode = False
|
||||
self.sync_target = uint32(0)
|
||||
self.finished_sync_up_to = uint32(0)
|
||||
multiprocessing_start_method = process_config_start_method(config=self.config, log=self.log)
|
||||
self.multiprocessing_context = multiprocessing.get_context(method=multiprocessing_start_method)
|
||||
self.weight_proof_handler = WalletWeightProofHandler(
|
||||
@@ -604,7 +602,7 @@ class WalletStateManager:
|
||||
assert parent_coin_state.spent_height == coin_state.created_height
|
||||
|
||||
coin_spend: Optional[CoinSpend] = await self.wallet_node.fetch_puzzle_solution(
|
||||
peer, parent_coin_state.spent_height, parent_coin_state.coin
|
||||
parent_coin_state.spent_height, parent_coin_state.coin, peer
|
||||
)
|
||||
if coin_spend is None:
|
||||
return None, None
|
||||
@@ -991,7 +989,7 @@ class WalletStateManager:
|
||||
)
|
||||
await self.tx_store.add_transaction_record(tx_record, True)
|
||||
|
||||
children = await self.wallet_node.fetch_children(peer, coin_name, fork_height)
|
||||
children = await self.wallet_node.fetch_children(coin_name, fork_height, peer)
|
||||
assert children is not None
|
||||
additions = [state.coin for state in children]
|
||||
if len(children) > 0:
|
||||
@@ -1068,7 +1066,7 @@ class WalletStateManager:
|
||||
|
||||
while curr_coin_state.spent_height is not None:
|
||||
cs: CoinSpend = await self.wallet_node.fetch_puzzle_solution(
|
||||
peer, curr_coin_state.spent_height, curr_coin_state.coin
|
||||
curr_coin_state.spent_height, curr_coin_state.coin, peer
|
||||
)
|
||||
success = await wallet.apply_state_transition(cs, curr_coin_state.spent_height)
|
||||
if not success:
|
||||
@@ -1093,7 +1091,7 @@ class WalletStateManager:
|
||||
curr_coin_state = new_coin_state[0]
|
||||
if record.wallet_type == WalletType.DATA_LAYER:
|
||||
singleton_spend = await self.wallet_node.fetch_puzzle_solution(
|
||||
peer, coin_state.spent_height, coin_state.coin
|
||||
coin_state.spent_height, coin_state.coin, peer
|
||||
)
|
||||
dl_wallet = self.wallets[uint32(record.wallet_id)]
|
||||
await dl_wallet.singleton_removed(
|
||||
@@ -1109,7 +1107,7 @@ class WalletStateManager:
|
||||
|
||||
# Check if a child is a singleton launcher
|
||||
if children is None:
|
||||
children = await self.wallet_node.fetch_children(peer, coin_name, fork_height)
|
||||
children = await self.wallet_node.fetch_children(coin_name, fork_height, peer)
|
||||
assert children is not None
|
||||
for child in children:
|
||||
if child.coin.puzzle_hash != SINGLETON_LAUNCHER_HASH:
|
||||
@@ -1120,7 +1118,7 @@ class WalletStateManager:
|
||||
# TODO handle spending launcher later block
|
||||
continue
|
||||
launcher_spend: Optional[CoinSpend] = await self.wallet_node.fetch_puzzle_solution(
|
||||
peer, coin_state.spent_height, child.coin
|
||||
coin_state.spent_height, child.coin, peer
|
||||
)
|
||||
if launcher_spend is None:
|
||||
continue
|
||||
|
||||
@@ -30,9 +30,7 @@ class WalletTransactionStore:
|
||||
|
||||
db_connection: aiosqlite.Connection
|
||||
db_wrapper: DBWrapper
|
||||
tx_record_cache: Dict[bytes32, TransactionRecord]
|
||||
tx_submitted: Dict[bytes32, Tuple[int, int]] # tx_id: [time submitted: count]
|
||||
unconfirmed_for_wallet: Dict[int, Dict[bytes32, TransactionRecord]]
|
||||
last_wallet_tx_resend_time: int # Epoch time in seconds
|
||||
|
||||
@classmethod
|
||||
@@ -87,26 +85,10 @@ class WalletTransactionStore:
|
||||
)
|
||||
|
||||
await self.db_connection.commit()
|
||||
self.tx_record_cache = {}
|
||||
self.tx_submitted = {}
|
||||
self.unconfirmed_for_wallet = {}
|
||||
self.last_wallet_tx_resend_time = int(time.time())
|
||||
await self.rebuild_tx_cache()
|
||||
return self
|
||||
|
||||
async def rebuild_tx_cache(self):
|
||||
# init cache here
|
||||
all_records = await self.get_all_transactions()
|
||||
self.tx_record_cache = {}
|
||||
self.unconfirmed_for_wallet = {}
|
||||
|
||||
for record in all_records:
|
||||
self.tx_record_cache[record.name] = record
|
||||
if record.wallet_id not in self.unconfirmed_for_wallet:
|
||||
self.unconfirmed_for_wallet[record.wallet_id] = {}
|
||||
if not record.confirmed:
|
||||
self.unconfirmed_for_wallet[record.wallet_id][record.name] = record
|
||||
|
||||
async def _clear_database(self):
|
||||
cursor = await self.db_connection.execute("DELETE FROM transaction_record")
|
||||
await cursor.close()
|
||||
@@ -116,19 +98,10 @@ class WalletTransactionStore:
|
||||
"""
|
||||
Store TransactionRecord in DB and Cache.
|
||||
"""
|
||||
self.tx_record_cache[record.name] = record
|
||||
if record.wallet_id not in self.unconfirmed_for_wallet:
|
||||
self.unconfirmed_for_wallet[record.wallet_id] = {}
|
||||
unconfirmed_dict = self.unconfirmed_for_wallet[record.wallet_id]
|
||||
if record.confirmed and record.name in unconfirmed_dict:
|
||||
unconfirmed_dict.pop(record.name)
|
||||
if not record.confirmed:
|
||||
unconfirmed_dict[record.name] = record
|
||||
|
||||
if not in_transaction:
|
||||
await self.db_wrapper.lock.acquire()
|
||||
try:
|
||||
cursor = await self.db_connection.execute(
|
||||
await self.db_connection.execute_insert(
|
||||
"INSERT OR REPLACE INTO transaction_record VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
bytes(record),
|
||||
@@ -145,25 +118,13 @@ class WalletTransactionStore:
|
||||
record.type,
|
||||
),
|
||||
)
|
||||
await cursor.close()
|
||||
if not in_transaction:
|
||||
await self.db_connection.commit()
|
||||
except BaseException:
|
||||
if not in_transaction:
|
||||
await self.rebuild_tx_cache()
|
||||
raise
|
||||
finally:
|
||||
if not in_transaction:
|
||||
self.db_wrapper.lock.release()
|
||||
|
||||
async def delete_transaction_record(self, tx_id: bytes32) -> None:
|
||||
if tx_id in self.tx_record_cache:
|
||||
tx_record = self.tx_record_cache.pop(tx_id)
|
||||
if tx_record.wallet_id in self.unconfirmed_for_wallet:
|
||||
tx_cache = self.unconfirmed_for_wallet[tx_record.wallet_id]
|
||||
if tx_id in tx_cache:
|
||||
tx_cache.pop(tx_id)
|
||||
|
||||
c = await self.db_connection.execute("DELETE FROM transaction_record WHERE bundle_id=?", (tx_id,))
|
||||
await c.close()
|
||||
|
||||
@@ -249,7 +210,7 @@ class WalletTransactionStore:
|
||||
await self.add_transaction_record(tx, False)
|
||||
return True
|
||||
|
||||
async def tx_reorged(self, record: TransactionRecord, in_transaction: bool):
|
||||
async def tx_reorged(self, record: TransactionRecord, in_transaction: bool) -> None:
|
||||
"""
|
||||
Updates transaction sent count to 0 and resets confirmation data
|
||||
"""
|
||||
@@ -277,29 +238,26 @@ class WalletTransactionStore:
|
||||
"""
|
||||
Checks DB and cache for TransactionRecord with id: id and returns it.
|
||||
"""
|
||||
if tx_id in self.tx_record_cache:
|
||||
return self.tx_record_cache[tx_id]
|
||||
|
||||
# NOTE: bundle_id is being stored as bytes, not hex
|
||||
cursor = await self.db_connection.execute("SELECT * from transaction_record WHERE bundle_id=?", (tx_id,))
|
||||
row = await cursor.fetchone()
|
||||
await cursor.close()
|
||||
if row is not None:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
return record
|
||||
rows = list(
|
||||
await self.db_connection.execute_fetchall("SELECT * from transaction_record WHERE bundle_id=?", (tx_id,))
|
||||
)
|
||||
if len(rows) > 0:
|
||||
return TransactionRecord.from_bytes(rows[0][0])
|
||||
return None
|
||||
|
||||
# TODO: This should probably be split into separate function, one that
|
||||
# queries the state and one that updates it. Also, include_accepted_txs=True
|
||||
# might be a separate function too.
|
||||
# also, the current time should be passed in as a paramter
|
||||
async def get_not_sent(self, *, include_accepted_txs=False) -> List[TransactionRecord]:
|
||||
"""
|
||||
Returns the list of transactions that have not been received by full node yet.
|
||||
"""
|
||||
current_time = int(time.time())
|
||||
cursor = await self.db_connection.execute(
|
||||
"SELECT * from transaction_record WHERE confirmed=?",
|
||||
(0,),
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from transaction_record WHERE confirmed=0",
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
@@ -331,43 +289,27 @@ class WalletTransactionStore:
|
||||
"""
|
||||
fee_int = TransactionType.FEE_REWARD.value
|
||||
pool_int = TransactionType.COINBASE_REWARD.value
|
||||
cursor = await self.db_connection.execute(
|
||||
"SELECT * from transaction_record WHERE confirmed=? and (type=? or type=?)", (1, fee_int, pool_int)
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from transaction_record WHERE confirmed=1 and (type=? or type=?)", (fee_int, pool_int)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_all_unconfirmed(self) -> List[TransactionRecord]:
|
||||
"""
|
||||
Returns the list of all transaction that have not yet been confirmed.
|
||||
"""
|
||||
|
||||
cursor = await self.db_connection.execute("SELECT * from transaction_record WHERE confirmed=?", (0,))
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
rows = await self.db_connection.execute_fetchall("SELECT * from transaction_record WHERE confirmed=0")
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_unconfirmed_for_wallet(self, wallet_id: int) -> List[TransactionRecord]:
|
||||
"""
|
||||
Returns the list of transaction that have not yet been confirmed.
|
||||
"""
|
||||
if wallet_id in self.unconfirmed_for_wallet:
|
||||
return list(self.unconfirmed_for_wallet[wallet_id].values())
|
||||
else:
|
||||
return []
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT transaction_record from transaction_record WHERE confirmed=0 AND wallet_id=?", (wallet_id,)
|
||||
)
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_transactions_between(
|
||||
self, wallet_id: int, start, end, sort_key=None, reverse=False, to_puzzle_hash: Optional[bytes32] = None
|
||||
@@ -380,7 +322,7 @@ class WalletTransactionStore:
|
||||
if to_puzzle_hash is None:
|
||||
puzz_hash_where = ""
|
||||
else:
|
||||
puzz_hash_where = f' and to_puzzle_hash="{to_puzzle_hash.hex()}"'
|
||||
puzz_hash_where = f' AND to_puzzle_hash="{to_puzzle_hash.hex()}"'
|
||||
|
||||
if sort_key is None:
|
||||
sort_key = "CONFIRMED_AT_HEIGHT"
|
||||
@@ -392,113 +334,64 @@ class WalletTransactionStore:
|
||||
else:
|
||||
query_str = SortKey[sort_key].ascending()
|
||||
|
||||
cursor = await self.db_connection.execute(
|
||||
f"SELECT * from transaction_record where wallet_id=?{puzz_hash_where}"
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
f"SELECT * from transaction_record WHERE wallet_id=?{puzz_hash_where}"
|
||||
f" {query_str}, rowid"
|
||||
f" LIMIT {start}, {limit}",
|
||||
(wallet_id,),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_transaction_count_for_wallet(self, wallet_id) -> int:
|
||||
cursor = await self.db_connection.execute(
|
||||
"SELECT COUNT(*) FROM transaction_record where wallet_id=?", (wallet_id,)
|
||||
rows = list(
|
||||
await self.db_connection.execute_fetchall(
|
||||
"SELECT COUNT(*) FROM transaction_record where wallet_id=?", (wallet_id,)
|
||||
)
|
||||
)
|
||||
count_result = await cursor.fetchone()
|
||||
if count_result is not None:
|
||||
count = count_result[0]
|
||||
else:
|
||||
count = 0
|
||||
await cursor.close()
|
||||
return count
|
||||
return 0 if len(rows) == 0 else rows[0][0]
|
||||
|
||||
async def get_all_transactions_for_wallet(self, wallet_id: int, type: int = None) -> List[TransactionRecord]:
|
||||
"""
|
||||
Returns all stored transactions.
|
||||
"""
|
||||
if type is None:
|
||||
cursor = await self.db_connection.execute(
|
||||
"SELECT * from transaction_record where wallet_id=?", (wallet_id,)
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * FROM transaction_record WHERE wallet_id=?", (wallet_id,)
|
||||
)
|
||||
else:
|
||||
cursor = await self.db_connection.execute(
|
||||
"SELECT * from transaction_record where wallet_id=? and type=?",
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * FROM transaction_record WHERE wallet_id=? AND type=?",
|
||||
(
|
||||
wallet_id,
|
||||
type,
|
||||
),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
cache_set = set()
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
cache_set.add(record.name)
|
||||
|
||||
return records
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_all_transactions(self) -> List[TransactionRecord]:
|
||||
"""
|
||||
Returns all stored transactions.
|
||||
"""
|
||||
cursor = await self.db_connection.execute("SELECT * from transaction_record")
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
rows = await self.db_connection.execute_fetchall("SELECT * from transaction_record")
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_transaction_above(self, height: int) -> List[TransactionRecord]:
|
||||
# Can be -1 (get all tx)
|
||||
|
||||
cursor = await self.db_connection.execute(
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from transaction_record WHERE confirmed_at_height>?", (height,)
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def get_transactions_by_trade_id(self, trade_id: bytes32) -> List[TransactionRecord]:
|
||||
cursor = await self.db_connection.execute("SELECT * from transaction_record WHERE trade_id=?", (trade_id,))
|
||||
rows = await cursor.fetchall()
|
||||
await cursor.close()
|
||||
records = []
|
||||
|
||||
for row in rows:
|
||||
record = TransactionRecord.from_bytes(row[0])
|
||||
records.append(record)
|
||||
|
||||
return records
|
||||
rows = await self.db_connection.execute_fetchall(
|
||||
"SELECT * from transaction_record WHERE trade_id=?", (trade_id,)
|
||||
)
|
||||
return [TransactionRecord.from_bytes(row[0]) for row in rows]
|
||||
|
||||
async def rollback_to_block(self, height: int):
|
||||
# Delete from storage
|
||||
to_delete = []
|
||||
for tx in self.tx_record_cache.values():
|
||||
if tx.confirmed_at_height > height:
|
||||
to_delete.append(tx)
|
||||
for tx in to_delete:
|
||||
self.tx_record_cache.pop(tx.name)
|
||||
self.tx_submitted = {}
|
||||
c1 = await self.db_connection.execute("DELETE FROM transaction_record WHERE confirmed_at_height>?", (height,))
|
||||
await c1.close()
|
||||
|
||||
@@ -13,13 +13,13 @@ dependencies = [
|
||||
"aiohttp==3.8.1", # HTTP server for full node rpc
|
||||
"aiosqlite==0.17.0", # asyncio wrapper for sqlite, to store blocks
|
||||
"bitstring==3.1.9", # Binary data management library
|
||||
"colorama==0.4.4", # Colorizes terminal output
|
||||
"colorama==0.4.5", # Colorizes terminal output
|
||||
"colorlog==6.6.0", # Adds color to logs
|
||||
"concurrent-log-handler==0.9.19", # Concurrently log and rotate logs
|
||||
"cryptography==36.0.2", # Python cryptography library for TLS - keyring conflict
|
||||
"fasteners==0.16.3", # For interprocess file locking, expected to be replaced by filelock
|
||||
"filelock==3.4.2", # For reading and writing config multiprocess and multithread safely (non-reentrant locks)
|
||||
"keyring==23.0.1", # Store keys in MacOS Keychain, Windows Credential Locker
|
||||
"fasteners==0.17.3", # For interprocess file locking, expected to be replaced by filelock
|
||||
"filelock==3.7.1", # For reading and writing config multiprocess and multithread safely (non-reentrant locks)
|
||||
"keyring==23.6.0", # Store keys in MacOS Keychain, Windows Credential Locker
|
||||
"keyrings.cryptfile==1.3.4", # Secure storage for keys on Linux (Will be replaced)
|
||||
# "keyrings.cryptfile==1.3.8", # Secure storage for keys on Linux (Will be replaced)
|
||||
# See https://github.com/frispete/keyrings.cryptfile/issues/15
|
||||
@@ -31,9 +31,9 @@ dependencies = [
|
||||
"dnspython==2.2.0", # Query DNS seeds
|
||||
"watchdog==2.1.9", # Filesystem event watching - watches keyring.yaml
|
||||
"dnslib==0.9.17", # dns lib
|
||||
"typing-extensions==4.0.1", # typing backports like Protocol and TypedDict
|
||||
"typing-extensions==4.3.0", # typing backports like Protocol and TypedDict
|
||||
"zstd==1.5.0.4",
|
||||
"packaging==21.0",
|
||||
"packaging==21.3",
|
||||
]
|
||||
|
||||
upnp_dependencies = [
|
||||
@@ -109,7 +109,6 @@ kwargs = dict(
|
||||
"chia.util",
|
||||
"chia.wallet",
|
||||
"chia.wallet.db_wallet",
|
||||
"chia.wallet.dlo_wallet",
|
||||
"chia.wallet.puzzles",
|
||||
"chia.wallet.rl_wallet",
|
||||
"chia.wallet.cat_wallet",
|
||||
|
||||
@@ -50,9 +50,6 @@ wallet_program_files = set(
|
||||
"chia/wallet/puzzles/nft_state_layer.clvm",
|
||||
"chia/wallet/puzzles/nft_ownership_layer.clvm",
|
||||
"chia/wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clvm",
|
||||
"chia/wallet/puzzles/database_offer.clvm",
|
||||
"chia/wallet/puzzles/database_layer.clvm",
|
||||
"chia/wallet/puzzles/singleton_top_layer_atari_only.clvm",
|
||||
"chia/wallet/puzzles/graftroot_dl_offers.clvm",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
from pathlib import Path
|
||||
from typing import AsyncIterator, Dict, List, Tuple, Set
|
||||
import pytest
|
||||
@@ -32,6 +33,7 @@ nodes = Tuple[WalletNode, FullNodeSimulator]
|
||||
nodes_with_port = Tuple[WalletNode, FullNodeSimulator, int]
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def init_data_layer(wallet_rpc_port: int, bt: BlockTools, db_path: Path) -> AsyncIterator[DataLayer]:
|
||||
config = bt.config
|
||||
config["data_layer"]["wallet_peer"]["port"] = wallet_rpc_port
|
||||
@@ -43,11 +45,13 @@ async def init_data_layer(wallet_rpc_port: int, bt: BlockTools, db_path: Path) -
|
||||
save_config(bt.root_path, "config.yaml", config)
|
||||
kwargs = service_kwargs_for_data_layer(root_path=bt.root_path, config=config)
|
||||
kwargs.update(parse_cli_args=False)
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
await service.start()
|
||||
yield service._api.data_layer
|
||||
service.stop()
|
||||
await service.wait_closed()
|
||||
try:
|
||||
yield service._api.data_layer
|
||||
finally:
|
||||
service.stop()
|
||||
await service.wait_closed()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
@@ -94,7 +98,7 @@ async def test_create_insert_get(one_wallet_node_and_rpc: nodes_with_port, bt: B
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
# test insert
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
key = b"a"
|
||||
@@ -162,7 +166,7 @@ async def test_upsert(one_wallet_node_and_rpc: nodes_with_port, bt: BlockTools,
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
# test insert
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
key = b"a"
|
||||
@@ -207,7 +211,7 @@ async def test_create_double_insert(one_wallet_node_and_rpc: nodes_with_port, bt
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
assert res is not None
|
||||
@@ -269,7 +273,7 @@ async def test_keys_values_ancestors(one_wallet_node_and_rpc: nodes_with_port, b
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
# TODO: with this being a pseudo context manager'ish thing it doesn't actually handle shutdown
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
assert res is not None
|
||||
@@ -356,7 +360,7 @@ async def test_get_roots(one_wallet_node_and_rpc: nodes_with_port, bt: BlockTool
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
assert res is not None
|
||||
@@ -430,7 +434,7 @@ async def test_get_root_history(one_wallet_node_and_rpc: nodes_with_port, bt: Bl
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
assert res is not None
|
||||
@@ -507,7 +511,7 @@ async def test_get_kv_diff(one_wallet_node_and_rpc: nodes_with_port, bt: BlockTo
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
assert res is not None
|
||||
@@ -599,7 +603,7 @@ async def test_batch_update_matches_single_operations(
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
wallet_rpc_api = WalletRpcApi(wallet_node)
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
assert res is not None
|
||||
@@ -709,7 +713,7 @@ async def test_get_owned_stores(one_wallet_node_and_rpc: nodes_with_port, bt: Bl
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
|
||||
expected_store_ids = []
|
||||
@@ -745,7 +749,7 @@ async def test_subscriptions(one_wallet_node_and_rpc: nodes_with_port, bt: Block
|
||||
)
|
||||
await time_out_assert(15, wallet_node.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
|
||||
async for data_layer in init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path):
|
||||
async with init_data_layer(wallet_rpc_port=wallet_rpc_port, bt=bt, db_path=tmp_path) as data_layer:
|
||||
data_rpc_api = DataLayerRpcApi(data_layer)
|
||||
|
||||
res = await data_rpc_api.create_data_store({})
|
||||
|
||||
@@ -35,11 +35,11 @@ class TestRateLimits:
|
||||
# Too many messages
|
||||
r = RateLimiter(incoming=True)
|
||||
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
|
||||
for i in range(4900):
|
||||
for i in range(4999):
|
||||
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
|
||||
|
||||
saw_disconnect = False
|
||||
for i in range(4900):
|
||||
for i in range(4999):
|
||||
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
|
||||
if not response:
|
||||
saw_disconnect = True
|
||||
@@ -48,7 +48,7 @@ class TestRateLimits:
|
||||
# Non-tx message
|
||||
r = RateLimiter(incoming=True)
|
||||
new_peak_message = make_msg(ProtocolMessageTypes.new_peak, bytes([1] * 40))
|
||||
for i in range(20):
|
||||
for i in range(200):
|
||||
assert r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
|
||||
|
||||
saw_disconnect = False
|
||||
@@ -80,7 +80,7 @@ class TestRateLimits:
|
||||
# Too much data
|
||||
r = RateLimiter(incoming=True)
|
||||
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
|
||||
for i in range(10):
|
||||
for i in range(40):
|
||||
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2)
|
||||
|
||||
saw_disconnect = False
|
||||
@@ -110,14 +110,14 @@ class TestRateLimits:
|
||||
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 64))
|
||||
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 64))
|
||||
|
||||
for i in range(450):
|
||||
for i in range(500):
|
||||
assert r.process_msg_and_check(message_1, rl_v2, rl_v2)
|
||||
|
||||
for i in range(450):
|
||||
for i in range(500):
|
||||
assert r.process_msg_and_check(message_2, rl_v2, rl_v2)
|
||||
|
||||
saw_disconnect = False
|
||||
for i in range(450):
|
||||
for i in range(500):
|
||||
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
|
||||
if not response:
|
||||
saw_disconnect = True
|
||||
@@ -158,11 +158,11 @@ class TestRateLimits:
|
||||
# Counts reset also
|
||||
r = RateLimiter(True, 5)
|
||||
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
|
||||
for i in range(4900):
|
||||
for i in range(4999):
|
||||
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
|
||||
|
||||
saw_disconnect = False
|
||||
for i in range(4900):
|
||||
for i in range(4999):
|
||||
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
|
||||
if not response:
|
||||
saw_disconnect = True
|
||||
|
||||
@@ -19,7 +19,6 @@ from chia.util.ints import uint8, uint32, uint64
|
||||
from chia.util.streamable import (
|
||||
DefinitionError,
|
||||
Streamable,
|
||||
dataclass_from_dict,
|
||||
is_type_List,
|
||||
is_type_SpecificOptional,
|
||||
is_type_Tuple,
|
||||
@@ -32,6 +31,7 @@ from chia.util.streamable import (
|
||||
parse_tuple,
|
||||
parse_uint32,
|
||||
streamable,
|
||||
streamable_from_dict,
|
||||
write_uint32,
|
||||
)
|
||||
from tests.block_tools import BlockTools
|
||||
@@ -94,41 +94,27 @@ def test_plain_class_not_supported() -> None:
|
||||
a: PlainClass
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestDataclassFromDict1:
|
||||
a: int
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class StreamableFromDict1(Streamable):
|
||||
a: uint8
|
||||
b: str
|
||||
c: G1Element
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestDataclassFromDict2:
|
||||
a: TestDataclassFromDict1
|
||||
b: TestDataclassFromDict1
|
||||
c: float
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class StreamableFromDict2(Streamable):
|
||||
a: StreamableFromDict1
|
||||
b: StreamableFromDict1
|
||||
c: uint64
|
||||
|
||||
|
||||
def test_pure_dataclasses_in_dataclass_from_dict() -> None:
|
||||
|
||||
d1_dict = {"a": 1, "b": "2", "c": str(G1Element())}
|
||||
|
||||
d1: TestDataclassFromDict1 = dataclass_from_dict(TestDataclassFromDict1, d1_dict)
|
||||
assert d1.a == 1
|
||||
assert d1.b == "2"
|
||||
assert d1.c == G1Element()
|
||||
|
||||
d2_dict = {"a": d1, "b": d1_dict, "c": 1.2345}
|
||||
|
||||
d2: TestDataclassFromDict2 = dataclass_from_dict(TestDataclassFromDict2, d2_dict)
|
||||
assert d2.a == d1
|
||||
assert d2.b == d1
|
||||
assert d2.c == 1.2345
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConvertTupleFailures:
|
||||
a: Tuple[int, int]
|
||||
b: Tuple[int, Tuple[int, int]]
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class ConvertTupleFailures(Streamable):
|
||||
a: Tuple[uint8, uint8]
|
||||
b: Tuple[uint8, Tuple[uint8, uint8]]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -149,13 +135,14 @@ class ConvertTupleFailures:
|
||||
def test_convert_tuple_failures(input_dict: Dict[str, Any], error: Any) -> None:
|
||||
|
||||
with pytest.raises(error):
|
||||
dataclass_from_dict(ConvertTupleFailures, input_dict)
|
||||
streamable_from_dict(ConvertTupleFailures, input_dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConvertListFailures:
|
||||
a: List[int]
|
||||
b: List[List[int]]
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class ConvertListFailures(Streamable):
|
||||
a: List[uint8]
|
||||
b: List[List[uint8]]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -172,11 +159,12 @@ class ConvertListFailures:
|
||||
def test_convert_list_failures(input_dict: Dict[str, Any], error: Any) -> None:
|
||||
|
||||
with pytest.raises(error):
|
||||
dataclass_from_dict(ConvertListFailures, input_dict)
|
||||
streamable_from_dict(ConvertListFailures, input_dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConvertByteTypeFailures:
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class ConvertByteTypeFailures(Streamable):
|
||||
a: bytes4
|
||||
b: bytes
|
||||
|
||||
@@ -201,11 +189,12 @@ class ConvertByteTypeFailures:
|
||||
def test_convert_byte_type_failures(input_dict: Dict[str, Any], error: Any) -> None:
|
||||
|
||||
with pytest.raises(error):
|
||||
dataclass_from_dict(ConvertByteTypeFailures, input_dict)
|
||||
streamable_from_dict(ConvertByteTypeFailures, input_dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConvertUnhashableTypeFailures:
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class ConvertUnhashableTypeFailures(Streamable):
|
||||
a: G1Element
|
||||
|
||||
|
||||
@@ -226,7 +215,7 @@ class ConvertUnhashableTypeFailures:
|
||||
def test_convert_unhashable_type_failures(input_dict: Dict[str, Any], error: Any) -> None:
|
||||
|
||||
with pytest.raises(error):
|
||||
dataclass_from_dict(ConvertUnhashableTypeFailures, input_dict)
|
||||
streamable_from_dict(ConvertUnhashableTypeFailures, input_dict)
|
||||
|
||||
|
||||
class NoStrClass:
|
||||
@@ -234,9 +223,10 @@ class NoStrClass:
|
||||
raise RuntimeError("No string")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConvertPrimitiveFailures:
|
||||
a: int
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class ConvertPrimitiveFailures(Streamable):
|
||||
a: uint8
|
||||
b: uint8
|
||||
c: str
|
||||
|
||||
@@ -252,28 +242,28 @@ class ConvertPrimitiveFailures:
|
||||
def test_convert_primitive_failures(input_dict: Dict[str, Any], error: Any) -> None:
|
||||
|
||||
with pytest.raises(error):
|
||||
dataclass_from_dict(ConvertPrimitiveFailures, input_dict)
|
||||
streamable_from_dict(ConvertPrimitiveFailures, input_dict)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_class, input_dict, error",
|
||||
[
|
||||
[TestDataclassFromDict1, {"a": "asdf", "b": "2", "c": G1Element()}, TypeError],
|
||||
[TestDataclassFromDict1, {"a": 1, "b": "2"}, KeyError],
|
||||
[TestDataclassFromDict1, {"a": 1, "b": "2", "c": "asd"}, TypeError],
|
||||
[TestDataclassFromDict1, {"a": 1, "b": "2", "c": "00" * G1Element.SIZE}, TypeError],
|
||||
[TestDataclassFromDict1, {"a": [], "b": "2", "c": G1Element()}, TypeError],
|
||||
[TestDataclassFromDict1, {"a": {}, "b": "2", "c": G1Element()}, TypeError],
|
||||
[TestDataclassFromDict2, {"a": "asdf", "b": 1.2345, "c": 1.2345}, TypeError],
|
||||
[TestDataclassFromDict2, {"a": 1.2345, "b": {"a": 1, "b": "2"}, "c": 1.2345}, TypeError],
|
||||
[TestDataclassFromDict2, {"a": {"a": 1, "b": "2", "c": G1Element()}, "b": {"a": 1, "b": "2"}}, KeyError],
|
||||
[TestDataclassFromDict2, {"a": {"a": 1, "b": "2"}, "b": {"a": 1, "b": "2"}, "c": 1.2345}, KeyError],
|
||||
[StreamableFromDict1, {"a": "asdf", "b": "2", "c": G1Element()}, TypeError],
|
||||
[StreamableFromDict1, {"a": 1, "b": "2"}, KeyError],
|
||||
[StreamableFromDict1, {"a": 1, "b": "2", "c": "asd"}, TypeError],
|
||||
[StreamableFromDict1, {"a": 1, "b": "2", "c": "00" * G1Element.SIZE}, TypeError],
|
||||
[StreamableFromDict1, {"a": [], "b": "2", "c": G1Element()}, TypeError],
|
||||
[StreamableFromDict1, {"a": {}, "b": "2", "c": G1Element()}, TypeError],
|
||||
[StreamableFromDict2, {"a": "asdf", "b": 12345, "c": 12345}, TypeError],
|
||||
[StreamableFromDict2, {"a": 12345, "b": {"a": 1, "b": "2"}, "c": 12345}, TypeError],
|
||||
[StreamableFromDict2, {"a": {"a": 1, "b": "2", "c": G1Element()}, "b": {"a": 1, "b": "2"}}, KeyError],
|
||||
[StreamableFromDict2, {"a": {"a": 1, "b": "2"}, "b": {"a": 1, "b": "2"}, "c": 12345}, KeyError],
|
||||
],
|
||||
)
|
||||
def test_dataclass_from_dict_failures(test_class: Type[Any], input_dict: Dict[str, Any], error: Any) -> None:
|
||||
def test_streamable_from_dict_failures(test_class: Type[Streamable], input_dict: Dict[str, Any], error: Any) -> None:
|
||||
|
||||
with pytest.raises(error):
|
||||
dataclass_from_dict(test_class, input_dict)
|
||||
streamable_from_dict(test_class, input_dict)
|
||||
|
||||
|
||||
@streamable
|
||||
|
||||
@@ -51,38 +51,35 @@ class TestWalletPoolStore:
|
||||
solution_0_alt: CoinSpend = make_child_solution(None, coin_0_alt)
|
||||
solution_1: CoinSpend = make_child_solution(solution_0)
|
||||
|
||||
assert store.get_spends_for_wallet(0) == []
|
||||
assert store.get_spends_for_wallet(1) == []
|
||||
assert await store.get_spends_for_wallet(0) == []
|
||||
assert await store.get_spends_for_wallet(1) == []
|
||||
|
||||
await store.add_spend(1, solution_1, 100, True)
|
||||
assert store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
assert await store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
|
||||
# Idempotent
|
||||
await store.add_spend(1, solution_1, 100, True)
|
||||
assert store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
assert await store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await store.add_spend(1, solution_1, 101, True)
|
||||
|
||||
# Rebuild cache, no longer present
|
||||
await db_wrapper.rollback_transaction()
|
||||
await store.rebuild_cache()
|
||||
assert store.get_spends_for_wallet(1) == []
|
||||
assert await store.get_spends_for_wallet(1) == []
|
||||
|
||||
await store.rebuild_cache()
|
||||
await store.add_spend(1, solution_1, 100, False)
|
||||
assert store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
assert await store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
|
||||
solution_1_alt: CoinSpend = make_child_solution(solution_0_alt)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await store.add_spend(1, solution_1_alt, 100, False)
|
||||
|
||||
assert store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
assert await store.get_spends_for_wallet(1) == [(100, solution_1)]
|
||||
|
||||
solution_2: CoinSpend = make_child_solution(solution_1)
|
||||
await store.add_spend(1, solution_2, 100, False)
|
||||
await store.rebuild_cache()
|
||||
solution_3: CoinSpend = make_child_solution(solution_2)
|
||||
await store.add_spend(1, solution_3, 100)
|
||||
solution_4: CoinSpend = make_child_solution(solution_3)
|
||||
@@ -90,21 +87,16 @@ class TestWalletPoolStore:
|
||||
with pytest.raises(ValueError):
|
||||
await store.add_spend(1, solution_4, 99)
|
||||
|
||||
await store.rebuild_cache()
|
||||
await store.add_spend(1, solution_4, 101)
|
||||
await store.rebuild_cache()
|
||||
await store.rollback(101, 1, False)
|
||||
await store.rebuild_cache()
|
||||
assert store.get_spends_for_wallet(1) == [
|
||||
assert await store.get_spends_for_wallet(1) == [
|
||||
(100, solution_1),
|
||||
(100, solution_2),
|
||||
(100, solution_3),
|
||||
(101, solution_4),
|
||||
]
|
||||
await store.rebuild_cache()
|
||||
await store.rollback(100, 1, False)
|
||||
await store.rebuild_cache()
|
||||
assert store.get_spends_for_wallet(1) == [
|
||||
assert await store.get_spends_for_wallet(1) == [
|
||||
(100, solution_1),
|
||||
(100, solution_2),
|
||||
(100, solution_3),
|
||||
@@ -116,7 +108,7 @@ class TestWalletPoolStore:
|
||||
solution_5: CoinSpend = make_child_solution(solution_4)
|
||||
await store.add_spend(1, solution_5, 105)
|
||||
await store.rollback(99, 1, False)
|
||||
assert store.get_spends_for_wallet(1) == []
|
||||
assert await store.get_spends_for_wallet(1) == []
|
||||
|
||||
finally:
|
||||
await db_connection.close()
|
||||
|
||||
+6
-12
@@ -118,12 +118,11 @@ async def setup_full_node(
|
||||
kwargs.update(
|
||||
parse_cli_args=False,
|
||||
connect_to_daemon=connect_to_daemon,
|
||||
service_name_prefix="test_",
|
||||
)
|
||||
if disable_capabilities is not None:
|
||||
kwargs.update(override_capabilities=get_capabilities(disable_capabilities))
|
||||
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
|
||||
await service.start()
|
||||
|
||||
@@ -189,10 +188,9 @@ async def setup_wallet_node(
|
||||
kwargs.update(
|
||||
parse_cli_args=False,
|
||||
connect_to_daemon=False,
|
||||
service_name_prefix="test_",
|
||||
)
|
||||
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
|
||||
await service.start()
|
||||
|
||||
@@ -229,10 +227,9 @@ async def setup_harvester(
|
||||
kwargs.update(
|
||||
parse_cli_args=False,
|
||||
connect_to_daemon=False,
|
||||
service_name_prefix="test_",
|
||||
)
|
||||
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
|
||||
if start_service:
|
||||
await service.start()
|
||||
@@ -278,10 +275,9 @@ async def setup_farmer(
|
||||
kwargs.update(
|
||||
parse_cli_args=False,
|
||||
connect_to_daemon=False,
|
||||
service_name_prefix="test_",
|
||||
)
|
||||
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
|
||||
if start_service:
|
||||
await service.start()
|
||||
@@ -301,10 +297,9 @@ async def setup_introducer(bt: BlockTools, port):
|
||||
advertised_port=port,
|
||||
parse_cli_args=False,
|
||||
connect_to_daemon=False,
|
||||
service_name_prefix="test_",
|
||||
)
|
||||
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
|
||||
await service.start()
|
||||
|
||||
@@ -362,10 +357,9 @@ async def setup_timelord(
|
||||
kwargs.update(
|
||||
parse_cli_args=False,
|
||||
connect_to_daemon=False,
|
||||
service_name_prefix="test_",
|
||||
)
|
||||
|
||||
service = Service(**kwargs, running_new_process=False)
|
||||
service = Service(**kwargs)
|
||||
|
||||
await service.start()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from pathlib import Path
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
from chia.util.db_wrapper import DBWrapper
|
||||
import tempfile
|
||||
import aiosqlite
|
||||
|
||||
@@ -33,3 +34,18 @@ class DBConnection:
|
||||
async def __aexit__(self, exc_t, exc_v, exc_tb) -> None:
|
||||
await self._db_wrapper.close()
|
||||
self.db_path.unlink()
|
||||
|
||||
|
||||
# This is just here until all DBWrappers have been upgraded to DBWrapper2
|
||||
class DBConnection1:
|
||||
async def __aenter__(self) -> DBWrapper:
|
||||
self.db_path = Path(tempfile.NamedTemporaryFile().name)
|
||||
if self.db_path.exists():
|
||||
self.db_path.unlink()
|
||||
self._db_connection = await aiosqlite.connect(self.db_path)
|
||||
self._db_wrapper = DBWrapper(self._db_connection)
|
||||
return self._db_wrapper
|
||||
|
||||
async def __aexit__(self, exc_t, exc_v, exc_tb) -> None:
|
||||
await self._db_connection.close()
|
||||
self.db_path.unlink()
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
|
||||
from chia.full_node.mempool_manager import MempoolManager
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.peer_info import PeerInfo
|
||||
@@ -98,6 +98,10 @@ class TestCATWallet:
|
||||
assert new_cat_wallet.cat_info.my_tail == cat_wallet.cat_info.my_tail
|
||||
assert await cat_wallet.lineage_store.get_all_lineage_proofs() == all_lineage
|
||||
|
||||
height = full_node_api.full_node.blockchain.get_peak_height()
|
||||
await full_node_api.reorg_from_index_to_new_index(ReorgProtocol(height - num_blocks - 1, height + 1, 32 * b"1"))
|
||||
await time_out_assert(15, cat_wallet.get_confirmed_balance, 0)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cat_creation_unique_lineage_store(self, self_hostname, two_wallet_nodes):
|
||||
num_blocks = 3
|
||||
@@ -233,12 +237,15 @@ class TestCATWallet:
|
||||
15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()
|
||||
)
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
await time_out_assert(15, cat_wallet.get_confirmed_balance, 55)
|
||||
await time_out_assert(15, cat_wallet.get_unconfirmed_balance, 55)
|
||||
|
||||
height = full_node_api.full_node.blockchain.get_peak_height()
|
||||
await full_node_api.reorg_from_index_to_new_index(ReorgProtocol(height - 1, height + 1, 32 * b"1"))
|
||||
await time_out_assert(15, cat_wallet.get_confirmed_balance, 40)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
[True, False],
|
||||
|
||||
@@ -1,345 +0,0 @@
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from blspy import G2Element
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from chia.clvm.spend_sim import SpendSim, SimClient
|
||||
from chia.wallet.db_wallet.db_wallet_puzzles import (
|
||||
create_host_fullpuz,
|
||||
create_offer_fullpuz,
|
||||
SINGLETON_LAUNCHER,
|
||||
create_host_layer_puzzle,
|
||||
solve_data_layer_to_report,
|
||||
solve_data_layer_to_update,
|
||||
solve_dl_offer_for_claim,
|
||||
solve_dl_offer_for_recover,
|
||||
)
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.puzzles.singleton_top_layer import solution_for_singleton
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.util.merkle_tree import MerkleTree
|
||||
|
||||
from tests.clvm.benchmark_costs import cost_of_spend_bundle
|
||||
|
||||
|
||||
ACS = Program.to(1)
|
||||
ACS_2 = Program.to([3, "2", 1, []]) # (if "2" 1 ()) == 1
|
||||
ACS_PH = ACS.get_tree_hash()
|
||||
ACS_2_PH = ACS_2.get_tree_hash()
|
||||
SINGLETON_LAUNCHER_HASH = SINGLETON_LAUNCHER.get_tree_hash()
|
||||
OFFER_AMOUNT = uint64(13)
|
||||
|
||||
pytestmark = pytest.mark.data_layer
|
||||
|
||||
|
||||
SetupArgs = Tuple[SpendSim, SimClient, Coin, LineageProof, Coin, Coin, Program, Program]
|
||||
|
||||
|
||||
class TestDLLifecycle:
|
||||
cost: Dict[str, int] = {}
|
||||
|
||||
def get_merkle_tree(self, string: str) -> MerkleTree:
|
||||
return MerkleTree([Program.to(string).get_tree_hash(), Program.to(string).get_tree_hash()])
|
||||
|
||||
def get_merkle_root(self, string: str) -> bytes32:
|
||||
return self.get_merkle_tree(string).calculate_root()
|
||||
|
||||
def hash_merkle_value(self, string: str) -> bytes32:
|
||||
# https://github.com/Chia-Network/clvm/pull/102
|
||||
# https://github.com/Chia-Network/clvm/pull/106
|
||||
return Program.to(string).get_tree_hash() # type: ignore[no-any-return]
|
||||
|
||||
def get_merkle_proof(self, string: str) -> Program:
|
||||
proof = self.get_merkle_tree(string).generate_proof(self.hash_merkle_value(string))
|
||||
# https://github.com/Chia-Network/clvm/pull/102
|
||||
# https://github.com/Chia-Network/clvm/pull/106
|
||||
return Program.to(proof) # type: ignore[no-any-return]
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def setup_sim_and_singleton(self) -> SetupArgs:
|
||||
# https://github.com/Chia-Network/chia-blockchain/pull/11819
|
||||
sim = await SpendSim.create() # type: ignore[no-untyped-call]
|
||||
sim_client = SimClient(sim) # type: ignore[no-untyped-call]
|
||||
await sim.farm_block()
|
||||
await sim.farm_block(ACS_PH)
|
||||
fund_coin = (await sim_client.get_coin_records_by_puzzle_hash(ACS_PH))[0].coin
|
||||
launcher_coin = Coin(fund_coin.name(), SINGLETON_LAUNCHER_HASH, uint64(1))
|
||||
singleton_puzzle = create_host_fullpuz(ACS_PH, self.get_merkle_root("init"), launcher_coin.name())
|
||||
good_puzzle = create_offer_fullpuz(
|
||||
self.hash_merkle_value("init"),
|
||||
launcher_coin.name(),
|
||||
ACS_2_PH,
|
||||
ACS_PH,
|
||||
uint64(60), # 60 seconds
|
||||
)
|
||||
bad_puzzle = create_offer_fullpuz(
|
||||
self.hash_merkle_value("nope"),
|
||||
launcher_coin.name(),
|
||||
ACS_2_PH,
|
||||
ACS_PH,
|
||||
uint64(60), # 60 seconds
|
||||
)
|
||||
bundle = SpendBundle(
|
||||
[
|
||||
CoinSpend(
|
||||
fund_coin,
|
||||
ACS,
|
||||
Program.to(
|
||||
[
|
||||
[51, SINGLETON_LAUNCHER_HASH, 1],
|
||||
[51, good_puzzle.get_tree_hash(), OFFER_AMOUNT],
|
||||
[51, bad_puzzle.get_tree_hash(), OFFER_AMOUNT],
|
||||
]
|
||||
),
|
||||
),
|
||||
CoinSpend(
|
||||
launcher_coin,
|
||||
SINGLETON_LAUNCHER,
|
||||
Program.to([singleton_puzzle.get_tree_hash(), launcher_coin.amount, []]),
|
||||
),
|
||||
],
|
||||
G2Element(),
|
||||
)
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.SUCCESS
|
||||
self.cost["launch singleton and create two coins"] = cost_of_spend_bundle(bundle)
|
||||
await sim.farm_block()
|
||||
singleton = (await sim_client.get_coin_records_by_puzzle_hashes([singleton_puzzle.get_tree_hash()]))[0].coin
|
||||
good_offer_coin = (await sim_client.get_coin_records_by_puzzle_hashes([good_puzzle.get_tree_hash()]))[0].coin
|
||||
bad_offer_coin = (await sim_client.get_coin_records_by_puzzle_hashes([bad_puzzle.get_tree_hash()]))[0].coin
|
||||
return (
|
||||
sim,
|
||||
sim_client,
|
||||
singleton,
|
||||
LineageProof(parent_name=launcher_coin.parent_coin_info, amount=uint64(launcher_coin.amount)),
|
||||
good_offer_coin,
|
||||
bad_offer_coin,
|
||||
good_puzzle,
|
||||
bad_puzzle,
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_report(self, setup_sim_and_singleton: SetupArgs) -> None:
|
||||
sim, sim_client, singleton, lineage_proof = setup_sim_and_singleton[0:4]
|
||||
|
||||
try:
|
||||
bundle = SpendBundle(
|
||||
[
|
||||
CoinSpend(
|
||||
singleton,
|
||||
create_host_fullpuz(ACS_PH, self.get_merkle_root("init"), singleton.parent_coin_info),
|
||||
solution_for_singleton(
|
||||
lineage_proof,
|
||||
uint64(singleton.amount),
|
||||
solve_data_layer_to_report(uint64(singleton.amount)),
|
||||
),
|
||||
)
|
||||
],
|
||||
G2Element(),
|
||||
)
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.SUCCESS
|
||||
self.cost["report spend"] = cost_of_spend_bundle(bundle)
|
||||
await sim.farm_block()
|
||||
new_singleton = (await sim_client.get_coin_records_by_parent_ids([singleton.name()]))[0].coin
|
||||
assert new_singleton.puzzle_hash == singleton.puzzle_hash
|
||||
finally:
|
||||
# https://github.com/Chia-Network/chia-blockchain/pull/11819
|
||||
await sim.close() # type: ignore[no-untyped-call]
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_update(self, setup_sim_and_singleton: SetupArgs) -> None:
|
||||
sim, sim_client, singleton, lineage_proof = setup_sim_and_singleton[0:4]
|
||||
|
||||
try:
|
||||
bundle = SpendBundle(
|
||||
[
|
||||
CoinSpend(
|
||||
singleton,
|
||||
create_host_fullpuz(ACS_PH, self.get_merkle_root("init"), singleton.parent_coin_info),
|
||||
solution_for_singleton(
|
||||
lineage_proof,
|
||||
uint64(singleton.amount),
|
||||
solve_data_layer_to_update(
|
||||
ACS,
|
||||
Program.to(
|
||||
[
|
||||
[
|
||||
51,
|
||||
create_host_layer_puzzle(
|
||||
ACS_PH, self.get_merkle_root("update")
|
||||
).get_tree_hash(),
|
||||
singleton.amount,
|
||||
]
|
||||
]
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
G2Element(),
|
||||
)
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.SUCCESS
|
||||
self.cost["update spend"] = cost_of_spend_bundle(bundle)
|
||||
await sim.farm_block()
|
||||
new_singleton = (await sim_client.get_coin_records_by_parent_ids([singleton.name()]))[0].coin
|
||||
assert (
|
||||
new_singleton.puzzle_hash
|
||||
== create_host_fullpuz(
|
||||
ACS_PH, self.get_merkle_root("update"), singleton.parent_coin_info
|
||||
).get_tree_hash()
|
||||
)
|
||||
finally:
|
||||
# https://github.com/Chia-Network/chia-blockchain/pull/11819
|
||||
await sim.close() # type: ignore[no-untyped-call]
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_offer_cant_claim(self, setup_sim_and_singleton: SetupArgs) -> None:
|
||||
(
|
||||
sim,
|
||||
sim_client,
|
||||
singleton,
|
||||
lineage_proof,
|
||||
good_offer_coin,
|
||||
bad_offer_coin,
|
||||
good_offer_puzzle,
|
||||
bad_offer_puzzle,
|
||||
) = setup_sim_and_singleton
|
||||
|
||||
try:
|
||||
bundle = SpendBundle(
|
||||
[
|
||||
CoinSpend(
|
||||
singleton,
|
||||
create_host_fullpuz(ACS_PH, self.get_merkle_root("init"), singleton.parent_coin_info),
|
||||
solution_for_singleton(
|
||||
lineage_proof,
|
||||
uint64(singleton.amount),
|
||||
solve_data_layer_to_report(uint64(singleton.amount)),
|
||||
),
|
||||
),
|
||||
CoinSpend(
|
||||
bad_offer_coin,
|
||||
bad_offer_puzzle,
|
||||
solve_dl_offer_for_claim(
|
||||
OFFER_AMOUNT,
|
||||
ACS_PH,
|
||||
self.get_merkle_root("init"),
|
||||
self.get_merkle_proof("nope"),
|
||||
),
|
||||
),
|
||||
],
|
||||
G2Element(),
|
||||
)
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.FAILED
|
||||
offer_cs = bundle.coin_spends[1]
|
||||
with pytest.raises(ValueError, match="clvm raise"):
|
||||
offer_cs.puzzle_reveal.to_program().run(offer_cs.solution.to_program())
|
||||
finally:
|
||||
# https://github.com/Chia-Network/chia-blockchain/pull/11819
|
||||
await sim.close() # type: ignore[no-untyped-call]
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_offer_can_claim(self, setup_sim_and_singleton: SetupArgs) -> None:
|
||||
(
|
||||
sim,
|
||||
sim_client,
|
||||
singleton,
|
||||
lineage_proof,
|
||||
good_offer_coin,
|
||||
bad_offer_coin,
|
||||
good_offer_puzzle,
|
||||
bad_offer_puzzle,
|
||||
) = setup_sim_and_singleton
|
||||
|
||||
try:
|
||||
bundle = SpendBundle(
|
||||
[
|
||||
CoinSpend(
|
||||
singleton,
|
||||
create_host_fullpuz(ACS_PH, self.get_merkle_root("init"), singleton.parent_coin_info),
|
||||
solution_for_singleton(
|
||||
lineage_proof,
|
||||
uint64(singleton.amount),
|
||||
solve_data_layer_to_report(uint64(singleton.amount)),
|
||||
),
|
||||
),
|
||||
CoinSpend(
|
||||
good_offer_coin,
|
||||
good_offer_puzzle,
|
||||
solve_dl_offer_for_claim(
|
||||
OFFER_AMOUNT,
|
||||
ACS_PH,
|
||||
self.get_merkle_root("init"),
|
||||
self.get_merkle_proof("init"),
|
||||
),
|
||||
),
|
||||
],
|
||||
G2Element(),
|
||||
)
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.SUCCESS
|
||||
self.cost["offer claim"] = cost_of_spend_bundle(bundle)
|
||||
await sim.farm_block()
|
||||
offer_reward = (await sim_client.get_coin_records_by_parent_ids([good_offer_coin.name()]))[0].coin
|
||||
assert offer_reward.puzzle_hash == ACS_2_PH
|
||||
finally:
|
||||
# https://github.com/Chia-Network/chia-blockchain/pull/11819
|
||||
await sim.close() # type: ignore[no-untyped-call]
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_offer_recovery(self, setup_sim_and_singleton: SetupArgs) -> None:
|
||||
(
|
||||
sim,
|
||||
sim_client,
|
||||
singleton,
|
||||
lineage_proof,
|
||||
good_offer_coin,
|
||||
bad_offer_coin,
|
||||
good_offer_puzzle,
|
||||
bad_offer_puzzle,
|
||||
) = setup_sim_and_singleton
|
||||
|
||||
try:
|
||||
bundle = SpendBundle(
|
||||
[
|
||||
CoinSpend(
|
||||
bad_offer_coin,
|
||||
bad_offer_puzzle,
|
||||
solve_dl_offer_for_recover(OFFER_AMOUNT),
|
||||
),
|
||||
],
|
||||
G2Element(),
|
||||
)
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.FAILED
|
||||
|
||||
# Should work after a minute
|
||||
sim.pass_time(uint64(60))
|
||||
await sim.farm_block()
|
||||
result = (await sim_client.push_tx(bundle))[0]
|
||||
assert result == MempoolInclusionStatus.SUCCESS
|
||||
self.cost["offer recovery"] = cost_of_spend_bundle(bundle)
|
||||
await sim.farm_block()
|
||||
|
||||
offer_reward = (await sim_client.get_coin_records_by_parent_ids([bad_offer_coin.name()]))[0].coin
|
||||
assert offer_reward.puzzle_hash == ACS_PH
|
||||
finally:
|
||||
# https://github.com/Chia-Network/chia-blockchain/pull/11819
|
||||
await sim.close() # type: ignore[no-untyped-call]
|
||||
|
||||
def test_cost(self) -> None:
|
||||
import json
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.warning(json.dumps(self.cost))
|
||||
@@ -124,4 +124,4 @@ async def test_graftroot(setup_sim: Tuple[SpendSim, SimClient]) -> None:
|
||||
with pytest.raises(ValueError, match="clvm raise"):
|
||||
graftroot_puzzle.run(graftroot_spend.solution.to_program())
|
||||
finally:
|
||||
await sim.close() # type: ignore[no-untyped-call]
|
||||
await sim.close()
|
||||
|
||||
@@ -10,7 +10,6 @@ from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from tests.setup_nodes import setup_simulators_and_wallets
|
||||
from chia.data_layer.data_layer_wallet import DataLayerWallet
|
||||
|
||||
# from chia.wallet.dlo_wallet.dlo_wallet import DLOWallet
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from tests.time_out_assert import time_out_assert
|
||||
from chia.wallet.util.merkle_tree import MerkleTree
|
||||
@@ -280,12 +279,26 @@ class TestDLWallet:
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
previous_record = await dl_wallet.get_latest_singleton(launcher_id)
|
||||
assert previous_record is not None
|
||||
assert previous_record.lineage_proof.amount is not None
|
||||
|
||||
new_root = MerkleTree([Program.to("root").get_tree_hash()]).calculate_root()
|
||||
|
||||
txs = await dl_wallet.create_update_state_spend(launcher_id, new_root, fee=uint64(1999999999999))
|
||||
txs = await dl_wallet.generate_signed_transaction(
|
||||
[previous_record.lineage_proof.amount],
|
||||
[previous_record.inner_puzzle_hash],
|
||||
launcher_id=previous_record.launcher_id,
|
||||
new_root_hash=new_root,
|
||||
fee=uint64(1999999999999),
|
||||
)
|
||||
assert txs[0].spend_bundle is not None
|
||||
with pytest.raises(ValueError, match="is currently pending"):
|
||||
await dl_wallet.create_update_state_spend(launcher_id, new_root)
|
||||
await dl_wallet.generate_signed_transaction(
|
||||
[previous_record.lineage_proof.amount],
|
||||
[previous_record.inner_puzzle_hash],
|
||||
coins=set([txs[0].spend_bundle.removals()[0]]),
|
||||
fee=uint64(1999999999999),
|
||||
)
|
||||
|
||||
new_record = await dl_wallet.get_latest_singleton(launcher_id)
|
||||
assert new_record is not None
|
||||
@@ -301,24 +314,6 @@ class TestDLWallet:
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, funds - 2000000000000)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
for _ in range(0, 2):
|
||||
current_record = await dl_wallet.get_latest_singleton(launcher_id)
|
||||
txs, _ = await dl_wallet.create_report_spend(launcher_id, fee=uint64(2000000000000))
|
||||
new_record = await dl_wallet.get_latest_singleton(launcher_id)
|
||||
assert new_record is not None
|
||||
assert new_record != current_record
|
||||
assert not new_record.confirmed
|
||||
|
||||
for tx in txs:
|
||||
await wallet_node_0.wallet_state_manager.add_pending_transaction(tx)
|
||||
await full_node_api.process_transaction_records(records=txs)
|
||||
|
||||
await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds - 6000000000000)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, funds - 6000000000000)
|
||||
|
||||
previous_record = await dl_wallet.get_latest_singleton(launcher_id)
|
||||
|
||||
new_root = MerkleTree([Program.to("new root").get_tree_hash()]).calculate_root()
|
||||
@@ -335,6 +330,7 @@ class TestDLWallet:
|
||||
await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
@pytest.mark.skip(reason="maybe no longer relevant, needs to be rewritten at least")
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
[True, False],
|
||||
@@ -402,7 +398,9 @@ class TestDLWallet:
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# Because these have the same fee, the one that gets pushed first will win
|
||||
report_txs, _ = await dl_wallet_1.create_report_spend(launcher_id, fee=uint64(2000000000000))
|
||||
report_txs = await dl_wallet_1.create_update_state_spend(
|
||||
launcher_id, current_record.root, fee=uint64(2000000000000)
|
||||
)
|
||||
record_1 = await dl_wallet_1.get_latest_singleton(launcher_id)
|
||||
assert record_1 is not None
|
||||
assert current_record != record_1
|
||||
@@ -506,205 +504,3 @@ class TestDLWallet:
|
||||
for tx in update_txs_0:
|
||||
assert await wallet_node_0.wallet_state_manager.tx_store.get_transaction_record(tx.name) is None
|
||||
assert await dl_wallet_0.get_singleton_record(record_0.coin_id) is None
|
||||
|
||||
# @pytest.mark.skip(reason="DLO Wallet is not supported yet")
|
||||
# @pytest.mark.asyncio
|
||||
# async def test_dlo_wallet(self, three_wallet_nodes: SimulatorsAndWallets) -> None:
|
||||
# raise # for ignoring mypy :)
|
||||
# time_lock = uint64(10)
|
||||
# full_nodes, wallets = three_wallet_nodes
|
||||
# full_node_api = full_nodes[0]
|
||||
# full_node_api.time_per_block = 2 * time_lock
|
||||
# full_node_server = full_node_api.server
|
||||
# wallet_node_0, server_0 = wallets[0]
|
||||
# wallet_node_1, server_1 = wallets[1]
|
||||
# wallet_node_2, server_2 = wallets[2]
|
||||
# wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
|
||||
# wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
|
||||
# wallet_2 = wallet_node_2.wallet_state_manager.main_wallet
|
||||
#
|
||||
# ph2 = await wallet_2.get_new_puzzlehash()
|
||||
#
|
||||
# await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
# await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
# await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
#
|
||||
# funds = await full_node_api.farm_blocks(count=1, wallet=wallet_0)
|
||||
#
|
||||
# await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
|
||||
# await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
|
||||
#
|
||||
# nodes = [Program.to("thing").get_tree_hash(), Program.to([8]).get_tree_hash()]
|
||||
# current_tree = MerkleTree(nodes)
|
||||
# current_root = current_tree.calculate_root()
|
||||
#
|
||||
# # Wallet1 sets up DLWallet1
|
||||
# async with wallet_node_0.wallet_state_manager.lock:
|
||||
# creation_record = await DataLayerWallet.create_new_dl_wallet(
|
||||
# wallet_node_0.wallet_state_manager, wallet_0, uint64(101), current_root
|
||||
# )
|
||||
#
|
||||
# dl_wallet_0: DataLayerWallet = creation_record.item
|
||||
#
|
||||
# await full_node_api.process_transaction_records(records=creation_record.transaction_records)
|
||||
#
|
||||
# await time_out_assert(15, dl_wallet_0.get_confirmed_balance, 101)
|
||||
# await time_out_assert(15, dl_wallet_0.get_unconfirmed_balance, 101)
|
||||
#
|
||||
# # Wallet1 sets up DLOWallet1
|
||||
# async with wallet_node_1.wallet_state_manager.lock:
|
||||
# dlo_wallet_1: DLOWallet = await DLOWallet.create_new_dlo_wallet(
|
||||
# wallet_node_1.wallet_state_manager,
|
||||
# wallet_1,
|
||||
# )
|
||||
#
|
||||
# await full_node_api.farm_blocks(count=2, wallet=wallet_1)
|
||||
#
|
||||
# await time_out_assert(15, dlo_wallet_1.get_confirmed_balance, 0)
|
||||
# await time_out_assert(15, dlo_wallet_1.get_unconfirmed_balance, 0)
|
||||
# assert dl_wallet_0.dl_info.origin_coin is not None
|
||||
# tr = await dlo_wallet_1.generate_datalayer_offer_spend(
|
||||
# amount=uint64(201),
|
||||
# leaf_reveal=Program.to("thing").get_tree_hash(),
|
||||
# host_genesis_id=dl_wallet_0.dl_info.origin_coin.name(),
|
||||
# claim_target=await wallet_2.get_new_puzzlehash(),
|
||||
# recovery_target=await wallet_1.get_new_puzzlehash(),
|
||||
# recovery_timelock=time_lock,
|
||||
# )
|
||||
# await wallet_1.push_transaction(tr)
|
||||
# await full_node_api.process_transaction_records(records=[tr])
|
||||
#
|
||||
# await time_out_assert(15, dlo_wallet_1.get_confirmed_balance, 201)
|
||||
# await time_out_assert(15, dlo_wallet_1.get_unconfirmed_balance, 201)
|
||||
#
|
||||
# # create a second DLO Wallet and claim the coin
|
||||
# async with wallet_node_2.wallet_state_manager.lock:
|
||||
# dlo_wallet_2: DLOWallet = await DLOWallet.create_new_dlo_wallet(
|
||||
# wallet_node_2.wallet_state_manager,
|
||||
# wallet_2,
|
||||
# )
|
||||
#
|
||||
# offer_coin = await dlo_wallet_1.get_coin()
|
||||
# offer_full_puzzle = dlo_wallet_1.puzzle_for_pk(0x00)
|
||||
# db_puzzle, db_innerpuz, current_root = await dl_wallet_0.get_info_for_offer_claim()
|
||||
# inclusion_proof = current_tree.generate_proof(Program.to("thing").get_tree_hash())
|
||||
# assert db_innerpuz is not None
|
||||
# sb2 = await dlo_wallet_2.claim_dl_offer(
|
||||
# offer_coin,
|
||||
# offer_full_puzzle,
|
||||
# db_innerpuz.get_tree_hash(),
|
||||
# current_root,
|
||||
# inclusion_proof,
|
||||
# )
|
||||
# sb = await dl_wallet_0.create_report_spend()
|
||||
# sb = SpendBundle.aggregate([sb2, sb])
|
||||
# tr = TransactionRecord(
|
||||
# confirmed_at_height=uint32(0),
|
||||
# created_at_time=uint64(int(time.time())),
|
||||
# to_puzzle_hash=ph2,
|
||||
# amount=uint64(201),
|
||||
# fee_amount=uint64(0),
|
||||
# confirmed=False,
|
||||
# sent=uint32(0),
|
||||
# spend_bundle=sb,
|
||||
# additions=sb.additions(),
|
||||
# removals=sb.removals(),
|
||||
# memos=list(sb.get_memos().items()),
|
||||
# wallet_id=dl_wallet_0.id(),
|
||||
# sent_to=[],
|
||||
# trade_id=None,
|
||||
# type=uint32(TransactionType.OUTGOING_TX.value),
|
||||
# name=sb.name(),
|
||||
# )
|
||||
# await wallet_2.push_transaction(tr)
|
||||
# await full_node_api.process_transaction_records(records=[tr])
|
||||
#
|
||||
# await time_out_assert(15, wallet_2.get_confirmed_balance, 201)
|
||||
# await time_out_assert(15, wallet_2.get_unconfirmed_balance, 201)
|
||||
#
|
||||
# @pytest.mark.skip(reason="DLO wallet is not supported yet")
|
||||
# @pytest.mark.asyncio
|
||||
# async def test_dlo_wallet_reclaim(self, three_wallet_nodes: SimulatorsAndWallets) -> None:
|
||||
# raise # for ignoring mypy :)
|
||||
# time_lock = uint64(10)
|
||||
#
|
||||
# full_nodes, wallets = three_wallet_nodes
|
||||
# full_node_api = full_nodes[0]
|
||||
# full_node_api.time_per_block = 2 * time_lock
|
||||
# full_node_server = full_node_api.server
|
||||
# wallet_node_0, server_0 = wallets[0]
|
||||
# wallet_node_1, server_1 = wallets[1]
|
||||
# wallet_node_2, server_2 = wallets[2]
|
||||
# wallet_0 = wallet_node_0.wallet_state_manager.main_wallet
|
||||
# wallet_1 = wallet_node_1.wallet_state_manager.main_wallet
|
||||
# wallet_2 = wallet_node_2.wallet_state_manager.main_wallet
|
||||
#
|
||||
# await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
# await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
# await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None)
|
||||
#
|
||||
# funds = await full_node_api.farm_blocks(count=1, wallet=wallet_0)
|
||||
#
|
||||
# await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds)
|
||||
# await time_out_assert(10, wallet_0.get_confirmed_balance, funds)
|
||||
#
|
||||
# nodes = [Program.to("thing").get_tree_hash(), Program.to([8]).get_tree_hash()]
|
||||
# current_tree = MerkleTree(nodes)
|
||||
# current_root = current_tree.calculate_root()
|
||||
#
|
||||
# # Wallet1 sets up DLWallet1
|
||||
# async with wallet_node_0.wallet_state_manager.lock:
|
||||
# creation_record = await DataLayerWallet.create_new_dl_wallet(
|
||||
# wallet_node_0.wallet_state_manager, wallet_0, uint64(101), current_root
|
||||
# )
|
||||
#
|
||||
# dl_wallet_0: DataLayerWallet = creation_record.item
|
||||
#
|
||||
# await full_node_api.process_transaction_records(records=creation_record.transaction_records)
|
||||
#
|
||||
# await time_out_assert(15, dl_wallet_0.get_confirmed_balance, 101)
|
||||
# await time_out_assert(15, dl_wallet_0.get_unconfirmed_balance, 101)
|
||||
#
|
||||
# # Wallet1 sets up DLOWallet1
|
||||
# async with wallet_node_1.wallet_state_manager.lock:
|
||||
# dlo_wallet_1: DLOWallet = await DLOWallet.create_new_dlo_wallet(
|
||||
# wallet_node_1.wallet_state_manager,
|
||||
# wallet_1,
|
||||
# )
|
||||
#
|
||||
# wallet_1_funds = await full_node_api.farm_blocks(count=1, wallet=wallet_1)
|
||||
# offer_amount = 201
|
||||
#
|
||||
# await time_out_assert(15, dlo_wallet_1.get_confirmed_balance, 0)
|
||||
# await time_out_assert(15, dlo_wallet_1.get_unconfirmed_balance, 0)
|
||||
# assert dl_wallet_0.dl_info.origin_coin is not None
|
||||
# tr = await dlo_wallet_1.generate_datalayer_offer_spend(
|
||||
# amount=uint64(offer_amount),
|
||||
# leaf_reveal=Program.to("thing").get_tree_hash(),
|
||||
# host_genesis_id=dl_wallet_0.dl_info.origin_coin.name(),
|
||||
# claim_target=await wallet_2.get_new_puzzlehash(),
|
||||
# recovery_target=await wallet_1.get_new_puzzlehash(),
|
||||
# recovery_timelock=time_lock,
|
||||
# )
|
||||
# await wallet_1.push_transaction(tr)
|
||||
# await full_node_api.process_transaction_records(records=[tr])
|
||||
#
|
||||
# await time_out_assert(15, dlo_wallet_1.get_confirmed_balance, offer_amount)
|
||||
# await time_out_assert(15, dlo_wallet_1.get_unconfirmed_balance, offer_amount)
|
||||
# wallet_1_funds -= offer_amount
|
||||
#
|
||||
# await time_out_assert(15, wallet_1.get_confirmed_balance, wallet_1_funds)
|
||||
# await time_out_assert(15, wallet_1.get_unconfirmed_balance, wallet_1_funds)
|
||||
#
|
||||
# transaction_record = await dlo_wallet_1.create_recover_dl_offer_spend()
|
||||
# # Process a block to make sure the time lock for the offer has passed
|
||||
# await full_node_api.process_blocks(count=1)
|
||||
#
|
||||
# await full_node_api.process_transaction_records(records=[transaction_record])
|
||||
#
|
||||
# wallet_1_funds += offer_amount
|
||||
#
|
||||
# await time_out_assert(15, dlo_wallet_1.get_confirmed_balance, 0)
|
||||
# await time_out_assert(15, dlo_wallet_1.get_unconfirmed_balance, 0)
|
||||
# await time_out_assert(15, wallet_1.get_confirmed_balance, wallet_1_funds)
|
||||
# await time_out_assert(15, wallet_1.get_unconfirmed_balance, wallet_1_funds)
|
||||
|
||||
@@ -131,7 +131,7 @@ async def test_state_layer(setup_sim: Tuple[SpendSim, SimClient], metadata_updat
|
||||
await sim.farm_block()
|
||||
state_layer_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, METADATA_UPDATER_PUZZLE_HASH, ACS)
|
||||
finally:
|
||||
await sim.close() # type: ignore
|
||||
await sim.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@@ -238,7 +238,7 @@ async def test_ownership_layer(setup_sim: Tuple[SpendSim, SimClient]) -> None:
|
||||
ACS,
|
||||
).get_tree_hash()
|
||||
finally:
|
||||
await sim.close() # type: ignore
|
||||
await sim.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@@ -362,4 +362,4 @@ async def test_default_transfer_program(setup_sim: Tuple[SpendSim, SimClient]) -
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
finally:
|
||||
await sim.close() # type: ignore
|
||||
await sim.close()
|
||||
|
||||
@@ -13,6 +13,7 @@ from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.peer_info import PeerInfo
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.bech32m import encode_puzzle_hash
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.ints import uint16, uint32, uint64
|
||||
@@ -50,9 +51,10 @@ async def wait_rpc_state_condition(
|
||||
return {}
|
||||
|
||||
|
||||
async def make_new_block_with(resp, full_node_api, ph):
|
||||
async def make_new_block_with(resp: Dict, full_node_api: FullNodeSimulator, ph: bytes32) -> SpendBundle:
|
||||
assert resp.get("success")
|
||||
sb = resp["spend_bundle"]
|
||||
assert isinstance(sb, SpendBundle)
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
return sb
|
||||
@@ -863,8 +865,7 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
|
||||
spend_bundle = spend_bundle_list[0].spend_bundle
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
|
||||
|
||||
for _ in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
await time_out_assert(15, wallet_0.get_pending_change_balance, 0)
|
||||
hex_did_id = did_wallet.get_my_DID()
|
||||
hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), DID_HRP)
|
||||
@@ -886,22 +887,14 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
|
||||
"fee": fee,
|
||||
}
|
||||
)
|
||||
assert resp.get("success")
|
||||
sb = resp["spend_bundle"]
|
||||
|
||||
# ensure hints are generated
|
||||
assert compute_memos(sb)
|
||||
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name())
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
|
||||
await make_new_block_with(resp, full_node_api, ph1)
|
||||
|
||||
# Check DID NFT
|
||||
coins_response = await wait_rpc_state_condition(
|
||||
5, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_0_id)], lambda x: x["nft_list"]
|
||||
)
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, 7999999999898)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, 7999999999898)
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, 5999999999898)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, 5999999999898)
|
||||
coins = coins_response["nft_list"]
|
||||
assert len(coins) == 1
|
||||
assert coins[0].owner_did.hex() == hex_did_id
|
||||
@@ -929,8 +922,8 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
|
||||
await wait_rpc_state_condition(
|
||||
5, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_0_id)], lambda x: not x["nft_list"]
|
||||
)
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, 7999999999798)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, 7999999999798)
|
||||
await time_out_assert(10, wallet_0.get_unconfirmed_balance, 5999999999798)
|
||||
await time_out_assert(10, wallet_0.get_confirmed_balance, 5999999999798)
|
||||
# wait for all wallets to be created
|
||||
await time_out_assert(10, len, 3, wallet_1.wallet_state_manager.wallets)
|
||||
did_wallet_1 = wallet_1.wallet_state_manager.wallets[3]
|
||||
@@ -946,22 +939,19 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
|
||||
assert coins_response["nft_list"][0].owner_did is None
|
||||
nft_coin_id = coins_response["nft_list"][0].nft_coin_id
|
||||
|
||||
await time_out_assert(10, did_wallet_1.get_spendable_balance, 1)
|
||||
await time_out_assert(20, did_wallet_1.get_spendable_balance, 1)
|
||||
|
||||
# Set DID
|
||||
resp = await api_1.nft_set_nft_did(
|
||||
dict(wallet_id=nft_wallet_id_1, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex(), fee=fee)
|
||||
)
|
||||
assert resp.get("success")
|
||||
|
||||
for i in range(1, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
await make_new_block_with(resp, full_node_api, ph)
|
||||
|
||||
coins_response = await wait_rpc_state_condition(
|
||||
5, api_1.nft_get_by_did, [dict(did_id=hmr_did_id)], lambda x: x.get("wallet_id", 0) > 0
|
||||
)
|
||||
await time_out_assert(10, wallet_1.get_unconfirmed_balance, 12000000000100)
|
||||
await time_out_assert(10, wallet_1.get_confirmed_balance, 12000000000100)
|
||||
await time_out_assert(10, wallet_1.get_unconfirmed_balance, 10000000000100)
|
||||
await time_out_assert(10, wallet_1.get_confirmed_balance, 10000000000100)
|
||||
nft_wallet_1_id = coins_response.get("wallet_id")
|
||||
assert nft_wallet_1_id
|
||||
# Check NFT DID is set now
|
||||
|
||||
@@ -854,7 +854,14 @@ async def test_nft_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
|
||||
for _ in range(3):
|
||||
await farm_transaction_block(full_node_api, wallet_1_node)
|
||||
|
||||
await time_out_assert(15, wallet_is_synced, True, wallet_1_node, full_node_api)
|
||||
nft_wallet: NFTWallet = wallet_1_node.wallet_state_manager.wallets[nft_wallet_id]
|
||||
|
||||
def have_nfts():
|
||||
return len(nft_wallet.get_current_nfts()) > 0
|
||||
|
||||
await time_out_assert(15, have_nfts, True)
|
||||
|
||||
# Test with the hex version of nft_id
|
||||
nft_id = nft_wallet.get_current_nfts()[0].coin.name().hex()
|
||||
nft_info = (await wallet_1_rpc.get_nft_info(nft_id))["nft_info"]
|
||||
@@ -870,6 +877,7 @@ async def test_nft_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
|
||||
|
||||
for _ in range(3):
|
||||
await farm_transaction_block(full_node_api, wallet_1_node)
|
||||
await time_out_assert(15, wallet_is_synced, True, wallet_1_node, full_node_api)
|
||||
|
||||
nft_wallet_id_1 = (
|
||||
await wallet_2_node.wallet_state_manager.get_all_wallet_info_entries(wallet_type=WalletType.NFT)
|
||||
|
||||
@@ -160,9 +160,8 @@ class TestWalletSync:
|
||||
|
||||
# Tests a reorg with the wallet
|
||||
num_blocks = 30
|
||||
blocks_reorg = bt.get_consecutive_blocks(
|
||||
num_blocks, block_list_input=default_400_blocks[:-5], current_time=True
|
||||
)
|
||||
blocks_reorg = bt.get_consecutive_blocks(num_blocks - 1, block_list_input=default_400_blocks[:-5])
|
||||
blocks_reorg = bt.get_consecutive_blocks(1, blocks_reorg, guarantee_transaction_block=True, current_time=True)
|
||||
for i in range(1, len(blocks_reorg)):
|
||||
await full_node_api.full_node.respond_block(full_node_protocol.RespondBlock(blocks_reorg[i]))
|
||||
|
||||
|
||||
@@ -0,0 +1,652 @@
|
||||
import dataclasses
|
||||
from secrets import token_bytes
|
||||
from typing import Any, List
|
||||
|
||||
import pytest
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.util.errors import Err
|
||||
from chia.util.ints import uint8, uint32, uint64
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
from chia.wallet.wallet_transaction_store import WalletTransactionStore, filter_ok_mempool_status
|
||||
from tests.util.db_connection import DBConnection1
|
||||
|
||||
coin_1 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
coin_2 = Coin(token_bytes(32), token_bytes(32), uint64(1234))
|
||||
coin_3 = Coin(token_bytes(32), token_bytes(32), uint64(12312 - 1234))
|
||||
|
||||
tr1 = TransactionRecord(
|
||||
uint32(0), # confirmed height
|
||||
uint64(1000), # created_at_time
|
||||
bytes32(token_bytes(32)), # to_puzzle_hash
|
||||
uint64(1234), # amount
|
||||
uint64(12), # fee_amount
|
||||
False, # confirmed
|
||||
uint32(0), # sent
|
||||
None, # Optional[SpendBundle] spend_bundle
|
||||
[coin_2, coin_3], # additions
|
||||
[coin_1], # removals
|
||||
uint32(1), # wallet_id
|
||||
[], # List[Tuple[str, uint8, Optional[str]]] sent_to
|
||||
bytes32(token_bytes(32)), # trade_id
|
||||
uint32(TransactionType.OUTGOING_TX), # type
|
||||
bytes32(token_bytes(32)), # name
|
||||
[], # List[Tuple[bytes32, List[bytes]]] memos
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
assert await store.get_transaction_record(tr1.name) is None
|
||||
await store.add_transaction_record(tr1, False)
|
||||
assert await store.get_transaction_record(tr1.name) == tr1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
assert await store.get_transaction_record(tr1.name) == tr1
|
||||
await store.delete_transaction_record(tr1.name)
|
||||
assert await store.get_transaction_record(tr1.name) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_confirmed() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.set_confirmed(tr1.name, uint32(100))
|
||||
|
||||
assert await store.get_transaction_record(tr1.name) == dataclasses.replace(
|
||||
tr1, confirmed=True, confirmed_at_height=uint32(100)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_increment_sent_noop() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
assert (
|
||||
await store.increment_sent(bytes32(token_bytes(32)), "peer1", MempoolInclusionStatus.PENDING, None) is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_increment_sent() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 0
|
||||
assert tr.sent_to == []
|
||||
|
||||
assert await store.increment_sent(tr1.name, "peer1", MempoolInclusionStatus.PENDING, None) is True
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 1
|
||||
assert tr.sent_to == [("peer1", uint8(2), None)]
|
||||
|
||||
assert await store.increment_sent(tr1.name, "peer1", MempoolInclusionStatus.SUCCESS, None) is True
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 1
|
||||
assert tr.sent_to == [("peer1", uint8(2), None), ("peer1", uint8(1), None)]
|
||||
|
||||
assert await store.increment_sent(tr1.name, "peer2", MempoolInclusionStatus.SUCCESS, None) is True
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 2
|
||||
assert tr.sent_to == [("peer1", uint8(2), None), ("peer1", uint8(1), None), ("peer2", uint8(1), None)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_increment_sent_error() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 0
|
||||
assert tr.sent_to == []
|
||||
|
||||
await store.increment_sent(tr1.name, "peer1", MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED)
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 1
|
||||
assert tr.sent_to == [("peer1", uint8(3), "MEMPOOL_NOT_INITIALIZED")]
|
||||
|
||||
|
||||
def test_filter_ok_mempool_status() -> None:
|
||||
assert filter_ok_mempool_status([("peer1", uint8(1), None)]) == []
|
||||
assert filter_ok_mempool_status([("peer1", uint8(2), None)]) == []
|
||||
assert filter_ok_mempool_status([("peer1", uint8(3), None)]) == [("peer1", uint8(3), None)]
|
||||
assert filter_ok_mempool_status(
|
||||
[("peer1", uint8(2), None), ("peer1", uint8(1), None), ("peer1", uint8(3), None)]
|
||||
) == [("peer1", uint8(3), None)]
|
||||
|
||||
assert filter_ok_mempool_status([("peer1", uint8(3), "message does not matter")]) == [
|
||||
("peer1", uint8(3), "message does not matter")
|
||||
]
|
||||
assert filter_ok_mempool_status([("peer1", uint8(2), "message does not matter")]) == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tx_reorged_update() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr = dataclasses.replace(tr1, sent=2, sent_to=[("peer1", uint8(1), None), ("peer2", uint8(1), None)])
|
||||
await store.add_transaction_record(tr, False)
|
||||
tr = await store.get_transaction_record(tr.name)
|
||||
assert tr.sent == 2
|
||||
assert tr.sent_to == [("peer1", uint8(1), None), ("peer2", uint8(1), None)]
|
||||
|
||||
await store.tx_reorged(tr, False)
|
||||
tr = await store.get_transaction_record(tr1.name)
|
||||
assert tr.sent == 0
|
||||
assert tr.sent_to == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tx_reorged_add() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr = dataclasses.replace(tr1, sent=2, sent_to=[("peer1", uint8(1), None), ("peer2", uint8(1), None)])
|
||||
|
||||
await store.get_transaction_record(tr.name) is None
|
||||
await store.tx_reorged(tr, False)
|
||||
tr = await store.get_transaction_record(tr.name)
|
||||
assert tr.sent == 0
|
||||
assert tr.sent_to == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tx_record() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32))
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32))
|
||||
|
||||
assert await store.get_transaction_record(tr1.name) is None
|
||||
await store.add_transaction_record(tr1, False)
|
||||
assert await store.get_transaction_record(tr1.name) == tr1
|
||||
|
||||
assert await store.get_transaction_record(tr2.name) is None
|
||||
await store.add_transaction_record(tr2, False)
|
||||
assert await store.get_transaction_record(tr2.name) == tr2
|
||||
|
||||
assert await store.get_transaction_record(tr3.name) is None
|
||||
await store.add_transaction_record(tr3, False)
|
||||
assert await store.get_transaction_record(tr3.name) == tr3
|
||||
|
||||
assert await store.get_transaction_record(tr1.name) == tr1
|
||||
assert await store.get_transaction_record(tr2.name) == tr2
|
||||
assert await store.get_transaction_record(tr3.name) == tr3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_farming_rewards() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
test_trs: List[TransactionRecord] = []
|
||||
# tr1 is type OUTGOING_TX
|
||||
|
||||
for conf in [True, False]:
|
||||
for type in [
|
||||
TransactionType.INCOMING_TX,
|
||||
TransactionType.OUTGOING_TX,
|
||||
TransactionType.COINBASE_REWARD,
|
||||
TransactionType.FEE_REWARD,
|
||||
TransactionType.INCOMING_TRADE,
|
||||
TransactionType.OUTGOING_TRADE,
|
||||
]:
|
||||
test_trs.append(
|
||||
dataclasses.replace(
|
||||
tr1,
|
||||
name=token_bytes(32),
|
||||
confirmed=conf,
|
||||
confirmed_at_height=uint32(100 if conf else 0),
|
||||
type=type,
|
||||
)
|
||||
)
|
||||
|
||||
for tr in test_trs:
|
||||
await store.add_transaction_record(tr, False)
|
||||
assert await store.get_transaction_record(tr.name) == tr
|
||||
|
||||
rewards = await store.get_farming_rewards()
|
||||
assert len(rewards) == 2
|
||||
assert test_trs[2] in rewards
|
||||
assert test_trs[3] in rewards
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_unconfirmed() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(100))
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(tr2, False)
|
||||
|
||||
assert await store.get_all_unconfirmed() == [tr1]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unconfirmed_for_wallet() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(100))
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32), wallet_id=2)
|
||||
tr4 = dataclasses.replace(tr2, name=token_bytes(32), wallet_id=2)
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(tr2, False)
|
||||
await store.add_transaction_record(tr3, False)
|
||||
await store.add_transaction_record(tr4, False)
|
||||
|
||||
assert await store.get_unconfirmed_for_wallet(1) == [tr1]
|
||||
assert await store.get_unconfirmed_for_wallet(2) == [tr3]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_transaction_count_for_wallet() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), wallet_id=2)
|
||||
|
||||
# 5 transactions in wallet_id 1
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(dataclasses.replace(tr1, name=token_bytes(32)), False)
|
||||
await store.add_transaction_record(dataclasses.replace(tr1, name=token_bytes(32)), False)
|
||||
await store.add_transaction_record(dataclasses.replace(tr1, name=token_bytes(32)), False)
|
||||
await store.add_transaction_record(dataclasses.replace(tr1, name=token_bytes(32)), False)
|
||||
|
||||
# 2 transactions in wallet_id 2
|
||||
await store.add_transaction_record(tr2, False)
|
||||
await store.add_transaction_record(dataclasses.replace(tr2, name=token_bytes(32)), False)
|
||||
|
||||
assert await store.get_transaction_count_for_wallet(1) == 5
|
||||
assert await store.get_transaction_count_for_wallet(2) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_all_transactions_for_wallet() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
test_trs: List[TransactionRecord] = []
|
||||
for wallet_id in [1, 2]:
|
||||
for type in [
|
||||
TransactionType.INCOMING_TX,
|
||||
TransactionType.OUTGOING_TX,
|
||||
TransactionType.COINBASE_REWARD,
|
||||
TransactionType.FEE_REWARD,
|
||||
TransactionType.INCOMING_TRADE,
|
||||
TransactionType.OUTGOING_TRADE,
|
||||
]:
|
||||
test_trs.append(dataclasses.replace(tr1, name=token_bytes(32), wallet_id=wallet_id, type=type))
|
||||
|
||||
for tr in test_trs:
|
||||
await store.add_transaction_record(tr, False)
|
||||
|
||||
assert await store.get_all_transactions_for_wallet(1) == test_trs[:6]
|
||||
assert await store.get_all_transactions_for_wallet(2) == test_trs[6:]
|
||||
|
||||
assert await store.get_all_transactions_for_wallet(1, TransactionType.INCOMING_TX) == [test_trs[0]]
|
||||
assert await store.get_all_transactions_for_wallet(1, TransactionType.OUTGOING_TX) == [test_trs[1]]
|
||||
assert await store.get_all_transactions_for_wallet(1, TransactionType.INCOMING_TRADE) == [test_trs[4]]
|
||||
assert await store.get_all_transactions_for_wallet(1, TransactionType.OUTGOING_TRADE) == [test_trs[5]]
|
||||
|
||||
assert await store.get_all_transactions_for_wallet(2, TransactionType.INCOMING_TX) == [test_trs[6]]
|
||||
assert await store.get_all_transactions_for_wallet(2, TransactionType.OUTGOING_TX) == [test_trs[7]]
|
||||
assert await store.get_all_transactions_for_wallet(2, TransactionType.INCOMING_TRADE) == [test_trs[10]]
|
||||
assert await store.get_all_transactions_for_wallet(2, TransactionType.OUTGOING_TRADE) == [test_trs[11]]
|
||||
|
||||
|
||||
def cmp(lhs: List[Any], rhs: List[Any]) -> bool:
|
||||
if len(rhs) != len(lhs):
|
||||
return False
|
||||
|
||||
for e in lhs:
|
||||
if e not in rhs:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_transactions() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
test_trs: List[TransactionRecord] = []
|
||||
assert await store.get_all_transactions() == []
|
||||
for wallet_id in [1, 2, 3, 4]:
|
||||
test_trs.append(dataclasses.replace(tr1, name=token_bytes(32), wallet_id=wallet_id))
|
||||
|
||||
for tr in test_trs:
|
||||
await store.add_transaction_record(tr, False)
|
||||
|
||||
all_trs = await store.get_all_transactions()
|
||||
assert cmp(all_trs, test_trs)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_transaction_above() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
test_trs: List[TransactionRecord] = []
|
||||
assert await store.get_transaction_above(uint32(0)) == []
|
||||
for height in range(10):
|
||||
test_trs.append(dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(height)))
|
||||
|
||||
for tr in test_trs:
|
||||
await store.add_transaction_record(tr, False)
|
||||
|
||||
for height in range(10):
|
||||
trs = await store.get_transaction_above(uint32(height))
|
||||
assert cmp(trs, test_trs[height + 1 :])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tx_by_trade_id() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), trade_id=token_bytes(32))
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32), trade_id=token_bytes(32))
|
||||
tr4 = dataclasses.replace(tr1, name=token_bytes(32))
|
||||
|
||||
assert await store.get_transactions_by_trade_id(tr1.trade_id) == []
|
||||
await store.add_transaction_record(tr1, False)
|
||||
assert await store.get_transactions_by_trade_id(tr1.trade_id) == [tr1]
|
||||
|
||||
assert await store.get_transactions_by_trade_id(tr2.trade_id) == []
|
||||
await store.add_transaction_record(tr2, False)
|
||||
assert await store.get_transactions_by_trade_id(tr2.trade_id) == [tr2]
|
||||
|
||||
assert await store.get_transactions_by_trade_id(tr3.trade_id) == []
|
||||
await store.add_transaction_record(tr3, False)
|
||||
assert await store.get_transactions_by_trade_id(tr3.trade_id) == [tr3]
|
||||
|
||||
# tr1 and tr4 have the same trade_id
|
||||
assert await store.get_transactions_by_trade_id(tr4.trade_id) == [tr1]
|
||||
await store.add_transaction_record(tr4, False)
|
||||
assert cmp(await store.get_transactions_by_trade_id(tr4.trade_id), [tr1, tr4])
|
||||
|
||||
assert cmp(await store.get_transactions_by_trade_id(tr1.trade_id), [tr1, tr4])
|
||||
assert await store.get_transactions_by_trade_id(tr2.trade_id) == [tr2]
|
||||
assert await store.get_transactions_by_trade_id(tr3.trade_id) == [tr3]
|
||||
assert cmp(await store.get_transactions_by_trade_id(tr4.trade_id), [tr1, tr4])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rollback_to_block() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
test_trs: List[TransactionRecord] = []
|
||||
for height in range(10):
|
||||
test_trs.append(dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(height)))
|
||||
|
||||
for tr in test_trs:
|
||||
await store.add_transaction_record(tr, False)
|
||||
|
||||
await store.rollback_to_block(uint32(6))
|
||||
all_trs = await store.get_all_transactions()
|
||||
assert cmp(all_trs, test_trs[:7])
|
||||
|
||||
await store.rollback_to_block(uint32(5))
|
||||
all_trs = await store.get_all_transactions()
|
||||
assert cmp(all_trs, test_trs[:6])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_unconfirmed() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), confirmed=True)
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32), confirmed=True, wallet_id=2)
|
||||
tr4 = dataclasses.replace(tr1, name=token_bytes(32), wallet_id=2)
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(tr2, False)
|
||||
await store.add_transaction_record(tr3, False)
|
||||
await store.add_transaction_record(tr4, False)
|
||||
|
||||
assert cmp(await store.get_all_transactions(), [tr1, tr2, tr3, tr4])
|
||||
await store.delete_unconfirmed_transactions(1)
|
||||
assert cmp(await store.get_all_transactions(), [tr2, tr3, tr4])
|
||||
await store.delete_unconfirmed_transactions(2)
|
||||
assert cmp(await store.get_all_transactions(), [tr2, tr3])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_transactions_between_confirmed() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(1))
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(2))
|
||||
tr4 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(3))
|
||||
tr5 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(4))
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(tr2, False)
|
||||
await store.add_transaction_record(tr3, False)
|
||||
await store.add_transaction_record(tr4, False)
|
||||
await store.add_transaction_record(tr5, False)
|
||||
|
||||
# test different limits
|
||||
assert await store.get_transactions_between(1, 0, 1) == [tr1]
|
||||
assert await store.get_transactions_between(1, 0, 2) == [tr1, tr2]
|
||||
assert await store.get_transactions_between(1, 0, 3) == [tr1, tr2, tr3]
|
||||
assert await store.get_transactions_between(1, 0, 100) == [tr1, tr2, tr3, tr4, tr5]
|
||||
|
||||
# test different start offsets
|
||||
assert await store.get_transactions_between(1, 1, 100) == [tr2, tr3, tr4, tr5]
|
||||
assert await store.get_transactions_between(1, 2, 100) == [tr3, tr4, tr5]
|
||||
assert await store.get_transactions_between(1, 3, 100) == [tr4, tr5]
|
||||
|
||||
# wallet 2 is empty
|
||||
assert await store.get_transactions_between(2, 0, 100) == []
|
||||
|
||||
# reverse
|
||||
|
||||
# test different limits
|
||||
assert await store.get_transactions_between(1, 0, 1, reverse=True) == [tr5]
|
||||
assert await store.get_transactions_between(1, 0, 2, reverse=True) == [tr5, tr4]
|
||||
assert await store.get_transactions_between(1, 0, 3, reverse=True) == [tr5, tr4, tr3]
|
||||
assert await store.get_transactions_between(1, 0, 100, reverse=True) == [tr5, tr4, tr3, tr2, tr1]
|
||||
|
||||
# test different start offsets
|
||||
assert await store.get_transactions_between(1, 1, 100, reverse=True) == [tr4, tr3, tr2, tr1]
|
||||
assert await store.get_transactions_between(1, 2, 100, reverse=True) == [tr3, tr2, tr1]
|
||||
assert await store.get_transactions_between(1, 3, 100, reverse=True) == [tr2, tr1]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_transactions_between_relevance() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
t1 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=False, confirmed_at_height=uint32(2), created_at_time=1000
|
||||
)
|
||||
t2 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=False, confirmed_at_height=uint32(2), created_at_time=999
|
||||
)
|
||||
t3 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=False, confirmed_at_height=uint32(1), created_at_time=1000
|
||||
)
|
||||
t4 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=False, confirmed_at_height=uint32(1), created_at_time=999
|
||||
)
|
||||
|
||||
t5 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(2), created_at_time=1000
|
||||
)
|
||||
t6 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(2), created_at_time=999
|
||||
)
|
||||
t7 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(1), created_at_time=1000
|
||||
)
|
||||
t8 = dataclasses.replace(
|
||||
tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(1), created_at_time=999
|
||||
)
|
||||
|
||||
await store.add_transaction_record(t1, False)
|
||||
await store.add_transaction_record(t2, False)
|
||||
await store.add_transaction_record(t3, False)
|
||||
await store.add_transaction_record(t4, False)
|
||||
await store.add_transaction_record(t5, False)
|
||||
await store.add_transaction_record(t6, False)
|
||||
await store.add_transaction_record(t7, False)
|
||||
await store.add_transaction_record(t8, False)
|
||||
|
||||
# test different limits
|
||||
assert await store.get_transactions_between(1, 0, 1, sort_key="RELEVANCE") == [t1]
|
||||
assert await store.get_transactions_between(1, 0, 2, sort_key="RELEVANCE") == [t1, t2]
|
||||
assert await store.get_transactions_between(1, 0, 3, sort_key="RELEVANCE") == [t1, t2, t3]
|
||||
assert await store.get_transactions_between(1, 0, 100, sort_key="RELEVANCE") == [t1, t2, t3, t4, t5, t6, t7, t8]
|
||||
|
||||
# test different start offsets
|
||||
assert await store.get_transactions_between(1, 1, 100, sort_key="RELEVANCE") == [t2, t3, t4, t5, t6, t7, t8]
|
||||
assert await store.get_transactions_between(1, 2, 100, sort_key="RELEVANCE") == [t3, t4, t5, t6, t7, t8]
|
||||
assert await store.get_transactions_between(1, 3, 100, sort_key="RELEVANCE") == [t4, t5, t6, t7, t8]
|
||||
assert await store.get_transactions_between(1, 4, 100, sort_key="RELEVANCE") == [t5, t6, t7, t8]
|
||||
|
||||
# wallet 2 is empty
|
||||
assert await store.get_transactions_between(2, 0, 100, sort_key="RELEVANCE") == []
|
||||
|
||||
# reverse
|
||||
|
||||
# test different limits
|
||||
assert await store.get_transactions_between(1, 0, 1, sort_key="RELEVANCE", reverse=True) == [t8]
|
||||
assert await store.get_transactions_between(1, 0, 2, sort_key="RELEVANCE", reverse=True) == [t8, t7]
|
||||
assert await store.get_transactions_between(1, 0, 3, sort_key="RELEVANCE", reverse=True) == [t8, t7, t6]
|
||||
assert await store.get_transactions_between(1, 0, 100, sort_key="RELEVANCE", reverse=True) == [
|
||||
t8,
|
||||
t7,
|
||||
t6,
|
||||
t5,
|
||||
t4,
|
||||
t3,
|
||||
t2,
|
||||
t1,
|
||||
]
|
||||
|
||||
# test different start offsets
|
||||
assert await store.get_transactions_between(1, 1, 100, sort_key="RELEVANCE", reverse=True) == [
|
||||
t7,
|
||||
t6,
|
||||
t5,
|
||||
t4,
|
||||
t3,
|
||||
t2,
|
||||
t1,
|
||||
]
|
||||
assert await store.get_transactions_between(1, 2, 100, sort_key="RELEVANCE", reverse=True) == [
|
||||
t6,
|
||||
t5,
|
||||
t4,
|
||||
t3,
|
||||
t2,
|
||||
t1,
|
||||
]
|
||||
assert await store.get_transactions_between(1, 3, 100, sort_key="RELEVANCE", reverse=True) == [
|
||||
t5,
|
||||
t4,
|
||||
t3,
|
||||
t2,
|
||||
t1,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_transactions_between_to_puzzle_hash() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
ph1 = token_bytes(32)
|
||||
ph2 = token_bytes(32)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(1), to_puzzle_hash=ph1)
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(2), to_puzzle_hash=ph1)
|
||||
tr4 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(3), to_puzzle_hash=ph2)
|
||||
tr5 = dataclasses.replace(tr1, name=token_bytes(32), confirmed_at_height=uint32(4), to_puzzle_hash=ph2)
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(tr2, False)
|
||||
await store.add_transaction_record(tr3, False)
|
||||
await store.add_transaction_record(tr4, False)
|
||||
await store.add_transaction_record(tr5, False)
|
||||
|
||||
# test different limits
|
||||
assert await store.get_transactions_between(1, 0, 100, to_puzzle_hash=ph1) == [tr2, tr3]
|
||||
assert await store.get_transactions_between(1, 0, 100, to_puzzle_hash=ph2) == [tr4, tr5]
|
||||
|
||||
# test different start offsets
|
||||
assert await store.get_transactions_between(1, 1, 100, to_puzzle_hash=ph1) == [tr3]
|
||||
assert await store.get_transactions_between(1, 1, 100, to_puzzle_hash=ph2) == [tr5]
|
||||
|
||||
# reverse
|
||||
|
||||
# test different limits
|
||||
assert await store.get_transactions_between(1, 0, 100, to_puzzle_hash=ph1, reverse=True) == [tr3, tr2]
|
||||
assert await store.get_transactions_between(1, 0, 100, to_puzzle_hash=ph2, reverse=True) == [tr5, tr4]
|
||||
|
||||
# test different start offsets
|
||||
assert await store.get_transactions_between(1, 1, 100, to_puzzle_hash=ph1, reverse=True) == [tr2]
|
||||
assert await store.get_transactions_between(1, 1, 100, to_puzzle_hash=ph2, reverse=True) == [tr4]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_not_sent() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletTransactionStore.create(db_wrapper)
|
||||
|
||||
tr2 = dataclasses.replace(tr1, name=token_bytes(32), confirmed=True, confirmed_at_height=uint32(1))
|
||||
tr3 = dataclasses.replace(tr1, name=token_bytes(32))
|
||||
tr4 = dataclasses.replace(tr1, name=token_bytes(32))
|
||||
|
||||
await store.add_transaction_record(tr1, False)
|
||||
await store.add_transaction_record(tr2, False)
|
||||
await store.add_transaction_record(tr3, False)
|
||||
await store.add_transaction_record(tr4, False)
|
||||
|
||||
not_sent = await store.get_not_sent()
|
||||
assert cmp(not_sent, [tr1, tr3, tr4])
|
||||
|
||||
not_sent = await store.get_not_sent()
|
||||
assert cmp(not_sent, [tr1, tr3, tr4])
|
||||
|
||||
not_sent = await store.get_not_sent()
|
||||
assert cmp(not_sent, [tr1, tr3, tr4])
|
||||
|
||||
not_sent = await store.get_not_sent()
|
||||
assert cmp(not_sent, [tr1, tr3, tr4])
|
||||
|
||||
not_sent = await store.get_not_sent()
|
||||
assert cmp(not_sent, [tr1, tr3, tr4])
|
||||
|
||||
# the 6th time we call this function, we don't get any unsent txs
|
||||
not_sent = await store.get_not_sent()
|
||||
assert cmp(not_sent, [])
|
||||
|
||||
# TODO: also cover include_accepted_txs=True
|
||||
+33
-33
@@ -85,7 +85,7 @@ class TestWalletSimulator:
|
||||
return True
|
||||
|
||||
await time_out_assert(20, check_tx_are_pool_farm_rewards, True)
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
@@ -123,7 +123,7 @@ class TestWalletSimulator:
|
||||
)
|
||||
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds)
|
||||
|
||||
tx = await wallet.generate_signed_transaction(
|
||||
uint64(10),
|
||||
@@ -132,9 +132,9 @@ class TestWalletSimulator:
|
||||
)
|
||||
await wallet.push_transaction(tx)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds - 10)
|
||||
await time_out_assert(5, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds - 10)
|
||||
await time_out_assert(10, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
@@ -146,8 +146,8 @@ class TestWalletSimulator:
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, new_funds - 10)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, new_funds - 10)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, new_funds - 10)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, new_funds - 10)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
@@ -193,7 +193,7 @@ class TestWalletSimulator:
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
@@ -250,7 +250,7 @@ class TestWalletSimulator:
|
||||
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, funds)
|
||||
|
||||
tx = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction(
|
||||
uint64(10), bytes32(32 * b"0"), uint64(0)
|
||||
@@ -435,8 +435,8 @@ class TestWalletSimulator:
|
||||
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds)
|
||||
|
||||
assert await wallet.get_confirmed_balance() == funds
|
||||
assert await wallet.get_unconfirmed_balance() == funds
|
||||
@@ -453,10 +453,10 @@ class TestWalletSimulator:
|
||||
assert fees == tx_fee
|
||||
|
||||
await wallet.push_transaction(tx)
|
||||
await time_out_assert(5, full_node_1.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
await time_out_assert(20, full_node_1.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds - tx_amount - tx_fee)
|
||||
await time_out_assert(20, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(20, wallet.get_unconfirmed_balance, funds - tx_amount - tx_fee)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(bytes32(32 * b"0")))
|
||||
@@ -468,8 +468,8 @@ class TestWalletSimulator:
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, new_funds - tx_amount - tx_fee)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, new_funds - tx_amount - tx_fee)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, new_funds - tx_amount - tx_fee)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, new_funds - tx_amount - tx_fee)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trusted",
|
||||
@@ -510,7 +510,7 @@ class TestWalletSimulator:
|
||||
[calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(20, wallet.get_confirmed_balance, funds)
|
||||
|
||||
primaries: List[AmountWithPuzzlehash] = []
|
||||
for i in range(0, 60):
|
||||
@@ -615,7 +615,7 @@ class TestWalletSimulator:
|
||||
)
|
||||
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds)
|
||||
|
||||
assert await wallet.get_confirmed_balance() == funds
|
||||
assert await wallet.get_unconfirmed_balance() == funds
|
||||
@@ -658,8 +658,8 @@ class TestWalletSimulator:
|
||||
)
|
||||
await wallet.push_transaction(stolen_tx)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds - stolen_cs.coin.amount)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds - stolen_cs.coin.amount)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(bytes32(32 * b"0")))
|
||||
@@ -718,14 +718,14 @@ class TestWalletSimulator:
|
||||
assert tx.spend_bundle is not None
|
||||
await wallet.push_transaction(tx)
|
||||
await full_node_api.full_node.respond_transaction(tx.spend_bundle, tx.name)
|
||||
await time_out_assert(5, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
for i in range(0, 2):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(bytes32(32 * b"0")))
|
||||
await time_out_assert(5, wallet_2.get_confirmed_balance, 1000)
|
||||
await time_out_assert(10, wallet_2.get_confirmed_balance, 1000)
|
||||
funds -= 1000
|
||||
|
||||
await time_out_assert(5, wallet_node.wallet_state_manager.blockchain.get_peak_height, 7)
|
||||
await time_out_assert(10, wallet_node.wallet_state_manager.blockchain.get_peak_height, 7)
|
||||
peak = full_node_api.full_node.blockchain.get_peak()
|
||||
assert peak is not None
|
||||
peak_height = peak.height
|
||||
@@ -743,8 +743,8 @@ class TestWalletSimulator:
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(7, full_node_api.full_node.blockchain.get_peak_height, peak_height + 3)
|
||||
await time_out_assert(7, wallet_node.wallet_state_manager.blockchain.get_peak_height, peak_height + 3)
|
||||
await time_out_assert(20, full_node_api.full_node.blockchain.get_peak_height, peak_height + 3)
|
||||
await time_out_assert(20, wallet_node.wallet_state_manager.blockchain.get_peak_height, peak_height + 3)
|
||||
|
||||
# Farm a few blocks so we can confirm the resubmitted transaction
|
||||
for i in range(0, num_blocks):
|
||||
@@ -864,8 +864,8 @@ class TestWalletSimulator:
|
||||
]
|
||||
)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds)
|
||||
|
||||
AMOUNT_TO_SEND = 4000000000000
|
||||
coins = await wallet.select_coins(uint64(AMOUNT_TO_SEND))
|
||||
@@ -883,12 +883,12 @@ class TestWalletSimulator:
|
||||
assert paid_coin.parent_coin_info == coin_list[2].name()
|
||||
await wallet.push_transaction(tx)
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds - AMOUNT_TO_SEND)
|
||||
await time_out_assert(5, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds - AMOUNT_TO_SEND)
|
||||
await time_out_assert(10, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name)
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(bytes32([0] * 32)))
|
||||
|
||||
await time_out_assert(5, wallet.get_confirmed_balance, funds - AMOUNT_TO_SEND)
|
||||
await time_out_assert(5, wallet.get_unconfirmed_balance, funds - AMOUNT_TO_SEND)
|
||||
await time_out_assert(10, wallet.get_confirmed_balance, funds - AMOUNT_TO_SEND)
|
||||
await time_out_assert(10, wallet.get_unconfirmed_balance, funds - AMOUNT_TO_SEND)
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
from secrets import token_bytes
|
||||
|
||||
import pytest
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
from chia.wallet.wallet_coin_store import WalletCoinStore
|
||||
from tests.util.db_connection import DBConnection1
|
||||
|
||||
coin_1 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
coin_2 = Coin(coin_1.parent_coin_info, token_bytes(32), uint64(12311))
|
||||
coin_3 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
coin_4 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
coin_5 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
coin_6 = Coin(token_bytes(32), coin_4.puzzle_hash, uint64(12312))
|
||||
coin_7 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
record_replaced = WalletCoinRecord(coin_1, uint32(8), uint32(0), False, True, WalletType.STANDARD_WALLET, 0)
|
||||
record_1 = WalletCoinRecord(coin_1, uint32(4), uint32(0), False, True, WalletType.STANDARD_WALLET, 0)
|
||||
record_2 = WalletCoinRecord(coin_2, uint32(5), uint32(0), False, True, WalletType.STANDARD_WALLET, 0)
|
||||
record_3 = WalletCoinRecord(
|
||||
coin_3,
|
||||
uint32(5),
|
||||
uint32(10),
|
||||
True,
|
||||
False,
|
||||
WalletType.STANDARD_WALLET,
|
||||
0,
|
||||
)
|
||||
record_4 = WalletCoinRecord(
|
||||
coin_4,
|
||||
uint32(5),
|
||||
uint32(15),
|
||||
True,
|
||||
False,
|
||||
WalletType.STANDARD_WALLET,
|
||||
0,
|
||||
)
|
||||
record_5 = WalletCoinRecord(
|
||||
coin_5,
|
||||
uint32(5),
|
||||
uint32(0),
|
||||
False,
|
||||
False,
|
||||
WalletType.STANDARD_WALLET,
|
||||
1,
|
||||
)
|
||||
record_6 = WalletCoinRecord(
|
||||
coin_6,
|
||||
uint32(5),
|
||||
uint32(15),
|
||||
True,
|
||||
False,
|
||||
WalletType.STANDARD_WALLET,
|
||||
2,
|
||||
)
|
||||
record_7 = WalletCoinRecord(
|
||||
coin_7,
|
||||
uint32(5),
|
||||
uint32(0),
|
||||
False,
|
||||
False,
|
||||
WalletType.POOLING_WALLET,
|
||||
2,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_replace_get() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
assert await store.get_coin_record(coin_1.name()) is None
|
||||
await store.add_coin_record(record_replaced)
|
||||
await store.add_coin_record(record_1)
|
||||
await store.add_coin_record(record_2)
|
||||
await store.add_coin_record(record_3)
|
||||
await store.add_coin_record(record_4)
|
||||
assert await store.get_coin_record(coin_1.name()) == record_1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persistance() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
await store.add_coin_record(record_1)
|
||||
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
assert await store.get_coin_record(coin_1.name()) == record_1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_spent() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
await store.add_coin_record(record_1)
|
||||
|
||||
assert not (await store.get_coin_record(coin_1.name())).spent
|
||||
await store.set_spent(coin_1.name(), uint32(12))
|
||||
assert (await store.get_coin_record(coin_1.name())).spent
|
||||
assert (await store.get_coin_record(coin_1.name())).spent_block_height == 12
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_records_by_puzzle_hash() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
await store.add_coin_record(record_4)
|
||||
await store.add_coin_record(record_5)
|
||||
await store.add_coin_record(record_5)
|
||||
await store.add_coin_record(record_6)
|
||||
assert len(await store.get_coin_records_by_puzzle_hash(record_6.coin.puzzle_hash)) == 2 # 4 and 6
|
||||
assert len(await store.get_coin_records_by_puzzle_hash(token_bytes(32))) == 0
|
||||
|
||||
assert await store.get_coin_record(coin_6.name()) == record_6
|
||||
assert await store.get_coin_record(token_bytes(32)) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unspent_coins_for_wallet() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
assert await store.get_unspent_coins_for_wallet(1) == set()
|
||||
|
||||
await store.add_coin_record(record_4) # this is spent and wallet 0
|
||||
await store.add_coin_record(record_5) # wallet 1
|
||||
await store.add_coin_record(record_6) # this is spent and wallet 2
|
||||
await store.add_coin_record(record_7) # wallet 2
|
||||
|
||||
assert await store.get_unspent_coins_for_wallet(1) == set([record_5])
|
||||
assert await store.get_unspent_coins_for_wallet(2) == set([record_7])
|
||||
assert await store.get_unspent_coins_for_wallet(3) == set()
|
||||
|
||||
await store.set_spent(coin_4.name(), uint32(12))
|
||||
|
||||
assert await store.get_unspent_coins_for_wallet(1) == set([record_5])
|
||||
assert await store.get_unspent_coins_for_wallet(2) == set([record_7])
|
||||
assert await store.get_unspent_coins_for_wallet(3) == set()
|
||||
|
||||
await store.set_spent(coin_7.name(), uint32(12))
|
||||
|
||||
assert await store.get_unspent_coins_for_wallet(1) == set([record_5])
|
||||
assert await store.get_unspent_coins_for_wallet(2) == set()
|
||||
assert await store.get_unspent_coins_for_wallet(3) == set()
|
||||
|
||||
await store.set_spent(coin_5.name(), uint32(12))
|
||||
|
||||
assert await store.get_unspent_coins_for_wallet(1) == set()
|
||||
assert await store.get_unspent_coins_for_wallet(2) == set()
|
||||
assert await store.get_unspent_coins_for_wallet(3) == set()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_records_by_parent_id() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
await store.add_coin_record(record_1)
|
||||
await store.add_coin_record(record_2)
|
||||
await store.add_coin_record(record_3)
|
||||
await store.add_coin_record(record_4)
|
||||
await store.add_coin_record(record_5)
|
||||
await store.add_coin_record(record_6)
|
||||
await store.add_coin_record(record_7)
|
||||
|
||||
assert set(await store.get_coin_records_by_parent_id(coin_1.parent_coin_info)) == set([record_1, record_2])
|
||||
assert set(await store.get_coin_records_by_parent_id(coin_2.parent_coin_info)) == set([record_1, record_2])
|
||||
assert await store.get_coin_records_by_parent_id(coin_3.parent_coin_info) == [record_3]
|
||||
assert await store.get_coin_records_by_parent_id(coin_4.parent_coin_info) == [record_4]
|
||||
assert await store.get_coin_records_by_parent_id(coin_5.parent_coin_info) == [record_5]
|
||||
assert await store.get_coin_records_by_parent_id(coin_6.parent_coin_info) == [record_6]
|
||||
assert await store.get_coin_records_by_parent_id(coin_7.parent_coin_info) == [record_7]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multiple_coin_records() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
await store.add_coin_record(record_1)
|
||||
await store.add_coin_record(record_2)
|
||||
await store.add_coin_record(record_3)
|
||||
await store.add_coin_record(record_4)
|
||||
await store.add_coin_record(record_5)
|
||||
await store.add_coin_record(record_6)
|
||||
await store.add_coin_record(record_7)
|
||||
|
||||
assert set(await store.get_multiple_coin_records([coin_1.name(), coin_2.name(), coin_3.name()])) == set(
|
||||
[record_1, record_2, record_3]
|
||||
)
|
||||
|
||||
assert set(await store.get_multiple_coin_records([coin_5.name(), coin_6.name(), coin_7.name()])) == set(
|
||||
[record_5, record_6, record_7]
|
||||
)
|
||||
|
||||
assert (
|
||||
set(
|
||||
await store.get_multiple_coin_records(
|
||||
[
|
||||
coin_1.name(),
|
||||
coin_2.name(),
|
||||
coin_3.name(),
|
||||
coin_4.name(),
|
||||
coin_5.name(),
|
||||
coin_6.name(),
|
||||
coin_7.name(),
|
||||
]
|
||||
)
|
||||
)
|
||||
== set([record_1, record_2, record_3, record_4, record_5, record_6, record_7])
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_coin_record() -> None:
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
await store.add_coin_record(record_1)
|
||||
await store.add_coin_record(record_2)
|
||||
await store.add_coin_record(record_3)
|
||||
await store.add_coin_record(record_4)
|
||||
await store.add_coin_record(record_5)
|
||||
await store.add_coin_record(record_6)
|
||||
await store.add_coin_record(record_7)
|
||||
|
||||
assert (
|
||||
set(
|
||||
await store.get_multiple_coin_records(
|
||||
[
|
||||
coin_1.name(),
|
||||
coin_2.name(),
|
||||
coin_3.name(),
|
||||
coin_4.name(),
|
||||
coin_5.name(),
|
||||
coin_6.name(),
|
||||
coin_7.name(),
|
||||
]
|
||||
)
|
||||
)
|
||||
== set([record_1, record_2, record_3, record_4, record_5, record_6, record_7])
|
||||
)
|
||||
|
||||
assert await store.get_coin_record(coin_1.name()) == record_1
|
||||
|
||||
await store.delete_coin_record(coin_1.name())
|
||||
|
||||
assert await store.get_coin_record(coin_1.name()) is None
|
||||
assert set(
|
||||
await store.get_multiple_coin_records(
|
||||
[coin_2.name(), coin_3.name(), coin_4.name(), coin_5.name(), coin_6.name(), coin_7.name()]
|
||||
)
|
||||
) == set([record_2, record_3, record_4, record_5, record_6, record_7])
|
||||
|
||||
|
||||
def record(c: Coin, *, confirmed: int, spent: int) -> WalletCoinRecord:
|
||||
return WalletCoinRecord(c, uint32(confirmed), uint32(spent), spent != 0, False, WalletType.STANDARD_WALLET, 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_coins_to_check() -> None:
|
||||
|
||||
r1 = record(coin_1, confirmed=1, spent=0)
|
||||
r2 = record(coin_2, confirmed=2, spent=4)
|
||||
r3 = record(coin_3, confirmed=3, spent=5)
|
||||
r4 = record(coin_4, confirmed=4, spent=6)
|
||||
r5 = record(coin_5, confirmed=5, spent=7)
|
||||
# these spent heights violate the invariant
|
||||
r6 = record(coin_6, confirmed=6, spent=1)
|
||||
r7 = record(coin_7, confirmed=7, spent=2)
|
||||
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
await store.add_coin_record(r1)
|
||||
await store.add_coin_record(r2)
|
||||
await store.add_coin_record(r3)
|
||||
await store.add_coin_record(r4)
|
||||
await store.add_coin_record(r5)
|
||||
await store.add_coin_record(r6)
|
||||
await store.add_coin_record(r7)
|
||||
|
||||
for i in range(10):
|
||||
|
||||
coins = await store.get_coins_to_check(i)
|
||||
|
||||
# r1 is unspent and should always be included, regardless of height
|
||||
assert r1 in coins
|
||||
# r2 was spent at height 4
|
||||
assert (r2 in coins) == (i < 4)
|
||||
# r3 was spent at height 5
|
||||
assert (r3 in coins) == (i < 5)
|
||||
# r4 was spent at height 6
|
||||
assert (r4 in coins) == (i < 6)
|
||||
# r5 was spent at height 7
|
||||
assert (r5 in coins) == (i < 7)
|
||||
# r6 was confirmed at height 6
|
||||
assert (r6 in coins) == (i < 6)
|
||||
# r7 was confirmed at height 7
|
||||
assert (r7 in coins) == (i < 7)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_first_coin_height() -> None:
|
||||
|
||||
r1 = record(coin_1, confirmed=1, spent=0)
|
||||
r2 = record(coin_2, confirmed=2, spent=4)
|
||||
r3 = record(coin_3, confirmed=3, spent=5)
|
||||
r4 = record(coin_4, confirmed=4, spent=6)
|
||||
r5 = record(coin_5, confirmed=5, spent=7)
|
||||
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
assert await store.get_first_coin_height() is None
|
||||
|
||||
await store.add_coin_record(r5)
|
||||
assert await store.get_first_coin_height() == 5
|
||||
await store.add_coin_record(r4)
|
||||
assert await store.get_first_coin_height() == 4
|
||||
await store.add_coin_record(r3)
|
||||
assert await store.get_first_coin_height() == 3
|
||||
await store.add_coin_record(r2)
|
||||
assert await store.get_first_coin_height() == 2
|
||||
await store.add_coin_record(r1)
|
||||
assert await store.get_first_coin_height() == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rollback_to_block() -> None:
|
||||
|
||||
r1 = record(coin_1, confirmed=1, spent=0)
|
||||
r2 = record(coin_2, confirmed=2, spent=4)
|
||||
r3 = record(coin_3, confirmed=3, spent=5)
|
||||
r4 = record(coin_4, confirmed=4, spent=6)
|
||||
r5 = record(coin_5, confirmed=5, spent=7)
|
||||
|
||||
async with DBConnection1() as db_wrapper:
|
||||
store = await WalletCoinStore.create(db_wrapper)
|
||||
|
||||
await store.add_coin_record(r1)
|
||||
await store.add_coin_record(r2)
|
||||
await store.add_coin_record(r3)
|
||||
await store.add_coin_record(r4)
|
||||
await store.add_coin_record(r5)
|
||||
|
||||
assert set(
|
||||
await store.get_multiple_coin_records(
|
||||
[
|
||||
coin_1.name(),
|
||||
coin_2.name(),
|
||||
coin_3.name(),
|
||||
coin_4.name(),
|
||||
coin_5.name(),
|
||||
]
|
||||
)
|
||||
) == set(
|
||||
[
|
||||
r1,
|
||||
r2,
|
||||
r3,
|
||||
r4,
|
||||
r5,
|
||||
]
|
||||
)
|
||||
|
||||
assert await store.get_coin_record(coin_5.name()) == r5
|
||||
|
||||
await store.rollback_to_block(6)
|
||||
|
||||
new_r5 = await store.get_coin_record(coin_5.name())
|
||||
assert not new_r5.spent
|
||||
assert new_r5.spent_block_height == 0
|
||||
assert new_r5 != r5
|
||||
|
||||
assert await store.get_coin_record(coin_4.name()) == r4
|
||||
|
||||
await store.rollback_to_block(4)
|
||||
|
||||
assert await store.get_coin_record(coin_5.name()) is None
|
||||
new_r4 = await store.get_coin_record(coin_4.name())
|
||||
assert not new_r4.spent
|
||||
assert new_r4.spent_block_height == 0
|
||||
assert new_r4 != r4
|
||||
@@ -1,230 +0,0 @@
|
||||
# TODO: write tests for other stores
|
||||
# import asyncio
|
||||
# from pathlib import Path
|
||||
# from secrets import token_bytes
|
||||
# import aiosqlite
|
||||
# import pytest
|
||||
# from chia.util.ints import uint32, uint64, uint128
|
||||
# from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
# from chia.wallet.util.wallet_types import WalletType
|
||||
# from chia.types.coin import Coin
|
||||
#
|
||||
#
|
||||
# @pytest.fixture(scope="module")
|
||||
# def event_loop():
|
||||
# loop = asyncio.get_event_loop()
|
||||
# yield loop
|
||||
#
|
||||
#
|
||||
# class TestWalletStore:
|
||||
# @pytest.mark.asyncio
|
||||
# async def test_store(self):
|
||||
# db_filename = Path("blockchain_wallet_store_test.db")
|
||||
#
|
||||
# if db_filename.exists():
|
||||
# db_filename.unlink()
|
||||
#
|
||||
# db_connection = await aiosqlite.connect(db_filename)
|
||||
# store = await WalletStore.create(db_connection)
|
||||
# try:
|
||||
# coin_1 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# coin_2 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# coin_3 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# coin_4 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# record_replaced = WalletCoinRecord(coin_1, uint32(8), uint32(0),
|
||||
# False, True, WalletType.STANDARD_WALLET, 0)
|
||||
# record_1 = WalletCoinRecord(coin_1, uint32(4), uint32(0), False,
|
||||
# True, WalletType.STANDARD_WALLET, 0)
|
||||
# record_2 = WalletCoinRecord(coin_2, uint32(5), uint32(0),
|
||||
# False, True, WalletType.STANDARD_WALLET, 0)
|
||||
# record_3 = WalletCoinRecord(
|
||||
# coin_3,
|
||||
# uint32(5),
|
||||
# uint32(10),
|
||||
# True,
|
||||
# False,
|
||||
# WalletType.STANDARD_WALLET,
|
||||
# 0,
|
||||
# )
|
||||
# record_4 = WalletCoinRecord(
|
||||
# coin_4,
|
||||
# uint32(5),
|
||||
# uint32(15),
|
||||
# True,
|
||||
# False,
|
||||
# WalletType.STANDARD_WALLET,
|
||||
# 0,
|
||||
# )
|
||||
#
|
||||
# # Test add (replace) and get
|
||||
# assert await store.get_coin_record(coin_1.name()) is None
|
||||
# await store.add_coin_record(record_replaced)
|
||||
# await store.add_coin_record(record_1)
|
||||
# await store.add_coin_record(record_2)
|
||||
# await store.add_coin_record(record_3)
|
||||
# await store.add_coin_record(record_4)
|
||||
# assert await store.get_coin_record(coin_1.name()) == record_1
|
||||
#
|
||||
# # Test persistance
|
||||
# await db_connection.close()
|
||||
# db_connection = await aiosqlite.connect(db_filename)
|
||||
# store = await WalletStore.create(db_connection)
|
||||
# assert await store.get_coin_record(coin_1.name()) == record_1
|
||||
#
|
||||
# # Test set spent
|
||||
# await store.set_spent(coin_1.name(), uint32(12))
|
||||
# assert (await store.get_coin_record(coin_1.name())).spent
|
||||
# assert (await store.get_coin_record(coin_1.name())).spent_block_index == 12
|
||||
#
|
||||
# # No coins at height 3
|
||||
# assert len(await store.get_unspent_coins_at_height(3)) == 0
|
||||
# assert len(await store.get_unspent_coins_at_height(4)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(5)) == 4
|
||||
# assert len(await store.get_unspent_coins_at_height(11)) == 3
|
||||
# assert len(await store.get_unspent_coins_at_height(12)) == 2
|
||||
# assert len(await store.get_unspent_coins_at_height(15)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(16)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height()) == 1
|
||||
#
|
||||
# assert len(await store.get_unspent_coins_for_wallet(0)) == 1
|
||||
# assert len(await store.get_unspent_coins_for_wallet(1)) == 0
|
||||
#
|
||||
# coin_5 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# record_5 = WalletCoinRecord(
|
||||
# coin_5,
|
||||
# uint32(5),
|
||||
# uint32(15),
|
||||
# False,
|
||||
# False,
|
||||
# WalletType.STANDARD_WALLET,
|
||||
# 1,
|
||||
# )
|
||||
# await store.add_coin_record(record_5)
|
||||
# assert len(await store.get_unspent_coins_for_wallet(1)) == 1
|
||||
#
|
||||
# assert len(await store.get_spendable_for_index(100, 1)) == 1
|
||||
# assert len(await store.get_spendable_for_index(100, 0)) == 1
|
||||
# assert len(await store.get_spendable_for_index(0, 0)) == 0
|
||||
#
|
||||
# coin_6 = Coin(token_bytes(32), coin_4.puzzle_hash, uint64(12312))
|
||||
# await store.add_coin_record(record_5)
|
||||
# record_6 = WalletCoinRecord(
|
||||
# coin_6,
|
||||
# uint32(5),
|
||||
# uint32(15),
|
||||
# True,
|
||||
# False,
|
||||
# WalletType.STANDARD_WALLET,
|
||||
# 2,
|
||||
# )
|
||||
# await store.add_coin_record(record_6)
|
||||
# assert len(await store.get_coin_records_by_puzzle_hash(record_6.coin.puzzle_hash)) == 2 # 4 and 6
|
||||
# assert len(await store.get_coin_records_by_puzzle_hash(token_bytes(32))) == 0
|
||||
#
|
||||
# assert await store.get_coin_record_by_coin_id(coin_6.name()) == record_6
|
||||
# assert await store.get_coin_record_by_coin_id(token_bytes(32)) is None
|
||||
#
|
||||
# # BLOCKS
|
||||
# assert len(await store.get_lca_path()) == 0
|
||||
#
|
||||
# # NOT lca block
|
||||
# br_1 = BlockRecord(
|
||||
# token_bytes(32),
|
||||
# token_bytes(32),
|
||||
# uint32(0),
|
||||
# uint128(100),
|
||||
# None,
|
||||
# None,
|
||||
# None,
|
||||
# None,
|
||||
# uint64(0),
|
||||
# )
|
||||
# assert await store.get_block_record(br_1.header_hash) is None
|
||||
# await store.add_block_record(br_1, False)
|
||||
# assert len(await store.get_lca_path()) == 0
|
||||
# assert await store.get_block_record(br_1.header_hash) == br_1
|
||||
#
|
||||
# # LCA genesis
|
||||
# await store.add_block_record(br_1, True)
|
||||
# assert await store.get_block_record(br_1.header_hash) == br_1
|
||||
# assert len(await store.get_lca_path()) == 1
|
||||
# assert (await store.get_lca_path())[br_1.header_hash] == br_1
|
||||
#
|
||||
# br_2 = BlockRecord(
|
||||
# token_bytes(32),
|
||||
# token_bytes(32),
|
||||
# uint32(1),
|
||||
# uint128(100),
|
||||
# None,
|
||||
# None,
|
||||
# None,
|
||||
# None,
|
||||
# uint64(0),
|
||||
# )
|
||||
# await store.add_block_record(br_2, False)
|
||||
# assert len(await store.get_lca_path()) == 1
|
||||
# await store.add_block_to_path(br_2.header_hash)
|
||||
# assert len(await store.get_lca_path()) == 2
|
||||
# assert (await store.get_lca_path())[br_2.header_hash] == br_2
|
||||
#
|
||||
# br_3 = BlockRecord(
|
||||
# token_bytes(32),
|
||||
# token_bytes(32),
|
||||
# uint32(2),
|
||||
# uint128(100),
|
||||
# None,
|
||||
# None,
|
||||
# None,
|
||||
# None,
|
||||
# uint64(0),
|
||||
# )
|
||||
# await store.add_block_record(br_3, True)
|
||||
# assert len(await store.get_lca_path()) == 3
|
||||
# await store.remove_block_records_from_path(1)
|
||||
# assert len(await store.get_lca_path()) == 2
|
||||
#
|
||||
# await store.rollback_lca_to_block(0)
|
||||
# assert len(await store.get_unspent_coins_at_height()) == 0
|
||||
#
|
||||
# coin_7 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# coin_8 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# coin_9 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# coin_10 = Coin(token_bytes(32), token_bytes(32), uint64(12312))
|
||||
# record_7 = WalletCoinRecord(coin_7, uint32(0), uint32(1), True, False, WalletType.STANDARD_WALLET, 1)
|
||||
# record_8 = WalletCoinRecord(coin_8, uint32(1), uint32(2), True, False, WalletType.STANDARD_WALLET, 1)
|
||||
# record_9 = WalletCoinRecord(coin_9, uint32(2), uint32(3), True, False, WalletType.STANDARD_WALLET, 1)
|
||||
# record_10 = WalletCoinRecord(
|
||||
# coin_10,
|
||||
# uint32(3),
|
||||
# uint32(4),
|
||||
# True,
|
||||
# False,
|
||||
# WalletType.STANDARD_WALLET,
|
||||
# 1,
|
||||
# )
|
||||
#
|
||||
# await store.add_coin_record(record_7)
|
||||
# await store.add_coin_record(record_8)
|
||||
# await store.add_coin_record(record_9)
|
||||
# await store.add_coin_record(record_10)
|
||||
# assert len(await store.get_unspent_coins_at_height(0)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(1)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(2)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(3)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(4)) == 0
|
||||
#
|
||||
# await store.add_block_record(br_2, True)
|
||||
# await store.add_block_record(br_3, True)
|
||||
#
|
||||
# await store.rollback_lca_to_block(1)
|
||||
#
|
||||
# assert len(await store.get_unspent_coins_at_height(0)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(1)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(2)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(3)) == 1
|
||||
# assert len(await store.get_unspent_coins_at_height(4)) == 1
|
||||
#
|
||||
# except AssertionError:
|
||||
# await db_connection.close()
|
||||
# raise
|
||||
# await db_connection.close()
|
||||
Reference in New Issue
Block a user