Merge branch 'long_lived/atari' into fc.get_keys

This commit is contained in:
Florin Chirica
2022-08-01 15:08:23 +03:00
committed by GitHub
77 changed files with 2331 additions and 2730 deletions
+2 -1
View File
@@ -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
#
+6 -5
View File
@@ -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'
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+7 -3
View File
@@ -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'")
+5 -5
View File
@@ -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(
+1 -1
View File
@@ -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
-1
View File
@@ -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:
+96 -149
View File
@@ -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 #
###########
+7 -5
View File
@@ -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)
+3 -3
View File
@@ -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")
+19 -19
View File
@@ -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
View File
@@ -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:
+25 -25
View File
@@ -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"])
+7 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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]
+2 -2
View File
@@ -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)
+11 -21
View File
@@ -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()
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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:
+2 -2
View File
@@ -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
View File
@@ -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)
+9 -16
View File
@@ -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()
+18 -83
View File
@@ -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(())
+16 -21
View File
@@ -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(
View File
-314
View File
@@ -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
-13
View File
@@ -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)
+2 -12
View File
@@ -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)
-45
View File
@@ -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
-70
View File
@@ -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
+19 -9
View File
@@ -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()
+2
View File
@@ -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 -2
View File
@@ -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")
+3 -1
View File
@@ -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
+27 -110
View File
@@ -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
View File
@@ -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)
)
+4
View File
@@ -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
+49 -45
View File
@@ -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)
)
+6 -8
View File
@@ -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
+44 -151
View File
@@ -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()
+1 -1
View File
File diff suppressed because one or more lines are too long
+6 -7
View File
@@ -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",
-3
View File
@@ -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",
]
)
+18 -14
View File
@@ -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({})
+9 -9
View File
@@ -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
+48 -58
View File
@@ -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
+10 -18
View File
@@ -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
View File
@@ -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()
+16
View File
@@ -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()
+10 -3
View File
@@ -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],
-345
View File
@@ -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))
+1 -1
View File
@@ -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()
+20 -224
View File
@@ -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 -23
View File
@@ -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
+8
View File
@@ -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)
+2 -3
View File
@@ -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]))
+652
View File
@@ -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
View File
@@ -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)
+387
View File
@@ -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
-230
View File
@@ -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()