diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ad9920b5062f..85c52a45e3c7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ * @Chia-Network/required-reviewers -/.github/* @Chia-Network/actions-reviewers +/.github/**/* @Chia-Network/actions-reviewers /PRETTY_GOOD_PRACTICES.md @altendky @Chia-Network/required-reviewers /pylintrc @altendky @Chia-Network/required-reviewers /tests/ether.py @altendky @Chia-Network/required-reviewers diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e19adddbab02..912fc0cc64b9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,22 +3,17 @@ - In order to be merged, you must add the most appropriate category Label (Added, Changed, Fixed) to your PR --> -### Purpose: - +### Purpose: -### Current Behavior: - +### Current Behavior: ### New Behavior: - - -### Testing Notes: - +### Testing Notes: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1bc1efb25594..910400b75710 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,7 @@ updates: interval: "weekly" day: "tuesday" open-pull-requests-limit: 10 + rebase-strategy: auto labels: - dependencies - go @@ -25,6 +26,7 @@ updates: interval: "weekly" day: "tuesday" open-pull-requests-limit: 10 + rebase-strategy: disabled labels: - dependencies - python @@ -37,6 +39,7 @@ updates: interval: "weekly" day: "tuesday" open-pull-requests-limit: 10 + rebase-strategy: auto labels: - dependencies - github_actions @@ -49,6 +52,7 @@ updates: interval: "weekly" day: "tuesday" open-pull-requests-limit: 10 + rebase-strategy: auto labels: - dependencies - javascript @@ -61,6 +65,7 @@ updates: interval: "weekly" day: "tuesday" open-pull-requests-limit: 10 + rebase-strategy: auto labels: - dependencies - rust diff --git a/.github/workflows/build-linux-installer-deb.yml b/.github/workflows/build-linux-installer-deb.yml index 8d65b6df9019..9d0425702513 100644 --- a/.github/workflows/build-linux-installer-deb.yml +++ b/.github/workflows/build-linux-installer-deb.yml @@ -57,10 +57,12 @@ jobs: arch: amd64 madmax-suffix: "x86-64" bladebit-suffix: "ubuntu-x86-64.tar.gz" + arch-artifact-name: intel - runs-on: [Linux, ARM64] arch: arm64 madmax-suffix: "arm64" bladebit-suffix: "ubuntu-arm64.tar.gz" + arch-artifact-name: arm env: CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} @@ -140,7 +142,7 @@ jobs: with: python-version: ${{ matrix.python-version }} development: true - constraints-file-artifact-name: constraints-file-${{ matrix.os.arch }} + constraints-file-artifact-name: constraints-file-${{ matrix.os.arch-artifact-name }} - uses: chia-network/actions/activate-venv@main @@ -176,164 +178,68 @@ jobs: - name: Upload Linux artifacts uses: actions/upload-artifact@v4 with: - name: chia-installers-linux-deb-${{ matrix.os.arch }} - path: ${{ github.workspace }}/build_scripts/final_installer/ + name: chia-installers-linux-deb-${{ matrix.os.arch-artifact-name }} + path: build_scripts/final_installer/ - name: Remove working files to exclude from cache run: | rm -rf ./chia-blockchain-gui/packages/gui/daemon publish: - name: Publish ${{ matrix.os.arch }} - runs-on: ubuntu-latest + name: 📦 Publish Installers + uses: ./.github/workflows/reflow-publish-installer.yml + with: + concurrency-name: deb + chia-installer-version: ${{ needs.version.outputs.chia-installer-version }} + chia-dev-version: ${{ needs.version.outputs.chia-dev-version }} + configuration: ${{ toJSON( matrix.configuration ) }} + secrets: inherit needs: - version - build - timeout-minutes: ${{ matrix.os.timeout }} strategy: fail-fast: false matrix: - python-version: ["3.10"] - os: - - arch: amd64 - glue-name: "build-amd64-deb" - timeout: 5 - - arch: arm64 - glue-name: "build-arm64-deb" - timeout: 5 - - env: - CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} - - steps: - - uses: Chia-Network/actions/clean-workspace@main - - - uses: Chia-Network/actions/setup-python@main - with: - python-version: ${{ matrix.python-version }} - - - uses: chia-network/actions/create-venv@main - id: create-venv - - - uses: chia-network/actions/activate-venv@main - with: - directories: ${{ steps.create-venv.outputs.activate-venv-directories }} - - - name: Download constraints file - uses: actions/download-artifact@v4 - with: - name: constraints-file-${{ matrix.os.arch }} - path: venv - - - name: Install utilities - run: | - pip install --constraint venv/constraints.txt py3createtorrent - - - name: Download packages - uses: actions/download-artifact@v4 - with: - name: chia-installers-linux-deb-${{ matrix.os.arch }} - path: build_scripts/final_installer/ - - - name: Set Env - uses: Chia-Network/actions/setjobenv@main - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test for secrets access - id: check_secrets - shell: bash - run: | - unset HAS_AWS_SECRET - unset HAS_GLUE_SECRET - - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi - echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >> "$GITHUB_OUTPUT" - - if [ -n "$GLUE_API_URL" ]; then HAS_GLUE_SECRET='true' ; fi - echo HAS_GLUE_SECRET=${HAS_GLUE_SECRET} >> "$GITHUB_OUTPUT" - env: - AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" - GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" - - - name: Configure AWS credentials - if: steps.check_secrets.outputs.HAS_AWS_SECRET - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload - aws-region: us-west-2 - - - name: Upload to s3 - if: steps.check_secrets.outputs.HAS_AWS_SECRET - run: | - GIT_SHORT_HASH=$(echo "${GITHUB_SHA}" | cut -c1-8) - CHIA_DEV_BUILD=${CHIA_INSTALLER_VERSION}-$GIT_SHORT_HASH - echo "CHIA_DEV_BUILD=$CHIA_DEV_BUILD" >> "$GITHUB_ENV" - aws s3 cp "$GITHUB_WORKSPACE/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb" "s3://download.chia.net/dev/chia-blockchain_${CHIA_DEV_BUILD}_${{ matrix.os.arch }}.deb" - aws s3 cp "$GITHUB_WORKSPACE/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb" "s3://download.chia.net/dev/chia-blockchain-cli_${CHIA_DEV_BUILD}-1_${{ matrix.os.arch }}.deb" - - - name: Create Checksums - run: | - ls "$GITHUB_WORKSPACE"/build_scripts/final_installer/ - sha256sum "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb > "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb.sha256 - sha256sum "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb > "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb.sha256 - ls "$GITHUB_WORKSPACE"/build_scripts/final_installer/ - - - name: Create .deb torrent - if: env.FULL_RELEASE == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - py3createtorrent -f -t udp://tracker.opentrackr.org:1337/announce "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb -o "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb.torrent --webseed https://download.chia.net/install/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb - py3createtorrent -f -t udp://tracker.opentrackr.org:1337/announce "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb -o "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb.torrent --webseed https://download.chia.net/install/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb - gh release upload --repo ${{ github.repository }} $RELEASE_TAG "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb.torrent - - - name: Upload Dev Installer - if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/main' - run: | - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb s3://download.chia.net/latest-dev/chia-blockchain_${{ matrix.os.arch }}_latest_dev.deb - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb.sha256 s3://download.chia.net/latest-dev/chia-blockchain_${{ matrix.os.arch }}_latest_dev.deb.sha256 - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb s3://download.chia.net/latest-dev/chia-blockchain-cli_${{ matrix.os.arch }}_latest_dev.deb - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb.sha256 s3://download.chia.net/latest-dev/chia-blockchain-cli_${{ matrix.os.arch }}_latest_dev.deb.sha256 - - - name: Upload Release Files - if: steps.check_secrets.outputs.HAS_AWS_SECRET && env.FULL_RELEASE == 'true' - run: | - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb.sha256 s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb.torrent s3://download.chia.net/torrents/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb.sha256 s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb.torrent s3://download.chia.net/torrents/ - - - name: Upload release artifacts - if: env.RELEASE == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release upload \ - --repo ${{ github.repository }} \ - $RELEASE_TAG \ - build_scripts/final_installer/chia-blockchain_${CHIA_INSTALLER_VERSION}_${{ matrix.os.arch }}.deb \ - build_scripts/final_installer/chia-blockchain-cli_${CHIA_INSTALLER_VERSION}-1_${{ matrix.os.arch }}.deb - - - name: Mark pre-release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.PRE_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}-prerelease/${{ env.RELEASE_TAG }}" - glue_path: "success/${{ matrix.os.glue-name }}" - - - name: Mark release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.FULL_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}/${{ env.RELEASE_TAG }}" - glue_path: "success/${{ matrix.os.glue-name }}" + configuration: + - python-version: ["3.10"] + os: + - matrix: debian + file-type: + name: DEB + extension: deb + glue-name: deb + artifact-platform-name: linux + file-arch-name: + arm: arm64 + intel: amd64 + file-suffix: + arm: "" + intel: "" + names: + cli: + file: chia-blockchain-cli_{0}-1_{2}.deb + dev-file: chia-blockchain-cli_{1}-1_{2}.deb + latest-dev-file: chia-blockchain-cli_{2}_latest_dev.deb + gui: + file: chia-blockchain_{0}_{2}.deb + dev-file: chia-blockchain_{1}_{2}.deb + latest-dev-file: chia-blockchain_{2}_latest_dev.deb + mode: + - name: GUI + matrix: gui + glue-name: gui + - name: CLI + matrix: cli + glue-name: cli + arch: + - name: ARM64 + matrix: arm + artifact-name: arm + glue-name: arm + - name: Intel + matrix: intel + artifact-name: intel + glue-name: intel test: name: Test ${{ matrix.distribution.name }} ${{ matrix.mode.name }} ${{ matrix.arch.name }} @@ -380,10 +286,10 @@ jobs: arch: - name: ARM64 matrix: arm - artifact-name: arm64 + artifact-name: arm - name: Intel matrix: intel - artifact-name: amd64 + artifact-name: intel env: DEBIAN_FRONTEND: noninteractive diff --git a/.github/workflows/build-linux-installer-rpm.yml b/.github/workflows/build-linux-installer-rpm.yml index c1f401115f23..6a768525e94c 100644 --- a/.github/workflows/build-linux-installer-rpm.yml +++ b/.github/workflows/build-linux-installer-rpm.yml @@ -53,6 +53,8 @@ jobs: fail-fast: false matrix: python-version: ["3.10"] + os: + - arch-artifact-name: intel env: CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} @@ -130,7 +132,7 @@ jobs: with: python-version: ${{ matrix.python-version }} development: true - constraints-file-artifact-name: constraints-file-intel + constraints-file-artifact-name: constraints-file-${{ matrix.os.arch-artifact-name }} - uses: chia-network/actions/activate-venv@main @@ -175,158 +177,72 @@ jobs: - name: Upload Linux artifacts uses: actions/upload-artifact@v4 with: - name: chia-installers-linux-rpm-intel - path: ${{ github.workspace }}/build_scripts/final_installer/ + name: chia-installers-linux-rpm-${{ matrix.os.arch-artifact-name }} + path: build_scripts/final_installer/ - name: Remove working files to exclude from cache run: | rm -rf ./chia-blockchain-gui/packages/gui/daemon publish: - name: Publish amd64 RPM - runs-on: ubuntu-latest + name: 📦 Publish Installers + uses: ./.github/workflows/reflow-publish-installer.yml + with: + concurrency-name: rpm + chia-installer-version: ${{ needs.version.outputs.chia-installer-version }} + chia-dev-version: ${{ needs.version.outputs.chia-dev-version }} + configuration: ${{ toJSON( matrix.configuration ) }} + secrets: inherit needs: - version - build - timeout-minutes: 5 strategy: fail-fast: false matrix: - python-version: ["3.10"] - - env: - CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} - - steps: - - uses: Chia-Network/actions/clean-workspace@main - - - uses: Chia-Network/actions/setup-python@main - with: - python-version: ${{ matrix.python-version }} - - - uses: chia-network/actions/create-venv@main - id: create-venv - - - uses: chia-network/actions/activate-venv@main - with: - directories: ${{ steps.create-venv.outputs.activate-venv-directories }} - - - name: Download constraints file - uses: actions/download-artifact@v4 - with: - name: constraints-file-intel - path: venv - - - name: Install utilities - run: | - pip install --constraint venv/constraints.txt py3createtorrent - - - name: Download packages - uses: actions/download-artifact@v4 - with: - name: chia-installers-linux-rpm-intel - path: build_scripts/final_installer/ - - - name: Set Env - uses: Chia-Network/actions/setjobenv@main - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test for secrets access - id: check_secrets - shell: bash - run: | - unset HAS_AWS_SECRET - unset HAS_GLUE_SECRET - - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi - echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >> "$GITHUB_OUTPUT" - - if [ -n "$GLUE_API_URL" ]; then HAS_GLUE_SECRET='true' ; fi - echo HAS_GLUE_SECRET=${HAS_GLUE_SECRET} >> "$GITHUB_OUTPUT" - env: - AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" - GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" - - - name: Configure AWS credentials - if: steps.check_secrets.outputs.HAS_AWS_SECRET - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload - aws-region: us-west-2 - - - name: Upload to s3 - if: steps.check_secrets.outputs.HAS_AWS_SECRET - run: | - GIT_SHORT_HASH=$(echo "${GITHUB_SHA}" | cut -c1-8) - CHIA_DEV_BUILD=${CHIA_INSTALLER_VERSION}-$GIT_SHORT_HASH - echo "CHIA_DEV_BUILD=$CHIA_DEV_BUILD" >> "$GITHUB_ENV" - ls "$GITHUB_WORKSPACE"/build_scripts/final_installer/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm s3://download.chia.net/dev/chia-blockchain-${CHIA_DEV_BUILD}-1.x86_64.rpm - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm s3://download.chia.net/dev/chia-blockchain-cli-${CHIA_DEV_BUILD}-1.x86_64.rpm - - - name: Create Checksums - run: | - ls "$GITHUB_WORKSPACE"/build_scripts/final_installer/ - sha256sum "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm > "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.sha256 - sha256sum "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm > "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.sha256 - ls "$GITHUB_WORKSPACE"/build_scripts/final_installer/ - - - name: Create .rpm torrent - if: env.FULL_RELEASE == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - py3createtorrent -f -t udp://tracker.opentrackr.org:1337/announce "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm -o "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.torrent --webseed https://download.chia.net/install/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm - py3createtorrent -f -t udp://tracker.opentrackr.org:1337/announce "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm -o "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.torrent --webseed https://download.chia.net/install/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm - gh release upload --repo ${{ github.repository }} $RELEASE_TAG "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.torrent - - - name: Upload Dev Installer - if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/main' - run: | - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm s3://download.chia.net/latest-dev/chia-blockchain-1.x86_64_latest_dev.rpm - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.sha256 s3://download.chia.net/latest-dev/chia-blockchain-1.x86_64_latest_dev.rpm.sha256 - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm s3://download.chia.net/latest-dev/chia-blockchain-cli-1.x86_64_latest_dev.rpm - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.sha256 s3://download.chia.net/latest-dev/chia-blockchain-cli-1.x86_64_latest_dev.rpm.sha256 - - - name: Upload Release Files - if: steps.check_secrets.outputs.HAS_AWS_SECRET && env.FULL_RELEASE == 'true' - run: | - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.sha256 s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.torrent s3://download.chia.net/torrents/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.sha256 s3://download.chia.net/install/ - aws s3 cp "$GITHUB_WORKSPACE"/build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm.torrent s3://download.chia.net/torrents/ - - - name: Upload release artifacts - if: env.RELEASE == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release upload \ - --repo ${{ github.repository }} \ - $RELEASE_TAG \ - build_scripts/final_installer/chia-blockchain-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm \ - build_scripts/final_installer/chia-blockchain-cli-${CHIA_INSTALLER_VERSION}-1.x86_64.rpm - - - name: Mark pre-release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.PRE_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}-prerelease/${{ env.RELEASE_TAG }}" - glue_path: "success/build-linux-rpm" - - - name: Mark release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.FULL_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}/${{ env.RELEASE_TAG }}" - glue_path: "success/build-linux-rpm" + configuration: + - python-version: ["3.10"] + os: + - matrix: redhat + file-type: + name: RPM + extension: rpm + glue-name: rpm + artifact-platform-name: linux + file-arch-name: + intel: x86_64 + file-suffix: + arm: "" + intel: "" + names: + cli: + file: chia-blockchain-cli-{0}-1.{2}.rpm + def-file: chia-blockchain-cli-{1}-1.{2}.rpm + latest-dev-file: chia-blockchain-cli-1.{2}_latest_dev.rpm + gui: + file: chia-blockchain-{0}-1.{2}.rpm + def-file: chia-blockchain-{1}-1.{2}.rpm + latest-dev-file: chia-blockchain-1.{2}_latest_dev.rpm + mode: + - name: GUI + matrix: gui + glue-name: gui + - name: CLI + matrix: cli + glue-name: cli + arch: + - name: ARM64 + matrix: arm + artifact-name: arm + glue-name: arm + - name: Intel + matrix: intel + artifact-name: intel + glue-name: intel + exclude: + - os: + matrix: redhat + arch: + matrix: arm test: name: Test ${{ matrix.distribution.name }} ${{ matrix.mode.name }} ${{ matrix.state.name }} diff --git a/.github/workflows/build-macos-installers.yml b/.github/workflows/build-macos-installers.yml index b18e9f473b1d..c6322603c08c 100644 --- a/.github/workflows/build-macos-installers.yml +++ b/.github/workflows/build-macos-installers.yml @@ -55,9 +55,11 @@ jobs: - runs-on: macos-12 name: intel bladebit-suffix: macos-x86-64.tar.gz + arch-artifact-name: intel - runs-on: macos-13-arm64 name: m1 bladebit-suffix: macos-arm64.tar.gz + arch-artifact-name: arm env: CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} @@ -169,7 +171,7 @@ jobs: with: python-version: ${{ matrix.python-version }} development: true - constraints-file-artifact-name: constraints-file-${{ matrix.os.name }} + constraints-file-artifact-name: constraints-file-${{ matrix.os.arch-artifact-name }} - uses: chia-network/actions/activate-venv@main @@ -216,8 +218,8 @@ jobs: - name: Upload MacOS artifacts uses: actions/upload-artifact@v4 with: - name: chia-installers-macos-dmg-${{ matrix.os.name }} - path: ${{ github.workspace }}/build_scripts/final_installer/ + name: chia-installers-macos-dmg-${{ matrix.os.arch-artifact-name }} + path: build_scripts/final_installer/ - name: Remove working files to exclude from cache run: | @@ -229,149 +231,61 @@ jobs: run: security delete-keychain signing_temp.keychain || true publish: - name: Publish ${{ matrix.os.name }} DMG - runs-on: ubuntu-latest + name: 📦 Publish Installers + uses: ./.github/workflows/reflow-publish-installer.yml + with: + concurrency-name: macos + chia-installer-version: ${{ needs.version.outputs.chia-installer-version }} + chia-dev-version: ${{ needs.version.outputs.chia-dev-version }} + configuration: ${{ toJSON( matrix.configuration ) }} + secrets: inherit needs: - version - build - timeout-minutes: 5 strategy: fail-fast: false matrix: - python-version: ["3.10"] - os: - - name: intel - file-suffix: "" - glue-name: "build-macos" - - name: m1 - file-suffix: "-arm64" - glue-name: "build-mac-m1" - - env: - CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} - - steps: - - uses: Chia-Network/actions/clean-workspace@main - - - name: Setup Python environment - uses: Chia-Network/actions/setup-python@main - with: - python-version: ${{ matrix.python-version }} - - - uses: chia-network/actions/create-venv@main - id: create-venv - - - uses: chia-network/actions/activate-venv@main - with: - directories: ${{ steps.create-venv.outputs.activate-venv-directories }} - - - name: Download constraints file - uses: actions/download-artifact@v4 - with: - name: constraints-file-${{ matrix.os.name }} - path: venv - - - name: Install utilities - run: | - pip install --constraint venv/constraints.txt py3createtorrent - - - name: Download packages - uses: actions/download-artifact@v4 - with: - name: chia-installers-macos-dmg-${{ matrix.os.name }} - path: build_scripts/final_installer/ - - - name: Set Env - uses: Chia-Network/actions/setjobenv@main - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test for secrets access - id: check_secrets - shell: bash - run: | - unset HAS_AWS_SECRET - unset HAS_GLUE_SECRET - - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi - echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >> "$GITHUB_OUTPUT" - - if [ -n "$GLUE_API_URL" ]; then HAS_GLUE_SECRET='true' ; fi - echo HAS_GLUE_SECRET=${HAS_GLUE_SECRET} >> "$GITHUB_OUTPUT" - env: - AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" - GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" - - - name: Create Checksums - run: | - ls - sha256sum ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg > ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.sha256 - - - name: Configure AWS credentials - if: steps.check_secrets.outputs.HAS_AWS_SECRET - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload - aws-region: us-west-2 - - - name: Upload to s3 - if: steps.check_secrets.outputs.HAS_AWS_SECRET - run: | - GIT_SHORT_HASH=$(echo "${GITHUB_SHA}" | cut -c1-8) - CHIA_DEV_BUILD=${CHIA_INSTALLER_VERSION}-$GIT_SHORT_HASH - echo "CHIA_DEV_BUILD=$CHIA_DEV_BUILD" >> "$GITHUB_ENV" - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg s3://download.chia.net/dev/Chia-${CHIA_DEV_BUILD}${{ matrix.os.file-suffix }}.dmg - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.sha256 s3://download.chia.net/latest-dev/Chia-${CHIA_DEV_BUILD}${{ matrix.os.file-suffix }}.dmg.sha256 - - - name: Create torrent - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: env.FULL_RELEASE == 'true' - run: | - py3createtorrent -f -t udp://tracker.opentrackr.org:1337/announce ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg -o ${{ github.workspace }}/build_scripts/final_installer/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.torrent --webseed https://download.chia.net/install/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg - ls ${{ github.workspace }}/build_scripts/final_installer/ - gh release upload --repo ${{ github.repository }} $RELEASE_TAG ${{ github.workspace }}/build_scripts/final_installer/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.torrent - - - name: Upload Dev Installer - if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/main' - run: | - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg s3://download.chia.net/latest-dev/Chia${{ matrix.os.file-suffix }}_latest_dev.dmg - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.sha256 s3://download.chia.net/latest-dev/Chia${{ matrix.os.file-suffix }}_latest_dev.dmg.sha256 - - - name: Upload Release Files - if: steps.check_secrets.outputs.HAS_AWS_SECRET && env.FULL_RELEASE == 'true' - run: | - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg s3://download.chia.net/install/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.sha256 s3://download.chia.net/install/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.sha256 - aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.torrent s3://download.chia.net/torrents/Chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg.torrent - - - name: Upload release artifacts - if: env.RELEASE == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release upload \ - --repo ${{ github.repository }} \ - $RELEASE_TAG \ - build_scripts/final_installer/*.dmg - - - name: Mark pre-release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.PRE_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}-prerelease/${{ env.RELEASE_TAG }}" - glue_path: "success/${{ matrix.os.glue-name }}" - - - name: Mark release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.FULL_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}/${{ env.RELEASE_TAG }}" - glue_path: "success/${{ matrix.os.glue-name }}" + configuration: + - python-version: ["3.10"] + os: + - matrix: macos + file-type: + name: DMG + extension: dmg + glue-name: macos + artifact-platform-name: macos + file-arch-name: + arm: m1 + intel: intel + file-suffix: + arm: "-arm64" + intel: "" + names: + gui: + file: Chia-{0}{3}.dmg + dev-file: Chia-{1}{3}.dmg + latest-dev-file: Chia-{3}_latest_dev.dmg + mode: + - name: GUI + matrix: gui + glue-name: gui + - name: CLI + matrix: cli + glue-name: cli + arch: + - name: ARM64 + matrix: arm + artifact-name: arm + glue-name: arm + - name: Intel + matrix: intel + artifact-name: intel + glue-name: intel + exclude: + - os: + matrix: macos + mode: + matrix: cli test: name: Test ${{ matrix.os.name }} ${{ matrix.arch.name }} @@ -398,7 +312,7 @@ jobs: arch: - name: ARM64 matrix: arm - artifact-name: m1 + artifact-name: arm - name: Intel matrix: intel artifact-name: intel @@ -428,10 +342,10 @@ jobs: - name: Mount .dmg env: - PACKAGE_PATH: ${{ github.workspace }}/build_scripts/final_installer/ + PACKAGE_PATH: artifacts/ run: | ls -l "${{ steps.download.outputs.download-path }}" - hdiutil attach "${{ steps.download.outputs.download-path }}"/chia-*.dmg + hdiutil attach "${{ steps.download.outputs.download-path }}"/Chia-*.dmg - name: List .dmg contents run: | diff --git a/.github/workflows/build-windows-installer.yml b/.github/workflows/build-windows-installer.yml index 3d0c9235b749..53246f7e3a24 100644 --- a/.github/workflows/build-windows-installer.yml +++ b/.github/workflows/build-windows-installer.yml @@ -71,9 +71,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Set git urls to https instead of ssh - run: | - git config --global url."https://github.com/".insteadOf ssh://git@github.com/ + - uses: Chia-Network/actions/git-ssh-to-https@main - name: Get npm cache directory id: npm-cache @@ -232,7 +230,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: chia-installers-windows-exe-intel - path: ${{ github.workspace }}\chia-blockchain-gui\release-builds\ + path: chia-blockchain-gui\release-builds\windows-installer\ - name: Remove Windows exe and installer to exclude from cache run: | @@ -242,140 +240,64 @@ jobs: Remove-Item .\chia-blockchain-gui\release-builds -Recurse -Force publish: - name: Publish EXE - runs-on: ubuntu-latest + name: 📦 Publish Installers + uses: ./.github/workflows/reflow-publish-installer.yml + with: + concurrency-name: windows + chia-installer-version: ${{ needs.version.outputs.chia-installer-version }} + chia-dev-version: ${{ needs.version.outputs.chia-dev-version }} + configuration: ${{ toJSON( matrix.configuration ) }} + secrets: inherit needs: - version - build - timeout-minutes: 5 strategy: fail-fast: false matrix: - python-version: ["3.10"] - - env: - CHIA_INSTALLER_VERSION: ${{ needs.version.outputs.chia-installer-version }} - - steps: - - uses: Chia-Network/actions/clean-workspace@main - - - uses: Chia-Network/actions/setup-python@main - with: - python-version: ${{ matrix.python-version }} - - - uses: chia-network/actions/create-venv@main - id: create-venv - - - uses: chia-network/actions/activate-venv@main - with: - directories: ${{ steps.create-venv.outputs.activate-venv-directories }} - - - name: Download constraints file - uses: actions/download-artifact@v4 - with: - name: constraints-file-intel - path: venv - - - name: Install utilities - run: | - pip install --constraint venv/constraints.txt py3createtorrent - - - name: Download packages - uses: actions/download-artifact@v4 - with: - name: chia-installers-windows-exe-intel - path: chia-blockchain-gui/release-builds/ - - - name: Set Env - uses: Chia-Network/actions/setjobenv@main - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test for secrets access - id: check_secrets - run: | - unset HAS_AWS_SECRET - unset HAS_GLUE_SECRET - - if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi - echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >> "$GITHUB_OUTPUT" - - if [ -n "$GLUE_API_URL" ]; then HAS_GLUE_SECRET='true' ; fi - echo HAS_GLUE_SECRET=${HAS_GLUE_SECRET} >> "$GITHUB_OUTPUT" - env: - SIGNING_SECRET: "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" - AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" - GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" - - - name: Configure AWS credentials - if: steps.check_secrets.outputs.HAS_AWS_SECRET - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload - aws-region: us-west-2 - - - name: Upload to s3 - if: steps.check_secrets.outputs.HAS_AWS_SECRET - run: | - GIT_SHORT_HASH=$(echo "${GITHUB_SHA}" | cut -c1-8) - CHIA_DEV_BUILD=${CHIA_INSTALLER_VERSION}-$GIT_SHORT_HASH - echo CHIA_DEV_BUILD=${CHIA_DEV_BUILD} >> "$GITHUB_OUTPUT" - echo ${CHIA_DEV_BUILD} - pwd - aws s3 cp chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${CHIA_INSTALLER_VERSION}.exe s3://download.chia.net/dev/ChiaSetup-${CHIA_DEV_BUILD}.exe - - - name: Create Checksums - run: | - ls "$GITHUB_WORKSPACE"/chia-blockchain-gui/release-builds/windows-installer/ - sha256sum "$GITHUB_WORKSPACE"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe > "$GITHUB_WORKSPACE"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe.sha256 - ls "$GITHUB_WORKSPACE"/chia-blockchain-gui/release-builds/windows-installer/ - - - name: Create torrent - if: env.FULL_RELEASE == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - py3createtorrent -f -t udp://tracker.opentrackr.org:1337/announce "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe -o "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe.torrent --webseed https://download.chia.net/install/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe - ls - gh release upload --repo ${{ github.repository }} $RELEASE_TAG "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe.torrent - - - name: Upload Dev Installer - if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/main' - run: | - aws s3 cp "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe s3://download.chia.net/latest-dev/ChiaSetup-latest-dev.exe - aws s3 cp "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe.sha256 s3://download.chia.net/latest-dev/ChiaSetup-latest-dev.exe.sha256 - - - name: Upload Release Files - if: steps.check_secrets.outputs.HAS_AWS_SECRET && env.FULL_RELEASE == 'true' - run: | - aws s3 cp "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe s3://download.chia.net/install/ - aws s3 cp "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe.sha256 s3://download.chia.net/install/ - aws s3 cp "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe.torrent s3://download.chia.net/torrents/ - - - name: Upload release artifacts - if: env.RELEASE == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release upload --repo ${{ github.repository }} $RELEASE_TAG "${GITHUB_WORKSPACE}"/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe - - - name: Mark pre-release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.PRE_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}-prerelease/${{ env.RELEASE_TAG }}" - glue_path: "success/build-windows" - - - name: Mark release installer complete - uses: Chia-Network/actions/github/glue@main - if: steps.check_secrets.outputs.HAS_GLUE_SECRET && env.FULL_RELEASE == 'true' - with: - json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' - glue_url: "${{ secrets.GLUE_API_URL }}" - glue_project: "${{ env.RFC_REPO }}/${{ env.RELEASE_TAG }}" - glue_path: "success/build-windows" + configuration: + - python-version: ["3.10"] + os: + - matrix: windows + file-type: + name: EXE + extension: exe + glue-name: windows + artifact-platform-name: windows + file-arch-name: + intel: intel + file-suffix: + arm: "" + intel: "" + names: + gui: + file: ChiaSetup-{0}.exe + dev-file: ChiaSetup-{1}.exe + latest-dev-file: ChiaSetup-latest-dev.exe + mode: + - name: GUI + matrix: gui + glue-name: gui + - name: CLI + matrix: cli + glue-name: cli + arch: + - name: ARM64 + matrix: arm + artifact-name: arm + glue-name: arm + - name: Intel + matrix: intel + artifact-name: intel + glue-name: intel + exclude: + - os: + matrix: windows + arch: + matrix: arm + - os: + matrix: windows + mode: + matrix: cli test: name: Test ${{ matrix.os.name }} @@ -399,6 +321,9 @@ jobs: - name: Intel matrix: intel + env: + INSTALL_PATH: installed/ + steps: - uses: Chia-Network/actions/clean-workspace@main @@ -409,19 +334,26 @@ jobs: path: packages - name: Install package - env: - INSTALL_PATH: ${{ github.workspace }}\installed run: | dir ./packages/ - $env:INSTALLER_PATH = (Get-ChildItem packages/windows-installer/ChiaSetup-*.exe) - Start-Process -Wait -FilePath $env:INSTALLER_PATH -ArgumentList "/S", ("/D=" + $env:INSTALL_PATH) + $env:INSTALLER_PATH = (Get-ChildItem packages/ChiaSetup-*.exe) + # note that the installer requires the target path use backslashes + $env:RESOLVED_INSTALL_PATH = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($env:INSTALL_PATH) + Start-Process -Wait -FilePath $env:INSTALLER_PATH -ArgumentList "/S", ("/D=" + $env:RESOLVED_INSTALL_PATH) + echo ====================================== + dir ./ + echo ====================================== + dir ./installed/ - name: List installed files run: | Get-ChildItem -Recurse $env:INSTALL_PATH | Select FullName + - name: List all files + if: + run: | + Get-ChildItem -Recurse $env:INSTALL_PATH | Select FullName + - name: Run chia dev installers test - env: - INSTALL_PATH: ${{ github.workspace }}\installed run: | - & ($env:INSTALL_PATH + "\resources\app.asar.unpacked\daemon\chia.exe") dev installers test --expected-chia-version "${{ needs.version.outputs.chia-installer-version }}" + & ($env:INSTALL_PATH + "/resources/app.asar.unpacked/daemon/chia.exe") dev installers test --expected-chia-version "${{ needs.version.outputs.chia-installer-version }}" diff --git a/.github/workflows/reflow-publish-installer.yml b/.github/workflows/reflow-publish-installer.yml new file mode 100644 index 000000000000..edb1a903270a --- /dev/null +++ b/.github/workflows/reflow-publish-installer.yml @@ -0,0 +1,160 @@ +name: 📦 Publish Installer + +on: + workflow_call: + inputs: + concurrency-name: + required: true + type: string + chia-installer-version: + required: true + type: string + chia-dev-version: + required: true + type: string + configuration: + required: true + type: string + +concurrency: + # SHA is added to the end if on `main` to let all main workflows run + group: ${{ github.ref }}-${{ github.workflow }}-${{ inputs.concurrency-name }}-${{ 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 + +permissions: + id-token: write + contents: write + +jobs: + publish: + name: Publish ${{ matrix.arch.name }} ${{ matrix.mode.name }} ${{ matrix.os.file-type.name }} + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.configuration) }} + + env: + FILE: ${{ format(matrix.os.names[matrix.mode.matrix].file, inputs.chia-installer-version, inputs.chia-dev-version, matrix.os.file-arch-name[matrix.arch.matrix], matrix.os.file-suffix[matrix.arch.matrix]) }} + DEV_FILE: ${{ format(matrix.os.names[matrix.mode.matrix].dev-file, inputs.chia-installer-version, inputs.chia-dev-version, matrix.os.file-arch-name[matrix.arch.matrix], matrix.os.file-suffix[matrix.arch.matrix]) }} + LATEST_DEV_FILE: ${{ format(matrix.os.names[matrix.mode.matrix].latest-dev-file, inputs.chia-installer-version, inputs.chia-dev-version, matrix.os.file-arch-name[matrix.arch.matrix], matrix.os.file-suffix[matrix.arch.matrix]) }} + INSTALL_S3_URL: s3://download.chia.net/install/ + DEV_S3_URL: s3://download.chia.net/dev/ + LATEST_DEV_S3_URL: s3://download.chia.net/latest-dev/ + TORRENT_S3_URL: s3://download.chia.net/torrents/ + TRACKER_URL: udp://tracker.opentrackr.org:1337/announce + + steps: + - uses: Chia-Network/actions/clean-workspace@main + + - uses: Chia-Network/actions/setup-python@main + with: + python-version: ${{ matrix.python-version }} + + - uses: chia-network/actions/create-venv@main + id: create-venv + + - uses: chia-network/actions/activate-venv@main + with: + directories: ${{ steps.create-venv.outputs.activate-venv-directories }} + + - name: Download constraints file + uses: actions/download-artifact@v4 + with: + name: constraints-file-${{ matrix.arch.artifact-name }} + path: venv + + - name: Install utilities + run: | + pip install --constraint venv/constraints.txt py3createtorrent + + - name: Download packages + uses: actions/download-artifact@v4 + with: + name: chia-installers-${{ matrix.os.artifact-platform-name }}-${{ matrix.os.file-type.extension }}-${{ matrix.arch.artifact-name }} + path: artifacts/ + + - name: Set Env + uses: Chia-Network/actions/setjobenv@main + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test for secrets access + id: check_secrets + run: | + unset HAS_AWS_SECRET + unset HAS_GLUE_SECRET + + if [ -n "$AWS_SECRET" ]; then HAS_AWS_SECRET='true' ; fi + echo HAS_AWS_SECRET=${HAS_AWS_SECRET} >> "$GITHUB_OUTPUT" + + if [ -n "$GLUE_API_URL" ]; then HAS_GLUE_SECRET='true' ; fi + echo HAS_GLUE_SECRET=${HAS_GLUE_SECRET} >> "$GITHUB_OUTPUT" + env: + AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" + GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" + + - name: Configure AWS credentials + if: steps.check_secrets.outputs.HAS_AWS_SECRET + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ secrets.CHIA_AWS_ACCOUNT_ID }}:role/installer-upload + aws-region: us-west-2 + + - name: Create Checksums + run: | + ls artifacts/ + sha256sum "artifacts/${FILE}" > "artifacts/${FILE}.sha256" + + - name: Upload to s3 + if: steps.check_secrets.outputs.HAS_AWS_SECRET + run: | + ls artifacts/ + aws s3 cp "artifacts/${FILE}" "${DEV_S3_URL}/${DEV_FILE}" + aws s3 cp "artifacts/${FILE}.sha256" "${LATEST_DEV_S3_URL}/${DEV_FILE}.sha256" + + - name: Create torrent + if: env.RELEASE == 'true' && matrix.mode.matrix == 'gui' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + py3createtorrent -f -t ${TRACKER_URL} artifacts/${FILE} -o artifacts/${FILE}.torrent --webseed https://download.chia.net/install/${FILE} + gh release upload --repo ${{ github.repository }} $RELEASE_TAG artifacts/${FILE}.torrent + + - name: Upload Dev Installer + if: steps.check_secrets.outputs.HAS_AWS_SECRET && github.ref == 'refs/heads/main' + run: | + aws s3 cp artifacts/${FILE} ${LATEST_DEV_S3_URL}/${LATEST_DEV_FILE} + aws s3 cp artifacts/${FILE}.sha256 ${LATEST_DEV_S3_URL}/${LATEST_DEV_FILE}.sha256 + + - name: Upload Release Files + if: steps.check_secrets.outputs.HAS_AWS_SECRET && env.RELEASE == 'true' + run: | + aws s3 cp artifacts/${FILE} ${INSTALL_S3_URL} + aws s3 cp artifacts/${FILE}.sha256 ${INSTALL_S3_URL} + + - name: Upload Release Torrent + if: steps.check_secrets.outputs.HAS_AWS_SECRET && env.RELEASE == 'true' && matrix.mode.matrix == 'gui' + run: | + aws s3 cp artifacts/${FILE}.torrent ${TORRENT_S3_URL} + + - name: Upload release artifacts + if: env.RELEASE == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload \ + --repo ${{ github.repository }} \ + $RELEASE_TAG \ + artifacts/${FILE} + + - name: Mark installer complete + uses: Chia-Network/actions/github/glue@main + if: steps.check_secrets.outputs.HAS_GLUE_SECRET && (env.RELEASE == 'true') + env: + REPO_SUFFIX: ${{ env.PRE_RELEASE == 'true' && '-prerelease' || '' }} + with: + json_data: '{"chia_ref": "${{ env.RELEASE_TAG }}"}' + glue_url: "${{ secrets.GLUE_API_URL }}" + glue_project: "${{ env.RFC_REPO }}${{ env.REPO_SUFFIX }}/${{ env.RELEASE_TAG }}" + glue_path: "success/build-${{ matrix.os.glue-name }}-${{ matrix.arch.glue-name }}-${{ matrix.mode.glue-name }}" diff --git a/.github/workflows/reflow-version.yml b/.github/workflows/reflow-version.yml index 6339fc28eba8..695e77a27ec4 100644 --- a/.github/workflows/reflow-version.yml +++ b/.github/workflows/reflow-version.yml @@ -14,6 +14,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 outputs: + chia-dev-version: ${{ steps.version-number.outputs.chia-dev-version }} chia-installer-version: ${{ steps.version-number.outputs.chia-installer-version }} tag-type: ${{ steps.tag-type.outputs.tag-type }} @@ -46,5 +47,10 @@ jobs: . ../venv/bin/activate python -m pip install --upgrade pip pip install poetry "poetry-dynamic-versioning[plugin]" - echo "chia-installer-version=$(poetry version -s)" >> "$GITHUB_OUTPUT" + + VERSION=$(poetry version -s) + echo "chia-installer-version=${VERSION}" >> "$GITHUB_OUTPUT" + GIT_SHORT_HASH=$(echo "${GITHUB_SHA}" | cut -c1-8) + echo "chia-dev-version=${VERSION}-${GIT_SHORT_HASH}" >> "$GITHUB_OUTPUT" + deactivate diff --git a/.github/workflows/test-single.yml b/.github/workflows/test-single.yml index 6b88f586000a..31995c5ad28c 100644 --- a/.github/workflows/test-single.yml +++ b/.github/workflows/test-single.yml @@ -20,7 +20,7 @@ on: file_name: required: true type: string - concurrency_name: + concurrency-name: required: true type: string configuration: @@ -42,7 +42,7 @@ on: concurrency: # SHA is added to the end if on `main` to let all main workflows run - group: ${{ github.ref }}-${{ github.workflow }}-${{ inputs.concurrency_name }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || '' }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }} + group: ${{ github.ref }}-${{ github.workflow }}-${{ inputs.concurrency-name }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || '' }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }} cancel-in-progress: true defaults: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f49bb5340a4..2955f3ec3389 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -117,7 +117,7 @@ jobs: matrix: macos-intel name: macOS Intel file_name: macos - concurrency_name: macos-intel + concurrency-name: macos-intel configuration: ${{ needs.configure.outputs.configuration }} matrix_mode: ${{ needs.configure.outputs.matrix_mode }} runs-on: macos-12 @@ -131,7 +131,7 @@ jobs: matrix: macos name: macOS ARM file_name: macos-arm - concurrency_name: macos-arm + concurrency-name: macos-arm configuration: ${{ needs.configure.outputs.configuration }} matrix_mode: ${{ needs.configure.outputs.matrix_mode }} runs-on: macos-14 @@ -146,7 +146,7 @@ jobs: matrix: ubuntu name: Ubuntu file_name: ubuntu - concurrency_name: ubuntu + concurrency-name: ubuntu configuration: ${{ needs.configure.outputs.configuration }} matrix_mode: ${{ needs.configure.outputs.matrix_mode }} runs-on: ubuntu-latest @@ -161,7 +161,7 @@ jobs: matrix: windows name: Windows file_name: windows - concurrency_name: windows + concurrency-name: windows configuration: ${{ needs.configure.outputs.configuration }} matrix_mode: ${{ needs.configure.outputs.matrix_mode }} runs-on: windows-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58b2d57bf898..0a89e633313a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,7 +32,7 @@ repos: rev: v3.1.0 hooks: - id: prettier - types_or: [ini, json, toml, yaml] + types_or: [ini, json, toml, yaml, markdown] - repo: https://github.com/scop/pre-commit-shfmt rev: v3.8.0-1 hooks: diff --git a/.prettierrc.yaml b/.prettierrc.yaml index c4ac3fcf5f8d..27a0fcfbbfa3 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -5,3 +5,10 @@ overrides: singleQuote: false experimentalTernaries: true useTabs: false + - files: ["*.md"] + options: + singleQuote: false + - files: ["*.js", "*.jsx", "*.ts", "*.tsx", "*.cjs", "*.mjs"] + options: + printWidth: 120 + singleQuote: true diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 000000000000..27a0fcfbbfa3 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,14 @@ +overrides: + - files: ["*.yaml", "*.yml", "*.toml", "*.json", "*.ini"] + options: + tabWidth: 2 + singleQuote: false + experimentalTernaries: true + useTabs: false + - files: ["*.md"] + options: + singleQuote: false + - files: ["*.js", "*.jsx", "*.ts", "*.tsx", "*.cjs", "*.mjs"] + options: + printWidth: 120 + singleQuote: true diff --git a/.repo-content-updater.yml b/.repo-content-updater.yml index dbb0e6b105e5..b6b565bc5b60 100644 --- a/.repo-content-updater.yml +++ b/.repo-content-updater.yml @@ -1,2 +1,3 @@ var_overrides: DEPENDABOT_ACTIONS_REVIEWERS: '["cmmarslender", "altendky"]' + DEPENDABOT_PIP_REBASE_STRATEGY: disabled diff --git a/BUILD_TIMELORD.md b/BUILD_TIMELORD.md index 51ba6064a90d..c2fb431177d2 100644 --- a/BUILD_TIMELORD.md +++ b/BUILD_TIMELORD.md @@ -24,8 +24,8 @@ Timelord uses to run the VDF and prove the Proof of Time is `vdf_client` and `vdf_bench` is a utility to get a sense of a given CPU's iterations per second. - To build vdf_client set the environment variable BUILD_VDF_CLIENT to "Y". -`export BUILD_VDF_CLIENT=Y`. + `export BUILD_VDF_CLIENT=Y`. - Similarly, to build vdf_bench set the environment variable BUILD_VDF_BENCH -to "Y". `export BUILD_VDF_BENCH=Y`. + to "Y". `export BUILD_VDF_BENCH=Y`. Building and running Timelords in Windows x86-64 is not yet supported. diff --git a/CHANGELOG.md b/CHANGELOG.md index 77a7e518b9d8..bc8daa396953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,344 +7,372 @@ and this project does not yet adhere to [Semantic Versioning](https://semver.org for setuptools_scm/PEP 440 reasons. ## 2.4.2 Chia blockchain 2024-07-10 + ## What's Changed + ### Fixed -* Fix 12-word mnemonic support in keychain (Fixes #18243) -* Fix backwards compatibility for daemon RPC `add_private_key` + +- Fix 12-word mnemonic support in keychain (Fixes #18243) +- Fix backwards compatibility for daemon RPC `add_private_key` ### Deprecated + macOS 12 (Monterey) is deprecated. This release (2.4.2) will be the last release to support macOS 12 ## 2.4.1 Chia blockchain 2024-06-25 ## What's Changed + ### Fixed -* Fixed light wallet (wallet only) syncing issues introduced in 2.4.0 + +- Fixed light wallet (wallet only) syncing issues introduced in 2.4.0 ### Known Issues -* A breaking backwards compatibility issue was introduced in 2.4.0 in the daemon RPC call `add_private_key`. We expect to resolve this in a future release. -* You cannot import or use a 12-word mnemonic key with 2.4.0 or 2.4.1. To import and use a 12-word mnemonic key we recommend you use 2.3.1. This will be resolved in a future release + +- A breaking backwards compatibility issue was introduced in 2.4.0 in the daemon RPC call `add_private_key`. We expect to resolve this in a future release. +- You cannot import or use a 12-word mnemonic key with 2.4.0 or 2.4.1. To import and use a 12-word mnemonic key we recommend you use 2.3.1. This will be resolved in a future release ### Deprecated + macOS 11 (Big Sur) is deprecated. This release (2.4.1) will be the last release to support macOS 11 ## 2.4.0 Chia blockchain 2024-06-20 ## What's Changed + ### Added -* Soft fork 5: disallow infinity G1 points as public keys in AGG_SIG_* conditions -* DL: Added support for updating multiple datastores in a single batch update -* Add unfinished block to state change event (thanks @felixbrucker) -* CHIP-0026 Mempool Updates -* Preliminary support for observer mode. Ability to add public keys via CLI - -### Changed -* Remove `tx_records` from `dl_update_multiple` RPC (breaking change) -* DL: optimizations for autoinsert and upsert -* Increase farmer fill rate to 70% -* Use Rust types for `RecentChainData`, `ProofBlockHeader` and `WeightProof` -* Use Rust version of `MerkleSet` -* Remove unused files -* Make a couple of DAOWallet methods return lists of transaction records -* Simplify `MerkleSet` by making it immutable -* Add ability to profile the farmer process -* Remove unused current_inner from PoolState -* Optimize `launcher_id_to_p2_puzzle_hash()` -* Add genesis challenge to `get_network_info` RPC -* Puzzle hash optimizations -* Optimize key derivation in the wallet -* Add optional trusted CIDR list -* Make `BLSCache` a proper class -* Split capabilities for each service -* Use kv compressed in DL batch update -* Updated gui to `electron 30.0.9` -* Bump `chia_rs` to `0.9.0` and update G1Element handling -* Bump `boto3` to `1.34.114` -* Bump `chiabip158` to `1.5.1` -* Bump `clvm` to `0.9.10` -* Bump `aiohttp` to `3.9.4` -* Bump `filelock` to `3.14.0` -* Bump `importlib-resources` to `6.4.0` -* Bump `keyring` to `25.1.0` -* Bump `dnspython` to `2.6.1` -* Bump `typing-extensions` to `4.11.0`, -* Bump `packaging` to `24.0` -* Bump `hsms` to `0.3.1`, - -### Fixed -* Add bytes type to `DerivationRecord.pubkey` -* Do not return unexpected coins from `get_coin_state` -* Fix memo plotid -* Filter out duplicate coins returned by `RequestPuzzleState` -* fix confusion between prompt and don't prompt in the plotnft CLI -* drop deprecated `authentication_public_key` from pool config -* Fixed some typos (thanks @wersfeds) -* Make sure to use no more than 61 cpus on windows (fixes #17967) -* Handle reorgs in data layer wallet -* Modify `VerifiedCredential.launch` to handle multiple source coins -* Add tx_config and extra_conditions to DID creation endpoint -* DL: Return exception and error from `get_kv_diff` when either of the hashes has no data -* Link trade cancellations with announcements -* Add coin id index to coin state batching -* Remove homebrew rpaths from `_ssl.cpython.so` on macOS during build (fixes #18099) -* Aligned `lerna` and `nx` versions -* Set permissions in DEB `postinst.sh` for chrome-sandbox (fixes #17956) + +- Soft fork 5: disallow infinity G1 points as public keys in `AGG_SIG_*` conditions +- DL: Added support for updating multiple datastores in a single batch update +- Add unfinished block to state change event (thanks @felixbrucker) +- CHIP-0026 Mempool Updates +- Preliminary support for observer mode. Ability to add public keys via CLI + +### Changed + +- Remove `tx_records` from `dl_update_multiple` RPC (breaking change) +- DL: optimizations for autoinsert and upsert +- Increase farmer fill rate to 70% +- Use Rust types for `RecentChainData`, `ProofBlockHeader` and `WeightProof` +- Use Rust version of `MerkleSet` +- Remove unused files +- Make a couple of DAOWallet methods return lists of transaction records +- Simplify `MerkleSet` by making it immutable +- Add ability to profile the farmer process +- Remove unused current_inner from PoolState +- Optimize `launcher_id_to_p2_puzzle_hash()` +- Add genesis challenge to `get_network_info` RPC +- Puzzle hash optimizations +- Optimize key derivation in the wallet +- Add optional trusted CIDR list +- Make `BLSCache` a proper class +- Split capabilities for each service +- Use kv compressed in DL batch update +- Updated gui to `electron 30.0.9` +- Bump `chia_rs` to `0.9.0` and update G1Element handling +- Bump `boto3` to `1.34.114` +- Bump `chiabip158` to `1.5.1` +- Bump `clvm` to `0.9.10` +- Bump `aiohttp` to `3.9.4` +- Bump `filelock` to `3.14.0` +- Bump `importlib-resources` to `6.4.0` +- Bump `keyring` to `25.1.0` +- Bump `dnspython` to `2.6.1` +- Bump `typing-extensions` to `4.11.0`, +- Bump `packaging` to `24.0` +- Bump `hsms` to `0.3.1`, + +### Fixed + +- Add bytes type to `DerivationRecord.pubkey` +- Do not return unexpected coins from `get_coin_state` +- Fix memo plotid +- Filter out duplicate coins returned by `RequestPuzzleState` +- fix confusion between prompt and don't prompt in the plotnft CLI +- drop deprecated `authentication_public_key` from pool config +- Fixed some typos (thanks @wersfeds) +- Make sure to use no more than 61 cpus on windows (fixes #17967) +- Handle reorgs in data layer wallet +- Modify `VerifiedCredential.launch` to handle multiple source coins +- Add tx_config and extra_conditions to DID creation endpoint +- DL: Return exception and error from `get_kv_diff` when either of the hashes has no data +- Link trade cancellations with announcements +- Add coin id index to coin state batching +- Remove homebrew rpaths from `_ssl.cpython.so` on macOS during build (fixes #18099) +- Aligned `lerna` and `nx` versions +- Set permissions in DEB `postinst.sh` for chrome-sandbox (fixes #17956) ### Deprecated + macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release to support macOS 11 ## 2.3.1 Chia blockchain 2024-04-28 ### Added -* Added `warp.green` CATs (`wUSDC.b`, `wmilliETH.b`, `wUSDC`, `wmilliETH`, `wUSDT`) to the known CAT list + +- Added `warp.green` CATs (`wUSDC.b`, `wmilliETH.b`, `wUSDC`, `wmilliETH`, `wUSDT`) to the known CAT list ## 2.3.0 Chia blockchain 2024-05-01 ### Fixed -* Fixed `Install.ps1` for PowerShell 7.4 -* Fixed readability of `Could not find parent coin` error log by printing hex and not bytes -* Fixed some shutdown log spam by ensuring signal objects for signal handlers (fixes #17578) -* Fixed negative plot sync durations not crashing the harvester (fixes #15027) (thanks @felixbrucker) -* Fixed log spam by only logging warnings about protocol mismatches for farmer and harvester -* Fixed log spam by logging rollbacks only if heights are actually deleted -* Fixed DID update metadata issue (fixes #17412) -* Fixed error codes and add more test coverage for message conditions -* Fixed non-development source install -* Fixed reorg from 0 -* Fixed (again) Datalayer download banning -* Improved timelord skip peak logic. -* Used click.Path for make_offer command filename (fixes #10920) -* Handle when xch_target_address in config doesn't decode correctly (fixes #16995) -* Delete unconfirmed Clawback TX -* tighten up the check for duplicate UnfinishedBlocks before requesting that block -* Optimized Datalayer `get_key_by_node` -* Added test for observance of melted CAT balance (fixes #17727) -* increase backwards compatibility by using default values for peer file path -* Added `--skip-keyring` option to `chia start` and use in GUI (fixes #17848) - -### Added -* Added Python 3.12 support -* Added new subscription and wallet sync protocol support (will be used by the wallet in future releases) -* Added Chip-25 Message Conditions support (https://github.com/Chia-Network/chips/pull/98) -* Added support for HTTP redirect for the pool url (thanks @felixbrucker) -* Added `use_delta_sync` option for faster wallet sync (thanks @felixbrucker) -* Added Datalayer RPC pagination. -* Added Datalayer multiple batch updates with `submit_on_chain` option. -* Added `get_network_info` RPC to daemon -* Added `new_unfinished_block2` support in Chia seeder -* Added Hint support for SpendSim - -### Changed -* Transition `FullBlock`, `BlockRecord`, `CoinSpend`, and `HeaderBlock` to rust -* Move tests - all tests and infrastructure are now included in the chia-blockchain package under `chia/_tests` -* Remove `Announcement` class in favor of `Condition` subclasses -* Remove `ignore_max_send_amount` -* Use `psutil.cpu_affinity()` instead of `os.cpu_count()` -* Stop automatic transaction pushing by wallets -* Unify transaction pushing -* For testing purposes added support for non-ssl rpc clients -* Return TXs from CATWallet and Offer creation -* Remove all install.sh code that installs python and leave it to the user to install separately -* Remove old `unhashable` special case in `Streamable` -* Optimize `validate_removals()` -* Remove support for migrating peers from legacy file format -* Set unique peer filenames when swapping to/from testnets -* Cleaner cli output for rpc client fetch errors -* Optimized Datalayer subscription handling by using a `QueuedAsyncPool` for `DataLayer.periodically_manage_data()` -* Update README.md links for wiki & faq sunset -* Update README formatting and links (thanks @bknox83) -* Turned concatenation of strings to f-strings (thanks @eukub) -* Remove dead code in `multiprocess_validation` -* Improve logging of the height-to-hash and sub-epoch-summaries cache -* Pass full version in `Handshake` (thanks @felixbrucker) -* Separate protocol versions for full_node, farmer, harvester, wallet -* Optimized v1 to v2 DB upgrade -* Datalayer: Avoid manage data loop delay for self subscriptions -* Datalayer: Don't download DAT files that are already on disk -* Datalayer: `get_proof` optimizations - use get_ancestors_optimized -* Datalayer: Optimize insert/upsert/delete by using `get_node_by_key` -* Datalayer: stop using fee config setting and remove from initial config -* Datalayer: Optimize clean_node_table's query and speedup by leveraging relaxed foreign_keys -* Enabled compression for cli rpm -* Bump `chia_rs` to `0.6.1` -* Bump `clvm_tools` to `0.4.9` -* Bump `chiavdf` to `1.1.4` -* Bump `chiapos` to `2.0.4` -* Bump `clvm` to `0.9.9` -* Bump `aiohttp` to `3.9.2` -* Bump `anyio` to `4.3.0` -* Bump `boto3` to `1.34.46` -* Bump `aiosqlite` to `0.20.0` -* Bump `colorlog` to `6.8.2` -* Bump `cryptography` to `42.0.5` -* Bump `keyring` to `24.3.1` -* Bump `dnspython` to `2.5.0` -* Bump `watchdog` to `4.0.0` -* Bump `dnslib` to `0.9.24` -* Bump `typing-extensions` to `4.10.0` + +- Fixed `Install.ps1` for PowerShell 7.4 +- Fixed readability of `Could not find parent coin` error log by printing hex and not bytes +- Fixed some shutdown log spam by ensuring signal objects for signal handlers (fixes #17578) +- Fixed negative plot sync durations not crashing the harvester (fixes #15027) (thanks @felixbrucker) +- Fixed log spam by only logging warnings about protocol mismatches for farmer and harvester +- Fixed log spam by logging rollbacks only if heights are actually deleted +- Fixed DID update metadata issue (fixes #17412) +- Fixed error codes and add more test coverage for message conditions +- Fixed non-development source install +- Fixed reorg from 0 +- Fixed (again) Datalayer download banning +- Improved timelord skip peak logic. +- Used click.Path for make_offer command filename (fixes #10920) +- Handle when xch_target_address in config doesn't decode correctly (fixes #16995) +- Delete unconfirmed Clawback TX +- tighten up the check for duplicate UnfinishedBlocks before requesting that block +- Optimized Datalayer `get_key_by_node` +- Added test for observance of melted CAT balance (fixes #17727) +- increase backwards compatibility by using default values for peer file path +- Added `--skip-keyring` option to `chia start` and use in GUI (fixes #17848) + +### Added + +- Added Python 3.12 support +- Added new subscription and wallet sync protocol support (will be used by the wallet in future releases) +- Added Chip-25 Message Conditions support (https://github.com/Chia-Network/chips/pull/98) +- Added support for HTTP redirect for the pool url (thanks @felixbrucker) +- Added `use_delta_sync` option for faster wallet sync (thanks @felixbrucker) +- Added Datalayer RPC pagination. +- Added Datalayer multiple batch updates with `submit_on_chain` option. +- Added `get_network_info` RPC to daemon +- Added `new_unfinished_block2` support in Chia seeder +- Added Hint support for SpendSim + +### Changed + +- Transition `FullBlock`, `BlockRecord`, `CoinSpend`, and `HeaderBlock` to rust +- Move tests - all tests and infrastructure are now included in the chia-blockchain package under `chia/_tests` +- Remove `Announcement` class in favor of `Condition` subclasses +- Remove `ignore_max_send_amount` +- Use `psutil.cpu_affinity()` instead of `os.cpu_count()` +- Stop automatic transaction pushing by wallets +- Unify transaction pushing +- For testing purposes added support for non-ssl rpc clients +- Return TXs from CATWallet and Offer creation +- Remove all install.sh code that installs python and leave it to the user to install separately +- Remove old `unhashable` special case in `Streamable` +- Optimize `validate_removals()` +- Remove support for migrating peers from legacy file format +- Set unique peer filenames when swapping to/from testnets +- Cleaner cli output for rpc client fetch errors +- Optimized Datalayer subscription handling by using a `QueuedAsyncPool` for `DataLayer.periodically_manage_data()` +- Update README.md links for wiki & faq sunset +- Update README formatting and links (thanks @bknox83) +- Turned concatenation of strings to f-strings (thanks @eukub) +- Remove dead code in `multiprocess_validation` +- Improve logging of the height-to-hash and sub-epoch-summaries cache +- Pass full version in `Handshake` (thanks @felixbrucker) +- Separate protocol versions for full_node, farmer, harvester, wallet +- Optimized v1 to v2 DB upgrade +- Datalayer: Avoid manage data loop delay for self subscriptions +- Datalayer: Don't download DAT files that are already on disk +- Datalayer: `get_proof` optimizations - use get_ancestors_optimized +- Datalayer: Optimize insert/upsert/delete by using `get_node_by_key` +- Datalayer: stop using fee config setting and remove from initial config +- Datalayer: Optimize clean_node_table's query and speedup by leveraging relaxed foreign_keys +- Enabled compression for cli rpm +- Bump `chia_rs` to `0.6.1` +- Bump `clvm_tools` to `0.4.9` +- Bump `chiavdf` to `1.1.4` +- Bump `chiapos` to `2.0.4` +- Bump `clvm` to `0.9.9` +- Bump `aiohttp` to `3.9.2` +- Bump `anyio` to `4.3.0` +- Bump `boto3` to `1.34.46` +- Bump `aiosqlite` to `0.20.0` +- Bump `colorlog` to `6.8.2` +- Bump `cryptography` to `42.0.5` +- Bump `keyring` to `24.3.1` +- Bump `dnspython` to `2.5.0` +- Bump `watchdog` to `4.0.0` +- Bump `dnslib` to `0.9.24` +- Bump `typing-extensions` to `4.10.0` ### Known Issues -* Please be aware that logging at `DEBUG` log level may log your local keyring passphrase to the log file. Note this is **not** your key mnemonic. + +- Please be aware that logging at `DEBUG` log level may log your local keyring passphrase to the log file. Note this is **not** your key mnemonic. ## 2.2.1 Chia blockchain 2024-03-4 ### Fixed -* Fixed issue with finding bladebit and madmax plotters in CLI and GUI (thanks @nanofarmer) -* Fixed issue with banning peers due to incorrect `INVALID_TRANSACTIONS_FILTER_HASH` and `INVALID_BLOCK_COST` log errors (#17620) + +- Fixed issue with finding bladebit and madmax plotters in CLI and GUI (thanks @nanofarmer) +- Fixed issue with banning peers due to incorrect `INVALID_TRANSACTIONS_FILTER_HASH` and `INVALID_BLOCK_COST` log errors (#17620) ## 2.2.0 Chia blockchain 2024-02-28 -* Thanks to @bhorvitz for major help debugging a performance issue during coin DB lookup - -### Fixed -* Fix TX amount calculation in trade manager (fixes #16842) -* Subscribe to DIDs that come into wallet (fixes #17242) -* Remove duplicate short option from make_offer command (fixes #17371) -* add `SerializedProgram.to()` to simplify some code -* include information for `setuptools_scm` in git archives -* fix type mismatch with `Optional[bytes]` and `bytes` in `wallet/conditions.py` -* fixed typo in `get_coin_record_by_name` docstring (thanks @Abakrombie) -* Fixed readme links (thanks @Abakrombie) -* DL: Don't allow mirrors with empty urls (fixes #16920) -* DL: Improve input for CLI `add_missing_files` (fixes #17039) -* DL: Use unsubscribe queue to relax subscriptions lock -* DL: Use Datalayer banning logic for HTTP download failures -* extend the mempool tests for timelocks, and improve error codes -* extend measured sizes for plot check with value for larger K sizes (thanks @neurosis69) -* Add a few missing type annotations -* Log string header_hash on long validation warnings -* Fix sorted for dictionary keys of both bytes/xch -* Fixed an issue where `chia wallet did transfer` command mistreats the type of `fee` -* Fix signage point message for remote harvesters with large numbers of pools -* undo BlockRecord cache insert, when DB fails -* Warn if running `install-plotter.sh` as root - -### Added -* Support for third-party, farmer-rewarded, Harvesters (Chip-22) -* Singleton fast forward -* Verify p2 delegated conditions signatures and add a new SigningMode for Tangem cards (thanks @MarvinQuevedo) -* DL: add upsert action -* DL: Add support for generating and verifying DataLayer Proofs of Inclusions `get_proof` and `verify_proof` -* Improve transparency of what full nodes are doing and where they spend their time with additional Mempool logging -* add feature to profile just the block validation -* Add `--override` flag to `make_offer` -* Add full node RPC `get_aggsig_additional_data` to get the aggsig additional data -* Add fork height & rolled_back_records to block event for metrics -* extend Block validation timing logs to measure just the CLVM and conditions -* Add support for defining a list of full node peers to connect to (thanks @felixbrucker) -* Add preliminary support for getting coin states in batches -* improve mempool reorg logic when the peak is a non-transaction block -* Add `additions` and `removals` to `get_offer_summary` API response (thanks @mikehw) -* improve handling of `UnfinishedBlock`s -* Add testnet11 constants to config if missing when configuring to run on testnet -* We have added several new translations in this release. Thanks to WNFT, advlive, hezoushe - -### Changed -* reorg optimizations -* bump `chia_rs` to `0.4.1` -* initiate phasing out of the `coin_solutions` name in JSON structs -* slight simplification to `get_min_fee_rate()` -* Remove `coin_solutions` from `SpendBundle` entirely -* use rust types for `VDFInfo`, `VDFProof` and `ClassgroupElement` -* evict entries continuously from `seen_unfinished_blocks` -* move `tools/legacy_keyring.py` to `chia/legacy/keyring.py` -* Rust `proof-of-space`, `reward chain` and `foliage` types -* DL: Compress `get_keys_values` output by hash. -* replace hardcoded value for `db_readers` (thanks @neurosis69) -* use rust types for `slots`, `SubEpochSummary` and `SubEpochData` -* Update default testnet to testnet11 -* remove old work-around for a bug in version `1.1.4` and earlier -* use rust implementation of `SerializedProgram` -* Rework block fill logic to fill blocks with more SpendBundles (transactions) -* fix typo in logging -* increase farmer block fill rate to 60% -* Force the use of `coin_puzzle_hash` index to `get_unspent_lineage_info_for_puzzle_hash` +- Thanks to @bhorvitz for major help debugging a performance issue during coin DB lookup + +### Fixed + +- Fix TX amount calculation in trade manager (fixes #16842) +- Subscribe to DIDs that come into wallet (fixes #17242) +- Remove duplicate short option from make_offer command (fixes #17371) +- add `SerializedProgram.to()` to simplify some code +- include information for `setuptools_scm` in git archives +- fix type mismatch with `Optional[bytes]` and `bytes` in `wallet/conditions.py` +- fixed typo in `get_coin_record_by_name` docstring (thanks @Abakrombie) +- Fixed readme links (thanks @Abakrombie) +- DL: Don't allow mirrors with empty urls (fixes #16920) +- DL: Improve input for CLI `add_missing_files` (fixes #17039) +- DL: Use unsubscribe queue to relax subscriptions lock +- DL: Use Datalayer banning logic for HTTP download failures +- extend the mempool tests for timelocks, and improve error codes +- extend measured sizes for plot check with value for larger K sizes (thanks @neurosis69) +- Add a few missing type annotations +- Log string header_hash on long validation warnings +- Fix sorted for dictionary keys of both bytes/xch +- Fixed an issue where `chia wallet did transfer` command mistreats the type of `fee` +- Fix signage point message for remote harvesters with large numbers of pools +- undo BlockRecord cache insert, when DB fails +- Warn if running `install-plotter.sh` as root + +### Added + +- Support for third-party, farmer-rewarded, Harvesters (Chip-22) +- Singleton fast forward +- Verify p2 delegated conditions signatures and add a new SigningMode for Tangem cards (thanks @MarvinQuevedo) +- DL: add upsert action +- DL: Add support for generating and verifying DataLayer Proofs of Inclusions `get_proof` and `verify_proof` +- Improve transparency of what full nodes are doing and where they spend their time with additional Mempool logging +- add feature to profile just the block validation +- Add `--override` flag to `make_offer` +- Add full node RPC `get_aggsig_additional_data` to get the aggsig additional data +- Add fork height & rolled_back_records to block event for metrics +- extend Block validation timing logs to measure just the CLVM and conditions +- Add support for defining a list of full node peers to connect to (thanks @felixbrucker) +- Add preliminary support for getting coin states in batches +- improve mempool reorg logic when the peak is a non-transaction block +- Add `additions` and `removals` to `get_offer_summary` API response (thanks @mikehw) +- improve handling of `UnfinishedBlock`s +- Add testnet11 constants to config if missing when configuring to run on testnet +- We have added several new translations in this release. Thanks to WNFT, advlive, hezoushe + +### Changed + +- reorg optimizations +- bump `chia_rs` to `0.4.1` +- initiate phasing out of the `coin_solutions` name in JSON structs +- slight simplification to `get_min_fee_rate()` +- Remove `coin_solutions` from `SpendBundle` entirely +- use rust types for `VDFInfo`, `VDFProof` and `ClassgroupElement` +- evict entries continuously from `seen_unfinished_blocks` +- move `tools/legacy_keyring.py` to `chia/legacy/keyring.py` +- Rust `proof-of-space`, `reward chain` and `foliage` types +- DL: Compress `get_keys_values` output by hash. +- replace hardcoded value for `db_readers` (thanks @neurosis69) +- use rust types for `slots`, `SubEpochSummary` and `SubEpochData` +- Update default testnet to testnet11 +- remove old work-around for a bug in version `1.1.4` and earlier +- use rust implementation of `SerializedProgram` +- Rework block fill logic to fill blocks with more SpendBundles (transactions) +- fix typo in logging +- increase farmer block fill rate to 60% +- Force the use of `coin_puzzle_hash` index to `get_unspent_lineage_info_for_puzzle_hash` ## 2.1.4 Chia blockchain 2024-01-10 ### Fixed -* Update chia_rs to 0.2.15 for AMD K10 architecture (fixes #16386) + +- Update chia_rs to 0.2.15 for AMD K10 architecture (fixes #16386) ### Changed -* improved CPU usage due to tight loop in `send_transaction()` -* improve performance of `total_mempool_fees()` and `total_mempool_cost()` -* reduced the default maximum peer count to 40 from 80 (only applies to new configs) -* changed to `normal` SQlite db sync option (previously was `full`) -* reduced the mempool size to 10 blocks from 50 blocks (improves performance) -* improve performance of the mempool by batch fetching items from the db +- improved CPU usage due to tight loop in `send_transaction()` +- improve performance of `total_mempool_fees()` and `total_mempool_cost()` +- reduced the default maximum peer count to 40 from 80 (only applies to new configs) +- changed to `normal` SQlite db sync option (previously was `full`) +- reduced the mempool size to 10 blocks from 50 blocks (improves performance) +- improve performance of the mempool by batch fetching items from the db ## 2.1.3 Chia blockchain 2023-12-18 ### Fixed -* Fixed a regression in 2.1.2 that could cause a farmer to fail to be able to create a block in some cases + +- Fixed a regression in 2.1.2 that could cause a farmer to fail to be able to create a block in some cases ## 2.1.2 Chia blockchain 2023-12-13 ### Fixed -* Fix deep reorgs and add tests -* Reduce possible Signage Point bursts by forwarding 4 most recent cached SPs only -* Fix condition serialization in RPC client -* Fix DID resync to not create DID wallets that don't belong to the current key -* Fix `get_block_spends` to work correctly post hard-fork -* Shutdown on startup failure and log to the log if possible -* fix issue with syncing testnet10 from 0 -* Chunk SQLite query for old TR/TX conversion (fixes #16589) -* Allow set_status to overwrite trade in store (fixes #16461) -* Add cache to wallet node preventing resend of processing TX -* Correct `FullNodeDiscovery.pending_tasks` typo without `s` -* Fix `chia wallet coins list` by adding NFT, DID, DAO_CAT to wallets denominated in mojos -* generalize JSON serializer -* Fix possible peak height race -* Fix invalid sync request -* request blocks in batches of 32 instead of 33 (saves 3% bandwidth) -* Fix `get_block_generator` fork detection -* Fix set_status accidental arg (fixes #16817) -* Fix issues with upgrading Chia via RPM by claiming ownership of `/opt/chia` in the RPM -* clean out `/opt/chia` before install and after removal of rpm - -### Added -* Allow DApps to use WalletConnect to sign customized puzzles by extending sign APIs -* Add support for lists of peers in the config (thanks @felixbrucker) -* Update to support looking up mnemonic by just the first 4 letters of each word -* Allow the daemon to use TLS v1.2 via config flag (thanks @dkackman) -* Add systemd init files to CLI-only Linux packages -* DL: remove data from the DB on unsubscribe - -### Changed -* ban peers for 10 minutes when violating consensus rules -* Remove `tx_endpoint` from `select_coins` -* DID wallet coin_added by @ytx1991 in https://github.com/Chia-Network/chia-blockchain/pull/16256 -* Use network overrides for default port for WalletPeers -* Improve clarity of legacy support policy language -* Add config option to set rpc timeout and use it for simulator tests -* rename `ClassgroupElement.from_bytes()` -* Optimize CRCAT trades -* harmonize `SerializedProgram` with `Program` -* Swap some info logs in seeder to warning -* Distinguish `insufficient_partials` from `invalid_partials` -* transition away from `__bytes__` conversion for fixed-size integers -* Use BLS from `chia_rs` and stop using `blspy` wheel in chia-blockchain -* simplify the interface to `mempool_manager.new_peak()` -* reduce redundant calls to compute the header hash -* Change `-h` to `-k` for `--key` flag for datalayer `get_value` cli command -* Update `chia_rs` to `0.2.13` -* Update `clvm_tools` to `0.4.7` -* Update `aiohttp` to `3.9.1` (fixes a WebSocket bug introduced in 3.9.0) -* Change `chia show keys --show-mnemonic-seed` to also show farmer private key (thanks xchdata1) -* Adjust ban times when unable to download properly DL DAT files -* return `List[TransactionRecord]` from nft bulk mint functions -* DL: delete full files when subscribed to a datastore per config +- Fix deep reorgs and add tests +- Reduce possible Signage Point bursts by forwarding 4 most recent cached SPs only +- Fix condition serialization in RPC client +- Fix DID resync to not create DID wallets that don't belong to the current key +- Fix `get_block_spends` to work correctly post hard-fork +- Shutdown on startup failure and log to the log if possible +- fix issue with syncing testnet10 from 0 +- Chunk SQLite query for old TR/TX conversion (fixes #16589) +- Allow set_status to overwrite trade in store (fixes #16461) +- Add cache to wallet node preventing resend of processing TX +- Correct `FullNodeDiscovery.pending_tasks` typo without `s` +- Fix `chia wallet coins list` by adding NFT, DID, DAO_CAT to wallets denominated in mojos +- generalize JSON serializer +- Fix possible peak height race +- Fix invalid sync request +- request blocks in batches of 32 instead of 33 (saves 3% bandwidth) +- Fix `get_block_generator` fork detection +- Fix set_status accidental arg (fixes #16817) +- Fix issues with upgrading Chia via RPM by claiming ownership of `/opt/chia` in the RPM +- clean out `/opt/chia` before install and after removal of rpm + +### Added + +- Allow DApps to use WalletConnect to sign customized puzzles by extending sign APIs +- Add support for lists of peers in the config (thanks @felixbrucker) +- Update to support looking up mnemonic by just the first 4 letters of each word +- Allow the daemon to use TLS v1.2 via config flag (thanks @dkackman) +- Add systemd init files to CLI-only Linux packages +- DL: remove data from the DB on unsubscribe + +### Changed + +- ban peers for 10 minutes when violating consensus rules +- Remove `tx_endpoint` from `select_coins` +- DID wallet coin_added by @ytx1991 in https://github.com/Chia-Network/chia-blockchain/pull/16256 +- Use network overrides for default port for WalletPeers +- Improve clarity of legacy support policy language +- Add config option to set rpc timeout and use it for simulator tests +- rename `ClassgroupElement.from_bytes()` +- Optimize CRCAT trades +- harmonize `SerializedProgram` with `Program` +- Swap some info logs in seeder to warning +- Distinguish `insufficient_partials` from `invalid_partials` +- transition away from `__bytes__` conversion for fixed-size integers +- Use BLS from `chia_rs` and stop using `blspy` wheel in chia-blockchain +- simplify the interface to `mempool_manager.new_peak()` +- reduce redundant calls to compute the header hash +- Change `-h` to `-k` for `--key` flag for datalayer `get_value` cli command +- Update `chia_rs` to `0.2.13` +- Update `clvm_tools` to `0.4.7` +- Update `aiohttp` to `3.9.1` (fixes a WebSocket bug introduced in 3.9.0) +- Change `chia show keys --show-mnemonic-seed` to also show farmer private key (thanks xchdata1) +- Adjust ban times when unable to download properly DL DAT files +- return `List[TransactionRecord]` from nft bulk mint functions +- DL: delete full files when subscribed to a datastore per config ## 2.1.1 Chia blockchain 2023-10-11 ### Fixed + - Changed electron version for GUI to 25.9.0 to fix whitescreen issues seen on some linux systems (fixes #16538) ## 2.1.0 Chia blockchain 2023-10-05 ### Added + - Credential Restricted CATs - Add timelock information to Trades and Transactions - Add ergonomic timelock parsing to RPCs @@ -370,6 +398,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Bladebit Hybrid disk mode ### Changed + - Remove CAT1 UX guards - Dedup offer cancellation logic - upgrade electron-builder to 24.6.3 and Lerna to 7.1.3 @@ -394,6 +423,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Update install-gui.sh to check Node 18 and npm 9 ### Fixed + - Fixed python3-venv in install.sh (thanks @d1m1trus) - Change include_standard_libraries for CLVM compilation default to True - add dust warning message to chia coins commands & cleanup code @@ -419,7 +449,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - add fee for cat creation - max_coin_amount should default to None in wallet send command - Add extra_conditions to special offer making -- bump chia_rs to include bugfix for new AGG_SIG_* conditions in mempool mode +- bump `chia_rs` to include bugfix for new `AGG_SIG\*` conditions in mempool mode - Fix `chia farm summary` aborting early if no local full node present (fixes #16164) (thanks @xchdata1) - fix typo in PendingTxCache - rename `chia data add_missing_files` `-f`/`--foldername` to `-d`/`--directory` @@ -428,6 +458,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Fix glitch NFT wallet test ### Removed + - Support for MacOS 10.14 and 10.15 - Support for Chia database schema version 1 - Support for minting CATs via RPC @@ -463,7 +494,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Move CAT_MOD from cat_loader -> cat_utils - Use a more aggresive activation schedule on testnet10 - Full_node: More set usage in subscription code -- Rename exclude_coin_* -> excluded_coin_* for consistency +- Rename `exclude_coin_*` -> `excluded_coin_*` for consistency - Add `**kwargs` to all `generate_signed_transaction` definitions - Full_node: Add `max_height` to `CoinStore.get_coin_states_by_ids` - Util: Some tweaks to `StructStream` and sized ints @@ -556,6 +587,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release ## 1.8.2 Chia blockchain 2023-06-28 ### Added + - Add `chia wallet vcs` command for Verifiable Credential operations - Add `chia wallet clawback` command for clawback operations - Add `chia wallet did` commands `get_details`, `update_metadata`, `find_lost`, `message_spend`, `transfer` @@ -565,6 +597,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Add `confirmed` boolean to wallet RPC `get_transactions` ### Changed + - Identical spend aggregation - CAT wallet now will hint to CAT change - Move to Discord in docs and install scripts @@ -579,6 +612,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Updated `chiavdf` to `1.0.9` ### Fixed + - Fix `chia wallet make_offer` short-option collision on `-r` (Fixes #14874) (Thanks @yyolk) - Fix `GENERATOR_MOD2` to have the same cost as `GENERATOR_MOD` - Subscribe to the change children of CATs @@ -593,11 +627,13 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release ## 1.8.1 Chia blockchain 2023-05-17 ### Changed + - Updated testnet softfork height so softfork rules take effect on testnet immediately - Move to Discord in docs and install scripts (#15193) - Optimize compact proofs ### Fixed + - Issue where CLI only listed first 50 NFTs by hardcoding `num` param when listing NFTs from CLI - Issue where wallet might display `RuntimeError: dictionary changed size during iteration` by avoiding dict changes while iterating in `handle_nft` - Issue where node had trouble keeping peers with `assert self.peak is not None` error by not adding transactions to the mempool before it has a valid peak (fixes #15217) @@ -605,6 +641,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release ## 1.8.0 Chia blockchain 2023-05-03 ### Added + - Added `chia completion` command - Added wallet_removed to `state_changes` messages to support wallet removal in GUI - Add support to `cat_spend` RPC for running TAIL @@ -613,6 +650,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - DataLayer plugin support and infrastructure ### Changed + - Fix soft fork to 60 days - Don't subscribe to all coin ids in the DB - Handle trade coins in the `try` block of `new_coin_state` @@ -633,6 +671,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - List the columns for `INSERT` into `coin_record` ### Fixed + - Disconnect untrusted peers if we find a trusted synced one - Only compile CLVM if source newer than hex - Fixed windows issues with passphrase prompt on CLI by flushing prompt (Fixes #14889) @@ -659,6 +698,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release ## 1.7.1 Chia blockchain 2023-03-22 ### Added + - `get_transaction_memo` wallet RPC - `set_wallet_resync_on_startup` wallet RPC to reset wallet sync data on wallet restart - `nft_count_nfts` wallet RPC - counts NFTs per wallet or for all wallets @@ -671,6 +711,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - `curry` Chialisp library replaces `curry-and-treehash` ### Changed + - `chia show -f` changed to output proper JSON - `Rate limiting` log messages are themselves rate limited - Notified GUI when wallets are removed @@ -686,6 +727,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Changed mempool backend to use an in-memory SQLite DB ### Fixed + - Quieted wallet log output for `Record: ... not in mempool` (fixes #14452) - Quieted log output for `AttributeError: 'NoneType' object has no attribute '_get_extra_info` - Reduced log output for `Using previous generator for height` @@ -701,6 +743,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Improved the accuracy of the wallet sync status indication ### Deprecated + - `curry-and-treehash` Chialisp library replaced by new `curry` library ## 1.7.0 Chia blockchain 2023-02-15 @@ -714,7 +757,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Add gzip support to DataLayer download client (Thanks, @Chida82!) - Add proxy support to DataLayer download client (Thanks again, @Chida82!) - Add `get_timestamp_for_height` Wallet RPC for converting heights to timestamps -- Add `tools/legacy_keyring.py` to allow migration from the removed old key storage format. Available only from source installations. +- Add `tools/legacy_keyring.py` to allow migration from the removed old key storage format. Available only from source installations. - Add Arch Linux to install-gui.sh script (Thanks, @DaOneLuna!) - Add a `daemon_heartbeat` setting to config.yaml - add `trusted_max_subscribe_items` and `wallet:trusted_peers` to config.yaml @@ -1190,7 +1233,7 @@ macOS 11 (Big Sur) is deprecated. This release (2.4.0) will be the last release - Added RPCs for NFT (see ) - Enable stricter mempool rule when dealing with multiple extra arguments - Added a retry when loading pool info from a pool at 2 minute intervals -- Added CLI options `--sort-by-height` and –sort-by-relevance` to `chia wallet get_transactions` +- Added CLI options `--sort-by-height` and `-–sort-by-relevance` to `chia wallet get_transactions` - Harvester: Introduce `recursive_plot_scan` - Add libgmp-dev to Bladebit installation - thanks to @TheLastCicada - Add support for multiple of the same CAT in aggregate offers - Thanks to @roseiliend @@ -1414,7 +1457,7 @@ There is a known issue where harvesters will not reconnect to the farmer automat - Added new CLI option, chia keys derive, to allow deriving any number of keys in various ways. This is particularly useful to do an exhaustive search for a given address using chia keys derive search. - Div soft fork block height set to 2,300,000. - Added the ability to add an optional fee for creating and changing plot NFTs. -- Added *multiprocessing_start_method:* entry in config.yaml that allows setting the python *start method* for multiprocessing (default is *spawn* on Windows & MacOS, *fork* on Unix). +- Added `multiprocessing_start_method:` entry in config.yaml that allows setting the python _start method_ for multiprocessing (default is `spawn` on Windows & MacOS, `fork` on Unix). - Added option to "Cancel transaction" accepted offers that are stuck in "pending". ### Changed @@ -1479,7 +1522,7 @@ There is a known issue where harvesters will not reconnect to the farmer automat - If you start with wallet mode and then switch to farmer mode and back to wallet mode, the full node will continue to sync in the background. To get the full node to stop syncing after switching to wallet mode, you will need to close the Chia and relaunch the Chia app. - Wallets with large number of transactions or large number of coins will take longer to sync (more than a few minutes), but should take less time than a full node sync. It could fail in some cases. - Huge numbers cannot be put into amount/fee input for transactions in the GUI. -- Some Linux systems experience excessive memory usage with the value *default*/*python_default*/*fork* configured for *multiprocessing_start_method:*. Setting this value to *spawn* may produce better results, but in some uncommon cases, is know to cause crashes. +- Some Linux systems experience excessive memory usage with the value `default`/`python_default`/`fork` configured for `multiprocessing_start_method:`. Setting this value to `spawn` may produce better results, but in some uncommon cases, is know to cause crashes. - Sending a TX with too low of a fee can cause an infinite spinner in the GUI when the mempool is full. - Workaround: Restart the GUI, or clear unconfirmed TX. - Claiming rewards when self-pooling using CLI will show an error message, but it will actually create the transaction. @@ -1489,7 +1532,7 @@ There is a known issue where harvesters will not reconnect to the farmer automat ### Added - Farmers rejoice: today's release integrates two plotters in broad use in the Chia community: Bladebit, created by @harold-b, and Madmax, created by @madMAx43v3r. Both of these plotters bring significant improvements in plotting time. More plotting info [here](https://github.com/Chia-Network/chia-blockchain/wiki/Alternative--Plotters). -- This release also includes several important performance improvements as a result of last weekends "Dust Storm", with two goals in mind: make sure everyone can farm at all times, and improve how many transactions per second each node can accept, especially for low-end hardware. Please know that these optimizations are only the first wave in a series of many over the next few releases to help address this going forward. While the changes we have implemented in this update may not necessarily solve for *every* possible congestion scenario, they should go a long way towards helping low-end systems perform closer to expectations if this happens again. +- This release also includes several important performance improvements as a result of last weekends "Dust Storm", with two goals in mind: make sure everyone can farm at all times, and improve how many transactions per second each node can accept, especially for low-end hardware. Please know that these optimizations are only the first wave in a series of many over the next few releases to help address this going forward. While the changes we have implemented in this update may not necessarily solve for _every_ possible congestion scenario, they should go a long way towards helping low-end systems perform closer to expectations if this happens again. - Performance improvements for nodes to support higher transaction volumes, especially for low powered devices like RaspBerry Pi. Full details at [#9050](https://github.com/Chia-Network/chia-blockchain/pull/9050). - Improved multi-core usage through process pools. - Prioritized block validation. @@ -1535,7 +1578,7 @@ We have some great improvements in this release: We launched our migration of ke ### Changed -- Truncate points_[found,acknowledged]_24h to 24 hours at each signage point. +- Truncate points\_[found,acknowledged]\_24h to 24 hours at each signage point. - Improved reliability of test_farmer_harvester_rpc.py, by increasing the interval between harvester checks, which should avoid spamming logs with excessive plot refreshing and cache updates. - Thanks @cross for change that allows using IPv6 address in config.yaml for remote harvesters and other chia services. - Change to stop creating unused indexes in block_records and full_blocks tables. @@ -1657,15 +1700,15 @@ Today we’re releasing version 1.2.6 to address a resource bug with nodes, and - Added an option to sign bytes as well as UTF-8 strings, which is particularly helpful if you're writing Chialisp puzzles that require signatures and you want to test them without necessarily writing a whole python script for signing the relevant data. - Added a first version of .pre-commit-config.yaml and applied the changes required by the following initial hooks in separate commits. To use this you need to install pre-commit, see . - We have added many new translations in this release based on community -submissions. Thanks to @RuiZhe for Chinese, Traditional; @HansCZ for Czech; -@LUXDAD for English, Australia; @f00b4r for Finnish; @jimkoen, @ruvado for German; @Arielzikri for Hebrew; @A-Caccese for Italian; @Hodokami for Japanese; @LUXDAD for Latvian; @vaexperience for Lithuanian; @LUXDAD for Russian; @juands1644 for Spanish, Argentina; @MrDyngrak, @ordtrogen for Swedish; @richeyphu for Thai; @Ansugo, @baturman for Turkish. + submissions. Thanks to @RuiZhe for Chinese, Traditional; @HansCZ for Czech; + @LUXDAD for English, Australia; @f00b4r for Finnish; @jimkoen, @ruvado for German; @Arielzikri for Hebrew; @A-Caccese for Italian; @Hodokami for Japanese; @LUXDAD for Latvian; @vaexperience for Lithuanian; @LUXDAD for Russian; @juands1644 for Spanish, Argentina; @MrDyngrak, @ordtrogen for Swedish; @richeyphu for Thai; @Ansugo, @baturman for Turkish. ### Changed -- Thanks @altendky for Correct * to ** kwargs unpacking in time_out_assert(). +- Thanks @altendky for Correct `*` to `**` kwargs unpacking in time_out_assert(). - Thanks @altendky for changing the default to paginate to chia wallet get_transactions to address cases such as piping and output redirection to a file where the command previously just hung while waiting for the user to press c for the next page. - Removed commented-out debug breakpoints. -- Enabled Rust condition checker to add the ability to parse the output conditions from a generator program in Rust. It also validates some of the conditions in Rust. +- Enabled Rust condition checker to add the ability to parse the output conditions from a generator program in Rust. It also validates some of the conditions in Rust. - Switched IP address lookup to first use Chia's service ip.chia.net. - Made changes so that when creating SSL certificate and private key files, we ensure that files are written with the proper file permissions. - Define a new encrypted keyring format to be used to store keys, and which is optionally encrypted to a user-supplied passphrase. GUI for the passphrase will come in an upcoming release. @@ -1704,7 +1747,7 @@ submissions. Thanks to @RuiZhe for Chinese, Traditional; @HansCZ for Czech; - Thanks @aarcro for adding timing metrics to plot check. - Thanks @chadwick2143 for adding the ability to set the port to use for the harvester. - Added more friendly error reporting for peername errors. -- We have added many new translations in this release. Thanks to @L3Sota, @hodokami and @L3Sota for Japanese; @danielrangel6, @memph1x and @dvd101x for Spanish (Mexico); @fsavaget, @semnosao and @ygalvao for Portuguese (Brazilian); @juands1644 for Spanish (Argentina); @darkflare for Portuguese; @wong8888, @RuiZhe, @LM_MA, @ezio20121225, @GRIP123, @11221206 and @nicko1122 for Chinese Traditional; @atomsymbol for Slovak; @SirGeoff and @rolandfarkasCOM for Hungarian; @ordtrogen for Swedish; @HansCZ and @kafkic for Czech; @SupperDog for Chinese Simplified; @baturman and @Ansugo for Turkish; @thebacktrack for Russian; @itservicelukaswinter for German; @saeed508, @Amirr_ezA and @themehran for Persian; @hgthtung for Vietnamese; @f00b4r for Finnish; @IMIMIM for Latvian; @Rothnita and @vanntha85 for Khmer; @Rothnita and @Gammaubl for Thai; @marcin1990 for Polish; @mydienst for Bosnian; @dvd101x and @darkflare for Spanish; @ATSHOOTER for Albanian; @Munyuk81 for Indonesian; @loppefaaret for Danish; @sharjeelaziz and @nzjake for English; @nzjake for English (New Zealand). We apologize if we missed anyone and welcome corrections. +- We have added many new translations in this release. Thanks to @L3Sota, @hodokami and @L3Sota for Japanese; @danielrangel6, @memph1x and @dvd101x for Spanish (Mexico); @fsavaget, @semnosao and @ygalvao for Portuguese (Brazilian); @juands1644 for Spanish (Argentina); @darkflare for Portuguese; @wong8888, @RuiZhe, @LM_MA, @ezio20121225, @GRIP123, @11221206 and @nicko1122 for Chinese Traditional; @atomsymbol for Slovak; @SirGeoff and @rolandfarkasCOM for Hungarian; @ordtrogen for Swedish; @HansCZ and @kafkic for Czech; @SupperDog for Chinese Simplified; @baturman and @Ansugo for Turkish; @thebacktrack for Russian; @itservicelukaswinter for German; @saeed508, @Amirr_ezA and @themehran for Persian; @hgthtung for Vietnamese; @f00b4r for Finnish; @IMIMIM for Latvian; @Rothnita and @vanntha85 for Khmer; @Rothnita and @Gammaubl for Thai; @marcin1990 for Polish; @mydienst for Bosnian; @dvd101x and @darkflare for Spanish; @ATSHOOTER for Albanian; @Munyuk81 for Indonesian; @loppefaaret for Danish; @sharjeelaziz and @nzjake for English; @nzjake for English (New Zealand). We apologize if we missed anyone and welcome corrections. ### Changed @@ -1755,13 +1798,13 @@ submissions. Thanks to @RuiZhe for Chinese, Traditional; @HansCZ for Czech; ### Added - Portable pooled plots are now available using our new plot NFT. These allow you to plot new plots to an NFT that can either self farm or join and leave pools. During development there were changes to the plot NFT so portable pool plots (those made with `-c` option to `chia plots create`) using code from before June 25th are invalid on mainnet. -OG plots made before this release can continue to be farmed side by side with the new portable pool plots but can not join pools using the official pooling protocol. You can learn more as a farmer by checking out the [pool user guide](https://github.com/Chia-Network/chia-blockchain/wiki/Pooling-User-Guide). Pool operators and those wanting to understand how the official pooling protocol operates should check out our [pooling implementation reference repository](https://github.com/Chia-Network/pool-reference). If you plan to use plot NFT, all your farmers and harvesters must be on 1.2.0 to function properly for portable pool plots. + OG plots made before this release can continue to be farmed side by side with the new portable pool plots but can not join pools using the official pooling protocol. You can learn more as a farmer by checking out the [pool user guide](https://github.com/Chia-Network/chia-blockchain/wiki/Pooling-User-Guide). Pool operators and those wanting to understand how the official pooling protocol operates should check out our [pooling implementation reference repository](https://github.com/Chia-Network/pool-reference). If you plan to use plot NFT, all your farmers and harvesters must be on 1.2.0 to function properly for portable pool plots. - The exact commit after which Plot NFTs should be valid is the 89f7a4b3d6329493cd2b4bc5f346a819c99d3e7b commit (in which `pools.testnet9` branch was merged to main) or 5d62b3d1481c1e225d8354a012727ab263342c0a within the `pools.testnet9` branch. - `chia farm summary` and the GUI now use a new RPC endpoint to properly show plots for local and remote harvesters. This should address issues #6563, #5881, #3875, #1461. - `chia configure` now supports command line updates to peer count and target peer count. - Thank you @gldecurtins for adding logging support for remote syslog. - Thanks to @maran and @Animazing for adding farmer and pool public key display to the RPC. -- We have added translations for Hungarian, Belarusian, Catalan, and Albanian. For Hungarian thanks to @SirGeoff, @azazio @onokaxxx, @rolandfarkasCOM, @HUNDavid , @horvathpalzsolt, @stishun74, @tusdavgaming, @idotitusz, @rasocsabi, @mail.kope, @gsprblnt, @mbudahazi, @csiberius, @tomatos83, @zok42, @ocel0t, @rwtoptomi, @djxpitke, @ftamas85, @zotya0330, @fnni, @kapabeates, @zamery, @viktor.gonczi, @pal.suta, @miv, and @Joeman_. For Belarusian thanks to @shurix83, @haxycgm, and @metalomaniax. For Catalan thank you to @Poliwhirl, @Pep-33, @marqmarti, @meuca, @Guiwdin, @carlescampi, @jairobtx, @Neoares, @darknsis, @augustfarrerasgimeno, and @fornons. Finally for Albanian thanks to @ATSHOOTER and @lakedeejay. We apologize if we missed anyone and welcome corrections. +- We have added translations for Hungarian, Belarusian, Catalan, and Albanian. For Hungarian thanks to @SirGeoff, @azazio @onokaxxx, @rolandfarkasCOM, @HUNDavid , @horvathpalzsolt, @stishun74, @tusdavgaming, @idotitusz, @rasocsabi, @mail.kope, @gsprblnt, @mbudahazi, @csiberius, @tomatos83, @zok42, @ocel0t, @rwtoptomi, @djxpitke, @ftamas85, @zotya0330, @fnni, @kapabeates, @zamery, @viktor.gonczi, @pal.suta, @miv, and @Joeman\_. For Belarusian thanks to @shurix83, @haxycgm, and @metalomaniax. For Catalan thank you to @Poliwhirl, @Pep-33, @marqmarti, @meuca, @Guiwdin, @carlescampi, @jairobtx, @Neoares, @darknsis, @augustfarrerasgimeno, and @fornons. Finally for Albanian thanks to @ATSHOOTER and @lakedeejay. We apologize if we missed anyone and welcome corrections. - Our release process is now fully automated from tagging a release to publishing installers to all of the appropriate locations and now makes the release artifacts available via torrents as well. - All Chia repositories now automatically build M1 wheels and create a new MacOS M1 native installer. - New CLI command `chia plotnft` to manage pools. @@ -1776,7 +1819,7 @@ OG plots made before this release can continue to be farmed side by side with th - We have made a host of changes to the GUI to support pooling and to improve the wallet experience. - We updated chiapos to version 1.0.3. This adds parallel reads to GetFullProof. Thanks to @marcoabreu ! We now print target/final directory early in the logs refs and log process ID. Thanks to @grayfallstown ! We are now using Gulrak 1.5.6. -@683280 optimized code in phase1.hpp. @jespino and @mrhacky started migrating to flags instead of booleans parameters for `show_progress` and `nobitfield`. If you are providing third-party tools you may need to make adjustments if relying on the chiapos log. + @683280 optimized code in phase1.hpp. @jespino and @mrhacky started migrating to flags instead of booleans parameters for `show_progress` and `nobitfield`. If you are providing third-party tools you may need to make adjustments if relying on the chiapos log. - Updated chiavdf to version 1.0.2 to fix certain tests. - Windows builds now rely upon Python 3.9 which obviates the fix in 1.1.7. - We are now using miniupnpc version 2.2.2 so that we can support Python 3.9 on Windows. @@ -2013,7 +2056,7 @@ Batch process weight proof epochs in groups of 900 to fit below May 2020 sqlite ### Changed -- The plotter in bitfield mode is much improved in plotting speed (~15% faster than in 1.0.3), now requires 28% less temporary space (238.3 GiB/256 GB), and now uses its maximum memory in phase 1 and only needs 3389MiB for optimal sorting of a k32. Total writes should also be down by about 20%. On almost all machines we expect bitfield to be as fast or faster. For CPUs that predate the [Nehalem architecture](https://en.wikipedia.org/wiki/Nehalem_(microarchitecture)), bitfield plotting will not work and you will need to use no bitfield. Those CPUs were generally designed before 2010. +- The plotter in bitfield mode is much improved in plotting speed (~15% faster than in 1.0.3), now requires 28% less temporary space (238.3 GiB/256 GB), and now uses its maximum memory in phase 1 and only needs 3389MiB for optimal sorting of a k32. Total writes should also be down by about 20%. On almost all machines we expect bitfield to be as fast or faster. For CPUs that predate the [Nehalem architecture](), bitfield plotting will not work and you will need to use no bitfield. Those CPUs were generally designed before 2010. - The `src` directory in chia-blockchain has been changed to `chia` to avoid namespace collisions. - GUI install builds have been simplified to rely on one `.spec` file in `chia/` - The weight proof timeout can now be configured in config.yaml. @@ -2306,7 +2349,7 @@ Batch process weight proof epochs in groups of 900 to fit below May 2020 sqlite - The websocket address is no longer displayed in the GUI unless it is running as a remote GUI. Thanks @dkackman ! - `chia plots check` now will continue checking after it finds an error in a plot to the total number of checks you specified. - If you run install-gui.sh or install-timelord.sh without being in the venv, the script will warn you that you need to `. ./activate` and exit with error. -- If you attempt to install on a 32 bit Pi/ARM OS, the installer exits with a helpful error message. You can still fail when running under a 64 bit kernel but using a 32 bit Python 3. +- If you attempt to install on a 32 bit Pi/ARM OS, the installer exits with a helpful error message. You can still fail when running under a 64 bit kernel but using a 32 bit Python 3. - The application is now more aware of whether it is running a testnet or mainnet. This impacts wallet's display behavior and certain blockchain validation rules. - Interface improvements for `chia netspace`. - Now that aiosqlite included our upstream improvements we install version 0.17.0. @@ -2352,13 +2395,13 @@ Batch process weight proof epochs in groups of 900 to fit below May 2020 sqlite - We have added Italian, Russian, and Finnish. More to come soon. - There is now remote UI support. [Documents](https://github.com/Chia-Network/chia-blockchain-gui/blob/main/remote.md) will temporarily live in the repository but have moved to the [wiki](https://github.com/Chia-Network/chia-blockchain/wiki/Connecting-the-UI-to-a-remote-daemon). Thanks to @dkackman for this excellent addition! - Added the ability to specify an address for the pool when making plots (-c flag), as opposed to a public key. The block -validation was changed to allow blocks like these to be made. This will enable changing pools in the future, by specifying a smart transaction for your pool rewards. + validation was changed to allow blocks like these to be made. This will enable changing pools in the future, by specifying a smart transaction for your pool rewards. - Added `chia plots check --challenge-start [start]` that begins at a different `[start]` for `-n [challenges]`. Useful when you want to do more detailed checks on plots without restarting from lower challenge values you already have done. Huge thanks to @eFishCent for this and all of the debugging work behind the scenes confirming that plot failures were machine errors and not bugs! ### Changed - Sub blocks renamed to blocks, and blocks renamed to transaction blocks, everywhere. This effects the RPC, now -all fields that referred to sub blocks are changed to blocks. + all fields that referred to sub blocks are changed to blocks. - Base difficulty and weight have increased, so difficulty of "5" in the rc1 testnet will be equivalent to "21990232555520" in the previous testnet. - 'chia wallet send' now takes in TXCH or XCH as units instead of mojos. - Transactions have been further sped up. @@ -2671,7 +2714,7 @@ all fields that referred to sub blocks are changed to blocks. - A bug in bls-singatures/blspy could cause a stack overflow if too many signatures were verified at once. This caused the block of death at 11997 of the Beta 15 chain. Updated to 0.2.4 to address the issue. - GUI Wallet now correctly updates around reorgs. - chiapos 0.12.32 fixed a an out of bounds read that could crash the plotter. It also contains a fix to better handle the case of drive letters on Windows. -- Node would fail to start on Windows Server 2016 with lots of cores. This [python issue explains]( https://bugs.python.org/issue26903) the problem. +- Node would fail to start on Windows Server 2016 with lots of cores. This [python issue explains](https://bugs.python.org/issue26903) the problem. ### Known Issues @@ -2845,7 +2888,7 @@ all fields that referred to sub blocks are changed to blocks. - Handle disconnection and reconnection of hard drives properly. - Addressed pre-Haswell Windows signatures failing. - MacOS, Linux x64, and Linux aarch64 were not correctly compiling libsodium in -the blspy/bls-signatures library. + the blspy/bls-signatures library. - Removed outdated "200 plots" language from Plot tab. - Fixed spelling error for "folder" on Plot tab. - Various node dependency security vulnerabilities have been fixed. @@ -2861,76 +2904,76 @@ the blspy/bls-signatures library. ### Added - We have released a new plot file format. We believe that plots made in this -format and with these IETF BLS keys will work without significant changes on -mainnet at launch. + format and with these IETF BLS keys will work without significant changes on + mainnet at launch. - We now use [chacha8](https://cr.yp.to/chacha.html) and -[blake3](https://github.com/BLAKE3-team/BLAKE3) for proof of space instead of -the now deprecated AES methods. This should increase plotting speed and support -more processors. + [blake3](https://github.com/BLAKE3-team/BLAKE3) for proof of space instead of + the now deprecated AES methods. This should increase plotting speed and support + more processors. - Plot refreshing happens during all new challenges and only new/modified files -are read. + are read. - Updated [blspy](https://github.com/Chia-Network/bls-signatures) to use the -new [IETF standard for BLS signatures](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02). + new [IETF standard for BLS signatures](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02). - Added a faster VDF process which generates n-wesolowski proofs quickly -after the VDF result is known. This requires a high number of CPUs. To use it, -set timelord.fast_algorithm = True in the config file. + after the VDF result is known. This requires a high number of CPUs. To use it, + set timelord.fast_algorithm = True in the config file. - Added a new type of timelord helper - blue boxes, which generate compact -proofs of time for existing proven blocks. This helps reducing the database -size and speeds up syncing a node for new users joining the network. Full nodes -send 100 random un-compact blocks per hour to blue boxes, and if -timelord.sanitizer_mode = True, the blue box timelord will work on those -challenges. Unlike the main timelord, average machines can run blue boxes -and contribute to the chain. Expect improvements to the install method for -blue boxes in future releases. + proofs of time for existing proven blocks. This helps reducing the database + size and speeds up syncing a node for new users joining the network. Full nodes + send 100 random un-compact blocks per hour to blue boxes, and if + timelord.sanitizer_mode = True, the blue box timelord will work on those + challenges. Unlike the main timelord, average machines can run blue boxes + and contribute to the chain. Expect improvements to the install method for + blue boxes in future releases. - From the UI you can add a directory that harvester will always check for -existing and new plots. Harvester will only look in the specific directory you -specify so you'll have to add any subfolders you want to also contain plots. + existing and new plots. Harvester will only look in the specific directory you + specify so you'll have to add any subfolders you want to also contain plots. - The UI now asks for confirmation before closing and shows shutdown progress. - UI now tries to shut down servers gracefully before exiting, and also closes -the daemon before starting. + the daemon before starting. - The various sub repositories (chiapos, chiavdf, etc.) now build ARM64 binary -wheels for Linux with Python 3.8. This makes installing on Ubuntu 20.04 lts on -a Raspberry Pi 3 or 4 easy. + wheels for Linux with Python 3.8. This makes installing on Ubuntu 20.04 lts on + a Raspberry Pi 3 or 4 easy. - Ci's check to see if they have secret access and attempt to fail cleanly so -that ci runs successfully complete from PRs or forked repositories. + that ci runs successfully complete from PRs or forked repositories. - Farmer now sends challenges after a handshake with harvester. - The bls-signatures binary wheels include libsodium on all but Windows which -we expect to add in future releases. + we expect to add in future releases. - The chia executable is now available if installing from the Windows or MacOS -Graphical installer. Try `./chia -h` from -`~\AppData\Local\Chia-Blockchain\app-0.1.8\resources\app.asar.unpacked\daemon\` -in Windows or -`/Applications/Chia.app/Contents/Resources/app.asar.unpacked/daemon` on MacOS. + Graphical installer. Try `./chia -h` from + `~\AppData\Local\Chia-Blockchain\app-0.1.8\resources\app.asar.unpacked\daemon\` + in Windows or + `/Applications/Chia.app/Contents/Resources/app.asar.unpacked/daemon` on MacOS. ### Changed - Minor changes have been made across the repositories to better support -compiling on OpenBSD. HT @n1000. + compiling on OpenBSD. HT @n1000. - Changed XCH units to TXCH units for testnet. - A push to a branch will cancel all ci runs still running for that branch. - Ci's now cache pip and npm caches between runs. - Improve test speed with smaller discriminants, less blocks, less keys, and -smaller plots. + smaller plots. - RPC servers and clients were refactored. - The keychain no longer supports old keys that don't have mnemonics. - The keychain uses BIP39 for seed derivation, using the "" passphrase, and -also stores public keys. -- Plots.yaml has been replaced. Plot secret keys are stored in the plots, - and a list of directories that harvester can find plots in are in config.yaml. -You can move plots around to any directory in config.yaml as long as the farmer -has the correct farmer's secret key too. + also stores public keys. +- Plots.yaml has been replaced. Plot secret keys are stored in the plots, + and a list of directories that harvester can find plots in are in config.yaml. + You can move plots around to any directory in config.yaml as long as the farmer + has the correct farmer's secret key too. - Auto scanning of plot directories for .plot files. - The block header format was changed (puzzle hashes and pool signature). - Coinbase and fees coin are now in merkle set, and bip158 filter. - New harvester protocol with 2/2 harvester and farmer signatures, and modified -farmer and full node protocols. + farmer and full node protocols. - 255/256 filter which allows virtually unlimited plots per harvester or drive. - Improved create_plots and check_plots scripts, which are now -"chia plots create" and "chia plots check". + "chia plots create" and "chia plots check". - Add plot directories to config.yaml from the cli with "chia plots add". - Use real plot sizes in UI instead of a formula/ - HD keys now use EIP 2333 format instead of BIP32, for compatibility with -other chains. + other chains. - Keys are now derived with the EIP 2334 (m/12381/8444/a/b). - Removed the ability to pass in sk_seed to plotting, to increase security. - Linux builds of chiavdf and blspy now use a fresh build of gmp 6.2.1. @@ -2940,13 +2983,13 @@ other chains. - uPnP now works on Windows. - Log rotation should now properly rotate every 20MB and keep 7 historical logs. - Node had a significant memory leak under load due to an extraneous fork -in the network code. + in the network code. - Skylake processors on Windows without AVX would fail to run. - Harvester no longer runs into 512 maximum file handles open issue on Windows. - The version generator for new installers incorrectly handled the "dev" -versions after a release tag. + versions after a release tag. - Due to a python bug, ssl connections could randomly fail. Worked around -[Python issue 29288](https://bugs.python.org/issue29288) + [Python issue 29288](https://bugs.python.org/issue29288) - Removed websocket max message limit, allowing for more plots - Daemon was crashing when websocket gets improperly closed @@ -2954,14 +2997,14 @@ versions after a release tag. - All keys generated before Beta 1.8 are of an old format and no longer useful. - All plots generated before Beta 1.8 are no longer compatible with testnet and -should be deleted. + should be deleted. ### Known Issues - For Windows users on pre Haswell CPUs there is a known issue that causes -"Given G1 element failed g1_is_valid check" when attempting to generate -keys. This is a regression from our previous fix when it was upstreamed into -relic. We will make a patch available for these systems shortly. + "Given G1 element failed g1_is_valid check" when attempting to generate + keys. This is a regression from our previous fix when it was upstreamed into + relic. We will make a patch available for these systems shortly. ## [1.0beta7] aka Beta 1.7 - 2020-06-08 @@ -3158,7 +3201,7 @@ relic. We will make a patch available for these systems shortly. ### Added - There is now full transaction support on the Chia blockchain. In this initial Beta 1.0 release, all transaction types are supported though the wallets and UIs currently only directly support basic transactions like coinbase rewards and sending coins while paying fees. UI support for our [smart transactions](https://github.com/Chia-Network/wallets/blob/main/README.md) will be available in the UIs shortly. -- Wallet and Node GUI’s are available on Windows, Mac, and desktop Linux platforms. We now use an Electron UI that is a full light client wallet that can also serve as a node UI. Our Windows Electron Wallet can run standalone by connecting to other nodes on the network or another node you run. WSL 2 on Windows can run everything except the Wallet but you can run the Wallet on the native Windows side of the same machine. Also the WSL 2 install process is 3 times faster and *much* easier. Windows native node/farmer/plotting functionality are coming soon. +- Wallet and Node GUI’s are available on Windows, Mac, and desktop Linux platforms. We now use an Electron UI that is a full light client wallet that can also serve as a node UI. Our Windows Electron Wallet can run standalone by connecting to other nodes on the network or another node you run. WSL 2 on Windows can run everything except the Wallet but you can run the Wallet on the native Windows side of the same machine. Also the WSL 2 install process is 3 times faster and _much_ easier. Windows native node/farmer/plotting functionality are coming soon. - Install is significantly easier with less dependencies on all supported platforms. - If you’re a farmer you can use the Wallet to keep track of your earnings. Either use the same keys.yaml on the same machine or copy the keys.yaml to another machine where you want to track of and spend your coins. - We have continued to make improvements to the speed of VDF squaring, creating a VDF proof, and verifying a VDF proof. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8998906234a1..59adbe7884bc 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Insulting/derogatory comments, and personal or political attacks, or excessive trolling. -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Insulting/derogatory comments, and personal or political attacks, or excessive trolling. +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities diff --git a/Install.ps1 b/Install.ps1 index 543bb34f1452..74bb37245f41 100644 --- a/Install.ps1 +++ b/Install.ps1 @@ -104,8 +104,6 @@ foreach ($extra in $extras) ./Setup-poetry.ps1 -pythonVersion "$pythonVersion" .penv/Scripts/poetry env use $(py -"$pythonVersion" -c 'import sys; print(sys.executable)') -# TODO: Decide if this is needed or should be handled automatically in some way -.penv/Scripts/pip install "poetry-dynamic-versioning[plugin]" .penv/Scripts/poetry install @extras_cli if ($i) @@ -131,7 +129,7 @@ Write-Output "For assistance join us on Discord in the #support chat channel:" Write-Output "https://discord.gg/chia" Write-Output "" Write-Output "Try the Quick Start Guide to running chia-blockchain:" -Write-Output "https://github.com/Chia-Network/chia-blockchain/wiki/Quick-Start-Guide" +Write-Output "https://docs.chia.net/introduction" Write-Output "" Write-Output "To install the GUI run '.\.venv\scripts\Activate.ps1' then '.\Install-gui.ps1'." Write-Output "" diff --git a/LEGACY-SUPPORT-POLICY.md b/LEGACY-SUPPORT-POLICY.md index fbe166a1d0ab..3050cecc3b67 100644 --- a/LEGACY-SUPPORT-POLICY.md +++ b/LEGACY-SUPPORT-POLICY.md @@ -1,6 +1,6 @@ # Legacy Software and Operating System Support Policy -It is the official policy of the Chia Blockchain project to end software support when the original maintainer of the software deems it End of Life (EOL) or stops providing support. The most relevant targets of this policy are Python, Node.js, and operating systems. +It is the official policy of the Chia Blockchain project to end software support when the original maintainer of the software deems it End of Life (EOL) or stops providing support. The most relevant targets of this policy are Python, Node.js, and operating systems. ## Long-term Support Conflicts diff --git a/PRETTY_GOOD_PRACTICES.md b/PRETTY_GOOD_PRACTICES.md index 7bc2a833692f..be0e92541560 100644 --- a/PRETTY_GOOD_PRACTICES.md +++ b/PRETTY_GOOD_PRACTICES.md @@ -115,7 +115,6 @@ def use_open_directly(path: Path): return file.read() ``` - ### Context managers for single use scenarios Even when no reuse is necessary there are still reasons to use context managers. @@ -146,13 +145,11 @@ def f(x, y, z): z.process(x, y) ``` - ### Examples - https://github.com/Chia-Network/chia-blockchain/pull/11467 - https://github.com/Chia-Network/chia-blockchain/pull/10166 - ## Classes There are a few basic goals for classes that are targeted by the guidance provided below. @@ -437,7 +434,6 @@ class ThreeIntAdder(Protocol): Another option for additional expressivity around hinting a callable with a protocol is to use overloads to narrow the possible combinations of calls. It is often better to just avoid overload situations, but as we retrofit hints to existing code we may prefer this option sometimes. - ### Type variables `TypeVar` allows you to create 'variables' for type hints. @@ -617,8 +613,8 @@ class SomeWallet: ## Tests -- Do not import `test_*` modules. Instead locate shared tooling in non-test files within the `tests/` directory or subdirectories. -- Do not import fixtures. Fixtures are shared by locating them in `conftest.py` files at whatever directory layer you want them to be recursively available from. +- Do not import `test_*` modules. Instead locate shared tooling in non-test files within the `tests/` directory or subdirectories. +- Do not import fixtures. Fixtures are shared by locating them in `conftest.py` files at whatever directory layer you want them to be recursively available from. - Do not use test classes. `unittest` requires that tests be held in a class. pytest does not. @@ -647,7 +643,7 @@ class SomeWallet: ## Idioms -- Avoid use of non-booleans as booleans such as `if the_list:`. If you mean `if len(the_list) > 0:` write that, if you mean `if an_optional_thing is not None:` write that. +- Avoid use of non-booleans as booleans such as `if the_list:`. If you mean `if len(the_list) > 0:` write that, if you mean `if an_optional_thing is not None:` write that. ## Exceptions @@ -665,7 +661,6 @@ You want the code to fail quickly and clearly with a `NameError`, not silently c This is why linters discourage bare `except:` and overly broad `except Exception:` clauses. Especially don't `except BaseException:` as that can consume even shutdown requests. - ```python from datetime import datetime diff --git a/README.md b/README.md index 5b2cb4baee2a..bcbec273f809 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,21 @@ [![Chia Network logo][logo-chia]][link-chia] -| Releases | Repo Stats | Socials | -| -------- | ---------- | ------- | -[![Latest Release][badge-release]][link-latest]
[![Latest RC][badge-rc]][link-release]
[![Latest Beta][badge-beta]][link-release] | [![Coverage][badge-coverage]][link-coverage]
[![Downloads][badge-downloads]][link-downloads]
[![Commits][badge-commits]][link-commits]
[![Contributers][badge-contributers]][link-contributers] | [![Discord][badge-discord]][link-discord]
[![YouTube][badge-youtube]][link-youtube]
[![Reddit][badge-reddit]][link-reddit]
[![Twitter][badge-twitter]][link-twitter] +| Releases | Repo Stats | Socials | +| ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [![Latest Release][badge-release]][link-latest]
[![Latest RC][badge-rc]][link-release]
[![Latest Beta][badge-beta]][link-release] | [![Coverage][badge-coverage]][link-coverage]
[![Downloads][badge-downloads]][link-downloads]
[![Commits][badge-commits]][link-commits]
[![Contributers][badge-contributers]][link-contributers] | [![Discord][badge-discord]][link-discord]
[![YouTube][badge-youtube]][link-youtube]
[![Reddit][badge-reddit]][link-reddit]
[![Twitter][badge-twitter]][link-twitter] | Chia is a modern cryptocurrency built from scratch, designed to be efficient, decentralized, and secure. Here are some of the features and benefits: -* [Proof of space and time][link-consensus] based consensus which allows anyone to farm with commodity hardware -* Very easy to use full node and farmer GUI and cli (thousands of nodes active on mainnet) -* [Chia seeder][link-seeder], which maintains a list of reliable nodes within the Chia network via a built-in DNS server. -* Simplified UTXO based transaction model, with small on-chain state -* Lisp-style Turing-complete functional [programming language][link-chialisp] for money related use cases -* BLS keys and aggregate signatures (only one signature per block) -* [Pooling protocol][link-pool] that allows farmers to have control of making blocks -* Support for light clients with fast, objective syncing -* A growing community of farmers and developers around the world + +- [Proof of space and time][link-consensus] based consensus which allows anyone to farm with commodity hardware +- Very easy to use full node and farmer GUI and cli (thousands of nodes active on mainnet) +- [Chia seeder][link-seeder], which maintains a list of reliable nodes within the Chia network via a built-in DNS server. +- Simplified UTXO based transaction model, with small on-chain state +- Lisp-style Turing-complete functional [programming language][link-chialisp] for money related use cases +- BLS keys and aggregate signatures (only one signature per block) +- [Pooling protocol][link-pool] that allows farmers to have control of making blocks +- Support for light clients with fast, objective syncing +- A growing community of farmers and developers around the world Please check out the [Chia website][link-chia], the [Intro to Chia][link-intro], and [FAQ][link-faq] for information on this project. @@ -38,39 +39,37 @@ Install instructions are available in the [Installation Details][link-install] s Once installed, an [Intro to Chia][link-intro] guide is available in the [Chia Docs][link-docs]. -[badge-beta]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-beta.json&query=%24.message&logo=chianetwork&logoColor=black&label=Latest%20Beta&labelColor=%23e9fbbc&color=%231e2b2e -[badge-beta2]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-beta.json&query=%24.message&logo=chianetwork&logoColor=%23e9fbbc&label=Latest%20Beta&labelColor=%23474748&color=%231e2b2e&link=https%3A%2F%2Fgithub.com%2FChia-Network%2Fchia-blockchain%2Freleases&link=https%3A%2F%2Fgithub.com%2FChia-Network%2Fchia-blockchain%2Freleases -[badge-commits]: https://img.shields.io/github/commit-activity/w/Chia-Network/chia-blockchain?logo=GitHub +[badge-beta]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-beta.json&query=%24.message&logo=chianetwork&logoColor=black&label=Latest%20Beta&labelColor=%23e9fbbc&color=%231e2b2e +[badge-beta2]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-beta.json&query=%24.message&logo=chianetwork&logoColor=%23e9fbbc&label=Latest%20Beta&labelColor=%23474748&color=%231e2b2e&link=https%3A%2F%2Fgithub.com%2FChia-Network%2Fchia-blockchain%2Freleases&link=https%3A%2F%2Fgithub.com%2FChia-Network%2Fchia-blockchain%2Freleases +[badge-commits]: https://img.shields.io/github/commit-activity/w/Chia-Network/chia-blockchain?logo=GitHub [badge-contributers]: https://img.shields.io/github/contributors/Chia-Network/chia-blockchain?logo=GitHub -[badge-coverage]: https://img.shields.io/coverallsCoverage/github/Chia-Network/chia-blockchain?logo=Coveralls&logoColor=red&labelColor=%23212F39 -[badge-discord]: https://dcbadge.vercel.app/api/server/chia?style=flat-square&theme=full-presence -[badge-discord2]: https://img.shields.io/discord/1034523881404370984.svg?label=Discord&logo=discord&colorB=1e2b2f -[badge-downloads]: https://img.shields.io/github/downloads/Chia-Network/chia-blockchain/total?logo=GitHub -[badge-rc]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-rc.json&query=%24.message&logo=chianetwork&logoColor=white&label=Latest%20RC&labelColor=%230d3349&color=%23474748 -[badge-reddit]: https://img.shields.io/reddit/subreddit-subscribers/chia?style=flat-square&logo=reddit&labelColor=%230b1416&color=%23222222 -[badge-release]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data.json&query=%24.message&logo=chianetwork&label=Latest%20Release&labelColor=%231e2b2e&color=%230d3349 -[badge-twitter]: https://img.shields.io/twitter/follow/chia_project?style=flat-square&logo=x.org&logoColor=white&labelColor=black -[badge-youtube]: https://img.shields.io/youtube/channel/subscribers/UChFkJ3OAUvnHZdiQISWdWPA?style=flat-square&logo=youtube&logoColor=%23ff0000&labelColor=%230f0f0f&color=%23272727 - -[link-chia]: https://www.chia.net/ -[link-chialisp]: https://chialisp.com/ -[link-commits]: https://github.com/Chia-Network/chia-blockchain/commits/main/ -[link-consensus]: https://docs.chia.net/consensus-intro/ -[link-contributers]: https://github.com/Chia-Network/chia-blockchain/graphs/contributors -[link-coverage]: https://coveralls.io/github/Chia-Network/chia-blockchain -[link-discord]: https://discord.gg/chia -[link-docs]: https://docs.chia.net/docs-home/ -[link-downloads]: https://www.chia.net/downloads/ -[link-faq]: https://docs.chia.net/faq/ -[link-install]: https://docs.chia.net/installation/ -[link-intro]: https://docs.chia.net/introduction/ -[link-latest]: https://github.com/Chia-Network/chia-blockchain/releases/latest -[link-pool]: https://docs.chia.net/pool-farming/ -[link-reddit]: https://www.reddit.com/r/chia/ -[link-release]: https://github.com/Chia-Network/chia-blockchain/releases -[link-seeder]: https://docs.chia.net/guides/seeder-user-guide/ -[link-twitter]: https://twitter.com/chia_project -[link-upnp]: https://www.homenethowto.com/ports-and-nat/upnp-automatic-port-forward/ -[link-youtube]: https://www.youtube.com/chianetwork - -[logo-chia]: https://www.chia.net/wp-content/uploads/2022/09/chia-logo.svg "Chia logo" +[badge-coverage]: https://img.shields.io/coverallsCoverage/github/Chia-Network/chia-blockchain?logo=Coveralls&logoColor=red&labelColor=%23212F39 +[badge-discord]: https://dcbadge.vercel.app/api/server/chia?style=flat-square&theme=full-presence +[badge-discord2]: https://img.shields.io/discord/1034523881404370984.svg?label=Discord&logo=discord&colorB=1e2b2f +[badge-downloads]: https://img.shields.io/github/downloads/Chia-Network/chia-blockchain/total?logo=GitHub +[badge-rc]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data-rc.json&query=%24.message&logo=chianetwork&logoColor=white&label=Latest%20RC&labelColor=%230d3349&color=%23474748 +[badge-reddit]: https://img.shields.io/reddit/subreddit-subscribers/chia?style=flat-square&logo=reddit&labelColor=%230b1416&color=%23222222 +[badge-release]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdownload.chia.net%2Flatest%2Fbadge-data.json&query=%24.message&logo=chianetwork&label=Latest%20Release&labelColor=%231e2b2e&color=%230d3349 +[badge-twitter]: https://img.shields.io/twitter/follow/chia_project?style=flat-square&logo=x.org&logoColor=white&labelColor=black +[badge-youtube]: https://img.shields.io/youtube/channel/subscribers/UChFkJ3OAUvnHZdiQISWdWPA?style=flat-square&logo=youtube&logoColor=%23ff0000&labelColor=%230f0f0f&color=%23272727 +[link-chia]: https://www.chia.net/ +[link-chialisp]: https://chialisp.com/ +[link-commits]: https://github.com/Chia-Network/chia-blockchain/commits/main/ +[link-consensus]: https://docs.chia.net/consensus-intro/ +[link-contributers]: https://github.com/Chia-Network/chia-blockchain/graphs/contributors +[link-coverage]: https://coveralls.io/github/Chia-Network/chia-blockchain +[link-discord]: https://discord.gg/chia +[link-docs]: https://docs.chia.net/docs-home/ +[link-downloads]: https://www.chia.net/downloads/ +[link-faq]: https://docs.chia.net/faq/ +[link-install]: https://docs.chia.net/installation/ +[link-intro]: https://docs.chia.net/introduction/ +[link-latest]: https://github.com/Chia-Network/chia-blockchain/releases/latest +[link-pool]: https://docs.chia.net/pool-farming/ +[link-reddit]: https://www.reddit.com/r/chia/ +[link-release]: https://github.com/Chia-Network/chia-blockchain/releases +[link-seeder]: https://docs.chia.net/guides/seeder-user-guide/ +[link-twitter]: https://twitter.com/chia_project +[link-upnp]: https://www.homenethowto.com/ports-and-nat/upnp-automatic-port-forward/ +[link-youtube]: https://www.youtube.com/chianetwork +[logo-chia]: https://www.chia.net/wp-content/uploads/2022/09/chia-logo.svg "Chia logo" diff --git a/Setup-poetry.ps1 b/Setup-poetry.ps1 index 9ddd8d03e293..c8eea80f0433 100644 --- a/Setup-poetry.ps1 +++ b/Setup-poetry.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = "Stop" py -$pythonVersion -m venv .penv .penv/Scripts/python -m pip install --upgrade pip setuptools wheel # TODO: maybe make our own zipapp/shiv/pex of poetry and download that? -.penv/Scripts/python -m pip install poetry +.penv/Scripts/python -m pip install poetry "poetry-dynamic-versioning[plugin]" diff --git a/build_scripts/build_macos-2-installer.sh b/build_scripts/build_macos-2-installer.sh index fc7a526f5f4a..ed85041bb319 100644 --- a/build_scripts/build_macos-2-installer.sh +++ b/build_scripts/build_macos-2-installer.sh @@ -90,12 +90,14 @@ mv dist/* ../../../build_scripts/dist/ cd ../../../build_scripts || exit 1 mkdir final_installer -DMG_NAME="chia-${CHIA_INSTALLER_VERSION}.dmg" +ORIGINAL_DMG_NAME="chia-${CHIA_INSTALLER_VERSION}.dmg" if [ "$(arch)" = "arm64" ]; then - mv dist/"${DMG_NAME}" dist/chia-"${CHIA_INSTALLER_VERSION}"-arm64.dmg - DMG_NAME=chia-${CHIA_INSTALLER_VERSION}-arm64.dmg + DMG_NAME=Chia-${CHIA_INSTALLER_VERSION}-arm64.dmg +else + # NOTE: when coded, this changes the case to Chia + DMG_NAME=Chia-${CHIA_INSTALLER_VERSION}.dmg fi -mv dist/"$DMG_NAME" final_installer/ +mv dist/"$ORIGINAL_DMG_NAME" final_installer/"$DMG_NAME" ls -lh final_installer diff --git a/chia/_tests/conftest.py b/chia/_tests/conftest.py index 9cd5aa7c27c2..cbd72d0aca7f 100644 --- a/chia/_tests/conftest.py +++ b/chia/_tests/conftest.py @@ -1058,6 +1058,14 @@ async def crawler_service(root_path_populated_with_config: Path, database_uri: s yield service +@pytest.fixture(scope="function") +async def crawler_service_no_loop( + root_path_populated_with_config: Path, database_uri: str +) -> AsyncIterator[CrawlerService]: + async with setup_crawler(root_path_populated_with_config, database_uri, start_crawler_loop=False) as service: + yield service + + @pytest.fixture(scope="function") async def seeder_service(root_path_populated_with_config: Path, database_uri: str) -> AsyncIterator[DNSServer]: async with setup_seeder(root_path_populated_with_config, database_uri) as seeder: diff --git a/chia/_tests/core/data_layer/test_data_rpc.py b/chia/_tests/core/data_layer/test_data_rpc.py index 8cad29a9a766..1dda28e3c7a5 100644 --- a/chia/_tests/core/data_layer/test_data_rpc.py +++ b/chia/_tests/core/data_layer/test_data_rpc.py @@ -165,7 +165,7 @@ async def farm_block_check_singleton( await time_out_assert(10, check_mempool_spend_count, True, full_node_api, 1) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(10, check_singleton_confirmed, True, data_layer, store_id) - await full_node_api.wait_for_wallet_synced(wallet_node=wallet, timeout=10) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet, timeout=20) async def is_transaction_confirmed(api: WalletRpcApi, tx_id: bytes32) -> bool: diff --git a/chia/_tests/core/data_layer/test_data_store.py b/chia/_tests/core/data_layer/test_data_store.py index b7e390073bc1..eae37fe35eaa 100644 --- a/chia/_tests/core/data_layer/test_data_store.py +++ b/chia/_tests/core/data_layer/test_data_store.py @@ -1357,7 +1357,7 @@ async def mock_http_download( filename: str, proxy_url: str, server_info: ServerInfo, - timeout: int, + timeout: aiohttp.ClientTimeout, log: logging.Logger, ) -> None: if error: @@ -1373,7 +1373,7 @@ async def mock_http_download( root_hashes=[bytes32.random(seeded_random)], server_info=sinfo, client_foldername=tmp_path, - timeout=15, + timeout=aiohttp.ClientTimeout(total=15, sock_connect=5), log=log, proxy_url="", downloader=None, @@ -1396,7 +1396,7 @@ async def mock_http_download( root_hashes=[bytes32.random(seeded_random)], server_info=sinfo, client_foldername=tmp_path, - timeout=15, + timeout=aiohttp.ClientTimeout(total=15, sock_connect=5), log=log, proxy_url="", downloader=None, @@ -1903,7 +1903,7 @@ async def test_insert_from_delta_file_correct_file_exists( root_hashes=root_hashes, server_info=sinfo, client_foldername=tmp_path, - timeout=15, + timeout=aiohttp.ClientTimeout(total=15, sock_connect=5), log=log, proxy_url="", downloader=None, @@ -1962,7 +1962,7 @@ async def test_insert_from_delta_file_incorrect_file_exists( root_hashes=[incorrect_root_hash], server_info=sinfo, client_foldername=tmp_path, - timeout=15, + timeout=aiohttp.ClientTimeout(total=15, sock_connect=5), log=log, proxy_url="", downloader=None, diff --git a/chia/_tests/core/data_layer/test_plugin.py b/chia/_tests/core/data_layer/test_plugin.py new file mode 100644 index 000000000000..09243b5308d3 --- /dev/null +++ b/chia/_tests/core/data_layer/test_plugin.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import json +import logging +from pathlib import Path + +import pytest + +from chia.data_layer.data_layer_util import PluginRemote +from chia.data_layer.util.plugin import load_plugin_configurations + +log = logging.getLogger(__name__) + + +@pytest.mark.anyio +async def test_load_plugin_configurations(tmp_path: Path) -> None: + # Setup test environment + plugin_type = "downloaders" + root_path = tmp_path / "plugins_root" + config_path = root_path / "plugins" / plugin_type + config_path.mkdir(parents=True) + + # Create valid and invalid config files + valid_config = [ + {"url": "https://example.com/plugin1"}, + {"url": "https://example.com/plugin2", "headers": {"Authorization": "Bearer token"}}, + ] + invalid_config = {"config": "invalid"} + with open(config_path / "valid.conf", "w") as file: + json.dump(valid_config, file) + with open(config_path / "invalid.conf", "w") as file: + json.dump(invalid_config, file) + + # Test loading configurations + loaded_configs = await load_plugin_configurations(root_path, plugin_type, log) + + expected_configs = [ + PluginRemote.unmarshal(marshalled=config) if isinstance(config, dict) else None for config in valid_config + ] + # Filter out None values that may have been added due to invalid config structures + expected_configs = list(filter(None, expected_configs)) + assert set(loaded_configs) == set(expected_configs), "Should only load valid configurations" + + +@pytest.mark.anyio +async def test_load_plugin_configurations_no_configs(tmp_path: Path) -> None: + # Setup test environment with no config files + plugin_type = "uploaders" + root_path = tmp_path / "plugins_root" + + # Test loading configurations with no config files + loaded_configs = await load_plugin_configurations(root_path, plugin_type, log) + + assert loaded_configs == [], "Should return an empty list when no configurations are present" + + +@pytest.mark.anyio +async def test_load_plugin_configurations_unreadable_file(tmp_path: Path) -> None: + # Setup test environment + plugin_type = "downloaders" + root_path = tmp_path / "plugins_root" + config_path = root_path / "plugins" / plugin_type + config_path.mkdir(parents=True) + + # Create an unreadable config file + unreadable_config_file = config_path / "unreadable.conf" + unreadable_config_file.touch() + unreadable_config_file.chmod(0) # Make the file unreadable + + # Test loading configurations + loaded_configs = await load_plugin_configurations(root_path, plugin_type, log) + + assert loaded_configs == [], "Should gracefully handle unreadable files" + + +@pytest.mark.anyio +async def test_load_plugin_configurations_improper_json(tmp_path: Path) -> None: + # Setup test environment + plugin_type = "downloaders" + root_path = tmp_path / "plugins_root" + config_path = root_path / "plugins" / plugin_type + config_path.mkdir(parents=True) + + # Create a config file with improper JSON + with open(config_path / "improper_json.conf", "w") as file: + file.write("{not: 'a valid json'}") + + # Test loading configurations + loaded_configs = await load_plugin_configurations(root_path, plugin_type, log) + + assert loaded_configs == [], "Should gracefully handle files with improper JSON" diff --git a/chia/_tests/core/full_node/test_full_node.py b/chia/_tests/core/full_node/test_full_node.py index 48dc4e46fb43..a1372ee09f42 100644 --- a/chia/_tests/core/full_node/test_full_node.py +++ b/chia/_tests/core/full_node/test_full_node.py @@ -185,12 +185,14 @@ async def test_block_compression(self, setup_two_nodes_and_wallet, empty_blockch await full_node_1.wait_for_wallet_synced(wallet_node=wallet_node_1, timeout=30) # Send a transaction to mempool - [tr] = await wallet.generate_signed_transaction( - tx_size, - ph, - DEFAULT_TX_CONFIG, - ) - [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + tx_size, + ph, + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -217,12 +219,14 @@ async def check_transaction_confirmed(transaction) -> bool: assert len((await full_node_1.get_all_full_blocks())[-1].transactions_generator_ref_list) == 0 # Send another tx - [tr] = await wallet.generate_signed_transaction( - 20000, - ph, - DEFAULT_TX_CONFIG, - ) - [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + 20000, + ph, + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -256,24 +260,28 @@ async def check_transaction_confirmed(transaction) -> bool: await full_node_1.wait_for_wallet_synced(wallet_node=wallet_node_1, timeout=30) # Send another 2 tx - [tr] = await wallet.generate_signed_transaction( - 30000, - ph, - DEFAULT_TX_CONFIG, - ) - [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + 30000, + ph, + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, tr.spend_bundle, tr.name, ) - [tr] = await wallet.generate_signed_transaction( - 40000, - ph, - DEFAULT_TX_CONFIG, - ) - [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + 40000, + ph, + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -281,12 +289,14 @@ async def check_transaction_confirmed(transaction) -> bool: tr.name, ) - [tr] = await wallet.generate_signed_transaction( - 50000, - ph, - DEFAULT_TX_CONFIG, - ) - [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + 50000, + ph, + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -294,12 +304,14 @@ async def check_transaction_confirmed(transaction) -> bool: tr.name, ) - [tr] = await wallet.generate_signed_transaction( - 3000000000000, - ph, - DEFAULT_TX_CONFIG, - ) - [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + 3000000000000, + ph, + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -325,11 +337,14 @@ async def check_transaction_confirmed(transaction) -> bool: assert num_blocks == 0 # Creates a standard_transaction and an anyone-can-spend tx - [tr] = await wallet.generate_signed_transaction( - 30000, - Program.to(1).get_tree_hash(), - DEFAULT_TX_CONFIG, - ) + async with wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await wallet.generate_signed_transaction( + 30000, + Program.to(1).get_tree_hash(), + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions extra_spend = SpendBundle( [ make_spend( @@ -371,11 +386,14 @@ async def check_transaction_confirmed(transaction) -> bool: assert len(all_blocks[-1].transactions_generator_ref_list) == 0 # Make a standard transaction and an anyone-can-spend transaction - [tr] = await wallet.generate_signed_transaction( - 30000, - Program.to(1).get_tree_hash(), - DEFAULT_TX_CONFIG, - ) + async with wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await wallet.generate_signed_transaction( + 30000, + Program.to(1).get_tree_hash(), + DEFAULT_TX_CONFIG, + action_scope, + ) + [tr] = action_scope.side_effects.transactions extra_spend = SpendBundle( [ make_spend( diff --git a/chia/_tests/core/full_node/test_transactions.py b/chia/_tests/core/full_node/test_transactions.py index 4be4bf65f37a..d6726552c04b 100644 --- a/chia/_tests/core/full_node/test_transactions.py +++ b/chia/_tests/core/full_node/test_transactions.py @@ -83,10 +83,11 @@ async def peak_height(fna: FullNodeAPI): await time_out_assert(20, peak_height, num_blocks, full_node_api_1) await time_out_assert(20, peak_height, num_blocks, full_node_api_2) - [tx] = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( - 10, ph1, DEFAULT_TX_CONFIG, 0 - ) - [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( + 10, ph1, DEFAULT_TX_CONFIG, action_scope, 0 + ) + [tx] = action_scope.side_effects.transactions await time_out_assert( 10, @@ -154,10 +155,11 @@ async def test_mempool_tx_sync(self, three_nodes_two_wallets, self_hostname, see ) await time_out_assert(20, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, funds) - [tx] = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( - 10, bytes32.random(seeded_random), DEFAULT_TX_CONFIG, 0 - ) - [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( + 10, bytes32.random(seeded_random), DEFAULT_TX_CONFIG, action_scope, 0 + ) + [tx] = action_scope.side_effects.transactions await time_out_assert( 10, diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index 5e16c69e50a2..64972d39729f 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -1622,10 +1622,11 @@ async def make_setup_and_coins( for _ in range(2): await farm_a_block(full_node_api, wallet_node, ph) other_recipients = [Payment(puzzle_hash=p, amount=uint64(200), memos=[]) for p in phs[1:]] - [tx] = await wallet.generate_signed_transaction( - uint64(200), phs[0], DEFAULT_TX_CONFIG, primaries=other_recipients - ) - [tx], _ = await wallet.wallet_state_manager.sign_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=False, sign=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(200), phs[0], DEFAULT_TX_CONFIG, action_scope, primaries=other_recipients + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await send_to_mempool(full_node_api, tx.spend_bundle) await farm_a_block(full_node_api, wallet_node, ph) @@ -1640,10 +1641,11 @@ async def make_setup_and_coins( wallet, coins, ph = await make_setup_and_coins(full_node_api, wallet_node) # Make sure spending AB then BC would generate a conflict for the latter - [tx_a] = await wallet.generate_signed_transaction(uint64(30), ph, DEFAULT_TX_CONFIG, coins={coins[0].coin}) - [tx_b] = await wallet.generate_signed_transaction(uint64(30), ph, DEFAULT_TX_CONFIG, coins={coins[1].coin}) - [tx_c] = await wallet.generate_signed_transaction(uint64(30), ph, DEFAULT_TX_CONFIG, coins={coins[2].coin}) - [tx_a, tx_b, tx_c], _ = await wallet.wallet_state_manager.sign_transactions([tx_a, tx_b, tx_c]) + async with wallet.wallet_state_manager.new_action_scope(push=False, merge_spends=False, sign=True) as action_scope: + await wallet.generate_signed_transaction(uint64(30), ph, DEFAULT_TX_CONFIG, action_scope, coins={coins[0].coin}) + await wallet.generate_signed_transaction(uint64(30), ph, DEFAULT_TX_CONFIG, action_scope, coins={coins[1].coin}) + await wallet.generate_signed_transaction(uint64(30), ph, DEFAULT_TX_CONFIG, action_scope, coins={coins[2].coin}) + [tx_a, tx_b, tx_c] = action_scope.side_effects.transactions assert tx_a.spend_bundle is not None assert tx_b.spend_bundle is not None assert tx_c.spend_bundle is not None @@ -1657,10 +1659,11 @@ async def make_setup_and_coins( # Make sure DE and EF would aggregate on E when E is eligible for deduplication # Create a coin with the identity puzzle hash - [tx] = await wallet.generate_signed_transaction( - uint64(200), IDENTITY_PUZZLE_HASH, DEFAULT_TX_CONFIG, coins={coins[3].coin} - ) - [tx], _ = await wallet.wallet_state_manager.sign_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=False, merge_spends=False, sign=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(200), IDENTITY_PUZZLE_HASH, DEFAULT_TX_CONFIG, action_scope, coins={coins[3].coin} + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await send_to_mempool(full_node_api, tx.spend_bundle) await farm_a_block(full_node_api, wallet_node, ph) @@ -1682,23 +1685,26 @@ async def make_setup_and_coins( message = b"Identical spend aggregation test" e_announcement = AssertCoinAnnouncement(asserted_id=e_coin_id, asserted_msg=message) # Create transactions D and F that consume an announcement created by E - [tx_d] = await wallet.generate_signed_transaction( - uint64(100), - ph, - DEFAULT_TX_CONFIG, - fee=uint64(0), - coins={coins[4].coin}, - extra_conditions=(e_announcement,), - ) - [tx_f] = await wallet.generate_signed_transaction( - uint64(150), - ph, - DEFAULT_TX_CONFIG, - fee=uint64(0), - coins={coins[5].coin}, - extra_conditions=(e_announcement,), - ) - [tx_d, tx_f], _ = await wallet.wallet_state_manager.sign_transactions([tx_d, tx_f]) + async with wallet.wallet_state_manager.new_action_scope(push=False, merge_spends=False, sign=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(100), + ph, + DEFAULT_TX_CONFIG, + action_scope, + fee=uint64(0), + coins={coins[4].coin}, + extra_conditions=(e_announcement,), + ) + await wallet.generate_signed_transaction( + uint64(150), + ph, + DEFAULT_TX_CONFIG, + action_scope, + fee=uint64(0), + coins={coins[5].coin}, + extra_conditions=(e_announcement,), + ) + [tx_d, tx_f] = action_scope.side_effects.transactions assert tx_d.spend_bundle is not None assert tx_f.spend_bundle is not None # Create transaction E now that spends e_coin to create another eligible @@ -1724,10 +1730,11 @@ async def make_setup_and_coins( sb_e2 = spend_bundle_from_conditions(conditions, e_coin) g_coin = coins[6].coin g_coin_id = g_coin.name() - [tx_g] = await wallet.generate_signed_transaction( - uint64(13), ph, DEFAULT_TX_CONFIG, coins={g_coin}, extra_conditions=(e_announcement,) - ) - [tx_g], _ = await wallet.wallet_state_manager.sign_transactions([tx_g]) + async with wallet.wallet_state_manager.new_action_scope(push=False, merge_spends=False, sign=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(13), ph, DEFAULT_TX_CONFIG, action_scope, coins={g_coin}, extra_conditions=(e_announcement,) + ) + [tx_g] = action_scope.side_effects.transactions assert tx_g.spend_bundle is not None sb_e2g = SpendBundle.aggregate([sb_e2, tx_g.spend_bundle]) sb_e2g_name = sb_e2g.name() diff --git a/chia/_tests/core/mempool/test_mempool_performance.py b/chia/_tests/core/mempool/test_mempool_performance.py index dad89e6bd06b..4531eae8b45d 100644 --- a/chia/_tests/core/mempool/test_mempool_performance.py +++ b/chia/_tests/core/mempool/test_mempool_performance.py @@ -46,8 +46,9 @@ async def test_mempool_update_performance( await time_out_assert(30, wallet_balance_at_least, True, wallet_node, send_amount + fee_amount) ph = await wallet.get_new_puzzlehash() - [big_transaction] = await wallet.generate_signed_transaction(send_amount, ph, DEFAULT_TX_CONFIG, fee_amount) - [big_transaction], _ = await wallet.wallet_state_manager.sign_transactions([big_transaction]) + async with wallet.wallet_state_manager.new_action_scope(push=False, sign=True) as action_scope: + await wallet.generate_signed_transaction(send_amount, ph, DEFAULT_TX_CONFIG, action_scope, fee_amount) + [big_transaction] = action_scope.side_effects.transactions assert big_transaction.spend_bundle is not None status, err = await full_node.add_transaction( big_transaction.spend_bundle, big_transaction.spend_bundle.name(), test=True diff --git a/chia/_tests/core/test_crawler.py b/chia/_tests/core/test_crawler.py index 0c5ad6452cb0..2f90b5e08afd 100644 --- a/chia/_tests/core/test_crawler.py +++ b/chia/_tests/core/test_crawler.py @@ -2,6 +2,7 @@ import logging import time +from datetime import datetime, timedelta from typing import cast import pytest @@ -20,15 +21,33 @@ from chia.util.ints import uint32, uint64, uint128 +@pytest.mark.anyio +async def test_crawler_loops_by_default(crawler_service: CrawlerService) -> None: + """ + Ensures that when the crawler is started with all defaults, the crawling loop is started + """ + crawler = crawler_service._node + assert crawler.crawl_task is not None + + +@pytest.mark.anyio +async def test_crawler_no_loops(crawler_service_no_loop: CrawlerService) -> None: + """ + Ensures that when the crawler is call in no loop mode, there is no loop running + """ + crawler = crawler_service_no_loop._node + assert crawler.crawl_task is None + + @pytest.mark.anyio async def test_unknown_messages( self_hostname: str, one_node: SimulatorsAndWalletsServices, - crawler_service: CrawlerService, + crawler_service_no_loop: CrawlerService, caplog: pytest.LogCaptureFixture, ) -> None: [full_node_service], _, _ = one_node - crawler = crawler_service._node + crawler = crawler_service_no_loop._node full_node = full_node_service._node assert await crawler.server.start_client( PeerInfo(self_hostname, cast(FullNodeAPI, full_node_service._api).server.get_port()), None @@ -48,11 +67,11 @@ def receiving_failed() -> bool: async def test_valid_message( self_hostname: str, one_node: SimulatorsAndWalletsServices, - crawler_service: CrawlerService, + crawler_service_no_loop: CrawlerService, caplog: pytest.LogCaptureFixture, ) -> None: [full_node_service], _, _ = one_node - crawler = crawler_service._node + crawler = crawler_service_no_loop._node full_node = full_node_service._node assert await crawler.server.start_client( PeerInfo(self_hostname, cast(FullNodeAPI, full_node_service._api).server.get_port()), None @@ -71,14 +90,14 @@ def peer_added() -> bool: @pytest.mark.anyio -async def test_crawler_to_db(crawler_service: CrawlerService, one_node: SimulatorsAndWalletsServices) -> None: +async def test_crawler_to_db(crawler_service_no_loop: CrawlerService, one_node: SimulatorsAndWalletsServices) -> None: """ This is a lot more of an integration test, but it tests the whole process. We add a node to the crawler, then we save it to the db and validate. """ [full_node_service], _, _ = one_node full_node = full_node_service._node - crawler = crawler_service._node + crawler = crawler_service_no_loop._node crawl_store = crawler.crawl_store assert crawl_store is not None peer_address = "127.0.0.1" @@ -103,6 +122,54 @@ async def test_crawler_to_db(crawler_service: CrawlerService, one_node: Simulato # add peer to the db & mark it as connected await crawl_store.add_peer(peer_record, peer_reliability) assert peer_record == crawl_store.host_to_records[peer_address] + await crawler.save_to_db() + good_peers = await crawl_store.get_good_peers() + assert good_peers == [peer_address] + - # validate the db data - await time_out_assert(20, crawl_store.get_good_peers, [peer_address]) +@pytest.mark.anyio +async def test_crawler_peer_cleanup( + crawler_service_no_loop: CrawlerService, one_node: SimulatorsAndWalletsServices +) -> None: + """ + This is a lot more of an integration test, but it tests the whole process. We add multiple nodes to the crawler, + then we save them to the db and validate. One of the nodes is older than the 90 day cutoff, so we also + call the prune function and ensure the node is deleted as expected + """ + [full_node_service], _, _ = one_node + full_node = full_node_service._node + crawler = crawler_service_no_loop._node + crawl_store = crawler.crawl_store + assert crawl_store is not None + peer_addresses = ["10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4", "10.0.0.5"] + + for idx, peer_address in enumerate(peer_addresses): + # create peer records + peer_record = PeerRecord( + peer_address, + peer_address, + uint32(full_node.server.get_port()), + False, + uint64(0), + uint32(0), + uint64(0), + uint64(int(time.time())), + uint64(int((datetime.now() - timedelta(days=idx * 10)).timestamp())), + "undefined", + uint64(0), + tls_version="unknown", + ) + peer_reliability = PeerReliability(peer_address, tries=1, successes=1) + + # add peer to the db & mark it as connected + await crawl_store.add_peer(peer_record, peer_reliability) + assert peer_record == crawl_store.host_to_records[peer_address] + + await crawler.save_to_db() + good_peers = await crawl_store.get_good_peers() + assert set(good_peers) == set(peer_addresses) + + await crawl_store.prune_old_peers(older_than_days=31) + assert 4 == len(crawl_store.host_to_records) + good_peers = await crawl_store.get_good_peers() + assert set(good_peers) == {"10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4"}, good_peers diff --git a/chia/_tests/core/test_rpc_util.py b/chia/_tests/core/test_rpc_util.py index 8153459314c7..8fad668388cc 100644 --- a/chia/_tests/core/test_rpc_util.py +++ b/chia/_tests/core/test_rpc_util.py @@ -81,7 +81,7 @@ async def test_rpc_endpoint(self: None, request: TestClvmRequestType) -> TestClv assert await test_rpc_endpoint( None, { - "sub": "ff81ff80", + "sub": "ffff83717578818180", "CHIP-0029": True, }, - ) == {"sub": "ff81ff80"} + ) == {"sub": "ffff83717578818180"} diff --git a/chia/_tests/plot_sync/test_receiver.py b/chia/_tests/plot_sync/test_receiver.py index f5602067550d..7920d128824e 100644 --- a/chia/_tests/plot_sync/test_receiver.py +++ b/chia/_tests/plot_sync/test_receiver.py @@ -131,7 +131,7 @@ def post_function_validate(receiver: Receiver, data: Union[List[Plot], List[str] async def run_sync_step(receiver: Receiver, sync_step: SyncStepData) -> None: assert receiver.current_sync().state == sync_step.state last_sync_time_before = receiver._last_sync.time_done - # For the the list types invoke the trigger function in batches + # For the list types invoke the trigger function in batches if sync_step.payload_type == PlotSyncPlotList or sync_step.payload_type == PlotSyncPathList: step_data, _ = sync_step.args assert len(step_data) == 10 diff --git a/chia/_tests/pools/test_pool_rpc.py b/chia/_tests/pools/test_pool_rpc.py index 30d6d992234f..c5412f876315 100644 --- a/chia/_tests/pools/test_pool_rpc.py +++ b/chia/_tests/pools/test_pool_rpc.py @@ -533,8 +533,8 @@ async def test_absorb_self_multiple_coins( assert bal["confirmed_wallet_balance"] == pool_expected_confirmed_balance # Claim - absorb_tx: TransactionRecord = (await client.pw_absorb_rewards(2, uint64(fee), 1))["transaction"] - await full_node_api.process_transaction_records(records=[absorb_tx]) + absorb_txs: List[TransactionRecord] = (await client.pw_absorb_rewards(2, uint64(fee), 1))["transactions"] + await full_node_api.process_transaction_records(records=absorb_txs) main_expected_confirmed_balance -= fee main_expected_confirmed_balance += 1_750_000_000_000 pool_expected_confirmed_balance -= 1_750_000_000_000 @@ -602,13 +602,14 @@ async def farming_to_pool() -> bool: # Claim block_count * 1.75 ret = await client.pw_absorb_rewards(2, uint64(fee)) - absorb_tx: TransactionRecord = ret["transaction"] + absorb_txs: List[TransactionRecord] = ret["transactions"] if fee == 0: assert ret["fee_transaction"] is None else: assert ret["fee_transaction"].fee_amount == fee - assert absorb_tx.fee_amount == fee - await full_node_api.process_transaction_records(records=[absorb_tx]) + for tx in absorb_txs: + assert tx.fee_amount == fee + await full_node_api.process_transaction_records(records=absorb_txs) main_expected_confirmed_balance -= fee main_expected_confirmed_balance += block_count * 1_750_000_000_000 @@ -821,7 +822,7 @@ async def status_is_farming_to_pool() -> bool: leave_pool_tx: Dict[str, Any] = await client.pw_self_pool(wallet_id, uint64(fee)) assert leave_pool_tx["transaction"].wallet_id == wallet_id assert leave_pool_tx["transaction"].amount == 1 - await full_node_api.wait_transaction_records_entered_mempool(records=[leave_pool_tx["transaction"]]) + await full_node_api.wait_transaction_records_entered_mempool(records=leave_pool_tx["transactions"]) await full_node_api.farm_blocks_to_puzzlehash(count=1, farm_to=our_ph, guarantee_transaction_blocks=True) @@ -896,6 +897,7 @@ async def status_is_farming_to_pool() -> bool: assert pw_info.current.pool_url == "https://pool-a.org" assert pw_info.current.relative_lock_height == 5 + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) join_pool_tx: TransactionRecord = ( await client.pw_join_pool( wallet_id, @@ -962,7 +964,7 @@ async def status_is_farming_to_pool() -> bool: assert pw_info.current.pool_url == "https://pool-a.org" assert pw_info.current.relative_lock_height == 5 - join_pool_tx: TransactionRecord = ( + join_pool_txs: List[TransactionRecord] = ( await client.pw_join_pool( wallet_id, pool_b_ph, @@ -970,9 +972,8 @@ async def status_is_farming_to_pool() -> bool: uint32(10), uint64(fee), ) - )["transaction"] - assert join_pool_tx is not None - await full_node_api.wait_transaction_records_entered_mempool(records=[join_pool_tx]) + )["transactions"] + await full_node_api.wait_transaction_records_entered_mempool(records=join_pool_txs) await full_node_api.farm_blocks_to_puzzlehash(count=1, farm_to=our_ph, guarantee_transaction_blocks=True) async def status_is_leaving_no_blocks() -> bool: @@ -990,7 +991,7 @@ async def status_is_leaving_no_blocks() -> bool: force_overflow=True, guarantee_transaction_block=True, seed=32 * b"4", - transaction_data=join_pool_tx.spend_bundle, + transaction_data=next(tx.spend_bundle for tx in join_pool_txs if tx.spend_bundle is not None), ) for block in more_blocks[-3:]: diff --git a/chia/_tests/simulation/test_simulation.py b/chia/_tests/simulation/test_simulation.py index f9e097d67971..acd3e4880639 100644 --- a/chia/_tests/simulation/test_simulation.py +++ b/chia/_tests/simulation/test_simulation.py @@ -212,13 +212,15 @@ async def test_simulator_auto_farm_and_get_coins( await time_out_assert(10, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds) - [tx] = await wallet.generate_signed_transaction( - uint64(10), - await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash(), - DEFAULT_TX_CONFIG, - uint64(0), - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(10), + await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash(), + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) + [tx] = await wallet.wallet_state_manager.add_pending_transactions(action_scope.side_effects.transactions) # wait till out of mempool await time_out_assert(10, full_node_api.full_node.mempool_manager.get_spendbundle, None, tx.name) # wait until the transaction is confirmed @@ -387,15 +389,17 @@ async def test_wait_transaction_records_entered_mempool( # repeating just to try to expose any flakiness for coin in coins: - [tx] = await wallet.generate_signed_transaction( - amount=uint64(tx_amount), - puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), - tx_config=DEFAULT_TX_CONFIG, - coins={coin}, - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + amount=uint64(tx_amount), + puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), + tx_config=DEFAULT_TX_CONFIG, + action_scope=action_scope, + coins={coin}, + ) - await full_node_api.wait_transaction_records_entered_mempool(records=[tx]) + [tx] = action_scope.side_effects.transactions + await full_node_api.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) assert tx.spend_bundle is not None assert full_node_api.full_node.mempool_manager.get_spendbundle(tx.spend_bundle.name()) is not None # TODO: this fails but it seems like it shouldn't when above passes @@ -432,21 +436,20 @@ async def test_process_transactions( # repeating just to try to expose any flakiness for repeat in range(repeats): coins = [next(coins_iter) for _ in range(tx_per_repeat)] - transactions = [ - ( + async with wallet.wallet_state_manager.new_action_scope(push=True, merge_spends=False) as action_scope: + for coin in coins: await wallet.generate_signed_transaction( amount=uint64(tx_amount), puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), tx_config=DEFAULT_TX_CONFIG, + action_scope=action_scope, coins={coin}, ) - )[0] - for coin in coins - ] - for tx in transactions: + + for tx in action_scope.side_effects.transactions: assert tx.spend_bundle is not None, "the above created transaction is missing the expected spend bundle" - transactions = await wallet.wallet_state_manager.add_pending_transactions(transactions) + transactions = action_scope.side_effects.transactions if records_or_bundles_or_coins == "records": await full_node_api.process_transaction_records(records=transactions) diff --git a/chia/_tests/simulation/test_simulator.py b/chia/_tests/simulation/test_simulator.py index aaad90485728..4a60ea7c96f6 100644 --- a/chia/_tests/simulation/test_simulator.py +++ b/chia/_tests/simulation/test_simulator.py @@ -127,15 +127,17 @@ async def test_wait_transaction_records_entered_mempool( # repeating just to try to expose any flakiness for coin in coins: - [tx] = await wallet.generate_signed_transaction( - amount=uint64(tx_amount), - puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), - tx_config=DEFAULT_TX_CONFIG, - coins={coin}, - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) - - await full_node_api.wait_transaction_records_entered_mempool(records=[tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + amount=uint64(tx_amount), + puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), + tx_config=DEFAULT_TX_CONFIG, + action_scope=action_scope, + coins={coin}, + ) + + [tx] = action_scope.side_effects.transactions + await full_node_api.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) assert tx.spend_bundle is not None assert full_node_api.full_node.mempool_manager.get_spendbundle(tx.spend_bundle.name()) is not None @@ -162,15 +164,16 @@ async def test_process_transaction_records( # repeating just to try to expose any flakiness for coin in coins: - [tx] = await wallet.generate_signed_transaction( - amount=uint64(tx_amount), - puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), - tx_config=DEFAULT_TX_CONFIG, - coins={coin}, - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) - - await full_node_api.process_transaction_records(records=[tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + amount=uint64(tx_amount), + puzzle_hash=await wallet_node.wallet_state_manager.main_wallet.get_new_puzzlehash(), + tx_config=DEFAULT_TX_CONFIG, + action_scope=action_scope, + coins={coin}, + ) + + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) assert full_node_api.full_node.coin_store.get_coin_record(coin.name()) is not None diff --git a/chia/_tests/wallet/cat_wallet/test_cat_wallet.py b/chia/_tests/wallet/cat_wallet/test_cat_wallet.py index 2cf918ef040b..babc91214665 100644 --- a/chia/_tests/wallet/cat_wallet/test_cat_wallet.py +++ b/chia/_tests/wallet/cat_wallet/test_cat_wallet.py @@ -71,20 +71,21 @@ async def test_cat_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA await time_out_assert(20, wallet.get_confirmed_balance, funds) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG, + action_scope, fee=uint64(10), ) # The next 2 lines are basically a noop, it just adds test coverage cat_wallet = await CATWallet.create(wallet_node.wallet_state_manager, wallet, cat_wallet.wallet_info) await wallet_node.wallet_state_manager.add_new_wallet(cat_wallet) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 100) await time_out_assert(20, cat_wallet.get_spendable_balance, 100) @@ -138,12 +139,23 @@ async def test_cat_creation_unique_lineage_store(self_hostname: str, two_wallet_ await time_out_assert(20, wallet.get_confirmed_balance, funds) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) - async with wallet_node.wallet_state_manager.lock: - cat_wallet_1, _ = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_1 = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) - cat_wallet_2, _ = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(200), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_2 = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(200), + DEFAULT_TX_CONFIG, + action_scope, ) proofs_1 = await cat_wallet_1.lineage_store.get_all_lineage_proofs() @@ -186,9 +198,14 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: "cat": 2, } - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) await wallet_environments.process_pending_states( @@ -245,15 +262,16 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: assert cat_wallet.cat_info.limitations_program_hash == cat_wallet_2.cat_info.limitations_program_hash cat_2_hash = await cat_wallet_2.get_new_inner_hash() - tx_records = await cat_wallet.generate_signed_transaction( - [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, fee=uint64(1) - ) + async with cat_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, action_scope, fee=uint64(1) + ) tx_id = None - tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) - for tx_record in tx_records: - if tx_record.wallet_id is cat_wallet.id(): - tx_id = tx_record.name.hex() + for tx_record in action_scope.side_effects.transactions: + if tx_record.wallet_id == cat_wallet.id(): assert tx_record.to_puzzle_hash == cat_2_hash + if tx_record.spend_bundle is not None: + tx_id = tx_record.name.hex() assert tx_id is not None memos = await api_0.get_transaction_memo({"transaction_id": tx_id}) assert len(memos[tx_id]) == 2 # One for tx, one for change @@ -336,8 +354,8 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: assert len(memos[tx_id]) == 2 assert list(memos[tx_id].values())[0][0] == cat_2_hash.hex() cat_hash = await cat_wallet.get_new_inner_hash() - tx_records = await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash], DEFAULT_TX_CONFIG) - tx_records = await wallet2.wallet_state_manager.add_pending_transactions(tx_records) + async with cat_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash], DEFAULT_TX_CONFIG, action_scope) await wallet_environments.process_pending_states( [ @@ -434,11 +452,16 @@ async def test_cat_reuse_address(self_hostname: str, two_wallet_nodes: OldSimula await time_out_assert(20, wallet.get_confirmed_balance, funds) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 100) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 100) @@ -451,11 +474,12 @@ async def test_cat_reuse_address(self_hostname: str, two_wallet_nodes: OldSimula assert cat_wallet.cat_info.limitations_program_hash == cat_wallet_2.cat_info.limitations_program_hash cat_2_hash = await cat_wallet_2.get_new_inner_hash() - tx_records = await cat_wallet.generate_signed_transaction( - [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG.override(reuse_puzhash=True), fee=uint64(1) - ) - tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) - for tx_record in tx_records: + async with cat_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG.override(reuse_puzhash=True), action_scope, fee=uint64(1) + ) + + for tx_record in action_scope.side_effects.transactions: if tx_record.wallet_id is cat_wallet.id(): assert tx_record.to_puzzle_hash == cat_2_hash assert tx_record.spend_bundle is not None @@ -468,7 +492,7 @@ async def test_cat_reuse_address(self_hostname: str, two_wallet_nodes: OldSimula new_puzhash = [c.puzzle_hash.hex() for c in tx_record.additions] assert old_puzhash in new_puzhash - await time_out_assert(15, full_node_api.txs_in_mempool, True, tx_records) + await time_out_assert(15, full_node_api.txs_in_mempool, True, action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_pending_change_balance, 40) @@ -484,10 +508,10 @@ async def test_cat_reuse_address(self_hostname: str, two_wallet_nodes: OldSimula await time_out_assert(30, cat_wallet_2.get_unconfirmed_balance, 60) cat_hash = await cat_wallet.get_new_inner_hash() - tx_records = await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash], DEFAULT_TX_CONFIG) - tx_records = await wallet2.wallet_state_manager.add_pending_transactions(tx_records) + async with cat_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash], DEFAULT_TX_CONFIG, action_scope) - await time_out_assert(15, full_node_api.txs_in_mempool, True, tx_records) + await time_out_assert(15, full_node_api.txs_in_mempool, True, action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) @@ -531,9 +555,14 @@ async def test_get_wallet_for_asset_id( await time_out_assert(20, wallet.get_confirmed_balance, funds) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, _ = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) for _ in range(1, num_blocks): @@ -585,11 +614,16 @@ async def test_cat_doesnt_see_eve(self_hostname: str, two_wallet_nodes: OldSimul await time_out_assert(20, wallet.get_confirmed_balance, funds) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 100) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 100) @@ -602,11 +636,11 @@ async def test_cat_doesnt_see_eve(self_hostname: str, two_wallet_nodes: OldSimul assert cat_wallet.cat_info.limitations_program_hash == cat_wallet_2.cat_info.limitations_program_hash cat_2_hash = await cat_wallet_2.get_new_inner_hash() - tx_records = await cat_wallet.generate_signed_transaction( - [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, fee=uint64(1) - ) - tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) - await full_node_api.process_transaction_records(records=tx_records) + async with cat_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, action_scope, fee=uint64(1) + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, wallet.get_confirmed_balance, funds - 101) await time_out_assert(30, wallet.get_unconfirmed_balance, funds - 101) @@ -618,11 +652,11 @@ async def test_cat_doesnt_see_eve(self_hostname: str, two_wallet_nodes: OldSimul await time_out_assert(20, cat_wallet_2.get_unconfirmed_balance, 60) cc2_ph = await cat_wallet_2.get_new_cat_puzzle_hash() - [tx_record] = await wallet.wallet_state_manager.main_wallet.generate_signed_transaction( - uint64(10), cc2_ph, DEFAULT_TX_CONFIG - ) - [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) - await full_node_api.process_transaction_records(records=[tx_record]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.wallet_state_manager.main_wallet.generate_signed_transaction( + uint64(10), cc2_ph, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) id = cat_wallet_2.id() wsm = cat_wallet_2.wallet_state_manager @@ -675,15 +709,16 @@ async def test_cat_spend_multiple( await time_out_assert(20, wallet_0.get_confirmed_balance, funds) - async with wallet_node_0.wallet_state_manager.lock: - cat_wallet_0, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_0 = await CATWallet.create_new_cat_wallet( wallet_node_0.wallet_state_manager, wallet_0, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG, + action_scope, ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet_0.get_confirmed_balance, 100) await time_out_assert(20, cat_wallet_0.get_unconfirmed_balance, 100) @@ -701,11 +736,11 @@ async def test_cat_spend_multiple( cat_1_hash = await cat_wallet_1.get_new_inner_hash() cat_2_hash = await cat_wallet_2.get_new_inner_hash() - tx_records = await cat_wallet_0.generate_signed_transaction( - [uint64(60), uint64(20)], [cat_1_hash, cat_2_hash], DEFAULT_TX_CONFIG - ) - tx_records = await wallet_0.wallet_state_manager.add_pending_transactions(tx_records) - await full_node_api.process_transaction_records(records=tx_records) + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction( + [uint64(60), uint64(20)], [cat_1_hash, cat_2_hash], DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet_0.get_confirmed_balance, 20) await time_out_assert(20, cat_wallet_0.get_unconfirmed_balance, 20) @@ -718,13 +753,15 @@ async def test_cat_spend_multiple( cat_hash = await cat_wallet_0.get_new_inner_hash() - tx_records = await cat_wallet_1.generate_signed_transaction([uint64(15)], [cat_hash], DEFAULT_TX_CONFIG) - tx_records = await wallet_1.wallet_state_manager.add_pending_transactions(tx_records) + async with cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_1.generate_signed_transaction([uint64(15)], [cat_hash], DEFAULT_TX_CONFIG, action_scope) - tx_records_2 = await cat_wallet_2.generate_signed_transaction([uint64(20)], [cat_hash], DEFAULT_TX_CONFIG) - tx_records_2 = await wallet_2.wallet_state_manager.add_pending_transactions(tx_records_2) + async with cat_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope_2: + await cat_wallet_2.generate_signed_transaction([uint64(20)], [cat_hash], DEFAULT_TX_CONFIG, action_scope_2) - await full_node_api.process_transaction_records(records=[*tx_records, *tx_records_2]) + await full_node_api.process_transaction_records( + records=[*action_scope.side_effects.transactions, *action_scope_2.side_effects.transactions] + ) await time_out_assert(20, cat_wallet_0.get_confirmed_balance, 55) await time_out_assert(20, cat_wallet_0.get_unconfirmed_balance, 55) @@ -737,16 +774,21 @@ async def test_cat_spend_multiple( txs = await wallet_1.wallet_state_manager.tx_store.get_transactions_between(cat_wallet_1.id(), 0, 100000) # Test with Memo - tx_records_3 = await cat_wallet_1.generate_signed_transaction( - [uint64(30)], [cat_hash], DEFAULT_TX_CONFIG, memos=[[b"Markus Walburg"]] - ) - with pytest.raises(ValueError): + async with cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: await cat_wallet_1.generate_signed_transaction( - [uint64(30)], [cat_hash], DEFAULT_TX_CONFIG, memos=[[b"too"], [b"many"], [b"memos"]] + [uint64(30)], [cat_hash], DEFAULT_TX_CONFIG, action_scope, memos=[[b"Markus Walburg"]] ) + with pytest.raises(ValueError): + async with cat_wallet_1.wallet_state_manager.new_action_scope(push=False) as failed_action_scope: + await cat_wallet_1.generate_signed_transaction( + [uint64(30)], + [cat_hash], + DEFAULT_TX_CONFIG, + failed_action_scope, + memos=[[b"too"], [b"many"], [b"memos"]], + ) - tx_records_3 = await wallet_1.wallet_state_manager.add_pending_transactions(tx_records_3) - await time_out_assert(15, full_node_api.txs_in_mempool, True, tx_records_3) + await time_out_assert(15, full_node_api.txs_in_mempool, True, action_scope.side_effects.transactions) txs = await wallet_1.wallet_state_manager.tx_store.get_transactions_between(cat_wallet_1.id(), 0, 100000) for tx in txs: if tx.amount == 30: @@ -790,11 +832,16 @@ async def test_cat_max_amount_send( await time_out_assert(20, wallet.get_confirmed_balance, funds) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100000), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100000), + DEFAULT_TX_CONFIG, + action_scope, ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 100000) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 100000) @@ -809,11 +856,11 @@ async def test_cat_max_amount_send( amounts.append(uint64(i)) puzzle_hashes.append(cat_2_hash) spent_coint = (await cat_wallet.get_cat_spendable_coins())[0].coin - tx_records = await cat_wallet.generate_signed_transaction( - amounts, puzzle_hashes, DEFAULT_TX_CONFIG, coins={spent_coint} - ) - tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) - await full_node_api.process_transaction_records(records=tx_records) + async with cat_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet.generate_signed_transaction( + amounts, puzzle_hashes, DEFAULT_TX_CONFIG, action_scope, coins={spent_coint} + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await asyncio.sleep(2) @@ -834,20 +881,23 @@ async def check_all_there() -> bool: max_sent_amount = await cat_wallet.get_max_send_amount() # 1) Generate transaction that is under the limit - [transaction_record] = await cat_wallet.generate_signed_transaction( - [uint64(max_sent_amount - 1)], [ph], DEFAULT_TX_CONFIG - ) - assert transaction_record.amount == uint64(max_sent_amount - 1) + async with cat_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(max_sent_amount - 1)], [ph], DEFAULT_TX_CONFIG, action_scope + ) + assert action_scope.side_effects.transactions[0].amount == uint64(max_sent_amount - 1) # 2) Generate transaction that is equal to limit - [transaction_record] = await cat_wallet.generate_signed_transaction( - [uint64(max_sent_amount)], [ph], DEFAULT_TX_CONFIG - ) - assert transaction_record.amount == uint64(max_sent_amount) + async with cat_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await cat_wallet.generate_signed_transaction([uint64(max_sent_amount)], [ph], DEFAULT_TX_CONFIG, action_scope) + assert action_scope.side_effects.transactions[0].amount == uint64(max_sent_amount) # 3) Generate transaction that is greater than limit with pytest.raises(ValueError): - await cat_wallet.generate_signed_transaction([uint64(max_sent_amount + 1)], [ph], DEFAULT_TX_CONFIG) + async with cat_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(max_sent_amount + 1)], [ph], DEFAULT_TX_CONFIG, action_scope + ) @pytest.mark.parametrize("trusted", [True, False]) @@ -887,24 +937,28 @@ async def test_cat_hint( await time_out_assert(20, wallet.get_confirmed_balance, funds) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 100) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 100) assert cat_wallet.cat_info.limitations_program_hash is not None cat_2_hash = await wallet2.get_new_puzzlehash() - tx_records = await cat_wallet.generate_signed_transaction( - [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, memos=[[cat_2_hash]] - ) - - tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, action_scope, memos=[[cat_2_hash]] + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 40) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 40) @@ -926,13 +980,12 @@ async def test_cat_hint( } # Then we send another transaction - tx_records = await cat_wallet.generate_signed_transaction( - [uint64(10)], [cat_2_hash], DEFAULT_TX_CONFIG, memos=[[cat_2_hash]] - ) - - tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet.generate_signed_transaction( + [uint64(10)], [cat_2_hash], DEFAULT_TX_CONFIG, action_scope, memos=[[cat_2_hash]] + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 30) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 30) @@ -947,10 +1000,10 @@ async def test_cat_hint( await time_out_assert(30, cat_wallet_2.get_unconfirmed_balance, 70) cat_hash = await cat_wallet.get_new_inner_hash() - tx_records = await cat_wallet_2.generate_signed_transaction([uint64(5)], [cat_hash], DEFAULT_TX_CONFIG) - tx_records = await wallet2.wallet_state_manager.add_pending_transactions(tx_records) + async with cat_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_2.generate_signed_transaction([uint64(5)], [cat_hash], DEFAULT_TX_CONFIG, action_scope) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(20, cat_wallet.get_confirmed_balance, 35) await time_out_assert(20, cat_wallet.get_unconfirmed_balance, 35) diff --git a/chia/_tests/wallet/cat_wallet/test_trades.py b/chia/_tests/wallet/cat_wallet/test_trades.py index d0af2f49eda7..dbb65e92b913 100644 --- a/chia/_tests/wallet/cat_wallet/test_trades.py +++ b/chia/_tests/wallet/cat_wallet/test_trades.py @@ -140,12 +140,22 @@ async def test_cat_trades( } # Mint some DIDs - did_wallet_maker = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), wallet_environments.tx_config - ) - did_wallet_taker = await DIDWallet.create_new_did_wallet( - wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), wallet_environments.tx_config - ) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, + wallet_maker, + uint64(1), + wallet_environments.tx_config, + action_scope, + ) + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_taker.wallet_state_manager, + wallet_taker, + uint64(1), + wallet_environments.tx_config, + action_scope, + ) did_id_maker = bytes32.from_hexstr(did_wallet_maker.get_my_DID()) did_id_taker = bytes32.from_hexstr(did_wallet_taker.get_my_DID()) @@ -313,22 +323,24 @@ async def test_cat_trades( } # Mint some standard CATs - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, uint64(100), wallet_environments.tx_config, + action_scope, ) - async with wallet_node_taker.wallet_state_manager.lock: - new_cat_wallet_taker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + new_cat_wallet_taker = await CATWallet.create_new_cat_wallet( wallet_node_taker.wallet_state_manager, wallet_taker, {"identifier": "genesis_by_id"}, uint64(100), wallet_environments.tx_config, + action_scope, ) await wallet_environments.process_pending_states( @@ -462,9 +474,10 @@ async def test_cat_trades( taker_unused_index = taker_unused_dr.index # Execute all of the trades # chia_for_cat - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - chia_for_cat, wallet_environments.tx_config, fee=uint64(1) - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, wallet_environments.tx_config, action_scope, fee=uint64(1) + ) assert error is None assert success is True assert trade_make is not None @@ -473,17 +486,16 @@ async def test_cat_trades( [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - wallet_environments.tx_config, - fee=uint64(1), - ) - tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + maker_offer, + peer, + wallet_environments.tx_config, + action_scope, + fee=uint64(1), + ) if test_aggregation: first_offer = Offer.from_bytes(trade_take.offer) @@ -682,9 +694,10 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num ) # cat_for_chia - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - cat_for_chia, wallet_environments.tx_config - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + cat_for_chia, wallet_environments.tx_config, action_scope + ) assert error is None assert success is True assert trade_make is not None @@ -692,20 +705,19 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - wallet_environments.tx_config, - fee=uint64(1), - ) - tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + wallet_environments.tx_config, + action_scope, + fee=uint64(1), + ) # Testing a precious display bug real quick - xch_tx: TransactionRecord = next(tx for tx in tx_records if tx.wallet_id == 1) + xch_tx: TransactionRecord = next(tx for tx in action_scope.side_effects.transactions if tx.wallet_id == 1) assert xch_tx.amount == 3 assert xch_tx.fee_amount == 1 @@ -813,26 +825,25 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num ) assert taker_unused_dr is not None taker_unused_index = taker_unused_dr.index - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - cat_for_cat, wallet_environments.tx_config - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + cat_for_cat, wallet_environments.tx_config, action_scope + ) assert error is None assert success is True assert trade_make is not None [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - wallet_environments.tx_config, - ) - tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + wallet_environments.tx_config, + action_scope, + ) if test_aggregation: second_offer = Offer.from_bytes(trade_take.offer) @@ -893,8 +904,8 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num }, "new cat": { "unconfirmed_wallet_balance": -6, - "<=#spendable_balance": -6, "pending_change": 92, + "<=#spendable_balance": -6, "<=#max_send_amount": -6, "pending_coin_removal_count": 1, }, @@ -1012,11 +1023,13 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num assert taker_unused_index < taker_unused_dr.index # chia_for_multiple_cat - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - chia_for_multiple_cat, - wallet_environments.tx_config, - driver_dict=driver_dict, - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_multiple_cat, + wallet_environments.tx_config, + action_scope, + driver_dict=driver_dict, + ) assert error is None assert success is True assert trade_make is not None @@ -1024,17 +1037,15 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - wallet_environments.tx_config, - ) - tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + wallet_environments.tx_config, + action_scope, + ) if test_aggregation: third_offer = Offer.from_bytes(trade_take.offer) @@ -1116,16 +1127,16 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num }, "cat": { "unconfirmed_wallet_balance": -8, + "pending_change": 1, "<=#spendable_balance": -8, "<=#max_send_amount": -8, - "pending_change": 1, "pending_coin_removal_count": 2, # For the first time, we're using two coins in an offer }, "new cat": { "unconfirmed_wallet_balance": -9, + "pending_change": 83, "<=#spendable_balance": -9, "<=#max_send_amount": -9, - "pending_change": 83, "pending_coin_removal_count": 1, }, **( @@ -1147,17 +1158,17 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num }, "cat": { "confirmed_wallet_balance": -8, + "pending_change": -1, ">#spendable_balance": 0, ">#max_send_amount": 0, - "pending_change": -1, "pending_coin_removal_count": -2, "unspent_coin_count": -1, }, "new cat": { "confirmed_wallet_balance": -9, + "pending_change": -83, ">#spendable_balance": 0, ">#max_send_amount": 0, - "pending_change": -83, "pending_coin_removal_count": -1, }, **( @@ -1273,27 +1284,27 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num ) # multiple_cat_for_chia - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - multiple_cat_for_chia, - wallet_environments.tx_config, - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + multiple_cat_for_chia, + wallet_environments.tx_config, + action_scope, + ) assert error is None assert success is True assert trade_make is not None [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - wallet_environments.tx_config, - ) - tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + wallet_environments.tx_config, + action_scope, + ) if test_aggregation: fourth_offer = Offer.from_bytes(trade_take.offer) @@ -1405,10 +1416,12 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, trade_take) # chia_and_cat_for_cat - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - chia_and_cat_for_cat, - wallet_environments.tx_config, - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_and_cat_for_cat, + wallet_environments.tx_config, + action_scope, + ) assert error is None assert success is True assert trade_make is not None @@ -1416,17 +1429,15 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - wallet_environments.tx_config, - ) - tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + wallet_environments.tx_config, + action_scope, + ) if test_aggregation: fifth_offer = Offer.from_bytes(trade_take.offer) @@ -1495,9 +1506,9 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num }, "new cat": { "unconfirmed_wallet_balance": -15, + "pending_change": 68, "<=#spendable_balance": -15, "<=#max_send_amount": -15, - "pending_change": 68, "pending_coin_removal_count": 1, }, **( @@ -1525,9 +1536,9 @@ async def assert_trade_tx_number(wallet_node: WalletNode, trade_id: bytes32, num }, "new cat": { "confirmed_wallet_balance": -15, + "pending_change": -68, ">#spendable_balance": 0, ">#max_send_amount": 0, - "pending_change": -68, "pending_coin_removal_count": -1, }, **( @@ -1617,16 +1628,17 @@ async def test_trade_cancellation( xch_to_cat_amount = uint64(100) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, xch_to_cat_amount, DEFAULT_TX_CONFIG, + action_scope, ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, xch_to_cat_amount) await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, xch_to_cat_amount) @@ -1646,15 +1658,19 @@ async def test_trade_cancellation( trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + cat_for_chia, DEFAULT_TX_CONFIG, action_scope + ) assert error is None assert success is True assert trade_make is not None # Cancelling the trade and trying an ID that doesn't exist just in case - await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id, bytes32([0] * 32)], DEFAULT_TX_CONFIG, secure=False - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id, bytes32([0] * 32)], DEFAULT_TX_CONFIG, action_scope, secure=False + ) await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make) # Due to current mempool rules, trying to force a take out of the mempool with a cancel will not work. @@ -1663,11 +1679,11 @@ async def test_trade_cancellation( # [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( # [Offer.from_bytes(trade_make.offer)] # ) - # trade_take, tx_records = await trade_manager_taker.respond_to_offer( + # trade_take = await trade_manager_taker.respond_to_offer( # maker_offer, # ) # tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( - # tx_records, + # action_scope.side_effects.transactions, # additional_signing_responses=signing_response, # ) # await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) @@ -1683,17 +1699,16 @@ async def test_trade_cancellation( fee = uint64(2_000_000_000_000) - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=fee, secure=True - ) - txs = await wallet_maker.wallet_state_manager.add_pending_transactions(txs) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=fee, secure=True + ) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - assert txs is not None - await full_node.process_transaction_records(records=txs) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) sum_of_outgoing = uint64(0) sum_of_incoming = uint64(0) - for tx in txs: + for tx in action_scope.side_effects.transactions: if tx.type == TransactionType.OUTGOING_TX.value: sum_of_outgoing = uint64(sum_of_outgoing + tx.amount) elif tx.type == TransactionType.INCOMING_TX.value: @@ -1710,10 +1725,16 @@ async def test_trade_cancellation( peer = wallet_node_taker.get_full_node_peer() with pytest.raises(ValueError, match="This offer is no longer valid"): - await trade_manager_taker.respond_to_offer(Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG) + async with trade_manager_taker.wallet_state_manager.new_action_scope(push=False) as action_scope: + await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope + ) # Now we're going to create the other way around for test coverage sake - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, DEFAULT_TX_CONFIG, action_scope + ) assert error is None assert success is True assert trade_make is not None @@ -1723,15 +1744,17 @@ async def test_trade_cancellation( ValueError, match=f"Do not have a wallet for asset ID: {cat_wallet_maker.get_asset_id()} to fulfill offer", ): - await trade_manager_taker.respond_to_offer(Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG) + async with trade_manager_taker.wallet_state_manager.new_action_scope(push=False) as action_scope: + await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope + ) - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True - ) - txs = await wallet_maker.wallet_state_manager.add_pending_transactions(txs) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=uint64(0), secure=True + ) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - assert txs is not None - await full_node.process_transaction_records(records=txs) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make) @@ -1743,21 +1766,26 @@ async def test_trade_cancellation( } # Now we're going to create the other way around for test coverage sake - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - chia_and_cat_for_something, - DEFAULT_TX_CONFIG, - driver_dict={bytes32([0] * 32): PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + bytes(32).hex()})}, - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_and_cat_for_something, + DEFAULT_TX_CONFIG, + action_scope, + driver_dict={bytes32([0] * 32): PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + bytes(32).hex()})}, + ) assert error is None assert success is True assert trade_make is not None - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=uint64(0), secure=True + ) # Check an announcement ring has been created - total_spend = SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]) + total_spend = SpendBundle.aggregate( + [tx.spend_bundle for tx in action_scope.side_effects.transactions if tx.spend_bundle is not None] + ) all_conditions: List[Program] = [] creations: List[CreateCoinAnnouncement] = [] announcement_nonce = std_hash(trade_make.trade_id) @@ -1774,9 +1802,8 @@ async def test_trade_cancellation( for creation in creations: assert creation.corresponding_assertion().to_program() in all_conditions - txs = await wallet_maker.wallet_state_manager.add_pending_transactions(txs) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - await full_node.process_transaction_records(records=txs) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make) @@ -1791,16 +1818,17 @@ async def test_trade_cancellation_balance_check( xch_to_cat_amount = uint64(100) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, xch_to_cat_amount, DEFAULT_TX_CONFIG, + action_scope, ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, xch_to_cat_amount) await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, xch_to_cat_amount) @@ -1814,18 +1842,20 @@ async def test_trade_cancellation_balance_check( trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, DEFAULT_TX_CONFIG, action_scope + ) await time_out_assert(10, get_trade_and_status, TradeStatus.PENDING_ACCEPT, trade_manager_maker, trade_make) assert error is None assert success is True assert trade_make is not None - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True - ) - txs = await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=uint64(0), secure=True + ) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - assert txs is not None - await full_node.process_transaction_records(records=txs) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make) @@ -1843,16 +1873,17 @@ async def test_trade_conflict( wallet_maker = wallet_node_maker.wallet_state_manager.main_wallet xch_to_cat_amount = uint64(100) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, xch_to_cat_amount, DEFAULT_TX_CONFIG, + action_scope, ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, xch_to_cat_amount) await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, xch_to_cat_amount) @@ -1868,7 +1899,10 @@ async def test_trade_conflict( trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager trade_manager_trader = wallet_node_trader.wallet_state_manager.trade_manager - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, DEFAULT_TX_CONFIG, action_scope + ) await time_out_assert(10, get_trade_and_status, TradeStatus.PENDING_ACCEPT, trade_manager_maker, trade_make) assert error is None assert success is True @@ -1876,21 +1910,22 @@ async def test_trade_conflict( peer = wallet_node_taker.get_full_node_peer() offer = Offer.from_bytes(trade_make.offer) [offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers([offer]) - tr1, txs1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) - txs1 = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - txs1, additional_signing_responses=signing_response - ) - await full_node.wait_transaction_records_entered_mempool(records=txs1) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + tr1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(10)) + await full_node.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) # we shouldn't be able to respond to a duplicate offer with pytest.raises(ValueError): - await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) + async with trade_manager_taker.wallet_state_manager.new_action_scope(push=False) as action_scope: + await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(10)) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CONFIRM, trade_manager_taker, tr1) # pushing into mempool while already in it should fail [offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers([offer]) - tr2, txs2 = await trade_manager_trader.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) - txs2 = await trade_manager_trader.wallet_state_manager.add_pending_transactions( - txs2, additional_signing_responses=signing_response - ) + async with trade_manager_trader.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + tr2 = await trade_manager_trader.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(10)) assert await trade_manager_trader.get_coins_of_interest() offer_tx_records: List[TransactionRecord] = await wallet_node_maker.wallet_state_manager.tx_store.get_not_sent() await full_node.process_transaction_records(records=offer_tx_records) @@ -1907,16 +1942,17 @@ async def test_trade_bad_spend( wallet_maker = wallet_node_maker.wallet_state_manager.main_wallet xch_to_cat_amount = uint64(100) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, xch_to_cat_amount, DEFAULT_TX_CONFIG, + action_scope, ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, xch_to_cat_amount) await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, xch_to_cat_amount) @@ -1931,7 +1967,10 @@ async def test_trade_bad_spend( trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, DEFAULT_TX_CONFIG, action_scope + ) await time_out_assert(30, get_trade_and_status, TradeStatus.PENDING_ACCEPT, trade_manager_maker, trade_make) assert error is None assert success is True @@ -1940,8 +1979,8 @@ async def test_trade_bad_spend( offer = Offer.from_bytes(trade_make.offer) bundle = offer._bundle.replace(aggregated_signature=G2Element()) offer = dataclasses.replace(offer, _bundle=bundle) - tr1, txs1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) - txs1 = await trade_manager_taker.wallet_state_manager.add_pending_transactions(txs1, sign=False) + async with trade_manager_taker.wallet_state_manager.new_action_scope(push=True, sign=False) as action_scope: + tr1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(10)) wallet_node_taker.wallet_tx_resend_timeout_secs = 0 # don't wait for resend def check_wallet_cache_empty() -> bool: @@ -1964,16 +2003,17 @@ async def test_trade_high_fee( wallet_maker = wallet_node_maker.wallet_state_manager.main_wallet xch_to_cat_amount = uint64(100) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, xch_to_cat_amount, DEFAULT_TX_CONFIG, + action_scope, ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, xch_to_cat_amount) await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, xch_to_cat_amount) @@ -1988,21 +2028,25 @@ async def test_trade_high_fee( trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, DEFAULT_TX_CONFIG, action_scope + ) await time_out_assert(10, get_trade_and_status, TradeStatus.PENDING_ACCEPT, trade_manager_maker, trade_make) assert error is None assert success is True assert trade_make is not None peer = wallet_node_taker.get_full_node_peer() - offer = Offer.from_bytes(trade_make.offer) [offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - tr1, txs1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(1000000000000)) - txs1 = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - txs1, additional_signing_responses=signing_response - ) - await full_node.process_transaction_records(records=txs1) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + tr1 = await trade_manager_taker.respond_to_offer( + offer, peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(1000000000000) + ) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, tr1) @@ -2015,16 +2059,17 @@ async def test_aggregated_trade_state( wallet_maker = wallet_node_maker.wallet_state_manager.main_wallet xch_to_cat_amount = uint64(100) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, tx_records = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, xch_to_cat_amount, DEFAULT_TX_CONFIG, + action_scope, ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, cat_wallet_maker.get_confirmed_balance, xch_to_cat_amount) await time_out_assert(15, cat_wallet_maker.get_unconfirmed_balance, xch_to_cat_amount) @@ -2043,12 +2088,18 @@ async def test_aggregated_trade_state( trade_manager_maker = wallet_node_maker.wallet_state_manager.trade_manager trade_manager_taker = wallet_node_taker.wallet_state_manager.trade_manager - success, trade_make_1, _, error = await trade_manager_maker.create_offer_for_ids(chia_for_cat, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make_1, error = await trade_manager_maker.create_offer_for_ids( + chia_for_cat, DEFAULT_TX_CONFIG, action_scope + ) await time_out_assert(10, get_trade_and_status, TradeStatus.PENDING_ACCEPT, trade_manager_maker, trade_make_1) assert error is None assert success is True assert trade_make_1 is not None - success, trade_make_2, _, error = await trade_manager_maker.create_offer_for_ids(cat_for_chia, DEFAULT_TX_CONFIG) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make_2, error = await trade_manager_maker.create_offer_for_ids( + cat_for_chia, DEFAULT_TX_CONFIG, action_scope + ) await time_out_assert(10, get_trade_and_status, TradeStatus.PENDING_ACCEPT, trade_manager_maker, trade_make_2) assert error is None assert success is True @@ -2063,19 +2114,17 @@ async def test_aggregated_trade_state( agg_offer = Offer.aggregate([offer_1, offer_2]) peer = wallet_node_taker.get_full_node_peer() - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - agg_offer, - peer, - DEFAULT_TX_CONFIG, - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=[*signing_response_1, *signing_response_2] + ) as action_scope: + await trade_manager_taker.respond_to_offer( + agg_offer, + peer, + DEFAULT_TX_CONFIG, + action_scope, + ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, - additional_signing_responses=[*signing_response_1, *signing_response_2], - ) - await full_node.process_transaction_records(records=tx_records) + await full_node.process_transaction_records(records=action_scope.side_effects.transactions) await full_node.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=60) await time_out_assert(15, wallet_maker.get_confirmed_balance, maker_funds + 1) diff --git a/chia/_tests/wallet/dao_wallet/test_dao_clvm.py b/chia/_tests/wallet/dao_wallet/test_dao_clvm.py index 7840d143681c..a9b46f6da798 100644 --- a/chia/_tests/wallet/dao_wallet/test_dao_clvm.py +++ b/chia/_tests/wallet/dao_wallet/test_dao_clvm.py @@ -98,7 +98,7 @@ def test_proposal() -> None: ) self_destruct_time = 1000 # number of blocks oracle_spend_delay = 10 - active_votes_list = [0xFADEDDAB] # are the the ids of previously voted on proposals? + active_votes_list = [0xFADEDDAB] # are the ids of previously voted on proposals? acs: Program = Program.to(1) acs_ph: bytes32 = acs.get_tree_hash() diff --git a/chia/_tests/wallet/dao_wallet/test_dao_wallets.py b/chia/_tests/wallet/dao_wallet/test_dao_wallets.py index 965787a5d2d9..100494d60cdd 100644 --- a/chia/_tests/wallet/dao_wallet/test_dao_wallets.py +++ b/chia/_tests/wallet/dao_wallet/test_dao_wallets.py @@ -130,29 +130,34 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA # Try to create a DAO with more CATs than xch balance with pytest.raises(ValueError) as e_info: - await DAOWallet.create_new_dao_and_wallet( + async with wallet_0.wallet_state_manager.new_action_scope(push=False) as action_scope: + await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, + wallet_0, + uint64(funds + 1), + dao_rules, + DEFAULT_TX_CONFIG, + action_scope, + fee=fee, + fee_for_cat=fee_for_cat, + ) + assert e_info.value.args[0] == f"Your balance of {funds} mojos is not enough to create {funds + 1} CATs" + + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, - uint64(funds + 1), + uint64(cat_amt * 2), dao_rules, DEFAULT_TX_CONFIG, + action_scope, fee=fee, fee_for_cat=fee_for_cat, ) - assert e_info.value.args[0] == f"Your balance of {funds} mojos is not enough to create {funds + 1} CATs" - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, - wallet_0, - uint64(cat_amt * 2), - dao_rules, - DEFAULT_TX_CONFIG, - fee=fee, - fee_for_cat=fee_for_cat, + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -197,10 +202,12 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA # Send some cats to the dao_cat lockup dao_cat_amt = uint64(100) - txs = await dao_wallet_0.enter_dao_cat_voting_mode(dao_cat_amt, DEFAULT_TX_CONFIG) - txs = await dao_wallet_0.wallet_state_manager.add_pending_transactions(txs) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.enter_dao_cat_voting_mode(dao_cat_amt, DEFAULT_TX_CONFIG, action_scope) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -217,10 +224,12 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA assert list(coins)[0].coin.amount == dao_cat_amt # send some cats from wallet_0 to wallet_1 so we can test voting - cat_txs = await cat_wallet_0.generate_signed_transaction([cat_amt], [ph_1], DEFAULT_TX_CONFIG) - cat_txs = await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_txs) + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction([cat_amt], [ph_1], DEFAULT_TX_CONFIG, action_scope) - await full_node_api.wait_transaction_records_entered_mempool(records=cat_txs, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -307,15 +316,17 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators proposal_minimum_amount=uint64(1), ) - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG, action_scope + ) treasury_id = dao_wallet_0.dao_info.treasury_id # Get the full node sim to process the wallet creation spend - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -326,20 +337,24 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators # Create funding spends for xch and cat xch_funds = uint64(500000) cat_funds = uint64(100000) - funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG) - [funding_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[funding_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) # Check that the funding spend is found await time_out_assert(20, dao_wallet_0.get_balance_by_asset_type, xch_funds) - cat_funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend( - cat_funds, DEFAULT_TX_CONFIG, funding_wallet_id=cat_wallet_0.id() + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend( + cat_funds, DEFAULT_TX_CONFIG, action_scope, funding_wallet_id=cat_wallet_0.id() + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [cat_funding_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([cat_funding_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[cat_funding_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -353,10 +368,11 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators cat_wallet_0 = dao_wallet_0.wallet_state_manager.wallets[dao_wallet_0.dao_info.cat_wallet_id] dao_cat_wallet_0 = dao_wallet_0.wallet_state_manager.wallets[dao_wallet_0.dao_info.dao_cat_wallet_id] dao_cat_0_bal = await dao_cat_wallet_0.get_votable_balance() - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG) - txs = await dao_cat_wallet_0.wallet_state_manager.add_pending_transactions(txs) - await dao_cat_wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -368,9 +384,11 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators [proposal_amount_1], [None], ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal) - [proposal_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -379,9 +397,11 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) prop_0 = dao_wallet_0.dao_info.proposals_list[0] - close_tx_0 = await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG) - [close_tx_0] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([close_tx_0]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx_0], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -486,12 +506,19 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Create the DAO. # This takes two steps: create the treasury singleton, wait for oracle_spend_delay and # then complete the eve spend - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_issuance), dao_rules, DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, + wallet_0, + uint64(cat_issuance), + dao_rules, + DEFAULT_TX_CONFIG, + action_scope, + ) - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -524,35 +551,41 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Send 100k cats to wallet_1 and wallet_2 cat_amt = uint64(100000) - cat_tx = await cat_wallet_0.generate_signed_transaction( - [cat_amt, cat_amt], [ph_1, ph_2], DEFAULT_TX_CONFIG, fee=base_fee + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction( + [cat_amt, cat_amt], [ph_1, ph_2], DEFAULT_TX_CONFIG, action_scope, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - cat_tx = await wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) - await full_node_api.wait_transaction_records_entered_mempool(records=cat_tx, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) # Lockup voting cats for all wallets dao_cat_0_bal = await dao_cat_wallet_0.get_votable_balance() - txs_0 = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG, fee=base_fee) - txs_0 = await wallet_0.wallet_state_manager.add_pending_transactions(txs_0) - await full_node_api.wait_transaction_records_entered_mempool(records=txs_0, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) dao_cat_1_bal = await dao_cat_wallet_1.get_votable_balance() - txs_1 = await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG) - txs_1 = await wallet_1.wallet_state_manager.add_pending_transactions(txs_1) - await wallet_1.wallet_state_manager.add_pending_transactions(txs_1) - await full_node_api.wait_transaction_records_entered_mempool(records=txs_1, timeout=60) + async with dao_cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) dao_cat_2_bal = await dao_cat_wallet_2.get_votable_balance() - txs_2 = await dao_cat_wallet_2.enter_dao_cat_voting_mode(dao_cat_2_bal, DEFAULT_TX_CONFIG) - txs_2 = await wallet_2.wallet_state_manager.add_pending_transactions(txs_2) - await wallet_2.wallet_state_manager.add_pending_transactions(txs_2) - await full_node_api.wait_transaction_records_entered_mempool(records=txs_2, timeout=60) + async with dao_cat_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_2.enter_dao_cat_voting_mode(dao_cat_2_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_2, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -562,9 +595,11 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Create funding spend so the treasury holds some XCH xch_funds = uint64(500000) - funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG) - [funding_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[funding_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -584,11 +619,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [proposal_amount_1], [None], ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -606,11 +643,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato recipient_puzzle_hash, ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - mint_proposal_inner, DEFAULT_TX_CONFIG, vote_amount=dao_cat_0_bal, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + mint_proposal_inner, DEFAULT_TX_CONFIG, action_scope, vote_amount=dao_cat_0_bal, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -630,11 +669,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato current_innerpuz = dao_wallet_0.dao_info.current_treasury_innerpuz assert current_innerpuz is not None update_inner = await generate_update_proposal_innerpuz(current_innerpuz, new_dao_rules) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - update_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + update_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -646,11 +687,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato xch_proposal_inner = generate_simple_proposal_innerpuz( treasury_id, [recipient_puzzle_hash], [proposal_amount_2], [None] ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -659,11 +702,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Proposal 4: Create a 'bad' proposal (can't be executed, must be force-closed) xch_proposal_inner = Program.to(["x"]) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -673,19 +718,23 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato prop_4 = dao_wallet_0.dao_info.proposals_list[4] # Proposal 0 Voting: wallet 1 votes yes, wallet 2 votes no. Proposal Passes - [vote_tx_1] = await dao_wallet_1.generate_proposal_vote_spend( - prop_0.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop_0.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx_1] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_1]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx_1], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) - [vote_tx_2] = await dao_wallet_2.generate_proposal_vote_spend( - prop_0.proposal_id, dao_cat_2_bal, False, DEFAULT_TX_CONFIG + async with dao_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_2.generate_proposal_vote_spend( + prop_0.proposal_id, dao_cat_2_bal, False, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx_2] = await wallet_2.wallet_state_manager.add_pending_transactions([vote_tx_2]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx_2], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -702,24 +751,24 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_0_state["closable"] # Proposal 0 is closable, but soft_close_length has not passed. - close_tx_0_fail = await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG) - close_sb_0_fail = close_tx_0_fail.spend_bundle - assert close_sb_0_fail is not None - with pytest.raises(AssertionError) as e: - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_0_fail]) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG, action_scope) + with pytest.raises(AssertionError, match="Timed assertion timed out"): + assert action_scope.side_effects.transactions[0].spend_bundle is not None await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, close_sb_0_fail.name() + 5, + full_node_api.full_node.mempool_manager.get_spendbundle, + action_scope.side_effects.transactions[0].spend_bundle.name(), ) - assert e.value.args[0] == "Timed assertion timed out" for _ in range(5): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) # Proposal 0: Close - close_tx_0 = await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG) - [close_tx_0] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_0]) - close_sb_0 = close_tx_0.spend_bundle + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG, action_scope) + close_sb_0 = action_scope.side_effects.transactions[0].spend_bundle assert close_sb_0 is not None await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, close_sb_0.name()) await full_node_api.process_spend_bundles(bundles=[close_sb_0]) @@ -735,11 +784,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato await time_out_assert(20, get_proposal_state, (True, True), *[dao_wallet_2, 0]) # Proposal 1 vote and close - [vote_tx_1] = await dao_wallet_1.generate_proposal_vote_spend( - prop_1.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop_1.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx_1] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_1]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx_1], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -751,20 +802,24 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_1_state["passed"] assert prop_1_state["closable"] - close_tx_1 = await dao_wallet_0.create_proposal_close_spend(prop_1.proposal_id, DEFAULT_TX_CONFIG) - [close_tx_1] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_1]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx_1], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_1.proposal_id, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) await time_out_assert(20, cat_wallet_2.get_confirmed_balance, new_mint_amount) # Proposal 2 vote and close - [vote_tx_2] = await dao_wallet_1.generate_proposal_vote_spend( - prop_2.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop_2.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx_2] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_2]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx_2], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -776,9 +831,11 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_2_state["passed"] assert prop_2_state["closable"] - close_tx_2 = await dao_wallet_0.create_proposal_close_spend(prop_2.proposal_id, DEFAULT_TX_CONFIG) - [close_tx_2] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_2]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx_2], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_2.proposal_id, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -787,11 +844,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert dao_wallet_2.dao_rules == new_dao_rules # Proposal 3 - Close as FAILED - [vote_tx_3] = await dao_wallet_1.generate_proposal_vote_spend( - prop_3.proposal_id, dao_cat_1_bal, False, DEFAULT_TX_CONFIG + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop_3.proposal_id, dao_cat_1_bal, False, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx_3] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_3]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx_3], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -803,9 +862,11 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert not prop_3_state["passed"] assert prop_3_state["closable"] - close_tx_3 = await dao_wallet_0.create_proposal_close_spend(prop_3.proposal_id, DEFAULT_TX_CONFIG) - [close_tx_3] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_3]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx_3], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_3.proposal_id, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -818,11 +879,13 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato await time_out_assert(20, get_proposal_state, (False, True), *[dao_wallet_2, 3]) # Proposal 4 - Self Destruct a broken proposal - [vote_tx_4] = await dao_wallet_1.generate_proposal_vote_spend( - prop_4.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop_4.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx_4] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_4]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx_4], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -834,15 +897,17 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_4_state["passed"] assert prop_4_state["closable"] - with pytest.raises(Exception) as e_info: - close_tx_4 = await dao_wallet_0.create_proposal_close_spend(prop_4.proposal_id, DEFAULT_TX_CONFIG) - assert e_info.value.args[0] == "Unrecognised proposal type" + with pytest.raises(Exception, match="Unrecognised proposal type"): + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_4.proposal_id, DEFAULT_TX_CONFIG, action_scope) - close_tx_4 = await dao_wallet_0.create_proposal_close_spend( - prop_4.proposal_id, DEFAULT_TX_CONFIG, self_destruct=True + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend( + prop_4.proposal_id, DEFAULT_TX_CONFIG, action_scope, self_destruct=True + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [close_tx_4] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_4]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx_4], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -855,9 +920,11 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Remove Proposals from Memory and Free up locked coins await time_out_assert(20, len, 5, dao_wallet_0.dao_info.proposals_list) await dao_wallet_0.clear_finished_proposals_from_memory() - free_tx = await dao_wallet_0.free_coins_from_finished_proposals(DEFAULT_TX_CONFIG, fee=uint64(100)) - [free_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([free_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[free_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.free_coins_from_finished_proposals(DEFAULT_TX_CONFIG, action_scope, fee=uint64(100)) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) @@ -918,13 +985,15 @@ async def test_dao_proposal_partial_vote( proposal_minimum_amount=uint64(1), ) - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG, action_scope + ) # Get the full node sim to process the wallet creation spend - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -952,16 +1021,13 @@ async def test_dao_proposal_partial_vote( # Create funding spends for xch xch_funds = uint64(500000) - funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend( - xch_funds, - DEFAULT_TX_CONFIG, - ) - [funding_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) - assert isinstance(funding_tx, TransactionRecord) - funding_sb = funding_tx.spend_bundle - assert funding_sb is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, funding_sb.name()) - await full_node_api.process_transaction_records(records=[funding_tx]) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend( + xch_funds, + DEFAULT_TX_CONFIG, + action_scope, + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -975,23 +1041,18 @@ async def test_dao_proposal_partial_vote( assert cat_wallet_1 assert dao_cat_wallet_1 - cat_tx = await cat_wallet_0.generate_signed_transaction([100000], [ph_1], DEFAULT_TX_CONFIG) - cat_tx = await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) - cat_sb = cat_tx[0].spend_bundle - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, cat_sb.name()) - await full_node_api.process_transaction_records(records=cat_tx) + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction([100000], [ph_1], DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) await time_out_assert(10, cat_wallet_1.get_spendable_balance, 100000) # Create dao cats for voting dao_cat_0_bal = await dao_cat_wallet_0.get_votable_balance() - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG) - txs = await dao_cat_wallet_0.wallet_state_manager.add_pending_transactions(txs) - dao_cat_sb = txs[0].spend_bundle - assert dao_cat_sb is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, dao_cat_sb.name()) - await full_node_api.process_transaction_records(records=txs) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -1006,15 +1067,11 @@ async def test_dao_proposal_partial_vote( ) vote_amount = dao_cat_0_bal - 10 - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - mint_proposal_inner, DEFAULT_TX_CONFIG, vote_amount=vote_amount, fee=uint64(1000) - ) - [proposal_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - assert isinstance(proposal_tx, TransactionRecord) - proposal_sb = proposal_tx.spend_bundle - assert proposal_sb is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, proposal_sb.name()) - await full_node_api.process_spend_bundles(bundles=[proposal_sb]) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + mint_proposal_inner, DEFAULT_TX_CONFIG, action_scope, vote_amount=vote_amount, fee=uint64(1000) + ) + await full_node_api.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -1029,19 +1086,17 @@ async def test_dao_proposal_partial_vote( # Create votable dao cats and add a new vote dao_cat_1_bal = await dao_cat_wallet_1.get_votable_balance() - txs = await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG) - txs = await wallet_1.wallet_state_manager.add_pending_transactions(txs) - dao_cat_sb = txs[0].spend_bundle - assert dao_cat_sb is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, dao_cat_sb.name()) - await full_node_api.process_transaction_records(records=txs) + async with dao_cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) - [vote_tx] = await dao_wallet_1.generate_proposal_vote_spend( - prop.proposal_id, dao_cat_1_bal // 2, True, DEFAULT_TX_CONFIG - ) - [vote_tx] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx]) + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop.proposal_id, dao_cat_1_bal // 2, True, DEFAULT_TX_CONFIG, action_scope + ) + [vote_tx] = action_scope.side_effects.transactions vote_sb = vote_tx.spend_bundle assert vote_sb is not None await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, vote_sb.name()) @@ -1059,17 +1114,16 @@ async def test_dao_proposal_partial_vote( assert dao_wallet_1.dao_info.proposals_list[0].yes_votes == total_votes try: - close_tx = await dao_wallet_0.create_proposal_close_spend(prop.proposal_id, DEFAULT_TX_CONFIG, fee=uint64(100)) - [close_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) - close_sb = close_tx.spend_bundle + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend( + prop.proposal_id, DEFAULT_TX_CONFIG, action_scope, fee=uint64(100) + ) except Exception as e: # pragma: no cover print(e) - assert close_sb is not None - await full_node_api.process_spend_bundles(bundles=[close_sb]) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) balance = await cat_wallet_1.get_spendable_balance() - assert close_sb is not None await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -1080,18 +1134,19 @@ async def test_dao_proposal_partial_vote( # Can we spend the newly minted CATs? old_balance = await cat_wallet_0.get_spendable_balance() ph_0 = await cat_wallet_0.get_new_inner_hash() - cat_tx = await cat_wallet_1.generate_signed_transaction([balance + new_mint_amount], [ph_0], DEFAULT_TX_CONFIG) - cat_tx = await wallet_1.wallet_state_manager.add_pending_transactions(cat_tx) - cat_sb = cat_tx[0].spend_bundle - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, cat_sb.name()) - await full_node_api.process_transaction_records(records=cat_tx) + async with cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_1.generate_signed_transaction( + [balance + new_mint_amount], [ph_0], DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) await time_out_assert(20, cat_wallet_1.get_spendable_balance, 0) await time_out_assert(20, cat_wallet_0.get_spendable_balance, old_balance + balance + new_mint_amount) # release coins - await dao_wallet_0.free_coins_from_finished_proposals(DEFAULT_TX_CONFIG) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.free_coins_from_finished_proposals(DEFAULT_TX_CONFIG, action_scope) @pytest.mark.limit_consensus_modes(reason="does not depend on consensus rules") @@ -2424,13 +2479,15 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula proposal_minimum_amount=uint64(101), ) - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG, action_scope + ) # Get the full node sim to process the wallet creation spend - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2451,9 +2508,11 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula # Create funding spends for xch xch_funds = uint64(500000) - funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG) - [funding_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[funding_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2476,9 +2535,11 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula dao_cat_wallet_2 = dao_wallet_2.wallet_state_manager.wallets[dao_wallet_2.dao_info.dao_cat_wallet_id] assert dao_cat_wallet_2 - cat_tx = await cat_wallet_0.generate_signed_transaction([100000, 100000], [ph_1, ph_2], DEFAULT_TX_CONFIG) - cat_tx = await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) - await full_node_api.wait_transaction_records_entered_mempool(records=cat_tx, timeout=60) + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction([100000, 100000], [ph_1, ph_2], DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2491,9 +2552,11 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula # Create dao cats for voting dao_cat_0_bal = await dao_cat_wallet_0.get_votable_balance() assert dao_cat_0_bal == 100000 - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2506,11 +2569,13 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula [proposal_amount], [None], ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, uint64(1000) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal, uint64(1000) + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2532,28 +2597,34 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula # Create votable dao cats and add a new vote dao_cat_1_bal = await dao_cat_wallet_1.get_votable_balance() - txs = await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG) - txs = await wallet_1.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) - txs = await dao_cat_wallet_2.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG) - txs = await wallet_2.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_2.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_2, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2], timeout=30) - [vote_tx] = await dao_wallet_1.generate_proposal_vote_spend( - prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG - ) - [vote_tx] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx]) + async with dao_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + [vote_tx] = action_scope.side_effects.transactions vote_sb = vote_tx.spend_bundle assert vote_sb is not None - [vote_tx_2] = await dao_wallet_2.generate_proposal_vote_spend( - prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG - ) - [vote_tx_2] = await wallet_2.wallet_state_manager.add_pending_transactions([vote_tx_2]) + async with dao_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_2.generate_proposal_vote_spend( + prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + [vote_tx_2] = action_scope.side_effects.transactions vote_2 = vote_tx_2.spend_bundle assert vote_2 is not None await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, vote_sb.name()) @@ -2580,6 +2651,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula [True, False], ) @pytest.mark.anyio +@pytest.mark.standard_block_tools async def test_dao_cat_exits( two_wallet_nodes_services: SimulatorsAndWalletsServices, trusted: bool, self_hostname: str ) -> None: @@ -2660,8 +2732,7 @@ async def test_dao_cat_exits( cat_wallet_0 = wallet_node_0.wallet_state_manager.wallets[dao_wallet_res_0.cat_wallet_id] dao_cat_wallet_0 = wallet_node_0.wallet_state_manager.wallets[dao_wallet_res_0.dao_cat_wallet_id] txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - for tx in txs: - await full_node_api.wait_transaction_records_entered_mempool(records=[tx], timeout=60) + await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) await full_node_api.process_transaction_records(records=txs, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, 60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2811,13 +2882,15 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd proposal_minimum_amount=uint64(101), ) - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), dao_rules, DEFAULT_TX_CONFIG, action_scope + ) # Get the full node sim to process the wallet creation spend - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2850,12 +2923,15 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd # Create funding spends for xch xch_funds = uint64(500000) - funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend( - xch_funds, - DEFAULT_TX_CONFIG, + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend( + xch_funds, + DEFAULT_TX_CONFIG, + action_scope, + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [funding_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[funding_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2879,13 +2955,16 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd assert cat_wallet_1 assert dao_cat_wallet_1 - cat_tx = await cat_wallet_0.generate_signed_transaction( - [100000], - [ph_1], - DEFAULT_TX_CONFIG, + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction( + [100000], + [ph_1], + DEFAULT_TX_CONFIG, + action_scope, + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - cat_tx = await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) - await full_node_api.wait_transaction_records_entered_mempool(records=cat_tx, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2895,9 +2974,11 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd # Create dao cats for voting dao_cat_0_bal = await dao_cat_wallet_0.get_votable_balance() assert dao_cat_0_bal == 200000 - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dao_cat_0_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2910,11 +2991,13 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd [proposal_amount], [None], ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, uint64(1000) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, dao_cat_0_bal, uint64(1000) + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2946,17 +3029,21 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd # Create votable dao cats and add a new vote dao_cat_1_bal = await dao_cat_wallet_1.get_votable_balance() - txs = await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG) - txs = await wallet_1.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_1.enter_dao_cat_voting_mode(dao_cat_1_bal, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) - [vote_tx] = await dao_wallet_1.generate_proposal_vote_spend( - prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG + async with dao_cat_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_1.generate_proposal_vote_spend( + prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [vote_tx] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2981,9 +3068,13 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) - close_tx = await dao_wallet_0.create_proposal_close_spend(prop.proposal_id, DEFAULT_TX_CONFIG, fee=uint64(100)) - [close_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend( + prop.proposal_id, DEFAULT_TX_CONFIG, action_scope, fee=uint64(100) + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -3066,12 +3157,19 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn proposal_minimum_amount=proposal_min_amt, ) - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_issuance), dao_rules, DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, + wallet_0, + uint64(cat_issuance), + dao_rules, + DEFAULT_TX_CONFIG, + action_scope, + ) - tx_queue = await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -3089,33 +3187,43 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn dc_5 = uint64(10000) # Lockup voting cats for all wallets - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_1, DEFAULT_TX_CONFIG, fee=base_fee) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_1, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_2, DEFAULT_TX_CONFIG, fee=base_fee) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_2, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_3, DEFAULT_TX_CONFIG, fee=base_fee) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_3, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_4, DEFAULT_TX_CONFIG, fee=base_fee) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_4, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) - txs = await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_5, DEFAULT_TX_CONFIG, fee=base_fee) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + async with dao_cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_cat_wallet_0.enter_dao_cat_voting_mode(dc_5, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3123,9 +3231,11 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn # Create funding spend so the treasury holds some XCH xch_funds = uint64(500000) - funding_tx = await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG) - [funding_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[funding_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_add_funds_to_treasury_spend(xch_funds, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3145,11 +3255,13 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn vote_1 = uint64(120000) vote_2 = uint64(150000) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, vote_1, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, vote_1, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3158,11 +3270,13 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn assert dao_wallet_0.dao_info.proposals_list[0].timer_coin is not None prop_0 = dao_wallet_0.dao_info.proposals_list[0] - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, vote_2, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, vote_2, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3170,29 +3284,39 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn assert dao_wallet_0.dao_info.proposals_list[1].amount_voted == vote_2 vote_3 = uint64(30000) - [vote_tx] = await dao_wallet_0.generate_proposal_vote_spend(prop_0.proposal_id, vote_3, True, DEFAULT_TX_CONFIG) - [vote_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([vote_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_proposal_vote_spend( + prop_0.proposal_id, vote_3, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) assert dao_wallet_0.dao_info.proposals_list[0].amount_voted == vote_1 + vote_3 vote_4 = uint64(60000) - [vote_tx] = await dao_wallet_0.generate_proposal_vote_spend(prop_0.proposal_id, vote_4, True, DEFAULT_TX_CONFIG) - [vote_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([vote_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_proposal_vote_spend( + prop_0.proposal_id, vote_4, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) assert dao_wallet_0.dao_info.proposals_list[0].amount_voted == vote_1 + vote_3 + vote_4 vote_5 = uint64(1) - [proposal_tx] = await dao_wallet_0.generate_new_proposal( - xch_proposal_inner, DEFAULT_TX_CONFIG, vote_5, fee=base_fee + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal( + xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, vote_5, fee=base_fee + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 ) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3202,23 +3326,31 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn vote_6 = uint64(20000) for i in range(10): - [vote_tx] = await dao_wallet_0.generate_proposal_vote_spend(prop_2.proposal_id, vote_6, True, DEFAULT_TX_CONFIG) - [vote_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([vote_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[vote_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_proposal_vote_spend( + prop_2.proposal_id, vote_6, True, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) assert dao_wallet_0.dao_info.proposals_list[2].amount_voted == 200001 - close_tx = await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG) - [close_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[close_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) - [proposal_tx] = await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, fee=base_fee) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, fee=base_fee) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3279,18 +3411,21 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd fee = uint64(10) fee_for_cat = uint64(20) - dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( - wallet_node_0.wallet_state_manager, - wallet_0, - uint64(cat_amt * 2), - dao_rules, - DEFAULT_TX_CONFIG, - fee=fee, - fee_for_cat=fee_for_cat, - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, + wallet_0, + uint64(cat_amt * 2), + dao_rules, + DEFAULT_TX_CONFIG, + action_scope=action_scope, + fee=fee, + fee_for_cat=fee_for_cat, + ) - tx_queue = await dao_wallet_0.wallet_state_manager.add_pending_transactions(tx_queue) - await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -3311,18 +3446,22 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd # Send some cats to the dao_cat lockup dao_cat_amt = uint64(100) - txs = await dao_wallet_0.enter_dao_cat_voting_mode(dao_cat_amt, DEFAULT_TX_CONFIG) - txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.enter_dao_cat_voting_mode(dao_cat_amt, DEFAULT_TX_CONFIG, action_scope) - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) # send some cats from wallet_0 to wallet_1 so we can test voting - cat_txs = await cat_wallet_0.generate_signed_transaction([cat_amt], [ph_1], DEFAULT_TX_CONFIG) - cat_txs = await wallet_0.wallet_state_manager.add_pending_transactions(cat_txs) + async with cat_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_0.generate_signed_transaction([cat_amt], [ph_1], DEFAULT_TX_CONFIG, action_scope) - await full_node_api.wait_transaction_records_entered_mempool(records=cat_txs, timeout=60) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -3336,9 +3475,11 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd [proposal_amount_1], [None], ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, uint64(10)) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope, uint64(10)) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -3349,9 +3490,11 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd [proposal_amount_1], [None], ) - [proposal_tx] = await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG) - [proposal_tx] = await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) - await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) + async with dao_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.wait_transaction_records_entered_mempool( + records=action_scope.side_effects.transactions, timeout=60 + ) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) diff --git a/chia/_tests/wallet/db_wallet/test_dl_offers.py b/chia/_tests/wallet/db_wallet/test_dl_offers.py index f87ca89b7457..9e9b6723614b 100644 --- a/chia/_tests/wallet/db_wallet/test_dl_offers.py +++ b/chia/_tests/wallet/db_wallet/test_dl_offers.py @@ -62,18 +62,22 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: fee = uint64(1_999_999_999_999) - std_record, launcher_id_maker = await dl_wallet_maker.generate_new_reporter(maker_root, DEFAULT_TX_CONFIG, fee=fee) + async with dl_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_maker = await dl_wallet_maker.generate_new_reporter( + maker_root, DEFAULT_TX_CONFIG, action_scope, fee=fee + ) assert await dl_wallet_maker.get_latest_singleton(launcher_id_maker) is not None - [std_record] = await wsm_maker.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) maker_funds -= fee maker_funds -= 1 await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_maker, launcher_id_maker, maker_root) - std_record, launcher_id_taker = await dl_wallet_taker.generate_new_reporter(taker_root, DEFAULT_TX_CONFIG, fee=fee) + async with dl_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_taker = await dl_wallet_taker.generate_new_reporter( + taker_root, DEFAULT_TX_CONFIG, action_scope, fee=fee + ) assert await dl_wallet_taker.get_latest_singleton(launcher_id_taker) is not None - [std_record] = await wsm_taker.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) taker_funds -= fee taker_funds -= 1 await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_taker, launcher_id_taker, taker_root) @@ -98,24 +102,26 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: fee = uint64(2_000_000_000_000) - success, offer_maker, _, error = await trade_manager_maker.create_offer_for_ids( - {launcher_id_maker: -1, launcher_id_taker: 1}, - DEFAULT_TX_CONFIG, - solver=Solver( - { - launcher_id_maker.hex(): { - "new_root": "0x" + maker_root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_taker.hex(), - "values_to_prove": ["0x" + taker_branch.hex()], - }, - ], + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, offer_maker, error = await trade_manager_maker.create_offer_for_ids( + {launcher_id_maker: -1, launcher_id_taker: 1}, + DEFAULT_TX_CONFIG, + action_scope, + solver=Solver( + { + launcher_id_maker.hex(): { + "new_root": "0x" + maker_root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_taker.hex(), + "values_to_prove": ["0x" + taker_branch.hex()], + }, + ], + } } - } - ), - fee=fee, - ) + ), + fee=fee, + ) assert error is None assert success is True assert offer_maker is not None @@ -138,42 +144,42 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(offer_maker.offer)] ) - offer_taker, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - DEFAULT_TX_CONFIG, - solver=Solver( - { - launcher_id_taker.hex(): { - "new_root": "0x" + taker_root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_maker.hex(), - "values_to_prove": ["0x" + maker_branch.hex()], - }, - ], - }, - "proofs_of_inclusion": [ - [ - maker_root.hex(), - str(maker_branch_proof[0]), - ["0x" + sibling.hex() for sibling in maker_branch_proof[1]], - ], - [ - taker_root.hex(), - str(taker_branch_proof[0]), - ["0x" + sibling.hex() for sibling in taker_branch_proof[1]], + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + offer_taker = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(offer_maker.offer), + peer, + DEFAULT_TX_CONFIG, + action_scope, + solver=Solver( + { + launcher_id_taker.hex(): { + "new_root": "0x" + taker_root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_maker.hex(), + "values_to_prove": ["0x" + maker_branch.hex()], + }, + ], + }, + "proofs_of_inclusion": [ + [ + maker_root.hex(), + str(maker_branch_proof[0]), + ["0x" + sibling.hex() for sibling in maker_branch_proof[1]], + ], + [ + taker_root.hex(), + str(taker_branch_proof[0]), + ["0x" + sibling.hex() for sibling in taker_branch_proof[1]], + ], ], - ], - } - ), - fee=fee, - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + } + ), + fee=fee, + ) assert offer_taker is not None - assert tx_records is not None assert await trade_manager_maker.get_offer_summary(Offer.from_bytes(offer_taker.offer)) == { "offered": [ @@ -206,7 +212,7 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: await time_out_assert(15, wallet_maker.get_unconfirmed_balance, maker_funds) await time_out_assert(15, wallet_taker.get_unconfirmed_balance, taker_funds - fee) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) maker_funds -= fee taker_funds -= fee @@ -229,9 +235,11 @@ async def is_singleton_generation(wallet: DataLayerWallet, launcher_id: bytes32, await time_out_assert(15, is_singleton_generation, True, dl_wallet_taker, launcher_id_taker, 2) - txs = await dl_wallet_taker.create_update_state_spend(launcher_id_taker, bytes32([2] * 32), DEFAULT_TX_CONFIG) - txs = await wallet_node_taker.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.process_transaction_records(records=txs) + async with dl_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet_taker.create_update_state_spend( + launcher_id_taker, bytes32([2] * 32), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) @pytest.mark.parametrize( @@ -250,14 +258,14 @@ async def test_dl_offer_cancellation(wallets_prefarm: Any, trusted: bool) -> Non ROWS = [bytes32([i] * 32) for i in range(0, 10)] root, _ = build_merkle_tree(ROWS) - std_record, launcher_id = await dl_wallet.generate_new_reporter(root, DEFAULT_TX_CONFIG) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id = await dl_wallet.generate_new_reporter(root, DEFAULT_TX_CONFIG, action_scope) assert await dl_wallet.get_latest_singleton(launcher_id) is not None - [std_record] = await wsm.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet, launcher_id, root) - std_record_2, launcher_id_2 = await dl_wallet.generate_new_reporter(root, DEFAULT_TX_CONFIG) - [std_record_2] = await wsm.add_pending_transactions([std_record_2]) - await full_node_api.process_transaction_records(records=[std_record_2]) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_2 = await dl_wallet.generate_new_reporter(root, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) trade_manager = wsm.trade_manager @@ -265,35 +273,38 @@ async def test_dl_offer_cancellation(wallets_prefarm: Any, trusted: bool) -> Non ROWS.append(addition) root, proofs = build_merkle_tree(ROWS) - success, offer, _, error = await trade_manager.create_offer_for_ids( - {launcher_id: -1, launcher_id_2: 1}, - DEFAULT_TX_CONFIG, - solver=Solver( - { - launcher_id.hex(): { - "new_root": "0x" + root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_2.hex(), - "values_to_prove": ["0x" + addition.hex()], - }, - ], + async with trade_manager.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, offer, error = await trade_manager.create_offer_for_ids( + {launcher_id: -1, launcher_id_2: 1}, + DEFAULT_TX_CONFIG, + action_scope, + solver=Solver( + { + launcher_id.hex(): { + "new_root": "0x" + root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_2.hex(), + "values_to_prove": ["0x" + addition.hex()], + }, + ], + } } - } - ), - fee=uint64(2_000_000_000_000), - ) + ), + fee=uint64(2_000_000_000_000), + ) assert error is None assert success is True assert offer is not None - cancellation_txs = await trade_manager.cancel_pending_offers( - [offer.trade_id], DEFAULT_TX_CONFIG, fee=uint64(2_000_000_000_000), secure=True - ) - cancellation_txs = await trade_manager.wallet_state_manager.add_pending_transactions(cancellation_txs) - assert len(cancellation_txs) == 2 + async with trade_manager.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager.cancel_pending_offers( + [offer.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=uint64(2_000_000_000_000), secure=True + ) + # One outgoing for cancel, one outgoing for fee, one incoming from cancel + assert len(action_scope.side_effects.transactions) == 3 await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager, offer) - await full_node_api.process_transaction_records(records=cancellation_txs) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager, offer) @@ -322,40 +333,40 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: fee = uint64(1_999_999_999_999) - std_record, launcher_id_maker_1 = await dl_wallet_maker.generate_new_reporter( - maker_root, DEFAULT_TX_CONFIG, fee=fee - ) + async with dl_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_maker_1 = await dl_wallet_maker.generate_new_reporter( + maker_root, DEFAULT_TX_CONFIG, action_scope, fee=fee + ) assert await dl_wallet_maker.get_latest_singleton(launcher_id_maker_1) is not None - [std_record] = await wsm_maker.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) maker_funds -= fee maker_funds -= 1 await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_maker, launcher_id_maker_1, maker_root) - std_record, launcher_id_maker_2 = await dl_wallet_maker.generate_new_reporter( - maker_root, DEFAULT_TX_CONFIG, fee=fee - ) + async with dl_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_maker_2 = await dl_wallet_maker.generate_new_reporter( + maker_root, DEFAULT_TX_CONFIG, action_scope, fee=fee + ) assert await dl_wallet_maker.get_latest_singleton(launcher_id_maker_2) is not None - [std_record] = await wsm_maker.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) maker_funds -= fee maker_funds -= 1 await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_maker, launcher_id_maker_2, maker_root) - std_record, launcher_id_taker_1 = await dl_wallet_taker.generate_new_reporter( - taker_root, DEFAULT_TX_CONFIG, fee=fee - ) + async with dl_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_taker_1 = await dl_wallet_taker.generate_new_reporter( + taker_root, DEFAULT_TX_CONFIG, action_scope, fee=fee + ) assert await dl_wallet_taker.get_latest_singleton(launcher_id_taker_1) is not None - [std_record] = await wsm_taker.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) taker_funds -= fee taker_funds -= 1 await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_taker, launcher_id_taker_1, taker_root) - std_record, launcher_id_taker_2 = await dl_wallet_taker.generate_new_reporter( - taker_root, DEFAULT_TX_CONFIG, fee=fee - ) + async with dl_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_taker_2 = await dl_wallet_taker.generate_new_reporter( + taker_root, DEFAULT_TX_CONFIG, action_scope, fee=fee + ) assert await dl_wallet_taker.get_latest_singleton(launcher_id_taker_2) is not None - [std_record] = await wsm_taker.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) taker_funds -= fee taker_funds -= 1 await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_taker, launcher_id_taker_2, taker_root) @@ -384,37 +395,39 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: fee = uint64(2_000_000_000_000) - success, offer_maker, _, error = await trade_manager_maker.create_offer_for_ids( - {launcher_id_maker_1: -1, launcher_id_taker_1: 1, launcher_id_maker_2: -1, launcher_id_taker_2: 1}, - DEFAULT_TX_CONFIG, - solver=Solver( - { - launcher_id_maker_1.hex(): { - "new_root": "0x" + maker_root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_taker_1.hex(), - "values_to_prove": ["0x" + taker_branch.hex(), "0x" + taker_branch.hex()], - } - ], - }, - launcher_id_maker_2.hex(): { - "new_root": "0x" + maker_root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_taker_1.hex(), - "values_to_prove": ["0x" + taker_branch.hex()], - }, - { - "launcher_id": "0x" + launcher_id_taker_2.hex(), - "values_to_prove": ["0x" + taker_branch.hex()], - }, - ], - }, - } - ), - fee=fee, - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, offer_maker, error = await trade_manager_maker.create_offer_for_ids( + {launcher_id_maker_1: -1, launcher_id_taker_1: 1, launcher_id_maker_2: -1, launcher_id_taker_2: 1}, + DEFAULT_TX_CONFIG, + action_scope, + solver=Solver( + { + launcher_id_maker_1.hex(): { + "new_root": "0x" + maker_root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_taker_1.hex(), + "values_to_prove": ["0x" + taker_branch.hex(), "0x" + taker_branch.hex()], + } + ], + }, + launcher_id_maker_2.hex(): { + "new_root": "0x" + maker_root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_taker_1.hex(), + "values_to_prove": ["0x" + taker_branch.hex()], + }, + { + "launcher_id": "0x" + launcher_id_taker_2.hex(), + "values_to_prove": ["0x" + taker_branch.hex()], + }, + ], + }, + } + ), + fee=fee, + ) assert error is None assert success is True assert offer_maker is not None @@ -422,55 +435,55 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(offer_maker.offer)] ) - offer_taker, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - DEFAULT_TX_CONFIG, - solver=Solver( - { - launcher_id_taker_1.hex(): { - "new_root": "0x" + taker_root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_maker_1.hex(), - "values_to_prove": ["0x" + maker_branch.hex(), "0x" + maker_branch.hex()], - } - ], - }, - launcher_id_taker_2.hex(): { - "new_root": "0x" + taker_root.hex(), - "dependencies": [ - { - "launcher_id": "0x" + launcher_id_maker_1.hex(), - "values_to_prove": ["0x" + maker_branch.hex()], - }, - { - "launcher_id": "0x" + launcher_id_maker_2.hex(), - "values_to_prove": ["0x" + maker_branch.hex()], - }, - ], - }, - "proofs_of_inclusion": [ - [ - maker_root.hex(), - str(maker_branch_proof[0]), - ["0x" + sibling.hex() for sibling in maker_branch_proof[1]], + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + offer_taker = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(offer_maker.offer), + peer, + DEFAULT_TX_CONFIG, + action_scope, + solver=Solver( + { + launcher_id_taker_1.hex(): { + "new_root": "0x" + taker_root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_maker_1.hex(), + "values_to_prove": ["0x" + maker_branch.hex(), "0x" + maker_branch.hex()], + } + ], + }, + launcher_id_taker_2.hex(): { + "new_root": "0x" + taker_root.hex(), + "dependencies": [ + { + "launcher_id": "0x" + launcher_id_maker_1.hex(), + "values_to_prove": ["0x" + maker_branch.hex()], + }, + { + "launcher_id": "0x" + launcher_id_maker_2.hex(), + "values_to_prove": ["0x" + maker_branch.hex()], + }, + ], + }, + "proofs_of_inclusion": [ + [ + maker_root.hex(), + str(maker_branch_proof[0]), + ["0x" + sibling.hex() for sibling in maker_branch_proof[1]], + ], + [ + taker_root.hex(), + str(taker_branch_proof[0]), + ["0x" + sibling.hex() for sibling in taker_branch_proof[1]], + ], ], - [ - taker_root.hex(), - str(taker_branch_proof[0]), - ["0x" + sibling.hex() for sibling in taker_branch_proof[1]], - ], - ], - } - ), - fee=fee, - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + } + ), + fee=fee, + ) assert offer_taker is not None - assert tx_records is not None wallet_maker = wsm_maker.main_wallet wallet_taker = wsm_taker.main_wallet @@ -478,7 +491,7 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: await time_out_assert(15, wallet_maker.get_unconfirmed_balance, maker_funds) await time_out_assert(15, wallet_taker.get_unconfirmed_balance, taker_funds - fee) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) maker_funds -= fee taker_funds -= fee diff --git a/chia/_tests/wallet/db_wallet/test_dl_wallet.py b/chia/_tests/wallet/db_wallet/test_dl_wallet.py index 0b19967496fc..2d825f0e3541 100644 --- a/chia/_tests/wallet/db_wallet/test_dl_wallet.py +++ b/chia/_tests/wallet/db_wallet/test_dl_wallet.py @@ -75,17 +75,18 @@ async def test_initial_creation( current_root = current_tree.calculate_root() for i in range(0, 2): - std_record, launcher_id = await dl_wallet.generate_new_reporter( - current_root, DEFAULT_TX_CONFIG.override(reuse_puzhash=reuse_puzhash), fee=uint64(1999999999999) - ) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id = await dl_wallet.generate_new_reporter( + current_root, + DEFAULT_TX_CONFIG.override(reuse_puzhash=reuse_puzhash), + action_scope, + fee=uint64(1999999999999), + ) assert await dl_wallet.get_latest_singleton(launcher_id) is not None - - [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) 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, 0) await time_out_assert(10, wallet_0.get_confirmed_balance, 0) @@ -129,18 +130,17 @@ async def test_get_owned_singletons( expected_launcher_ids = set() for i in range(0, 2): - std_record, launcher_id = await dl_wallet.generate_new_reporter( - current_root, DEFAULT_TX_CONFIG, fee=uint64(1999999999999) - ) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id = await dl_wallet.generate_new_reporter( + current_root, DEFAULT_TX_CONFIG, action_scope, fee=uint64(1999999999999) + ) expected_launcher_ids.add(launcher_id) assert await dl_wallet.get_latest_singleton(launcher_id) is not None - [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) - await asyncio.sleep(0.5) owned_singletons = await dl_wallet.get_owned_singletons() owned_launcher_ids = sorted(singleton.launcher_id for singleton in owned_singletons) @@ -189,12 +189,11 @@ async def test_tracking_non_owned( current_tree = MerkleTree(nodes) current_root = current_tree.calculate_root() - std_record, launcher_id = await dl_wallet_0.generate_new_reporter(current_root, DEFAULT_TX_CONFIG) + async with dl_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id = await dl_wallet_0.generate_new_reporter(current_root, DEFAULT_TX_CONFIG, action_scope) assert await dl_wallet_0.get_latest_singleton(launcher_id) is not None - - [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet_0, launcher_id) await asyncio.sleep(0.5) @@ -205,13 +204,12 @@ async def test_tracking_non_owned( for i in range(0, 5): new_root = MerkleTree([Program.to("root").get_tree_hash()]).calculate_root() - txs = await dl_wallet_0.create_update_state_spend(launcher_id, new_root, DEFAULT_TX_CONFIG) + async with dl_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet_0.create_update_state_spend(launcher_id, new_root, DEFAULT_TX_CONFIG, action_scope) - txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.process_transaction_records(records=txs) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet_0, launcher_id) - await asyncio.sleep(0.5) async def do_tips_match() -> bool: latest_singleton_0 = await dl_wallet_0.get_latest_singleton(launcher_id) @@ -259,15 +257,17 @@ async def test_lifecycle( current_tree = MerkleTree(nodes) current_root = current_tree.calculate_root() - std_record, launcher_id = await dl_wallet.generate_new_reporter(current_root, DEFAULT_TX_CONFIG) + async with dl_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + launcher_id = await dl_wallet.generate_new_reporter(current_root, DEFAULT_TX_CONFIG, action_scope) assert await dl_wallet.get_latest_singleton(launcher_id) is not None - [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions([std_record]) + [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + action_scope.side_effects.transactions + ) await full_node_api.process_transaction_records(records=[std_record]) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) - await asyncio.sleep(0.5) previous_record = await dl_wallet.get_latest_singleton(launcher_id) assert previous_record is not None @@ -275,30 +275,40 @@ async def test_lifecycle( new_root = MerkleTree([Program.to("root").get_tree_hash()]).calculate_root() - txs = await dl_wallet.generate_signed_transaction( - [previous_record.lineage_proof.amount], - [previous_record.inner_puzzle_hash], - DEFAULT_TX_CONFIG, - 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"): + async with dl_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: await dl_wallet.generate_signed_transaction( [previous_record.lineage_proof.amount], [previous_record.inner_puzzle_hash], DEFAULT_TX_CONFIG, - coins={txs[0].spend_bundle.removals()[0]}, + action_scope, + launcher_id=previous_record.launcher_id, + new_root_hash=new_root, fee=uint64(1999999999999), ) + assert action_scope.side_effects.transactions[0].spend_bundle is not None + with pytest.raises(ValueError, match="is currently pending"): + async with dl_wallet.wallet_state_manager.new_action_scope(push=False) as failed_action_scope: + await dl_wallet.generate_signed_transaction( + [previous_record.lineage_proof.amount], + [previous_record.inner_puzzle_hash], + DEFAULT_TX_CONFIG, + failed_action_scope, + coins={ + next( + rem + for rem in action_scope.side_effects.transactions[0].spend_bundle.removals() + if rem.amount == 1 + ) + }, + fee=uint64(1999999999999), + ) new_record = await dl_wallet.get_latest_singleton(launcher_id) assert new_record is not None assert new_record != previous_record assert not new_record.confirmed - txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(action_scope.side_effects.transactions) await full_node_api.process_transaction_records(records=txs) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -313,13 +323,14 @@ async def test_lifecycle( previous_record = await dl_wallet.get_latest_singleton(launcher_id) new_root = MerkleTree([Program.to("new root").get_tree_hash()]).calculate_root() - txs = await dl_wallet.create_update_state_spend(launcher_id, new_root, DEFAULT_TX_CONFIG) + async with dl_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await dl_wallet.create_update_state_spend(launcher_id, new_root, DEFAULT_TX_CONFIG, action_scope) new_record = await dl_wallet.get_latest_singleton(launcher_id) assert new_record is not None assert new_record != previous_record assert not new_record.confirmed - txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(action_scope.side_effects.transactions) await full_node_api.process_transaction_records(records=txs) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -379,12 +390,15 @@ async def is_singleton_confirmed(wallet: DataLayerWallet, lid: bytes32) -> bool: return False return latest_singleton.confirmed - std_record, launcher_id = await dl_wallet_0.generate_new_reporter(current_root, DEFAULT_TX_CONFIG) + async with dl_wallet_0.wallet_state_manager.new_action_scope(push=False) as action_scope: + launcher_id = await dl_wallet_0.generate_new_reporter(current_root, DEFAULT_TX_CONFIG, action_scope) initial_record = await dl_wallet_0.get_latest_singleton(launcher_id) assert initial_record is not None - [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions([std_record]) + [std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + action_scope.side_effects.transactions + ) await asyncio.wait_for( full_node_api.process_transaction_records(records=[std_record]), timeout=adjusted_timeout(timeout=15), @@ -401,15 +415,19 @@ async def is_singleton_confirmed(wallet: DataLayerWallet, lid: bytes32) -> bool: 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_update_state_spend( - launcher_id, current_record.root, DEFAULT_TX_CONFIG, fee=uint64(2000000000000) - ) + async with dl_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet_1.create_update_state_spend( + launcher_id, current_record.root, DEFAULT_TX_CONFIG, action_scope, fee=uint64(2000000000000) + ) + report_txs = action_scope.side_effects.transactions record_1 = await dl_wallet_1.get_latest_singleton(launcher_id) assert record_1 is not None assert current_record != record_1 - update_txs = await dl_wallet_0.create_update_state_spend( - launcher_id, bytes32([0] * 32), DEFAULT_TX_CONFIG, fee=uint64(2000000000000) - ) + async with dl_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet_0.create_update_state_spend( + launcher_id, bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope, fee=uint64(2000000000000) + ) + update_txs = action_scope.side_effects.transactions record_0 = await dl_wallet_0.get_latest_singleton(launcher_id) assert record_0 is not None assert initial_record != record_0 @@ -462,12 +480,15 @@ async def is_singleton_generation(wallet: DataLayerWallet, launcher_id: bytes32, 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 - update_txs_1 = await dl_wallet_0.create_update_state_spend( - launcher_id, bytes32([1] * 32), DEFAULT_TX_CONFIG, fee=uint64(2000000000000) - ) + async with dl_wallet_0.wallet_state_manager.new_action_scope(push=False) as action_scope: + await dl_wallet_0.create_update_state_spend( + launcher_id, bytes32([1] * 32), DEFAULT_TX_CONFIG, action_scope, fee=uint64(2000000000000) + ) record_1 = await dl_wallet_0.get_latest_singleton(launcher_id) assert record_1 is not None - update_txs_1 = await wallet_node_0.wallet_state_manager.add_pending_transactions(update_txs_1) + update_txs_1 = await wallet_node_0.wallet_state_manager.add_pending_transactions( + action_scope.side_effects.transactions + ) await full_node_api.wait_transaction_records_entered_mempool(update_txs_1) # Delete any trace of that update @@ -475,12 +496,15 @@ async def is_singleton_generation(wallet: DataLayerWallet, launcher_id: bytes32, for tx in update_txs_1: await wallet_node_0.wallet_state_manager.tx_store.delete_transaction_record(tx.name) - update_txs_0 = await dl_wallet_0.create_update_state_spend(launcher_id, bytes32([2] * 32), DEFAULT_TX_CONFIG) + async with dl_wallet_0.wallet_state_manager.new_action_scope(push=False) as action_scope: + await dl_wallet_0.create_update_state_spend(launcher_id, bytes32([2] * 32), DEFAULT_TX_CONFIG, action_scope) record_0 = await dl_wallet_0.get_latest_singleton(launcher_id) assert record_0 is not None assert record_0 != record_1 - update_txs_0 = await wallet_node_0.wallet_state_manager.add_pending_transactions(update_txs_0) + update_txs_0 = await wallet_node_0.wallet_state_manager.add_pending_transactions( + action_scope.side_effects.transactions + ) await asyncio.wait_for( full_node_api.process_transaction_records(records=update_txs_1), timeout=adjusted_timeout(timeout=15) @@ -543,16 +567,16 @@ async def test_mirrors(wallets_prefarm: Any, trusted: bool) -> None: async with wsm_2.lock: dl_wallet_2 = await DataLayerWallet.create_new_dl_wallet(wsm_2) - std_record, launcher_id_1 = await dl_wallet_1.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG) + async with dl_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_1 = await dl_wallet_1.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope) assert await dl_wallet_1.get_latest_singleton(launcher_id_1) is not None - [std_record] = await wsm_1.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_1, launcher_id_1, bytes32([0] * 32)) - std_record, launcher_id_2 = await dl_wallet_2.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG) + async with dl_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id_2 = await dl_wallet_2.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope) assert await dl_wallet_2.get_latest_singleton(launcher_id_2) is not None - [std_record] = await wsm_2.add_pending_transactions([std_record]) - await full_node_api.process_transaction_records(records=[std_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_2, launcher_id_2, bytes32([0] * 32)) peer_1 = wallet_node_1.get_full_node_peer() @@ -562,15 +586,15 @@ async def test_mirrors(wallets_prefarm: Any, trusted: bool) -> None: await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_1, launcher_id_2, bytes32([0] * 32)) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_2, launcher_id_1, bytes32([0] * 32)) - txs = await dl_wallet_1.create_new_mirror( - launcher_id_2, uint64(3), [b"foo", b"bar"], DEFAULT_TX_CONFIG, fee=uint64(1_999_999_999_999) - ) + async with dl_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet_1.create_new_mirror( + launcher_id_2, uint64(3), [b"foo", b"bar"], DEFAULT_TX_CONFIG, action_scope, fee=uint64(1_999_999_999_999) + ) additions: List[Coin] = [] - txs = await wsm_1.add_pending_transactions(txs) - for tx in txs: + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: additions.extend(tx.spend_bundle.additions()) - await full_node_api.process_transaction_records(records=txs) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) mirror_coin: Coin = [c for c in additions if c.puzzle_hash == create_mirror_puzzle().get_tree_hash()][0] mirror = Mirror( @@ -586,9 +610,11 @@ async def test_mirrors(wallets_prefarm: Any, trusted: bool) -> None: 15, dl_wallet_2.get_mirrors_for_launcher, [dataclasses.replace(mirror, ours=False)], launcher_id_2 ) - txs = await dl_wallet_1.delete_mirror(mirror.coin_id, peer_1, DEFAULT_TX_CONFIG, fee=uint64(2_000_000_000_000)) - txs = await wsm_1.add_pending_transactions(txs) - await full_node_api.process_transaction_records(records=txs) + async with dl_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet_1.delete_mirror( + mirror.coin_id, peer_1, DEFAULT_TX_CONFIG, action_scope, fee=uint64(2_000_000_000_000) + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, dl_wallet_1.get_mirrors_for_launcher, [], launcher_id_2) await time_out_assert(15, dl_wallet_2.get_mirrors_for_launcher, [], launcher_id_2) @@ -622,8 +648,8 @@ async def test_datalayer_reorgs(wallet_environments: WalletTestFramework) -> Non async with env.wallet_state_manager.lock: dl_wallet = await DataLayerWallet.create_new_dl_wallet(env.wallet_state_manager) - std_record, launcher_id = await dl_wallet.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG) - await env.wallet_state_manager.add_pending_transactions([std_record]) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + launcher_id = await dl_wallet.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope) await wallet_environments.process_pending_states( [ @@ -690,8 +716,8 @@ async def test_datalayer_reorgs(wallet_environments: WalletTestFramework) -> Non ) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet, launcher_id, bytes32([0] * 32)) - update_txs = await dl_wallet.create_update_state_spend(launcher_id, bytes32([2] * 32), DEFAULT_TX_CONFIG) - await env.wallet_state_manager.add_pending_transactions(update_txs) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet.create_update_state_spend(launcher_id, bytes32([2] * 32), DEFAULT_TX_CONFIG, action_scope) await wallet_environments.process_pending_states( [ @@ -734,8 +760,8 @@ async def test_datalayer_reorgs(wallet_environments: WalletTestFramework) -> Non ) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet, launcher_id, bytes32([2] * 32)) - txs = await dl_wallet.create_new_mirror(launcher_id, uint64(0), [b"foo", b"bar"], DEFAULT_TX_CONFIG) - await env.wallet_state_manager.add_pending_transactions(txs) + async with dl_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dl_wallet.create_new_mirror(launcher_id, uint64(0), [b"foo", b"bar"], DEFAULT_TX_CONFIG, action_scope) await wallet_environments.process_pending_states( [ WalletStateTransition( diff --git a/chia/_tests/wallet/did_wallet/test_did.py b/chia/_tests/wallet/did_wallet/test_did.py index f6f589facc1c..dcc73dc66e9b 100644 --- a/chia/_tests/wallet/did_wallet/test_did.py +++ b/chia/_tests/wallet/did_wallet/test_did.py @@ -9,7 +9,7 @@ from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework from chia._tests.util.setup_nodes import OldSimulatorsAndWallets -from chia._tests.util.time_out_assert import time_out_assert, time_out_assert_not_none +from chia._tests.util.time_out_assert import time_out_assert from chia.rpc.wallet_rpc_api import WalletRpcApi from chia.simulator.simulator_protocol import FarmNewBlockProtocol from chia.types.blockchain_format.program import Program @@ -83,24 +83,17 @@ async def test_creation_from_coin_spend( await full_node_api.farm_blocks_to_wallet(1, wallet_1) # Wallet1 sets up DIDWallet1 without any backup set - async with wallet_node_0.wallet_state_manager.lock: + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(101), DEFAULT_TX_CONFIG + wallet_node_0.wallet_state_manager, wallet_0, uint64(101), DEFAULT_TX_CONFIG, action_scope ) with pytest.raises(RuntimeError): assert await did_wallet_0.get_coin() == set() assert await did_wallet_0.get_info_for_recovery() is None - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_0.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1]) await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) @@ -155,19 +148,13 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes await full_node_api.farm_blocks_to_wallet(1, wallet_2) # Wallet1 sets up DIDWallet1 without any backup set - async with wallet_node_0.wallet_state_manager.lock: + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(101), DEFAULT_TX_CONFIG + wallet_node_0.wallet_state_manager, wallet_0, uint64(101), DEFAULT_TX_CONFIG, action_scope ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_0.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) @@ -175,19 +162,13 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes # Wallet1 sets up DIDWallet_1 with DIDWallet_0 as backup backup_ids = [bytes.fromhex(did_wallet_0.get_my_DID())] - async with wallet_node_1.wallet_state_manager.lock: + async with wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_1.wallet_state_manager, wallet_1, uint64(201), DEFAULT_TX_CONFIG, backup_ids + wallet_node_1.wallet_state_manager, wallet_1, uint64(201), DEFAULT_TX_CONFIG, action_scope, backup_ids ) - spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 201) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201) @@ -206,19 +187,12 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes pubkey = bytes( (await did_wallet_2.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)).pubkey ) - message_tx, message_spend_bundle, attest_data = await did_wallet_0.create_attestment( - did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, DEFAULT_TX_CONFIG - ) - [message_tx] = await did_wallet_0.wallet_state_manager.add_pending_transactions([message_tx]) - assert message_spend_bundle is not None - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_0.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + async with did_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + message_spend_bundle, attest_data = await did_wallet_0.create_attestment( + did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) ( test_info_list, @@ -226,21 +200,18 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes ) = await did_wallet_2.load_attest_files_for_recovery_spend([attest_data]) assert message_spend_bundle == test_message_spend_bundle - txs = await did_wallet_2.recovery_spend( - did_wallet_2.did_info.temp_coin, - newpuzhash, - test_info_list, - pubkey, - test_message_spend_bundle, - ) - assert txs[0].spend_bundle is not None - txs = await did_wallet_2.wallet_state_manager.add_pending_transactions(txs) - - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, txs[0].spend_bundle.name() - ) + async with did_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_2.recovery_spend( + did_wallet_2.did_info.temp_coin, + newpuzhash, + test_info_list, + pubkey, + test_message_spend_bundle, + action_scope, + ) - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) await time_out_assert(45, did_wallet_2.get_confirmed_balance, 201) await time_out_assert(45, did_wallet_2.get_unconfirmed_balance, 201) @@ -249,17 +220,10 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes assert wallet.wallet_state_manager.wallets[wallet.id()] == wallet some_ph = 32 * b"\2" - txs = await did_wallet_2.create_exit_spend(some_ph, DEFAULT_TX_CONFIG) - txs = await did_wallet_2.wallet_state_manager.add_pending_transactions(txs) - - spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_2.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + async with did_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_2.create_exit_spend(some_ph, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1, wallet_node_2]) async def get_coins_with_ph() -> bool: coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, some_ph) @@ -300,37 +264,28 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w await server_3.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.farm_blocks_to_wallet(1, wallet2) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG + wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, action_scope ) assert did_wallet.get_name() == "Profile 1" - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) recovery_list = [bytes.fromhex(did_wallet.get_my_DID())] - async with wallet_node_2.wallet_state_manager.lock: + async with wallet2.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_2.wallet_state_manager, wallet2, uint64(101), DEFAULT_TX_CONFIG, recovery_list + wallet_node_2.wallet_state_manager, wallet2, uint64(101), DEFAULT_TX_CONFIG, action_scope, recovery_list ) - spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_2.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101) @@ -339,19 +294,13 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w recovery_list.append(bytes.fromhex(did_wallet_2.get_my_DID())) - async with wallet_node_2.wallet_state_manager.lock: + async with wallet2.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_3: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_2.wallet_state_manager, wallet2, uint64(201), DEFAULT_TX_CONFIG, recovery_list + wallet_node_2.wallet_state_manager, wallet2, uint64(201), DEFAULT_TX_CONFIG, action_scope, recovery_list ) - spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_3.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) assert did_wallet_3.did_info.backup_ids == recovery_list await time_out_assert(15, did_wallet_3.get_confirmed_balance, 201) @@ -372,26 +321,16 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id) ).pubkey new_ph = did_wallet_4.did_info.temp_puzhash - message_tx, message_spend_bundle, attest1 = await did_wallet.create_attestment( - coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG - ) - [message_tx] = await did_wallet.wallet_state_manager.add_pending_transactions([message_tx]) - assert message_spend_bundle is not None - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - 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()) - message_tx2, message_spend_bundle2, attest2 = await did_wallet_2.create_attestment( - coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG - ) - [message_tx2] = await did_wallet_2.wallet_state_manager.add_pending_transactions([message_tx2]) - assert message_spend_bundle2 is not None - spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_2.id() - ) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + message_spend_bundle, attest1 = await did_wallet.create_attestment( + coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG, action_scope + ) + + async with did_wallet_2.wallet_state_manager.new_action_scope(push=True) as action_scope_2: + message_spend_bundle2, attest2 = await did_wallet_2.create_attestment( + coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG, action_scope_2 + ) - 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()) message_spend_bundle = message_spend_bundle.aggregate([message_spend_bundle, message_spend_bundle2]) ( @@ -400,19 +339,16 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w ) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1, attest2]) assert message_spend_bundle == test_message_spend_bundle - await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.process_transaction_records( + records=[*action_scope.side_effects.transactions, *action_scope_2.side_effects.transactions] + ) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_4.get_confirmed_balance, 0) await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 0) - txs = await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle) - txs = await did_wallet_4.wallet_state_manager.add_pending_transactions(txs) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_4.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet) + async with did_wallet_4.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle, action_scope) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_4.get_confirmed_balance, 201) await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 201) @@ -451,17 +387,13 @@ async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG + wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, action_scope ) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) @@ -469,7 +401,10 @@ async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes info = Program.to([]) pubkey = (await did_wallet.wallet_state_manager.get_unused_derivation_record(did_wallet.wallet_info.id)).pubkey with pytest.raises(Exception): # We expect a CLVM 80 error for this test - await did_wallet.recovery_spend(coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))) + async with did_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await did_wallet.recovery_spend( + coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([])), action_scope + ) @pytest.mark.parametrize( "trusted", @@ -483,7 +418,6 @@ async def test_did_find_lost_did(self, self_hostname, two_wallet_nodes, trusted) wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet - wallet2 = wallet_node_2.wallet_state_manager.main_wallet api_0 = WalletRpcApi(wallet_node) if trusted: wallet_node.config["trusted_peers"] = { @@ -499,15 +433,12 @@ async def test_did_find_lost_did(self, self_hostname, two_wallet_nodes, trusted) await server_3.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG + wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, action_scope ) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) @@ -534,12 +465,9 @@ async def test_did_find_lost_did(self, self_hostname, two_wallet_nodes, trusted) recovery_list = [bytes32.fromhex(did_wallet.get_my_DID())] await did_wallet.update_recovery_list(recovery_list, uint64(1)) assert did_wallet.did_info.backup_ids == recovery_list - txs = await did_wallet.create_update_spend(DEFAULT_TX_CONFIG) - txs = await did_wallet.wallet_state_manager.add_pending_transactions(txs) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet.create_update_spend(DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) @@ -582,32 +510,25 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, await server_2.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await server_3.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.farm_blocks_to_wallet(1, wallet2) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG + wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, action_scope ) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) recovery_list = [bytes.fromhex(did_wallet.get_my_DID())] - async with wallet_node_2.wallet_state_manager.lock: + async with wallet2.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_2.wallet_state_manager, wallet2, uint64(101), DEFAULT_TX_CONFIG, recovery_list + wallet_node_2.wallet_state_manager, wallet2, uint64(101), DEFAULT_TX_CONFIG, action_scope, recovery_list ) - spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_2.id() - ) - - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(25, did_wallet_2.get_confirmed_balance, 101) await time_out_assert(25, did_wallet_2.get_unconfirmed_balance, 101) assert did_wallet_2.did_info.backup_ids == recovery_list @@ -616,15 +537,10 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, recovery_list = [bytes.fromhex(did_wallet_2.get_my_DID())] await did_wallet.update_recovery_list(recovery_list, uint64(1)) assert did_wallet.did_info.backup_ids == recovery_list - txs = await did_wallet.create_update_spend(DEFAULT_TX_CONFIG) - txs = await did_wallet.wallet_state_manager.add_pending_transactions(txs) - - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet2) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet.create_update_spend(DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) @@ -644,31 +560,21 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, await did_wallet_3.wallet_state_manager.get_unused_derivation_record(did_wallet_3.wallet_info.id) ).pubkey await time_out_assert(15, did_wallet.get_confirmed_balance, 101) - message_tx, message_spend_bundle, attest_data = await did_wallet.create_attestment( - coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG - ) - [message_tx] = await did_wallet.wallet_state_manager.add_pending_transactions([message_tx]) - assert message_spend_bundle is not None - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + message_spend_bundle, attest_data = await did_wallet.create_attestment( + coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) ( info, message_spend_bundle, ) = await did_wallet_3.load_attest_files_for_recovery_spend([attest_data]) - txs = await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle) - txs = await did_wallet_3.wallet_state_manager.add_pending_transactions(txs) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_3.id() - ) - - 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()) - - await full_node_api.farm_blocks_to_wallet(1, wallet) + async with did_wallet_3.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle, action_scope) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_3.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 101) @@ -687,34 +593,24 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, pubkey = ( await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_4.wallet_info.id) ).pubkey - message_tx, message_spend_bundle, attest1 = await did_wallet_3.create_attestment( - coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG - ) - [message_tx] = await did_wallet_3.wallet_state_manager.add_pending_transactions([message_tx]) - assert message_spend_bundle is not None - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_3.id() - ) - - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + async with did_wallet_3.wallet_state_manager.new_action_scope(push=True) as action_scope: + message_spend_bundle, attest1 = await did_wallet_3.create_attestment( + coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, wallet.get_pending_change_balance, 0) ( test_info_list, test_message_spend_bundle, ) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1]) - txs = await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, test_message_spend_bundle) - txs = await did_wallet_2.wallet_state_manager.add_pending_transactions(txs) - - spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_4.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) + async with did_wallet_4.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_4.recovery_spend( + coin, new_ph, test_info_list, pubkey, test_message_spend_bundle, action_scope + ) - await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_4.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 101) @@ -759,36 +655,29 @@ async def test_did_transfer(self, self_hostname, two_wallet_nodes, with_recovery await server_3.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, + action_scope, [bytes(ph)], uint64(1), {"Twitter": "Test", "GitHub": "测试"}, fee=fee, ) assert did_wallet_1.get_name() == "Profile 1" - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) # Transfer DID new_puzhash = await wallet2.get_new_puzzlehash() - txs = await did_wallet_1.transfer_did(new_puzhash, fee, with_recovery, DEFAULT_TX_CONFIG) - txs = await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + async with did_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_1.transfer_did(new_puzhash, fee, with_recovery, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) # Check if the DID wallet is created in the wallet2 await time_out_assert(30, get_wallet_num, 2, wallet_node_2.wallet_state_manager) @@ -845,22 +734,19 @@ async def test_update_recovery_list(self, self_hostname, two_wallet_nodes, trust await server_3.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, [] + wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, action_scope, [] ) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) await did_wallet_1.update_recovery_list([bytes(ph)], 1) - txs = await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG) - txs = await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) - await full_node_api.farm_blocks_to_wallet(1, wallet) + async with did_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) assert did_wallet_1.did_info.backup_ids[0] == bytes(ph) @@ -898,20 +784,19 @@ async def test_get_info(self, self_hostname, two_wallet_nodes, trusted): await full_node_api.farm_blocks_to_wallet(count=2, wallet=wallet) did_amount = uint64(101) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, did_amount, DEFAULT_TX_CONFIG, + action_scope, [], metadata={"twitter": "twitter"}, fee=fee, ) - transaction_records = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - await full_node_api.process_transaction_records(records=transaction_records) + + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=15) assert await did_wallet_1.get_confirmed_balance() == did_amount @@ -941,11 +826,11 @@ async def test_get_info(self, self_hostname, two_wallet_nodes, trusted): await wallet.select_coins(odd_amount, DEFAULT_COIN_SELECTION_CONFIG.override(excluded_coin_ids=[coin_id])) ).pop() assert coin_1.amount % 2 == 0 - [tx] = await wallet.generate_signed_transaction( - odd_amount, ph1, DEFAULT_TX_CONFIG.override(excluded_coin_ids=[coin_id]), fee - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) - await full_node_api.process_transaction_records(records=[tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + odd_amount, ph1, DEFAULT_TX_CONFIG.override(excluded_coin_ids=[coin_id]), action_scope, fee + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_2, timeout=15) assert await wallet1.get_confirmed_balance() == odd_amount @@ -965,7 +850,6 @@ async def test_message_spend(self, self_hostname, two_wallet_nodes, trusted): wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet - wallet1 = wallet_node_2.wallet_state_manager.main_wallet api_0 = WalletRpcApi(wallet_node) if trusted: wallet_node.config["trusted_peers"] = { @@ -983,16 +867,12 @@ async def test_message_spend(self, self_hostname, two_wallet_nodes, trusted): await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, [], fee=fee + wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, action_scope, [], fee=fee ) - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet1) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) response = await api_0.did_message_spend( @@ -1037,14 +917,11 @@ async def test_update_metadata(self, self_hostname, two_wallet_nodes, trusted): expected_confirmed_balance = await full_node_api.farm_blocks_to_wallet(count=2, wallet=wallet) did_amount = uint64(101) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, did_amount, DEFAULT_TX_CONFIG, [], fee=fee + wallet_node.wallet_state_manager, wallet, did_amount, DEFAULT_TX_CONFIG, action_scope, [], fee=fee ) - transaction_records = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - await full_node_api.process_transaction_records(records=transaction_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=15) expected_confirmed_balance -= did_amount + fee @@ -1063,12 +940,10 @@ async def test_update_metadata(self, self_hostname, two_wallet_nodes, trusted): metadata = {} metadata["Twitter"] = "http://www.twitter.com" await did_wallet_1.update_metadata(metadata) - txs = await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG, fee) - txs = await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) - transaction_records = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - await full_node_api.process_transaction_records(records=transaction_records) + async with did_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG, action_scope, fee) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) expected_confirmed_balance -= fee @@ -1097,7 +972,6 @@ async def test_did_sign_message(self, self_hostname, two_wallet_nodes, trusted): wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet - wallet2 = wallet_node_2.wallet_state_manager.main_wallet api_0 = WalletRpcApi(wallet_node) ph = await wallet.get_new_puzzlehash() @@ -1116,24 +990,21 @@ async def test_did_sign_message(self, self_hostname, two_wallet_nodes, trusted): await server_3.start_client(PeerInfo(self_hostname, server_1.get_port()), None) await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, + action_scope, [bytes(ph)], uint64(1), {"Twitter": "Test", "GitHub": "测试"}, fee=fee, ) assert did_wallet_1.get_name() == "Profile 1" - spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) # Test general string message = "Hello World" @@ -1249,26 +1120,19 @@ async def test_create_did_with_recovery_list(self, self_hostname, two_nodes_two_ # Node 0 sets up a DID Wallet with a backup set, but num_of_backup_ids_needed=0 # (a malformed solution, but legal for the clvm puzzle) recovery_list = [bytes.fromhex("00" * 32)] - async with wallet_node_0.wallet_state_manager.lock: + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(101), DEFAULT_TX_CONFIG, + action_scope, backups_ids=recovery_list, num_of_backup_ids_needed=0, ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_0.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - # Node 1 creates the DID Wallet with create_new_did_wallet_from_coin_spend - await full_node_api.farm_blocks_to_wallet(1, wallet_0) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0]) await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) @@ -1316,36 +1180,31 @@ async def test_did_resync(self, self_hostname, two_wallet_nodes, trusted) -> Non await wallet_server_2.start_client(PeerInfo(self_hostname, uint16(full_node_server._port)), None) await full_node_api.farm_blocks_to_wallet(1, wallet) - async with wallet_node_1.wallet_state_manager.lock: + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_1.wallet_state_manager, wallet, uint64(101), DEFAULT_TX_CONFIG, + action_scope, [bytes32(ph)], uint64(1), {"Twitter": "Test", "GitHub": "测试"}, fee=fee, ) assert did_wallet_1.get_name() == "Profile 1" - spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_1, wallet_node_2]) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) # Transfer DID new_puzhash = await wallet2.get_new_puzzlehash() - txs = await did_wallet_1.transfer_did(new_puzhash, fee, True, tx_config=DEFAULT_TX_CONFIG) - txs = await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) - spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_1.id() - ) - 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()) - await full_node_api.farm_blocks_to_wallet(1, wallet2) + async with did_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet_1.transfer_did( + new_puzhash, fee, True, tx_config=DEFAULT_TX_CONFIG, action_scope=action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_1, wallet_node_2]) # Check if the DID wallet is created in the wallet2 await time_out_assert(30, get_wallet_num, 2, wallet_node_2.wallet_state_manager) await time_out_assert(30, get_wallet_num, 1, wallet_node_1.wallet_state_manager) @@ -1395,12 +1254,12 @@ async def test_did_coin_records(wallet_environments: WalletTestFramework, monkey # Setup wallet_node = wallet_environments.environments[0].node wallet = wallet_environments.environments[0].xch_wallet - client = wallet_environments.environments[0].rpc_client # Generate DID wallet - did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node.wallet_state_manager, wallet, uint64(1), DEFAULT_TX_CONFIG - ) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node.wallet_state_manager, wallet, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) await wallet_environments.process_pending_states( [ @@ -1419,16 +1278,17 @@ async def test_did_coin_records(wallet_environments: WalletTestFramework, monkey ) for _ in range(0, 2): - txs = await did_wallet.transfer_did( - await wallet.get_puzzle_hash(new=False), uint64(0), True, wallet_environments.tx_config - ) - spend_bundles = [tx.spend_bundle for tx in txs if tx.spend_bundle is not None] - assert len(spend_bundles) > 0 - await client.push_tx(SpendBundle.aggregate(spend_bundles)) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet.transfer_did( + await wallet.get_puzzle_hash(new=False), uint64(0), True, wallet_environments.tx_config, action_scope + ) await wallet_environments.process_pending_states( [ WalletStateTransition( - pre_block_balance_updates={}, + pre_block_balance_updates={ + 1: {"set_remainder": True}, + 2: {"set_remainder": True}, + }, post_block_balance_updates={ 1: {"set_remainder": True}, 2: {"set_remainder": True}, diff --git a/chia/_tests/wallet/nft_wallet/test_nft_1_offers.py b/chia/_tests/wallet/nft_wallet/test_nft_1_offers.py index 9cb5c0703a73..fbb679484a30 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_1_offers.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_1_offers.py @@ -92,17 +92,11 @@ async def test_nft_offer_sell_nft( await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) await full_node_api.farm_rewards_to_wallet(funds, wallet_taker, timeout=30) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) await time_out_assert(20, wallet_maker.get_pending_change_balance, 0) @@ -125,16 +119,17 @@ async def test_nft_offer_sell_nft( ] ) - txs = await nft_wallet_maker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -167,9 +162,10 @@ async def test_nft_offer_sell_nft( offer_did_nft_for_xch = {nft_to_offer_asset_id: -1, wallet_maker.id(): xch_requested} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_did_nft_for_xch, DEFAULT_TX_CONFIG, {}, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_did_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, {}, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -181,17 +177,16 @@ async def test_nft_offer_sell_nft( [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(taker_fee) + ) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None - assert tx_records is not None async def maker_0_taker_1() -> bool: return await nft_wallet_maker.get_nft_count() == 0 and await nft_wallet_taker.get_nft_count() == 1 @@ -236,17 +231,11 @@ async def test_nft_offer_request_nft( await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) await full_node_api.farm_rewards_to_wallet(funds, wallet_taker, timeout=30) - did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_taker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_taker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) await time_out_assert(20, wallet_taker.get_pending_change_balance, 0) @@ -269,16 +258,17 @@ async def test_nft_offer_request_nft( await time_out_assert(20, wallet_taker.get_unconfirmed_balance, funds - 1) await time_out_assert(20, wallet_taker.get_confirmed_balance, funds - 1) - txs = await nft_wallet_taker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_taker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_taker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -314,9 +304,10 @@ async def test_nft_offer_request_nft( offer_dict = {nft_to_request_asset_id: 1, wallet_maker.id(): -xch_offered} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_dict, DEFAULT_TX_CONFIG, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_dict, DEFAULT_TX_CONFIG, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -327,16 +318,16 @@ async def test_nft_offer_request_nft( [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(taker_fee) + ) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) async def maker_1_taker_0() -> bool: @@ -378,17 +369,11 @@ async def test_nft_offer_sell_did_to_did( await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) await full_node_api.farm_rewards_to_wallet(funds, wallet_taker, timeout=30) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) await time_out_assert(20, wallet_maker.get_pending_change_balance, 0) @@ -412,16 +397,17 @@ async def test_nft_offer_sell_did_to_did( await time_out_assert(20, wallet_maker.get_unconfirmed_balance, funds - 1) await time_out_assert(20, wallet_maker.get_confirmed_balance, funds - 1) - txs = await nft_wallet_maker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -434,19 +420,11 @@ async def test_nft_offer_sell_did_to_did( await time_out_assert(20, get_nft_count, 1, nft_wallet_maker) # TAKER SETUP - WITH DID - did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list_taker = await wallet_node_taker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_taker.id() - ) - - spend_bundle_taker = spend_bundle_list_taker[0].spend_bundle - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle_taker.name() - ) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) await time_out_assert(20, wallet_taker.get_pending_change_balance, 0) @@ -472,9 +450,10 @@ async def test_nft_offer_sell_did_to_did( offer_did_nft_for_xch = {nft_to_offer_asset_id: -1, wallet_maker.id(): xch_requested} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_did_nft_for_xch, DEFAULT_TX_CONFIG, {}, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_did_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, {}, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -485,15 +464,15 @@ async def test_nft_offer_sell_did_to_did( [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(taker_fee) + ) + await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None - assert tx_records is not None async def maker_0_taker_1() -> bool: return ( @@ -543,17 +522,11 @@ async def test_nft_offer_sell_nft_for_cat( await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) await full_node_api.farm_rewards_to_wallet(funds, wallet_taker, timeout=30) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) await time_out_assert(20, wallet_maker.get_pending_change_balance, 0) @@ -576,16 +549,17 @@ async def test_nft_offer_sell_nft_for_cat( ] ) - txs = await nft_wallet_maker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -615,16 +589,17 @@ async def test_nft_offer_sell_nft_for_cat( # Trade them between maker and taker to ensure multiple coins for each cat cats_to_mint = 100000 cats_to_trade = uint64(10000) - async with wallet_node_maker.wallet_state_manager.lock: + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: full_node_api.full_node.log.warning(f"Mempool size: {full_node_api.full_node.mempool_manager.mempool.size()}") - cat_wallet_maker, _ = await CATWallet.create_new_cat_wallet( + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint), DEFAULT_TX_CONFIG, + action_scope, ) - await time_out_assert(20, mempool_not_empty, True, full_node_api) + await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -638,14 +613,15 @@ async def test_nft_offer_sell_nft_for_cat( ph_taker_cat_1 = await wallet_taker.get_new_puzzlehash() ph_taker_cat_2 = await wallet_taker.get_new_puzzlehash() - cat_tx_records = await cat_wallet_maker.generate_signed_transaction( - [cats_to_trade, cats_to_trade], - [ph_taker_cat_1, ph_taker_cat_2], - DEFAULT_TX_CONFIG, - memos=[[ph_taker_cat_1], [ph_taker_cat_2]], - ) - cat_tx_records = await wallet_maker.wallet_state_manager.add_pending_transactions(cat_tx_records) - await full_node_api.process_transaction_records(records=cat_tx_records) + async with cat_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_maker.generate_signed_transaction( + [cats_to_trade, cats_to_trade], + [ph_taker_cat_1, ph_taker_cat_2], + DEFAULT_TX_CONFIG, + action_scope, + memos=[[ph_taker_cat_1], [ph_taker_cat_2]], + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) maker_cat_balance = cats_to_mint - (2 * cats_to_trade) @@ -660,9 +636,10 @@ async def test_nft_offer_sell_nft_for_cat( offer_did_nft_for_xch = {nft_to_offer_asset_id: -1, cat_wallet_maker.id(): cats_requested} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_did_nft_for_xch, DEFAULT_TX_CONFIG, {}, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_did_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, {}, fee=maker_fee + ) assert success is True assert error is None @@ -674,15 +651,14 @@ async def test_nft_offer_sell_nft_for_cat( [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(taker_fee) + ) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None - assert tx_records is not None async def maker_0_taker_1() -> bool: return ( @@ -729,17 +705,11 @@ async def test_nft_offer_request_nft_for_cat( await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) await full_node_api.farm_rewards_to_wallet(funds, wallet_taker, timeout=30) - did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_taker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_taker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_taker.wallet_state_manager, wallet_taker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) await time_out_assert(20, wallet_taker.get_pending_change_balance, 0) @@ -762,16 +732,17 @@ async def test_nft_offer_request_nft_for_cat( ] ) - txs = await nft_wallet_taker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_taker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_taker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -801,15 +772,16 @@ async def test_nft_offer_request_nft_for_cat( # Trade them between maker and taker to ensure multiple coins for each cat cats_to_mint = 100000 cats_to_trade = uint64(20000) - async with wallet_node_maker.wallet_state_manager.lock: - cat_wallet_maker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_maker.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint), DEFAULT_TX_CONFIG, + action_scope, ) - await time_out_assert(20, mempool_not_empty, True, full_node_api) + await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -833,9 +805,9 @@ async def test_nft_offer_request_nft_for_cat( extra_change = cats_to_mint - (2 * cats_to_trade) amounts.append(uint64(extra_change)) puzzle_hashes.append(ph_taker_cat_1) - cat_tx_records = await cat_wallet_maker.generate_signed_transaction(amounts, puzzle_hashes, DEFAULT_TX_CONFIG) - cat_tx_records = await wallet_maker.wallet_state_manager.add_pending_transactions(cat_tx_records) - await full_node_api.process_transaction_records(records=cat_tx_records) + async with cat_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await cat_wallet_maker.generate_signed_transaction(amounts, puzzle_hashes, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) if test_change: @@ -856,9 +828,10 @@ async def test_nft_offer_request_nft_for_cat( offer_dict = {nft_to_request_asset_id: 1, cat_wallet_maker.id(): -cats_requested} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_dict, DEFAULT_TX_CONFIG, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_dict, DEFAULT_TX_CONFIG, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -869,15 +842,14 @@ async def test_nft_offer_request_nft_for_cat( [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=uint64(taker_fee) + ) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None - assert tx_records is not None async def maker_1_taker_0() -> bool: return ( @@ -928,17 +900,11 @@ async def test_nft_offer_sell_cancel( funds = sum(calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, 3)) await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker], timeout=20) await time_out_assert(20, wallet_maker.get_pending_change_balance, 0) @@ -961,16 +927,17 @@ async def test_nft_offer_sell_cancel( ] ) - txs = await nft_wallet_maker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -995,22 +962,23 @@ async def test_nft_offer_sell_cancel( offer_did_nft_for_xch = {nft_to_offer_asset_id: -1, wallet_maker.id(): xch_requested} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_did_nft_for_xch, DEFAULT_TX_CONFIG, {}, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_did_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, {}, fee=maker_fee + ) FEE = uint64(2000000000000) - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=FEE, secure=True - ) - txs = await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=FEE, secure=True + ) async def get_trade_and_status(trade_manager: Any, trade: Any) -> TradeStatus: trade_rec = await trade_manager.get_trade_by_id(trade.trade_id) return TradeStatus(trade_rec.status) await time_out_assert(20, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - await full_node_api.process_transaction_records(records=txs) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker], timeout=20) await time_out_assert(15, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make) @@ -1048,18 +1016,11 @@ async def test_nft_offer_sell_cancel_in_batch( ) await full_node_api.farm_rewards_to_wallet(funds, wallet_maker, timeout=30) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - 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_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(15, wallet_maker.get_pending_change_balance, 0) await time_out_assert(10, wallet_maker.get_unconfirmed_balance, funds - 1) @@ -1081,16 +1042,17 @@ async def test_nft_offer_sell_cancel_in_batch( ] ) - txs = await nft_wallet_maker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash, - royalty_puzhash, - royalty_basis_pts, - did_id, - ) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash, + royalty_puzhash, + royalty_basis_pts, + did_id, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert compute_memos(tx.spend_bundle) await time_out_assert_not_none( @@ -1116,22 +1078,23 @@ async def test_nft_offer_sell_cancel_in_batch( offer_did_nft_for_xch = {nft_to_offer_asset_id: -1, wallet_maker.id(): xch_requested} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_did_nft_for_xch, DEFAULT_TX_CONFIG, {}, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_did_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, {}, fee=maker_fee + ) FEE = uint64(2000000000000) - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=FEE, secure=True - ) - txs = await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=FEE, secure=True + ) async def get_trade_and_status(trade_manager: Any, trade: Any) -> TradeStatus: trade_rec = await trade_manager.get_trade_by_id(trade.trade_id) return TradeStatus(trade_rec.status) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - await full_node_api.process_transaction_records(records=txs) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(bytes32([0] * 32))) @@ -1190,52 +1153,34 @@ async def test_complex_nft_offer( await full_node_api.farm_rewards_to_wallet(amount=funds_taker, wallet=wsm_taker.main_wallet, timeout=60) CAT_AMOUNT = uint64(100000000) - async with wsm_maker.lock: - cat_wallet_maker, _ = await CATWallet.create_new_cat_wallet( - wsm_maker, wallet_maker, {"identifier": "genesis_by_id"}, CAT_AMOUNT, DEFAULT_TX_CONFIG + txs = [] + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( + wsm_maker, wallet_maker, {"identifier": "genesis_by_id"}, CAT_AMOUNT, DEFAULT_TX_CONFIG, action_scope ) - async with wsm_maker.lock: - cat_wallet_taker, _ = await CATWallet.create_new_cat_wallet( - wsm_taker, wallet_taker, {"identifier": "genesis_by_id"}, CAT_AMOUNT, DEFAULT_TX_CONFIG + txs.extend(action_scope.side_effects.transactions) + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_taker = await CATWallet.create_new_cat_wallet( + wsm_taker, wallet_taker, {"identifier": "genesis_by_id"}, CAT_AMOUNT, DEFAULT_TX_CONFIG, action_scope ) - cat_spend_bundle_maker = ( - await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_maker.id()) - )[0].spend_bundle - cat_spend_bundle_taker = ( - await wallet_node_taker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_taker.id()) - )[0].spend_bundle - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, cat_spend_bundle_maker.name() - ) - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, cat_spend_bundle_taker.name() - ) + txs.extend(action_scope.side_effects.transactions) # We'll need these later basic_nft_wallet_maker = await NFTWallet.create_new_nft_wallet(wsm_maker, wallet_maker, name="NFT WALLET MAKER") basic_nft_wallet_taker = await NFTWallet.create_new_nft_wallet(wsm_taker, wallet_taker, name="NFT WALLET TAKER") - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wsm_maker, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( - wsm_taker, wallet_taker, uint64(1), DEFAULT_TX_CONFIG - ) - did_spend_bundle_maker = ( - await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet_maker.id()) - )[0].spend_bundle - did_spend_bundle_taker = ( - await wallet_node_taker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet_taker.id()) - )[0].spend_bundle - - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, did_spend_bundle_maker.name() - ) - await time_out_assert_not_none( - 5, full_node_api.full_node.mempool_manager.get_spendbundle, did_spend_bundle_taker.name() - ) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wsm_maker, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + txs.extend(action_scope.side_effects.transactions) + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_taker: DIDWallet = await DIDWallet.create_new_did_wallet( + wsm_taker, wallet_taker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + txs.extend(action_scope.side_effects.transactions) - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + await full_node_api.process_transaction_records(records=txs) funds_maker = funds_maker - 1 - CAT_AMOUNT funds_taker = funds_taker - 1 - CAT_AMOUNT @@ -1274,41 +1219,45 @@ async def test_complex_nft_offer( ) if royalty_basis_pts_maker > 65535: with pytest.raises(ValueError): + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + await nft_wallet_maker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash_maker, + royalty_puzhash_maker, + royalty_basis_pts_maker, # type: ignore + did_id_maker, + ) + return + else: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, + action_scope, target_puzhash_maker, royalty_puzhash_maker, - royalty_basis_pts_maker, # type: ignore + uint16(royalty_basis_pts_maker), did_id_maker, ) - return - else: - txs = await nft_wallet_maker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash_maker, - royalty_puzhash_maker, - uint16(royalty_basis_pts_maker), - did_id_maker, - ) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() ) - txs = await nft_wallet_taker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash_taker, - royalty_puzhash_taker, - royalty_basis_pts_taker_1, - did_id_taker, - ) - txs = await nft_wallet_taker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_taker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash_taker, + royalty_puzhash_taker, + royalty_basis_pts_taker_1, + did_id_taker, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -1326,16 +1275,17 @@ async def test_complex_nft_offer( await time_out_assert(30, get_nft_count, 1, nft_wallet_taker) # MAke one more NFT for the taker - txs = await nft_wallet_taker.generate_new_nft( - metadata, - DEFAULT_TX_CONFIG, - target_puzhash_taker, - royalty_puzhash_taker, - royalty_basis_pts_taker_2, - did_id_taker, - ) - txs = await nft_wallet_taker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_taker.generate_new_nft( + metadata, + DEFAULT_TX_CONFIG, + action_scope, + target_puzhash_taker, + royalty_puzhash_taker, + royalty_basis_pts_taker_2, + did_id_taker, + ) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -1384,9 +1334,10 @@ async def test_complex_nft_offer( ), } - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - complex_nft_offer, DEFAULT_TX_CONFIG, driver_dict=driver_dict, fee=FEE - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + complex_nft_offer, DEFAULT_TX_CONFIG, action_scope, driver_dict=driver_dict, fee=FEE + ) assert error is None assert success assert trade_make is not None @@ -1396,27 +1347,31 @@ async def test_complex_nft_offer( ) if royalty_basis_pts_maker == 10000: with pytest.raises(ValueError): - trade_take, tx_records = await trade_manager_taker.respond_to_offer( + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + wallet_node_taker.get_full_node_peer(), + DEFAULT_TX_CONFIG, + action_scope, + fee=FEE, + ) + # all done for this test + return + else: + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( maker_offer, wallet_node_taker.get_full_node_peer(), DEFAULT_TX_CONFIG, + action_scope, fee=FEE, ) - # all done for this test - return - else: - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - wallet_node_taker.get_full_node_peer(), - DEFAULT_TX_CONFIG, - fee=FEE, - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) assert trade_take is not None - assert tx_records is not None - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=60) # Now let's make sure the final wallet state is correct @@ -1502,9 +1457,10 @@ async def get_cat_wallet_and_check_balance(asset_id: str, wsm: Any) -> uint128: ), } - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - complex_nft_offer, DEFAULT_TX_CONFIG, driver_dict=driver_dict, fee=uint64(0) - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + complex_nft_offer, DEFAULT_TX_CONFIG, action_scope, driver_dict=driver_dict, fee=uint64(0) + ) assert error is None assert success assert trade_make is not None @@ -1512,17 +1468,17 @@ async def get_cat_wallet_and_check_balance(asset_id: str, wsm: Any) -> uint128: [maker_offer], signing_response = await wallet_node_maker.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - wallet_node_taker.get_full_node_peer(), - DEFAULT_TX_CONFIG, - fee=uint64(0), - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + wallet_node_taker.get_full_node_peer(), + DEFAULT_TX_CONFIG, + action_scope, + fee=uint64(0), + ) assert trade_take is not None - assert tx_records is not None await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) diff --git a/chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py b/chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py index c848243f78be..944e5895ad51 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py @@ -45,7 +45,6 @@ async def test_nft_mint_from_did( wallet_node_1, server_1 = wallets[1] wallet_0 = wallet_node_0.wallet_state_manager.main_wallet wallet_1 = wallet_node_1.wallet_state_manager.main_wallet - api_0 = WalletRpcApi(wallet_node_0) ph_maker = await wallet_0.get_new_puzzlehash() ph_token = bytes32.random(seeded_random) @@ -71,14 +70,11 @@ async def test_nft_mint_from_did( await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - 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()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_0.get_pending_change_balance, 0) hex_did_id = did_wallet.get_my_DID() @@ -112,16 +108,21 @@ async def test_nft_mint_from_did( target_list = [(await wallet_1.get_new_puzzlehash()) for x in range(mint_total)] - tx_records = await nft_wallet_maker.mint_from_did( - metadata_list, DEFAULT_TX_CONFIG, target_list=target_list, mint_number_start=1, mint_total=mint_total, fee=fee - ) - sb = tx_records[0].spend_bundle - assert sb is not None - - bundle, _ = await nft_wallet_maker.wallet_state_manager.sign_bundle(sb.coin_spends) - await api_0.push_tx({"spend_bundle": bytes(bundle).hex()}) - - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, bundle.name()) + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.mint_from_did( + metadata_list, + DEFAULT_TX_CONFIG, + action_scope, + target_list=target_list, + mint_number_start=1, + mint_total=mint_total, + fee=fee, + ) + for record in action_scope.side_effects.transactions: + if record.spend_bundle is not None: + await time_out_assert_not_none( + 5, full_node_api.full_node.mempool_manager.get_spendbundle, record.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await time_out_assert(30, nft_count, mint_total, nft_wallet_taker) @@ -214,18 +215,11 @@ async def test_nft_mint_from_did_rpc( self_hostname, full_node_service.rpc_server.listen_port, full_node_service.root_path, full_node_service.config ) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_maker.get_pending_change_balance, 0) await time_out_assert(30, wallet_maker.get_unconfirmed_balance, funds - 1) @@ -409,18 +403,11 @@ async def test_nft_mint_from_did_rpc_no_royalties( self_hostname, full_node_service.rpc_server.listen_port, full_node_service.root_path, full_node_service.config ) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_maker.get_pending_change_balance, 0) await time_out_assert(30, wallet_maker.get_unconfirmed_balance, funds - 1) @@ -532,7 +519,6 @@ async def test_nft_mint_from_did_multiple_xch( wallet_node_1, server_1 = wallets[1] wallet_maker = wallet_node_0.wallet_state_manager.main_wallet wallet_taker = wallet_node_1.wallet_state_manager.main_wallet - api_0 = WalletRpcApi(wallet_node_0) ph_maker = await wallet_maker.get_new_puzzlehash() ph_taker = await wallet_taker.get_new_puzzlehash() ph_token = bytes32.random(seeded_random) @@ -559,14 +545,11 @@ async def test_nft_mint_from_did_multiple_xch( await time_out_assert(30, wallet_maker.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_maker.get_confirmed_balance, funds) - did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - 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()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_maker.get_pending_change_balance, 0) hex_did_id = did_wallet.get_my_DID() @@ -608,22 +591,21 @@ async def test_nft_mint_from_did_multiple_xch( target_list = [ph_taker for x in range(mint_total)] - tx_records = await nft_wallet_maker.mint_from_did( - metadata_list, - DEFAULT_TX_CONFIG, - target_list=target_list, - mint_number_start=1, - mint_total=mint_total, - xch_coins=xch_coins, - fee=fee, - ) - sb = tx_records[0].spend_bundle + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.mint_from_did( + metadata_list, + DEFAULT_TX_CONFIG, + action_scope, + target_list=target_list, + mint_number_start=1, + mint_total=mint_total, + xch_coins=xch_coins, + fee=fee, + ) + sb = action_scope.side_effects.transactions[0].spend_bundle assert sb is not None - bundle, _ = await nft_wallet_maker.wallet_state_manager.sign_bundle(sb.coin_spends) - await api_0.push_tx({"spend_bundle": bytes(bundle).hex()}) - - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, bundle.name()) + 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_token)) await time_out_assert(30, nft_count, mint_total, nft_wallet_taker) @@ -649,7 +631,6 @@ async def test_nft_mint_from_xch( wallet_node_1, server_1 = wallets[1] wallet_0 = wallet_node_0.wallet_state_manager.main_wallet wallet_1 = wallet_node_1.wallet_state_manager.main_wallet - api_0 = WalletRpcApi(wallet_node_0) ph_maker = await wallet_0.get_new_puzzlehash() ph_token = bytes32.random(seeded_random) @@ -675,14 +656,11 @@ async def test_nft_mint_from_xch( await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - 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()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_0.get_pending_change_balance, 0) hex_did_id = did_wallet.get_my_DID() @@ -716,17 +694,18 @@ async def test_nft_mint_from_xch( target_list = [(await wallet_1.get_new_puzzlehash()) for x in range(mint_total)] - tx_records = await nft_wallet_maker.mint_from_xch( - metadata_list, DEFAULT_TX_CONFIG, target_list=target_list, mint_number_start=1, mint_total=mint_total, fee=fee - ) - sb = tx_records[0].spend_bundle - assert sb is not None - - bundle, _ = await nft_wallet_maker.wallet_state_manager.sign_bundle(sb.coin_spends) - await api_0.push_tx({"spend_bundle": bytes(bundle).hex()}) + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.mint_from_xch( + metadata_list, + DEFAULT_TX_CONFIG, + action_scope, + target_list=target_list, + mint_number_start=1, + mint_total=mint_total, + fee=fee, + ) - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, bundle.name()) - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, nft_count, mint_total, nft_wallet_taker) await time_out_assert(30, nft_count, 0, nft_wallet_maker) @@ -817,18 +796,11 @@ async def test_nft_mint_from_xch_rpc( self_hostname, full_node_service.rpc_server.listen_port, full_node_service.root_path, full_node_service.config ) - did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_maker.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( - did_wallet_maker.id() - ) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet_maker: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_maker.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_maker.get_pending_change_balance, 0) await time_out_assert(30, wallet_maker.get_unconfirmed_balance, funds - 1) @@ -951,7 +923,6 @@ async def test_nft_mint_from_xch_multiple_xch( wallet_node_1, server_1 = wallets[1] wallet_maker = wallet_node_0.wallet_state_manager.main_wallet wallet_taker = wallet_node_1.wallet_state_manager.main_wallet - api_0 = WalletRpcApi(wallet_node_0) ph_maker = await wallet_maker.get_new_puzzlehash() ph_taker = await wallet_taker.get_new_puzzlehash() ph_token = bytes32.random(seeded_random) @@ -978,14 +949,11 @@ async def test_nft_mint_from_xch_multiple_xch( await time_out_assert(30, wallet_maker.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_maker.get_confirmed_balance, funds) - did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - 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()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_maker, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, wallet_maker.get_pending_change_balance, 0) hex_did_id = did_wallet.get_my_DID() @@ -1027,23 +995,19 @@ async def test_nft_mint_from_xch_multiple_xch( target_list = [ph_taker for x in range(mint_total)] - tx_records = await nft_wallet_maker.mint_from_xch( - metadata_list, - DEFAULT_TX_CONFIG, - target_list=target_list, - mint_number_start=1, - mint_total=mint_total, - xch_coins=xch_coins, - fee=fee, - ) - sb = tx_records[0].spend_bundle - assert sb is not None - - bundle, _ = await nft_wallet_maker.wallet_state_manager.sign_bundle(sb.coin_spends) - await api_0.push_tx({"spend_bundle": bytes(bundle).hex()}) + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.mint_from_xch( + metadata_list, + DEFAULT_TX_CONFIG, + action_scope, + target_list=target_list, + mint_number_start=1, + mint_total=mint_total, + xch_coins=xch_coins, + fee=fee, + ) - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, bundle.name()) - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, nft_count, mint_total, nft_wallet_taker) await time_out_assert(30, nft_count, 0, nft_wallet_maker) diff --git a/chia/_tests/wallet/nft_wallet/test_nft_offers.py b/chia/_tests/wallet/nft_wallet/test_nft_offers.py index 590ce6b297af..75645d675338 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_offers.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_offers.py @@ -102,9 +102,9 @@ async def test_nft_offer_with_fee( ] ) - txs = await nft_wallet_maker.generate_new_nft(metadata, tx_config) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft(metadata, tx_config, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -135,9 +135,10 @@ async def test_nft_offer_with_fee( await wallet_taker.wallet_state_manager.puzzle_store.get_current_derivation_record_for_wallet(uint32(1)) ).index - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_nft_for_xch, tx_config, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_nft_for_xch, tx_config, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -148,19 +149,18 @@ async def test_nft_offer_with_fee( [Offer.from_bytes(trade_make.offer)] ) peer = wallet_node_1.get_full_node_peer() - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - tx_config, - fee=taker_fee, - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + tx_config, + action_scope, + fee=taker_fee, + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make) @@ -212,9 +212,10 @@ async def test_nft_offer_with_fee( maker_fee = uint64(10) offer_xch_for_nft = {wallet_maker.id(): -xch_offered, nft_to_buy_asset_id: 1} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_xch_for_nft, tx_config, driver_dict_to_buy, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_xch_for_nft, tx_config, action_scope, driver_dict_to_buy, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -224,14 +225,14 @@ async def test_nft_offer_with_fee( [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer(maker_offer, peer, tx_config, fee=taker_fee) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, tx_config, action_scope, fee=taker_fee + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make) @@ -307,9 +308,9 @@ async def test_nft_offer_cancellations( ] ) - txs = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -333,9 +334,10 @@ async def test_nft_offer_cancellations( maker_fee = uint64(10) offer_nft_for_xch = {wallet_maker.id(): xch_request, nft_asset_id: -1} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_nft_for_xch, DEFAULT_TX_CONFIG, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -345,13 +347,13 @@ async def test_nft_offer_cancellations( cancel_fee = uint64(10) - txs = await trade_manager_maker.cancel_pending_offers( - [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=cancel_fee, secure=True - ) - txs = await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await trade_manager_maker.cancel_pending_offers( + [trade_make.trade_id], DEFAULT_TX_CONFIG, action_scope, fee=cancel_fee, secure=True + ) await time_out_assert(20, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) - await full_node_api.process_transaction_records(records=txs) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CANCELLED, trade_manager_maker, trade_make) @@ -429,9 +431,9 @@ async def test_nft_offer_with_metadata_update( ] ) - txs = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -448,10 +450,12 @@ async def test_nft_offer_with_metadata_update( url_to_add = "https://new_url.com" key = "mu" fee_for_update = uint64(10) - txs = await nft_wallet_maker.update_metadata(nft_to_update, key, url_to_add, DEFAULT_TX_CONFIG, fee=fee_for_update) + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.update_metadata( + nft_to_update, key, url_to_add, DEFAULT_TX_CONFIG, action_scope, fee=fee_for_update + ) mempool_mgr = full_node_api.full_node.mempool_manager - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none(20, mempool_mgr.get_spendbundle, tx.spend_bundle.name()) @@ -477,9 +481,10 @@ async def test_nft_offer_with_metadata_update( maker_fee = uint64(10) offer_nft_for_xch = {wallet_maker.id(): xch_request, nft_asset_id: -1} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_nft_for_xch, DEFAULT_TX_CONFIG, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_nft_for_xch, DEFAULT_TX_CONFIG, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -490,16 +495,13 @@ async def test_nft_offer_with_metadata_update( [Offer.from_bytes(trade_make.offer)] ) peer = wallet_node_1.get_full_node_peer() - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=taker_fee - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None - - await full_node_api.process_transaction_records(records=tx_records) + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=taker_fee + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make) @@ -580,9 +582,9 @@ async def test_nft_offer_nft_for_cat( ] ) - txs = await nft_wallet_maker.generate_new_nft(metadata, tx_config) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft(metadata, tx_config, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -596,27 +598,29 @@ async def test_nft_offer_nft_for_cat( assert await nft_wallet_taker.get_nft_count() == 0 # Create two new CATs and wallets for maker and taker cats_to_mint = 10000 - async with wallet_node_0.wallet_state_manager.lock: - cat_wallet_maker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_0.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint), tx_config, + action_scope, ) - await time_out_assert(20, mempool_not_empty, True, full_node_api) + await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) - async with wallet_node_1.wallet_state_manager.lock: - cat_wallet_taker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_taker = await CATWallet.create_new_cat_wallet( wallet_node_1.wallet_state_manager, wallet_taker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint), tx_config, + action_scope, ) - await time_out_assert(20, mempool_not_empty, True, full_node_api) + await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -656,9 +660,10 @@ async def test_nft_offer_nft_for_cat( await wallet_taker.wallet_state_manager.puzzle_store.get_current_derivation_record_for_wallet(uint32(1)) ).index - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_nft_for_cat, tx_config, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_nft_for_cat, tx_config, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -669,19 +674,18 @@ async def test_nft_offer_nft_for_cat( [Offer.from_bytes(trade_make.offer)] ) peer = wallet_node_1.get_full_node_peer() - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - tx_config, - fee=taker_fee, - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), + peer, + tx_config, + action_scope, + fee=taker_fee, + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make) @@ -745,9 +749,10 @@ async def test_nft_offer_nft_for_cat( cat_wallet_maker.id(): -maker_cat_amount, } - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_multi_cats_for_nft, tx_config, driver_dict_to_buy, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_multi_cats_for_nft, tx_config, action_scope, driver_dict_to_buy, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -757,14 +762,14 @@ async def test_nft_offer_nft_for_cat( [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( [Offer.from_bytes(trade_make.offer)] ) - trade_take, tx_records = await trade_manager_taker.respond_to_offer(maker_offer, peer, tx_config, fee=taker_fee) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, tx_config, action_scope, fee=taker_fee + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) # check balances: taker wallet down an NFT, up cats await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -848,9 +853,9 @@ async def test_nft_offer_nft_for_nft( ] ) - txs = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -863,9 +868,9 @@ async def test_nft_offer_nft_for_nft( ] ) - txs = await nft_wallet_taker.generate_new_nft(metadata_2, DEFAULT_TX_CONFIG) - txs = await nft_wallet_taker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_taker.generate_new_nft(metadata_2, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -898,9 +903,10 @@ async def test_nft_offer_nft_for_nft( maker_fee = uint64(10) offer_nft_for_nft = {nft_to_take_asset_id: 1, nft_to_offer_asset_id: -1} - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_nft_for_nft, DEFAULT_TX_CONFIG, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_nft_for_nft, DEFAULT_TX_CONFIG, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -911,16 +917,14 @@ async def test_nft_offer_nft_for_nft( [Offer.from_bytes(trade_make.offer)] ) peer = wallet_node_1.get_full_node_peer() - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, peer, DEFAULT_TX_CONFIG, fee=taker_fee - ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, action_scope, fee=taker_fee + ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make) @@ -1002,9 +1006,9 @@ async def test_nft_offer_nft0_and_xch_for_cat( ] ) - txs = await nft_wallet_maker.generate_new_nft(metadata, tx_config) - txs = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_maker.generate_new_nft(metadata, tx_config, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: await time_out_assert_not_none( 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() @@ -1018,27 +1022,29 @@ async def test_nft_offer_nft0_and_xch_for_cat( assert await nft_wallet_taker.get_nft_count() == 0 # Create two new CATs and wallets for maker and taker cats_to_mint = 10000 - async with wallet_node_0.wallet_state_manager.lock: - cat_wallet_maker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_maker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_maker = await CATWallet.create_new_cat_wallet( wallet_node_0.wallet_state_manager, wallet_maker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint), tx_config, + action_scope, ) - await time_out_assert(20, mempool_not_empty, True, full_node_api) + await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) - async with wallet_node_1.wallet_state_manager.lock: - cat_wallet_taker, _ = await CATWallet.create_new_cat_wallet( + async with wallet_taker.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet_taker = await CATWallet.create_new_cat_wallet( wallet_node_1.wallet_state_manager, wallet_taker, {"identifier": "genesis_by_id"}, uint64(cats_to_mint), tx_config, + action_scope, ) - await time_out_assert(20, mempool_not_empty, True, full_node_api) + await time_out_assert(20, mempool_not_empty, True, full_node_api) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -1084,9 +1090,10 @@ async def test_nft_offer_nft0_and_xch_for_cat( await wallet_taker.wallet_state_manager.puzzle_store.get_current_derivation_record_for_wallet(uint32(1)) ).index - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_nft_for_cat, tx_config, driver_dict, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_nft_for_cat, tx_config, action_scope, driver_dict, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -1098,20 +1105,18 @@ async def test_nft_offer_nft0_and_xch_for_cat( taker_fee = uint64(1) peer = wallet_node_1.get_full_node_peer() - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - maker_offer, - peer, - tx_config, - fee=taker_fee, - ) - - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + maker_offer, + peer, + tx_config, + action_scope, + fee=taker_fee, + ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) await time_out_assert(20, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_maker, trade_make) @@ -1175,9 +1180,10 @@ async def test_nft_offer_nft0_and_xch_for_cat( cat_wallet_maker.id(): -maker_cat_amount, } - success, trade_make, _, error = await trade_manager_maker.create_offer_for_ids( - offer_multi_cats_for_nft, tx_config, driver_dict_to_buy, fee=maker_fee - ) + async with trade_manager_maker.wallet_state_manager.new_action_scope(push=False) as action_scope: + success, trade_make, error = await trade_manager_maker.create_offer_for_ids( + offer_multi_cats_for_nft, tx_config, action_scope, driver_dict_to_buy, fee=maker_fee + ) assert success is True assert error is None assert trade_make is not None @@ -1188,15 +1194,14 @@ async def test_nft_offer_nft0_and_xch_for_cat( taker_fee = uint64(1) - trade_take, tx_records = await trade_manager_taker.respond_to_offer(maker_offer, peer, tx_config, fee=taker_fee) - - assert trade_take is not None - assert tx_records is not None + async with trade_manager_taker.wallet_state_manager.new_action_scope( + push=True, additional_signing_responses=signing_response + ) as action_scope: + trade_take = await trade_manager_taker.respond_to_offer( + maker_offer, peer, tx_config, action_scope, fee=taker_fee + ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( - tx_records, additional_signing_responses=signing_response - ) - await full_node_api.process_transaction_records(records=tx_records) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) # check balances: taker wallet down an NFT, up cats await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) diff --git a/chia/_tests/wallet/nft_wallet/test_nft_wallet.py b/chia/_tests/wallet/nft_wallet/test_nft_wallet.py index 93d2845e6722..f26ca00c18d6 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_wallet.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_wallet.py @@ -140,32 +140,21 @@ async def test_nft_wallet_creation_automatically( [("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), ("h", "0xD4584AD463139FA8C0D9F68F4B59F185")] ) - txs = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - txs = await nft_wallet_0.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: - if tx.spend_bundle is not None: - await time_out_assert_not_none( - 30, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() - ) + async with nft_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG, action_scope) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await time_out_assert(30, get_nft_count, 1, nft_wallet_0) coins = await nft_wallet_0.get_current_nfts() assert len(coins) == 1, "nft not generated" - txs = await nft_wallet_0.generate_signed_transaction( - [uint64(coins[0].coin.amount)], [ph1], DEFAULT_TX_CONFIG, coins={coins[0].coin} - ) - assert len(txs) == 1 - txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) - assert txs[0].spend_bundle is not None - await time_out_assert_not_none( - 30, full_node_api.full_node.mempool_manager.get_spendbundle, txs[0].spend_bundle.name() - ) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + async with nft_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_0.generate_signed_transaction( + [uint64(coins[0].coin.amount)], [ph1], DEFAULT_TX_CONFIG, action_scope, coins={coins[0].coin} + ) + + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) async def num_wallets() -> int: return len(await wallet_node_1.wallet_state_manager.get_all_wallet_info_entries()) @@ -210,9 +199,9 @@ async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestF metadata = Program.to( [("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), ("h", "0xD4584AD463139FA8C0D9F68F4B59F185")] ) - txs = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - txs = await nft_wallet_0.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: # ensure hints are generated assert len(compute_memos(tx.spend_bundle)) > 0 @@ -290,9 +279,9 @@ async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestF new_metadata = Program.to([("u", ["https://www.test.net/logo.svg"]), ("h", "0xD4584AD463139FA8C0D9F68F4B59F181")]) - txs = await nft_wallet_0.generate_new_nft(new_metadata, DEFAULT_TX_CONFIG) - txs = await nft_wallet_0.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with nft_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_0.generate_new_nft(new_metadata, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: # ensure hints are generated assert len(compute_memos(tx.spend_bundle)) > 0 @@ -341,17 +330,17 @@ async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestF nft_wallet_1 = await NFTWallet.create_new_nft_wallet( wallet_node_1.wallet_state_manager, wallet_1, name="NFT WALLET 2" ) - txs = await nft_wallet_0.generate_signed_transaction( - [uint64(coins[1].coin.amount)], - [await wallet_1.get_puzzle_hash(False)], - DEFAULT_TX_CONFIG, - coins={coins[1].coin}, - ) - assert len(txs) == 1 - txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) - assert txs[0].spend_bundle is not None + async with nft_wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_0.generate_signed_transaction( + [uint64(coins[1].coin.amount)], + [await wallet_1.get_puzzle_hash(False)], + DEFAULT_TX_CONFIG, + action_scope, + coins={coins[1].coin}, + ) + assert action_scope.side_effects.transactions[0].spend_bundle is not None - assert len(compute_memos(txs[0].spend_bundle)) > 0 + assert len(compute_memos(action_scope.side_effects.transactions[0].spend_bundle)) > 0 await wallet_environments.process_pending_states( [ @@ -397,17 +386,17 @@ async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestF assert len(coins) == 1 # Send it back to original owner - txs = await nft_wallet_1.generate_signed_transaction( - [uint64(coins[0].coin.amount)], - [await wallet_0.get_puzzle_hash(False)], - DEFAULT_TX_CONFIG, - coins={coins[0].coin}, - ) - assert len(txs) == 1 - txs = await wallet_node_1.wallet_state_manager.add_pending_transactions(txs) - assert txs[0].spend_bundle is not None + async with nft_wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await nft_wallet_1.generate_signed_transaction( + [uint64(coins[0].coin.amount)], + [await wallet_0.get_puzzle_hash(False)], + DEFAULT_TX_CONFIG, + action_scope, + coins={coins[0].coin}, + ) + assert action_scope.side_effects.transactions[0].spend_bundle is not None - assert len(compute_memos(txs[0].spend_bundle)) > 0 + assert len(compute_memos(action_scope.side_effects.transactions[0].spend_bundle)) > 0 await wallet_environments.process_pending_states( [ @@ -525,16 +514,15 @@ async def test_nft_wallet_rpc_creation_and_list( "artist_address": ph, "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], - } + }, ) assert isinstance(tr1, dict) assert tr1.get("success") sb = tr1["spend_bundle"] - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in tr1["transactions"]] + await full_node_api.process_transaction_records(transactions) await wait_rpc_state_condition(30, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_0_id)], lambda x: x["nft_list"]) tr2 = await api_0.nft_mint_nft( @@ -547,14 +535,13 @@ async def test_nft_wallet_rpc_creation_and_list( "https://bafybeigzcazxeu7epmm4vtkuadrvysv74lbzzbl2evphtae6k57yhgynp4.ipfs.nftstorage.link/6590.json" ], "meta_hash": "0x6a9cb99b7b9a987309e8dd4fd14a7ca2423858585da68cc9ec689669dd6dd6ab", - } + }, ) assert isinstance(tr2, dict) assert tr2.get("success") sb = tr2["spend_bundle"] - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in tr2["transactions"]] + await full_node_api.process_transaction_records(transactions) coins_response = await wait_rpc_state_condition( 5, api_0.nft_get_nfts, [{"wallet_id": nft_wallet_0_id}], lambda x: x["success"] and len(x["nft_list"]) == 2 ) @@ -650,15 +637,14 @@ async def test_nft_wallet_rpc_update_metadata( "artist_address": ph, "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], - } + }, ) assert resp.get("success") sb = resp["spend_bundle"] - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + await full_node_api.process_transaction_records(transactions) 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"] ) @@ -686,7 +672,7 @@ async def test_nft_wallet_rpc_update_metadata( bytes32.from_hexstr(coin["nft_coin_id"]), AddressType.NFT.hrp(api_0.service.config) ) tr1 = await api_0.nft_add_uri( - {"wallet_id": nft_wallet_0_id, "nft_coin_id": nft_coin_id, "uri": "http://metadata", "key": "mu"} + {"wallet_id": nft_wallet_0_id, "nft_coin_id": nft_coin_id, "uri": "http://metadata", "key": "mu"}, ) assert tr1.get("success") @@ -695,9 +681,8 @@ async def test_nft_wallet_rpc_update_metadata( assert coins[0].pending_transaction sb = tr1["spend_bundle"] assert isinstance(sb, SpendBundle) - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in tr1["transactions"]] + await full_node_api.process_transaction_records(transactions) # check that new URI was added coins_response = await wait_rpc_state_condition( 5, @@ -726,9 +711,8 @@ async def test_nft_wallet_rpc_update_metadata( assert isinstance(tr1, dict) assert tr1.get("success") sb = tr1["spend_bundle"] - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in tr1["transactions"]] + await full_node_api.process_transaction_records(transactions) coins_response = await wait_rpc_state_condition( 5, api_0.nft_get_nfts, @@ -783,15 +767,11 @@ async def test_nft_with_did_wallet_creation( await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, 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), AddressType.DID.hrp(wallet_node_0.config)) @@ -856,12 +836,11 @@ async def test_nft_with_did_wallet_creation( matched += 1 assert matched > 0 - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + await full_node_api.process_transaction_records(transactions) - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) - - await time_out_assert(30, wallet_0.get_unconfirmed_balance, 5999999999999 - 1) - await time_out_assert(30, wallet_0.get_confirmed_balance, 5999999999999 - 1) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999999 - 1) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999999 - 1) # Create a NFT without DID, this will go the unassigned NFT wallet resp = await api_0.nft_mint_nft( { @@ -876,11 +855,10 @@ async def test_nft_with_did_wallet_creation( # ensure hints are generated assert len(compute_memos(sb)) > 0 - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) - await time_out_assert(30, wallet_0.get_unconfirmed_balance, 7999999999998 - 1) - await time_out_assert(30, wallet_0.get_confirmed_balance, 7999999999998 - 1) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + await full_node_api.process_transaction_records(transactions) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999998 - 1) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999998 - 1) # 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"] @@ -949,16 +927,11 @@ async def test_nft_rpc_mint(self_hostname: str, two_wallet_nodes: OldSimulatorsA await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, 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)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, wallet_0.get_pending_change_balance, 0) did_id = encode_puzzle_hash(bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(wallet_node_0.config)) @@ -969,8 +942,8 @@ async def test_nft_rpc_mint(self_hostname: str, two_wallet_nodes: OldSimulatorsA assert res.get("success") nft_wallet_0_id = res["wallet_id"] - await time_out_assert(30, wallet_0.get_unconfirmed_balance, 5999999999999) - await time_out_assert(30, wallet_0.get_confirmed_balance, 5999999999999) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999999) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999999) # Create a NFT with DID royalty_address = ph1 data_hash_param = "0xD4584AD463139FA8C0D9F68F4B59F185" @@ -1003,12 +976,10 @@ async def test_nft_rpc_mint(self_hostname: str, two_wallet_nodes: OldSimulatorsA # ensure hints are generated assert len(compute_memos(sb)) > 0 - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) - await time_out_assert(30, wallet_0.get_unconfirmed_balance, 9999999999998) - await time_out_assert(30, wallet_0.get_confirmed_balance, 9999999999998) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + await full_node_api.process_transaction_records(transactions) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999998) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999998) 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"] ) @@ -1071,15 +1042,11 @@ async def test_nft_transfer_nft_with_did( await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) # Create DID - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, 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), AddressType.DID.hrp(wallet_node_0.config)) @@ -1111,8 +1078,8 @@ async def test_nft_transfer_nft_with_did( 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(30, wallet_0.get_unconfirmed_balance, 5999999999898) - await time_out_assert(30, wallet_0.get_confirmed_balance, 5999999999898) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999898) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999898) coins: List[NFTInfo] = coins_response["nft_list"] assert len(coins) == 1 assert coins[0].owner_did is not None @@ -1123,15 +1090,9 @@ async def test_nft_transfer_nft_with_did( await full_node_api.wait_for_wallet_synced(wallet_node_0, 20) await full_node_api.wait_for_wallet_synced(wallet_node_1, 20) # transfer DID to the other wallet - txs = await did_wallet.transfer_did(ph1, uint64(0), True, DEFAULT_TX_CONFIG) - txs = await did_wallet.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: - if tx.spend_bundle is not None: - await time_out_assert_not_none( - 30, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() - ) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet.transfer_did(ph1, uint64(0), True, DEFAULT_TX_CONFIG, action_scope) + await full_node_api.process_transaction_records(action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node_0, 20) await full_node_api.wait_for_wallet_synced(wallet_node_1, 20) await time_out_assert(15, len, 2, wallet_0.wallet_state_manager.wallets) @@ -1149,8 +1110,8 @@ async def test_nft_transfer_nft_with_did( assert len(compute_memos(sb)) > 0 await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) await make_new_block_with(resp, full_node_api, ph1) - await time_out_assert(30, wallet_0.get_unconfirmed_balance, 5999999999798) - await time_out_assert(30, wallet_0.get_confirmed_balance, 5999999999798) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999798) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999798) await time_out_assert(30, len, 1, wallet_0.wallet_state_manager.wallets) # wait for all wallets to be created @@ -1176,14 +1137,13 @@ async def test_nft_transfer_nft_with_did( dict(wallet_id=nft_wallet_id_1, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex(), fee=fee) ) txs = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] - txs = await wallet_node_1.wallet_state_manager.add_pending_transactions(txs) - await make_new_block_with(resp, full_node_api, ph) + await full_node_api.process_transaction_records(txs) 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(30, wallet_1.get_unconfirmed_balance, 12000000000100) - await time_out_assert(30, wallet_1.get_confirmed_balance, 12000000000100) + await time_out_assert(30, wallet_1.get_unconfirmed_balance, 8000000000100) + await time_out_assert(30, wallet_1.get_confirmed_balance, 8000000000100) nft_wallet_1_id = coins_response.get("wallet_id") assert nft_wallet_1_id # Check NFT DID is set now @@ -1211,10 +1171,8 @@ async def test_update_metadata_for_nft_did( wallet_node_0, server_0 = wallets[0] wallet_node_1, server_1 = wallets[1] wallet_0 = wallet_node_0.wallet_state_manager.main_wallet - wallet_1 = wallet_node_1.wallet_state_manager.main_wallet api_0 = WalletRpcApi(wallet_node_0) ph = await wallet_0.get_new_puzzlehash() - ph1 = await wallet_1.get_new_puzzlehash() if trusted: wallet_node_0.config["trusted_peers"] = { @@ -1239,17 +1197,11 @@ async def test_update_metadata_for_nft_did( await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, 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)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, 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), AddressType.DID.hrp(wallet_node_0.config)) @@ -1278,10 +1230,8 @@ async def test_update_metadata_for_nft_did( # ensure hints are generated assert len(compute_memos(sb)) > 0 - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + await full_node_api.process_transaction_records(transactions) # Check DID NFT @@ -1310,12 +1260,11 @@ async def test_update_metadata_for_nft_did( assert coins_response["nft_list"][0].pending_transaction sb = tr1["spend_bundle"] - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) - for _ in range(1, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + transactions = [TransactionRecord.from_json_dict_convenience(tx) for tx in tr1["transactions"]] + await full_node_api.process_transaction_records(transactions) # check that new URI was added - await time_out_assert(30, wallet_0.get_unconfirmed_balance, 11999999999898) - await time_out_assert(30, wallet_0.get_confirmed_balance, 11999999999898) + await time_out_assert(30, wallet_0.get_unconfirmed_balance, 3999999999898) + await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999898) coins_response = await wait_rpc_state_condition( 5, api_0.nft_get_info, @@ -1370,16 +1319,11 @@ async def test_nft_bulk_set_did(self_hostname: str, two_wallet_nodes: OldSimulat await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, 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)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, wallet_0.get_pending_change_balance, 0) await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999999) hex_did_id = did_wallet.get_my_DID() @@ -1472,7 +1416,7 @@ async def test_nft_bulk_set_did(self_hostname: str, two_wallet_nodes: OldSimulat assert len(sb.coin_spends) == 5 tx_num = resp["tx_num"] assert isinstance(tx_num, int) - assert tx_num == 4 + assert tx_num == 5 # 1 for each NFT being spent (3), 1 for fee tx, 1 for did tx coins_response = await wait_rpc_state_condition( 30, api_0.nft_get_nfts, [{"wallet_id": nft_wallet_0_id}], lambda x: len(x["nft_list"]) == 2 ) @@ -1545,16 +1489,11 @@ async def test_nft_bulk_transfer(self_hostname: str, two_wallet_nodes: OldSimula await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, 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)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, wallet_0.get_pending_change_balance, 0) await time_out_assert(30, wallet_0.get_confirmed_balance, 3999999999999) hex_did_id = did_wallet.get_my_DID() @@ -1697,17 +1636,11 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: OldSimulatorsAn await time_out_assert(30, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(30, wallet_0.get_confirmed_balance, funds) - did_wallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, 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 time_out_assert(30, wallet_0.get_pending_change_balance, 0) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) hex_did_id = did_wallet.get_my_DID() hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), AddressType.DID.hrp(wallet_node_0.config)) @@ -1744,16 +1677,11 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: OldSimulatorsAn nft_coin_id = coins[0].nft_coin_id # Test set None -> DID1 - did_wallet1 = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet1.id()) - - spend_bundle = spend_bundle_list[0].spend_bundle - assert spend_bundle is not None - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet1 = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await time_out_assert(30, did_wallet1.get_spendable_balance, 1) resp = await api_0.nft_set_nft_did( dict(wallet_id=nft_wallet_0_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex()) @@ -1887,7 +1815,7 @@ async def test_set_nft_status(self_hostname: str, two_wallet_nodes: OldSimulator "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], "mu": ["https://www.chia.net/img/branding/chia-logo.svg"], - } + }, ) assert resp.get("success") sb = resp["spend_bundle"] @@ -1970,7 +1898,7 @@ async def test_nft_sign_message(self_hostname: str, two_wallet_nodes: OldSimulat "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], "mu": ["https://www.chia.net/img/branding/chia-logo.svg"], - } + }, ) assert resp.get("success") sb = resp["spend_bundle"] diff --git a/chia/_tests/wallet/rpc/test_wallet_rpc.py b/chia/_tests/wallet/rpc/test_wallet_rpc.py index 112c7b79f4c5..7d226ed00724 100644 --- a/chia/_tests/wallet/rpc/test_wallet_rpc.py +++ b/chia/_tests/wallet/rpc/test_wallet_rpc.py @@ -416,11 +416,16 @@ async def test_get_balance(wallet_rpc_environment: WalletRpcTestEnvironment): full_node_api: FullNodeSimulator = env.full_node.api wallet_rpc_client = env.wallet_1.rpc_client await full_node_api.farm_blocks_to_wallet(2, wallet) - async with wallet_node.wallet_state_manager.lock: - cat_wallet, tx_records = await CATWallet.create_new_cat_wallet( - wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100), DEFAULT_TX_CONFIG + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + cat_wallet = await CATWallet.create_new_cat_wallet( + wallet_node.wallet_state_manager, + wallet, + {"identifier": "genesis_by_id"}, + uint64(100), + DEFAULT_TX_CONFIG, + action_scope, ) - await full_node_api.wait_transaction_records_entered_mempool(tx_records) + await full_node_api.wait_transaction_records_entered_mempool(action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node) await assert_get_balance(wallet_rpc_client, wallet_node, wallet) await assert_get_balance(wallet_rpc_client, wallet_node, cat_wallet) @@ -461,16 +466,17 @@ async def test_get_farmed_amount_with_fee(wallet_rpc_environment: WalletRpcTestE await generate_funds(full_node_api, env.wallet_1) fee_amount = 100 - [tx] = await wallet.generate_signed_transaction( - amount=uint64(5), - puzzle_hash=bytes32([0] * 32), - tx_config=DEFAULT_TX_CONFIG, - fee=uint64(fee_amount), - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + amount=uint64(5), + puzzle_hash=bytes32([0] * 32), + tx_config=DEFAULT_TX_CONFIG, + action_scope=action_scope, + fee=uint64(fee_amount), + ) our_ph = await wallet.get_new_puzzlehash() - await full_node_api.wait_transaction_records_entered_mempool(records=[tx]) + await full_node_api.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) await full_node_api.farm_blocks_to_puzzlehash(count=2, farm_to=our_ph, guarantee_transaction_blocks=True) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) @@ -555,7 +561,7 @@ async def test_create_signed_transaction( ) assert len(selected_coin) == 1 - tx = ( + txs = ( await wallet_1_rpc.create_signed_transactions( outputs, coins=selected_coin, @@ -567,12 +573,12 @@ async def test_create_signed_transaction( ), push=True, ) - ).signed_tx + ).transactions change_expected = not selected_coin or selected_coin[0].amount - amount_total > 0 - assert_tx_amounts(tx, outputs, amount_fee=amount_fee, change_expected=change_expected, is_cat=is_cat) + assert_tx_amounts(txs[-1], outputs, amount_fee=amount_fee, change_expected=change_expected, is_cat=is_cat) # Farm the transaction and make sure the wallet balance reflects it correct - spend_bundle = tx.spend_bundle + spend_bundle = txs[0].spend_bundle assert spend_bundle is not None await farm_transaction(full_node_api, wallet_1_node, spend_bundle) await time_out_assert(20, get_confirmed_balance, generated_funds - amount_total, wallet_1_rpc, wallet_id) @@ -1094,7 +1100,7 @@ async def test_cat_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): ["the cat memo"], ) tx_res = await client.cat_spend(cat_0_id, DEFAULT_TX_CONFIG, uint64(4), addr_1, uint64(0), ["the cat memo"]) - assert tx_res.transaction.wallet_id == cat_0_id + spend_bundle = tx_res.transaction.spend_bundle assert spend_bundle is not None assert uncurry_puzzle(spend_bundle.coin_spends[0].puzzle_reveal.to_program()).mod == CAT_MOD @@ -1104,10 +1110,9 @@ async def test_cat_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): # Test CAT spend with a fee tx_res = await client.cat_spend(cat_0_id, DEFAULT_TX_CONFIG, uint64(1), addr_1, uint64(5_000_000), ["the cat memo"]) - assert tx_res.transaction.wallet_id == cat_0_id + spend_bundle = tx_res.transaction.spend_bundle assert spend_bundle is not None - assert uncurry_puzzle(spend_bundle.coin_spends[0].puzzle_reveal.to_program()).mod == CAT_MOD await farm_transaction(full_node_api, wallet_node, spend_bundle) # Test CAT spend with a fee and pre-specified removals / coins @@ -1117,11 +1122,10 @@ async def test_cat_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): tx_res = await client.cat_spend( cat_0_id, DEFAULT_TX_CONFIG, uint64(1), addr_1, uint64(5_000_000), ["the cat memo"], removals=removals ) - assert tx_res.transaction.wallet_id == cat_0_id + spend_bundle = tx_res.transaction.spend_bundle assert spend_bundle is not None - assert removals[0] in tx_res.transaction.removals - assert uncurry_puzzle(spend_bundle.coin_spends[0].puzzle_reveal.to_program()).mod == CAT_MOD + assert removals[0] in {removal for tx in tx_res.transactions for removal in tx.removals} await farm_transaction(full_node_api, wallet_node, spend_bundle) # Test unacknowledged CAT @@ -1495,8 +1499,6 @@ async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment): await wallet_1_rpc.update_did_metadata(wallet_1_id, {"Twitter": "Https://test"}, DEFAULT_TX_CONFIG) await wallet_1_rpc.update_did_metadata(did_wallet_id_0, {"Twitter": "Https://test"}, DEFAULT_TX_CONFIG) - await farm_transaction_block(full_node_api, wallet_1_node) - res = await wallet_1_rpc.get_did_metadata(did_wallet_id_0) assert res["metadata"]["Twitter"] == "Https://test" diff --git a/chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py b/chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py index baef40b05ae3..a4c1a3a20007 100644 --- a/chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py +++ b/chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py @@ -171,14 +171,14 @@ async def test_subscribe_for_ph(simulator_and_wallet: OldSimulatorsAndWallets, s await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) - [tx_record] = await wallet.generate_signed_transaction(uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, uint64(0)) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction(uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, action_scope, uint64(0)) + [tx_record] = action_scope.side_effects.transactions assert tx_record.spend_bundle is not None assert len(tx_record.spend_bundle.removals()) == 1 spent_coin = tx_record.spend_bundle.removals()[0] assert spent_coin.puzzle_hash == puzzle_hash - [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) - await full_node_api.process_transaction_records(records=[tx_record]) # Let's make sure the wallet can handle a non ephemeral launcher @@ -186,20 +186,20 @@ async def test_subscribe_for_ph(simulator_and_wallet: OldSimulatorsAndWallets, s await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) - [tx_record] = await wallet.generate_signed_transaction( - uint64(10), SINGLETON_LAUNCHER_HASH, DEFAULT_TX_CONFIG, uint64(0) - ) - [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(10), SINGLETON_LAUNCHER_HASH, DEFAULT_TX_CONFIG, action_scope, uint64(0) + ) - await full_node_api.process_transaction_records(records=[tx_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) # Send a transaction to make sure the wallet is still running - [tx_record] = await wallet.generate_signed_transaction(uint64(10), junk_ph, DEFAULT_TX_CONFIG, uint64(0)) - [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction(uint64(10), junk_ph, DEFAULT_TX_CONFIG, action_scope, uint64(0)) - await full_node_api.process_transaction_records(records=[tx_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) all_messages = await get_all_messages_in_queue(incoming_queue) @@ -255,12 +255,12 @@ async def test_subscribe_for_coin_id(simulator_and_wallet: OldSimulatorsAndWalle coins = set() coins.add(coin_to_spend) - [tx_record] = await standard_wallet.generate_signed_transaction( - uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, uint64(0), coins=coins - ) - [tx_record] = await standard_wallet.wallet_state_manager.add_pending_transactions([tx_record]) + async with standard_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await standard_wallet.generate_signed_transaction( + uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, action_scope, uint64(0), coins=coins + ) - await full_node_api.process_transaction_records(records=[tx_record]) + await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions) all_messages = await get_all_messages_in_queue(incoming_queue) @@ -277,9 +277,12 @@ async def test_subscribe_for_coin_id(simulator_and_wallet: OldSimulatorsAndWalle # Test getting notification for coin that is about to be created await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20) - [tx_record] = await standard_wallet.generate_signed_transaction( - uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, uint64(0) - ) + async with standard_wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await standard_wallet.generate_signed_transaction( + uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, action_scope, uint64(0) + ) + + [tx_record] = action_scope.side_effects.transactions added_target = None assert tx_record.spend_bundle is not None diff --git a/chia/_tests/wallet/sync/test_wallet_sync.py b/chia/_tests/wallet/sync/test_wallet_sync.py index 5f5b65a23676..d110660bf628 100644 --- a/chia/_tests/wallet/sync/test_wallet_sync.py +++ b/chia/_tests/wallet/sync/test_wallet_sync.py @@ -576,8 +576,9 @@ async def test_request_additions_success(simulator_and_wallet: OldSimulatorsAndW payees.append(Payment(payee_ph, uint64(i + 100))) payees.append(Payment(payee_ph, uint64(i + 200))) - [tx] = await wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) await full_node_api.wait_transaction_records_entered_mempool([tx]) @@ -789,8 +790,9 @@ async def test_dusted_wallet( payees.append(Payment(payee_ph, uint64(dust_value))) # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) await full_node_api.wait_transaction_records_entered_mempool([tx]) @@ -847,8 +849,11 @@ async def test_dusted_wallet( # This greatly speeds up the overall process if dust_remaining % 100 == 0 and dust_remaining != new_dust: # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction( + uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -865,8 +870,11 @@ async def test_dusted_wallet( # Only need to create tx if there was new dust to be added if new_dust >= 1: # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction( + uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -911,8 +919,9 @@ async def test_dusted_wallet( payees.append(Payment(payee_ph, uint64(xch_spam_amount))) # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -949,8 +958,9 @@ async def test_dusted_wallet( payees.append(Payment(payee_ph, uint64(dust_value))) # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1007,8 +1017,9 @@ async def test_dusted_wallet( large_dust_balance += dust_value # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1042,8 +1053,9 @@ async def test_dusted_wallet( payees = [Payment(payee_ph, uint64(balance))] # construct and send tx - [tx] = await dust_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await dust_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with dust_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dust_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1085,8 +1097,11 @@ async def test_dusted_wallet( # This greatly speeds up the overall process if coins_remaining % 100 == 0 and coins_remaining != spam_filter_after_n_txs: # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction( + uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) await full_node_api.wait_transaction_records_entered_mempool([tx]) @@ -1099,8 +1114,9 @@ async def test_dusted_wallet( coins_remaining -= 1 # construct and send tx - [tx] = await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await farm_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with farm_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1128,8 +1144,9 @@ async def test_dusted_wallet( payees = [Payment(payee_ph, uint64(1))] # construct and send tx - [tx] = await dust_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, primaries=payees) - [tx] = await dust_wallet.wallet_state_manager.add_pending_transactions([tx]) + async with dust_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await dust_wallet.generate_signed_transaction(uint64(0), ph, DEFAULT_TX_CONFIG, action_scope, primaries=payees) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1173,9 +1190,9 @@ async def test_dusted_wallet( metadata = Program.to( [("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), ("h", "0xD4584AD463139FA8C0D9F68F4B59F185")] ) - txs = await farm_nft_wallet.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - txs = await farm_nft_wallet.wallet_state_manager.add_pending_transactions(txs) - for tx in txs: + async with farm_nft_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_nft_wallet.generate_new_nft(metadata, DEFAULT_TX_CONFIG, action_scope) + for tx in action_scope.side_effects.transactions: if tx.spend_bundle is not None: assert len(compute_memos(tx.spend_bundle)) > 0 await time_out_assert_not_none( @@ -1198,11 +1215,12 @@ async def test_dusted_wallet( nft_coins = await farm_nft_wallet.get_current_nfts() # Send the NFT to the dust wallet - txs = await farm_nft_wallet.generate_signed_transaction( - [uint64(nft_coins[0].coin.amount)], [dust_ph], DEFAULT_TX_CONFIG, coins={nft_coins[0].coin} - ) - assert len(txs) == 1 - txs = await farm_wallet_node.wallet_state_manager.add_pending_transactions(txs) + async with farm_nft_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await farm_nft_wallet.generate_signed_transaction( + [uint64(nft_coins[0].coin.amount)], [dust_ph], DEFAULT_TX_CONFIG, action_scope, coins={nft_coins[0].coin} + ) + assert len(action_scope.side_effects.transactions) == 1 + txs = await farm_wallet_node.wallet_state_manager.add_pending_transactions(action_scope.side_effects.transactions) assert txs[0].spend_bundle is not None assert len(compute_memos(txs[0].spend_bundle)) > 0 @@ -1361,10 +1379,11 @@ async def assert_coin_state_retry() -> None: await assert_coin_state_retry() - [tx] = await wallet.generate_signed_transaction( - uint64(1_000_000_000_000), bytes32([0] * 32), DEFAULT_TX_CONFIG, memos=[ph] - ) - [tx] = await wallet_node.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(1_000_000_000_000), bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope, memos=[ph] + ) + [tx] = action_scope.side_effects.transactions await time_out_assert(30, wallet.get_confirmed_balance, 2_000_000_000_000) async def tx_in_mempool() -> bool: diff --git a/chia/_tests/wallet/test_clvm_streamable.py b/chia/_tests/wallet/test_clvm_streamable.py index 19a84bf14be3..60fe2d4d8578 100644 --- a/chia/_tests/wallet/test_clvm_streamable.py +++ b/chia/_tests/wallet/test_clvm_streamable.py @@ -31,12 +31,12 @@ class BasicCLVMStreamable(Streamable): def test_basic_serialization() -> None: instance = BasicCLVMStreamable(a="1") - assert program_serialize_clvm_streamable(instance) == Program.to(["1"]) - assert byte_serialize_clvm_streamable(instance).hex() == "ff3180" - assert json_serialize_with_clvm_streamable(instance) == "ff3180" - assert program_deserialize_clvm_streamable(Program.to(["1"]), BasicCLVMStreamable) == instance - assert byte_deserialize_clvm_streamable(bytes.fromhex("ff3180"), BasicCLVMStreamable) == instance - assert json_deserialize_with_clvm_streamable("ff3180", BasicCLVMStreamable) == instance + assert program_serialize_clvm_streamable(instance) == Program.to([("a", "1")]) + assert byte_serialize_clvm_streamable(instance).hex() == "ffff613180" + assert json_serialize_with_clvm_streamable(instance) == "ffff613180" + assert program_deserialize_clvm_streamable(Program.to([("a", "1")]), BasicCLVMStreamable) == instance + assert byte_deserialize_clvm_streamable(bytes.fromhex("ffff613180"), BasicCLVMStreamable) == instance + assert json_deserialize_with_clvm_streamable("ffff613180", BasicCLVMStreamable) == instance @streamable @@ -55,17 +55,23 @@ class OutsideCLVM(Streamable): def test_nested_serialization() -> None: instance = OutsideStreamable(a="1", inside=BasicCLVMStreamable(a="1")) - assert json_serialize_with_clvm_streamable(instance) == {"inside": "ff3180", "a": "1"} - assert json_deserialize_with_clvm_streamable({"inside": "ff3180", "a": "1"}, OutsideStreamable) == instance + assert json_serialize_with_clvm_streamable(instance) == {"inside": "ffff613180", "a": "1"} + assert json_deserialize_with_clvm_streamable({"inside": "ffff613180", "a": "1"}, OutsideStreamable) == instance assert OutsideStreamable.from_json_dict({"a": "1", "inside": {"a": "1"}}) == instance instance_clvm = OutsideCLVM(a="1", inside=BasicCLVMStreamable(a="1")) - assert program_serialize_clvm_streamable(instance_clvm) == Program.to([["1"], "1"]) - assert byte_serialize_clvm_streamable(instance_clvm).hex() == "ffff3180ff3180" - assert json_serialize_with_clvm_streamable(instance_clvm) == "ffff3180ff3180" - assert program_deserialize_clvm_streamable(Program.to([["1"], "1"]), OutsideCLVM) == instance_clvm - assert byte_deserialize_clvm_streamable(bytes.fromhex("ffff3180ff3180"), OutsideCLVM) == instance_clvm - assert json_deserialize_with_clvm_streamable("ffff3180ff3180", OutsideCLVM) == instance_clvm + assert program_serialize_clvm_streamable(instance_clvm) == Program.to([["inside", ("a", "1")], ("a", "1")]) + assert byte_serialize_clvm_streamable(instance_clvm).hex() == "ffff86696e73696465ffff613180ffff613180" + assert json_serialize_with_clvm_streamable(instance_clvm) == "ffff86696e73696465ffff613180ffff613180" + assert ( + program_deserialize_clvm_streamable(Program.to([["inside", ("a", "1")], ("a", "1")]), OutsideCLVM) + == instance_clvm + ) + assert ( + byte_deserialize_clvm_streamable(bytes.fromhex("ffff86696e73696465ffff613180ffff613180"), OutsideCLVM) + == instance_clvm + ) + assert json_deserialize_with_clvm_streamable("ffff86696e73696465ffff613180ffff613180", OutsideCLVM) == instance_clvm @streamable @@ -85,8 +91,10 @@ class CompoundCLVM(Streamable): def test_compound_type_serialization() -> None: # regular streamable + regular values instance = Compound(optional=BasicCLVMStreamable(a="1"), list=[BasicCLVMStreamable(a="1")]) - assert json_serialize_with_clvm_streamable(instance) == {"optional": "ff3180", "list": ["ff3180"]} - assert json_deserialize_with_clvm_streamable({"optional": "ff3180", "list": ["ff3180"]}, Compound) == instance + assert json_serialize_with_clvm_streamable(instance) == {"optional": "ffff613180", "list": ["ffff613180"]} + assert ( + json_deserialize_with_clvm_streamable({"optional": "ffff613180", "list": ["ffff613180"]}, Compound) == instance + ) assert Compound.from_json_dict({"optional": {"a": "1"}, "list": [{"a": "1"}]}) == instance # regular streamable + falsey values @@ -97,21 +105,48 @@ def test_compound_type_serialization() -> None: # clvm streamable + regular values instance_clvm = CompoundCLVM(optional=BasicCLVMStreamable(a="1"), list=[BasicCLVMStreamable(a="1")]) - assert program_serialize_clvm_streamable(instance_clvm) == Program.to([[True, "1"], [["1"]]]) - assert byte_serialize_clvm_streamable(instance_clvm).hex() == "ffff01ff3180ffffff31808080" - assert json_serialize_with_clvm_streamable(instance_clvm) == "ffff01ff3180ffffff31808080" - assert program_deserialize_clvm_streamable(Program.to([[True, "1"], [["1"]]]), CompoundCLVM) == instance_clvm - assert byte_deserialize_clvm_streamable(bytes.fromhex("ffff01ff3180ffffff31808080"), CompoundCLVM) == instance_clvm - assert json_deserialize_with_clvm_streamable("ffff01ff3180ffffff31808080", CompoundCLVM) == instance_clvm + assert program_serialize_clvm_streamable(instance_clvm) == Program.to( + [["optional", 1, (97, 49)], ["list", [(97, 49)]]] + ) + assert ( + byte_serialize_clvm_streamable(instance_clvm).hex() + == "ffff886f7074696f6e616cff01ffff613180ffff846c697374ffffff6131808080" + ) + assert ( + json_serialize_with_clvm_streamable(instance_clvm) + == "ffff886f7074696f6e616cff01ffff613180ffff846c697374ffffff6131808080" + ) + assert ( + program_deserialize_clvm_streamable(Program.to([["optional", 1, (97, 49)], ["list", [(97, 49)]]]), CompoundCLVM) + == instance_clvm + ) + assert ( + byte_deserialize_clvm_streamable( + bytes.fromhex("ffff886f7074696f6e616cff01ffff613180ffff846c697374ffffff6131808080"), CompoundCLVM + ) + == instance_clvm + ) + assert ( + json_deserialize_with_clvm_streamable( + "ffff886f7074696f6e616cff01ffff613180ffff846c697374ffffff6131808080", CompoundCLVM + ) + == instance_clvm + ) # clvm streamable + falsey values instance_clvm = CompoundCLVM(optional=None, list=[]) - assert program_serialize_clvm_streamable(instance_clvm) == Program.to([[0], []]) - assert byte_serialize_clvm_streamable(instance_clvm).hex() == "ffff8080ff8080" - assert json_serialize_with_clvm_streamable(instance_clvm) == "ffff8080ff8080" - assert program_deserialize_clvm_streamable(Program.to([[0, 0], []]), CompoundCLVM) == instance_clvm - assert byte_deserialize_clvm_streamable(bytes.fromhex("ffff8080ff8080"), CompoundCLVM) == instance_clvm - assert json_deserialize_with_clvm_streamable("ffff8080ff8080", CompoundCLVM) == instance_clvm + assert program_serialize_clvm_streamable(instance_clvm) == Program.to([["optional", 0], ["list"]]) + assert byte_serialize_clvm_streamable(instance_clvm).hex() == "ffff886f7074696f6e616cff8080ffff846c6973748080" + assert json_serialize_with_clvm_streamable(instance_clvm) == "ffff886f7074696f6e616cff8080ffff846c6973748080" + assert program_deserialize_clvm_streamable(Program.to([["optional", 0], ["list"]]), CompoundCLVM) == instance_clvm + assert ( + byte_deserialize_clvm_streamable(bytes.fromhex("ffff886f7074696f6e616cff8080ffff846c6973748080"), CompoundCLVM) + == instance_clvm + ) + assert ( + json_deserialize_with_clvm_streamable("ffff886f7074696f6e616cff8080ffff846c6973748080", CompoundCLVM) + == instance_clvm + ) with pytest.raises(ValueError, match="@clvm_streamable"): diff --git a/chia/_tests/wallet/test_notifications.py b/chia/_tests/wallet/test_notifications.py index 723f10aad699..6c1c63dbc9cf 100644 --- a/chia/_tests/wallet/test_notifications.py +++ b/chia/_tests/wallet/test_notifications.py @@ -140,8 +140,11 @@ async def track_coin_state(*args: Any) -> bool: allow_height = peak.height + 1 if case == "allow_larger": allow_larger_height = peak.height + 1 - tx = await notification_manager_1.send_new_notification(ph_2, msg, AMOUNT, DEFAULT_TX_CONFIG, fee=FEE) - [tx] = await wsm_1.add_pending_transactions([tx]) + async with notification_manager_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await notification_manager_1.send_new_notification( + ph_2, msg, AMOUNT, DEFAULT_TX_CONFIG, action_scope, fee=FEE + ) + [tx] = action_scope.side_effects.transactions await time_out_assert_not_none( 5, full_node_api.full_node.mempool_manager.get_spendbundle, diff --git a/chia/_tests/wallet/test_signer_protocol.py b/chia/_tests/wallet/test_signer_protocol.py index cc6b33146b82..08134434dfe5 100644 --- a/chia/_tests/wallet/test_signer_protocol.py +++ b/chia/_tests/wallet/test_signer_protocol.py @@ -612,7 +612,9 @@ async def test_signer_commands(wallet_environments: WalletTestFramework) -> None ) AMOUNT = uint64(1) - [tx] = await wallet.generate_signed_transaction(AMOUNT, bytes32([0] * 32), DEFAULT_TX_CONFIG) + async with wallet_state_manager.new_action_scope(sign=False, push=False) as action_scope: + await wallet.generate_signed_transaction(AMOUNT, bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope) + [tx] = action_scope.side_effects.transactions runner = CliRunner() with runner.isolated_filesystem(): diff --git a/chia/_tests/wallet/test_wallet.py b/chia/_tests/wallet/test_wallet.py index 82290777c82d..199627c53043 100644 --- a/chia/_tests/wallet/test_wallet.py +++ b/chia/_tests/wallet/test_wallet.py @@ -74,13 +74,14 @@ async def test_wallet_make_transaction(self, wallet_environments: WalletTestFram tx_amount = 10 - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - bytes32([0] * 32), - DEFAULT_TX_CONFIG, - uint64(0), - ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + bytes32([0] * 32), + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) await wallet_environments.process_pending_states( [ @@ -124,12 +125,15 @@ async def test_wallet_reuse_address(self, wallet_environments: WalletTestFramewo tx_amount = 10 - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - bytes32([0] * 32), - DEFAULT_TX_CONFIG.override(reuse_puzhash=True), - uint64(0), - ) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + bytes32([0] * 32), + DEFAULT_TX_CONFIG.override(reuse_puzhash=True), + action_scope, + uint64(0), + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None assert len(tx.spend_bundle.coin_spends) == 1 new_puzhash = [c.puzzle_hash.hex() for c in tx.additions] @@ -184,14 +188,15 @@ async def test_wallet_clawback_claim_auto( # Transfer to normal wallet for _ in range(0, number_of_coins): - [tx1] = await wallet.generate_signed_transaction( - uint64(tx_amount), - normal_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 10}], - ) - [tx1] = await wallet.wallet_state_manager.add_pending_transactions([tx1]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + normal_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 10}], + ) await wallet_environments.process_pending_states( [ @@ -227,14 +232,16 @@ async def test_wallet_clawback_claim_auto( 20, wsm_1.coin_store.count_small_unspent, number_of_coins, tx_amount * 2, CoinType.CLAWBACK ) - [tx_bad] = await wallet.generate_signed_transaction( - uint64(tx_amount), - normal_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 10}], - ) - [tx_bad] = await wallet.wallet_state_manager.add_pending_transactions([tx_bad]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + normal_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 10}], + ) + [tx_bad] = action_scope.side_effects.transactions await wallet_environments.process_pending_states( [ @@ -331,16 +338,16 @@ async def test_wallet_clawback_clawback(self, wallet_environments: WalletTestFra tx_amount = 500 normal_puzhash = await wallet_1.get_new_puzzlehash() # Transfer to normal wallet - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - normal_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 500}], - memos=[b"Test"], - ) - - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + normal_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 500}], + memos=[b"Test"], + ) await wallet_environments.process_pending_states( [ @@ -378,6 +385,7 @@ async def test_wallet_clawback_clawback(self, wallet_environments: WalletTestFra dict(type_filter={"values": [TransactionType.INCOMING_CLAWBACK_SEND], "mode": 1}, wallet_id=1) ) # clawback merkle coin + [tx] = action_scope.side_effects.transactions merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1] interested_coins = await wsm_2.interested_store.get_interested_coin_ids() assert merkle_coin.name() in set(interested_coins) @@ -473,16 +481,16 @@ async def test_wallet_clawback_sent_self(self, wallet_environments: WalletTestFr tx_amount = 500 normal_puzhash = await wallet.get_new_puzzlehash() # Transfer to normal wallet - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - normal_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], - memos=[b"Test"], - ) - - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + normal_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], + memos=[b"Test"], + ) await wallet_environments.process_pending_states( [ @@ -516,6 +524,7 @@ async def test_wallet_clawback_sent_self(self, wallet_environments: WalletTestFr # Check merkle coins await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK) # Claim merkle coin + [tx] = action_scope.side_effects.transactions merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1] test_fee = 10 resp = await api_0.spend_clawback_coins( @@ -590,15 +599,15 @@ async def test_wallet_clawback_claim_manual(self, wallet_environments: WalletTes tx_amount = 500 normal_puzhash = await wallet_1.get_new_puzzlehash() # Transfer to normal wallet - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - normal_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], - ) - - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + normal_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], + ) await wallet_environments.process_pending_states( [ @@ -648,6 +657,7 @@ async def test_wallet_clawback_claim_manual(self, wallet_environments: WalletTes ) # Claim merkle coin + [tx] = action_scope.side_effects.transactions merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1] test_fee = 10 resp = await api_1.spend_clawback_coins( @@ -722,15 +732,15 @@ async def test_wallet_clawback_reorg(self, wallet_environments: WalletTestFramew tx_amount = 500 normal_puzhash = await wallet_1.get_new_puzzlehash() # Transfer to normal wallet - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - normal_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], - ) - - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + normal_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], + ) await wallet_environments.process_pending_states( [ @@ -905,15 +915,15 @@ async def test_get_clawback_coins(self, wallet_environments: WalletTestFramework tx_amount = 500 # Transfer to normal wallet - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - bytes32([0] * 32), - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 500}], - ) - - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + bytes32([0] * 32), + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 500}], + ) await wallet_environments.process_pending_states( [ @@ -947,6 +957,7 @@ async def test_get_clawback_coins(self, wallet_environments: WalletTestFramework # Check merkle coins await time_out_assert(20, wsm.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK) # clawback merkle coin + [tx] = action_scope.side_effects.transactions merkle_coin = tx.additions[0] if tx.additions[0].amount == tx_amount else tx.additions[1] resp = await env.rpc_api.get_coin_records({"wallet_id": 1, "coin_type": 1}) assert len(resp["coin_records"]) == 1 @@ -974,17 +985,19 @@ async def test_clawback_resync(self, self_hostname: str, wallet_environments: Wa tx_amount = 500 # Transfer to normal wallet - [tx1] = await wallet_1.generate_signed_transaction( - uint64(tx_amount), - wallet_2_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], - ) + async with wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_1.generate_signed_transaction( + uint64(tx_amount), + wallet_2_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], + ) + [tx1] = action_scope.side_effects.transactions clawback_coin_id_1 = tx1.additions[0].name() assert tx1.spend_bundle is not None - [tx1] = await wallet_1.wallet_state_manager.add_pending_transactions([tx1]) await wallet_environments.process_pending_states( [ @@ -1020,16 +1033,18 @@ async def test_clawback_resync(self, self_hostname: str, wallet_environments: Wa await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK) tx_amount2 = 700 - [tx2] = await wallet_1.generate_signed_transaction( - uint64(tx_amount2), - wallet_1_puzhash, - DEFAULT_TX_CONFIG, - uint64(0), - puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], - ) + async with wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_1.generate_signed_transaction( + uint64(tx_amount2), + wallet_1_puzhash, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + puzzle_decorator_override=[{"decorator": "CLAWBACK", "clawback_timelock": 5}], + ) + [tx2] = action_scope.side_effects.transactions clawback_coin_id_2 = tx2.additions[0].name() assert tx2.spend_bundle is not None - [tx2] = await wallet_1.wallet_state_manager.add_pending_transactions([tx2]) await wallet_environments.process_pending_states( [ @@ -1292,23 +1307,23 @@ async def test_wallet_send_to_three_peers( await full_node_1.add_block(block) await full_node_2.add_block(block) - [tx] = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( - uint64(10), - bytes32(32 * b"0"), - DEFAULT_TX_CONFIG, - uint64(0), - ) - assert tx.spend_bundle is not None - [tx] = await wallet_0.wallet_state_manager.main_wallet.wallet_state_manager.add_pending_transactions([tx]) - await full_node_api_0.wait_transaction_records_entered_mempool(records=[tx]) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( + uint64(10), + bytes32(32 * b"0"), + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) + await full_node_api_0.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) # wallet0 <-> sever1 await wallet_server_0.start_client(PeerInfo(self_hostname, server_1.get_port()), wallet_0.on_connect) - await full_node_api_1.wait_transaction_records_entered_mempool(records=[tx]) + await full_node_api_1.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) # wallet0 <-> sever2 await wallet_server_0.start_client(PeerInfo(self_hostname, server_2.get_port()), wallet_0.on_connect) - await full_node_api_2.wait_transaction_records_entered_mempool(records=[tx]) + await full_node_api_2.wait_transaction_records_entered_mempool(records=action_scope.side_effects.transactions) @pytest.mark.parametrize( "wallet_environments", @@ -1324,13 +1339,14 @@ async def test_wallet_make_transaction_hop(self, wallet_environments: WalletTest wallet_1 = env_1.xch_wallet tx_amount = 10 - [tx] = await wallet_0.generate_signed_transaction( - uint64(tx_amount), - await wallet_1.get_puzzle_hash(False), - DEFAULT_TX_CONFIG, - uint64(0), - ) - [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_0.generate_signed_transaction( + uint64(tx_amount), + await wallet_1.get_puzzle_hash(False), + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) await wallet_environments.process_pending_states( [ @@ -1370,10 +1386,10 @@ async def test_wallet_make_transaction_hop(self, wallet_environments: WalletTest ) tx_amount = 5 - [tx] = await wallet_1.generate_signed_transaction( - uint64(tx_amount), await wallet_0.get_puzzle_hash(False), DEFAULT_TX_CONFIG, uint64(0) - ) - [tx] = await wallet_1.wallet_state_manager.add_pending_transactions([tx]) + async with wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_1.generate_signed_transaction( + uint64(tx_amount), await wallet_0.get_puzzle_hash(False), DEFAULT_TX_CONFIG, action_scope, uint64(0) + ) await wallet_environments.process_pending_states( [ @@ -1427,19 +1443,20 @@ async def test_wallet_make_transaction_with_fee(self, wallet_environments: Walle tx_amount = 1_750_000_000_000 # ensures we grab both coins tx_fee = 10 - [tx] = await wallet_0.generate_signed_transaction( - uint64(tx_amount), - await wallet_1.get_new_puzzlehash(), - DEFAULT_TX_CONFIG, - uint64(tx_fee), - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_0.generate_signed_transaction( + uint64(tx_amount), + await wallet_1.get_new_puzzlehash(), + DEFAULT_TX_CONFIG, + action_scope, + uint64(tx_fee), + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None fees = estimate_fees(tx.spend_bundle) assert fees == tx_fee - [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) - await wallet_environments.process_pending_states( [ WalletStateTransition( @@ -1494,15 +1511,16 @@ async def test_wallet_make_transaction_with_memo(self, wallet_environments: Wall tx_amount = 1_750_000_000_000 # ensures we grab both coins tx_fee = 10 ph_2 = await wallet_1.get_new_puzzlehash() - [tx] = await wallet_0.generate_signed_transaction( - uint64(tx_amount), ph_2, DEFAULT_TX_CONFIG, uint64(tx_fee), memos=[ph_2] - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_0.generate_signed_transaction( + uint64(tx_amount), ph_2, DEFAULT_TX_CONFIG, action_scope, uint64(tx_fee), memos=[ph_2] + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None fees = estimate_fees(tx.spend_bundle) assert fees == tx_fee - [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) tx_id = tx.name.hex() memos = await env_0.rpc_api.get_transaction_memo(dict(transaction_id=tx_id)) # test json serialization @@ -1567,12 +1585,10 @@ async def test_wallet_create_hit_max_send_amount(self, wallet_environments: Wall ph = await wallet.get_puzzle_hash(False) primaries = [Payment(ph, uint64(1000000000 + i)) for i in range(int(wallet.max_send_quantity) + 1)] - [tx_split_coins] = await wallet.generate_signed_transaction( - uint64(1), ph, DEFAULT_TX_CONFIG, uint64(0), primaries=primaries - ) - assert tx_split_coins.spend_bundle is not None - - [tx_split_coins] = await wallet.wallet_state_manager.add_pending_transactions([tx_split_coins]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(1), ph, DEFAULT_TX_CONFIG, action_scope, uint64(0), primaries=primaries + ) await wallet_environments.process_pending_states( [ @@ -1605,24 +1621,28 @@ async def test_wallet_create_hit_max_send_amount(self, wallet_environments: Wall assert max_sent_amount < (await wallet.get_spendable_balance()) # 1) Generate transaction that is under the limit - [transaction_record] = await wallet.generate_signed_transaction( - uint64(max_sent_amount - 1), - ph, - DEFAULT_TX_CONFIG, - uint64(0), - ) + async with wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await wallet.generate_signed_transaction( + uint64(max_sent_amount - 1), + ph, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) - assert transaction_record.amount == uint64(max_sent_amount - 1) + assert action_scope.side_effects.transactions[0].amount == uint64(max_sent_amount - 1) # 2) Generate transaction that is equal to limit - [transaction_record] = await wallet.generate_signed_transaction( - uint64(max_sent_amount), - ph, - DEFAULT_TX_CONFIG, - uint64(0), - ) + async with wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await wallet.generate_signed_transaction( + uint64(max_sent_amount), + ph, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) - assert transaction_record.amount == uint64(max_sent_amount) + assert action_scope.side_effects.transactions[0].amount == uint64(max_sent_amount) # 3) Generate transaction that is greater than limit with pytest.raises( @@ -1630,12 +1650,14 @@ async def test_wallet_create_hit_max_send_amount(self, wallet_environments: Wall match=f"Transaction for {max_sent_amount + 1} is greater than max spendable balance in a block of " f"{max_sent_amount}. There may be other transactions pending or our minimum coin amount is too high.", ): - await wallet.generate_signed_transaction( - uint64(max_sent_amount + 1), - ph, - DEFAULT_TX_CONFIG, - uint64(0), - ) + async with wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await wallet.generate_signed_transaction( + uint64(max_sent_amount + 1), + ph, + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + ) @pytest.mark.parametrize( "wallet_environments", @@ -1650,12 +1672,15 @@ async def test_wallet_prevent_fee_theft(self, wallet_environments: WalletTestFra tx_amount = 1_750_000_000_000 tx_fee = 2_000_000_000_000 - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), - bytes32([0] * 32), - DEFAULT_TX_CONFIG, - uint64(tx_fee), - ) + async with wallet.wallet_state_manager.new_action_scope(push=False) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), + bytes32([0] * 32), + DEFAULT_TX_CONFIG, + action_scope, + uint64(tx_fee), + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None stolen_cs: Optional[CoinSpend] = None @@ -1723,11 +1748,10 @@ async def test_wallet_tx_reorg(self, wallet_environments: WalletTestFramework) - assert reorg_height is not None await full_node_api.farm_blocks_to_puzzlehash(count=3) - [tx] = await wallet.generate_signed_transaction( - uint64(tx_amount), await wallet_2.get_puzzle_hash(False), DEFAULT_TX_CONFIG, coins={coin} - ) - assert tx.spend_bundle is not None - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(tx_amount), await wallet_2.get_puzzle_hash(False), DEFAULT_TX_CONFIG, action_scope, coins={coin} + ) await wallet_environments.process_pending_states( [ @@ -1827,6 +1851,7 @@ async def test_wallet_tx_reorg(self, wallet_environments: WalletTestFramework) - unconfirmed = await wsm.tx_store.get_unconfirmed_for_wallet(int(wallet.id())) assert len(unconfirmed) == 0 + [tx] = action_scope.side_effects.transactions tx_record = await wsm.tx_store.get_transaction_record(tx.name) assert tx_record is not None removed = tx_record.removals[0] @@ -2024,14 +2049,17 @@ async def test_wallet_transaction_options(self, wallet_environments: WalletTestF coins = await wallet.select_coins(uint64(AMOUNT_TO_SEND), DEFAULT_TX_CONFIG.coin_selection_config) coin_list = list(coins) - [tx] = await wallet.generate_signed_transaction( - uint64(AMOUNT_TO_SEND), - bytes32([0] * 32), - DEFAULT_TX_CONFIG, - uint64(0), - coins=coins, - origin_id=coin_list[2].name(), - ) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( + uint64(AMOUNT_TO_SEND), + bytes32([0] * 32), + DEFAULT_TX_CONFIG, + action_scope, + uint64(0), + coins=coins, + origin_id=coin_list[2].name(), + ) + [tx] = action_scope.side_effects.transactions assert tx.spend_bundle is not None paid_coin = [coin for coin in tx.spend_bundle.additions() if coin.amount == AMOUNT_TO_SEND][0] assert paid_coin.parent_coin_info == coin_list[2].name() diff --git a/chia/_tests/wallet/test_wallet_action_scope.py b/chia/_tests/wallet/test_wallet_action_scope.py index 54583e96bc9c..cec1412b4ca4 100644 --- a/chia/_tests/wallet/test_wallet_action_scope.py +++ b/chia/_tests/wallet/test_wallet_action_scope.py @@ -71,7 +71,7 @@ async def test_wallet_action_scope() -> None: assert wsm.most_recent_call == ([STD_TX], True, False, True, [], []) async with wsm.new_action_scope( # type: ignore[attr-defined] - push=False, merge_spends=True, sign=True, additional_signing_responses=[] + push=False, merge_spends=True, sign=True, additional_signing_responses=[], extra_spends=[] ) as action_scope: async with action_scope.use() as interface: interface.side_effects.transactions = [] diff --git a/chia/_tests/wallet/test_wallet_node.py b/chia/_tests/wallet/test_wallet_node.py index a099e285aee8..580879b966f4 100644 --- a/chia/_tests/wallet/test_wallet_node.py +++ b/chia/_tests/wallet/test_wallet_node.py @@ -640,8 +640,9 @@ async def send_transaction( ) # Generate the transaction - [tx] = await wallet.generate_signed_transaction(uint64(0), bytes32([0] * 32), DEFAULT_TX_CONFIG) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction(uint64(0), bytes32([0] * 32), DEFAULT_TX_CONFIG, action_scope) + [tx] = action_scope.side_effects.transactions # Make sure it is sent to the peer await wallet_node._resend_queue() diff --git a/chia/_tests/wallet/test_wallet_retry.py b/chia/_tests/wallet/test_wallet_retry.py index d84a9619ac09..39d7e941163f 100644 --- a/chia/_tests/wallet/test_wallet_retry.py +++ b/chia/_tests/wallet/test_wallet_retry.py @@ -54,8 +54,9 @@ async def test_wallet_tx_retry( await farm_blocks(full_node_1, reward_ph, 2) await full_node_1.wait_for_wallet_synced(wallet_node=wallet_node_1, timeout=wait_secs) - [transaction] = await wallet_1.generate_signed_transaction(uint64(100), reward_ph, DEFAULT_TX_CONFIG) - [transaction] = await wallet_1.wallet_state_manager.add_pending_transactions([transaction]) + async with wallet_1.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet_1.generate_signed_transaction(uint64(100), reward_ph, DEFAULT_TX_CONFIG, action_scope) + [transaction] = action_scope.side_effects.transactions sb1: Optional[SpendBundle] = transaction.spend_bundle assert sb1 is not None diff --git a/chia/_tests/wallet/test_wallet_state_manager.py b/chia/_tests/wallet/test_wallet_state_manager.py index 826db346e8d3..699c075054e7 100644 --- a/chia/_tests/wallet/test_wallet_state_manager.py +++ b/chia/_tests/wallet/test_wallet_state_manager.py @@ -120,18 +120,29 @@ async def test_commit_transactions_to_db(wallet_environments: WalletTestFramewor uint64(2_000_000_000_000), coin_selection_config=wallet_environments.tx_config.coin_selection_config ) ) - [tx1] = await wsm.main_wallet.generate_signed_transaction( - uint64(0), - bytes32([0] * 32), - wallet_environments.tx_config, - coins={coins[0]}, - ) - [tx2] = await wsm.main_wallet.generate_signed_transaction( - uint64(0), - bytes32([0] * 32), - wallet_environments.tx_config, - coins={coins[1]}, - ) + + async with wsm.new_action_scope( + push=False, + merge_spends=False, + sign=False, + extra_spends=[], + ) as action_scope: + await wsm.main_wallet.generate_signed_transaction( + uint64(0), + bytes32([0] * 32), + wallet_environments.tx_config, + action_scope, + coins={coins[0]}, + ) + await wsm.main_wallet.generate_signed_transaction( + uint64(0), + bytes32([0] * 32), + wallet_environments.tx_config, + action_scope, + coins={coins[1]}, + ) + + created_txs = action_scope.side_effects.transactions def flatten_spend_bundles(txs: List[TransactionRecord]) -> List[SpendBundle]: return [tx.spend_bundle for tx in txs if tx.spend_bundle is not None] @@ -140,14 +151,8 @@ def flatten_spend_bundles(txs: List[TransactionRecord]) -> List[SpendBundle]: len(await wsm.tx_store.get_all_transactions_for_wallet(wsm.main_wallet.id(), type=TransactionType.OUTGOING_TX)) == 0 ) - new_txs = await wsm.add_pending_transactions( - [tx1, tx2], - push=False, - merge_spends=False, - sign=False, - extra_spends=[], - ) - bundles = flatten_spend_bundles(new_txs) + + bundles = flatten_spend_bundles(created_txs) assert len(bundles) == 2 for bundle in bundles: assert bundle.aggregated_signature == G2Element() @@ -157,12 +162,12 @@ def flatten_spend_bundles(txs: List[TransactionRecord]) -> List[SpendBundle]: ) extra_coin_spend = make_spend( - Coin(bytes32(b"1" * 32), bytes32(b"1" * 32), uint64(0)), Program.to(1), Program.to([None]) + Coin(bytes32(b"1" * 32), bytes32(b"1" * 32), uint64(0)), Program.to(1), Program.to([]) ) extra_spend = SpendBundle([extra_coin_spend], G2Element()) new_txs = await wsm.add_pending_transactions( - [tx1, tx2], + created_txs, push=False, merge_spends=False, sign=False, @@ -179,7 +184,7 @@ def flatten_spend_bundles(txs: List[TransactionRecord]) -> List[SpendBundle]: assert extra_coin_spend in [spend for bundle in bundles for spend in bundle.coin_spends] new_txs = await wsm.add_pending_transactions( - [tx1, tx2], + created_txs, push=False, merge_spends=True, sign=False, @@ -195,7 +200,7 @@ def flatten_spend_bundles(txs: List[TransactionRecord]) -> List[SpendBundle]: ) assert extra_coin_spend in [spend for bundle in bundles for spend in bundle.coin_spends] - [tx1, tx2] = await wsm.add_pending_transactions([tx1, tx2], push=True, merge_spends=True, sign=True) + new_txs = await wsm.add_pending_transactions(created_txs, push=True, merge_spends=True, sign=True) bundles = flatten_spend_bundles(new_txs) assert len(bundles) == 1 assert ( @@ -203,4 +208,4 @@ def flatten_spend_bundles(txs: List[TransactionRecord]) -> List[SpendBundle]: == 2 ) - await wallet_environments.full_node.wait_transaction_records_entered_mempool([tx1, tx2]) + await wallet_environments.full_node.wait_transaction_records_entered_mempool(new_txs) diff --git a/chia/_tests/wallet/vc_wallet/test_vc_wallet.py b/chia/_tests/wallet/vc_wallet/test_vc_wallet.py index 80206d4cdf9a..9d9ce11f3037 100644 --- a/chia/_tests/wallet/vc_wallet/test_vc_wallet.py +++ b/chia/_tests/wallet/vc_wallet/test_vc_wallet.py @@ -149,13 +149,14 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None: } # Generate DID as an "authorized provider" - did_id: bytes32 = bytes32.from_hexstr( - ( - await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) - ).get_my_DID() - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_id: bytes32 = bytes32.from_hexstr( + ( + await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) + ).get_my_DID() + ) # Mint a VC vc_record = ( @@ -348,7 +349,7 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None: assert await wallet_node_0.wallet_state_manager.get_wallet_for_asset_id(cr_cat_wallet_0.get_asset_id()) is not None wallet_1_ph = await wallet_1.get_new_puzzlehash() wallet_1_addr = encode_puzzle_hash(wallet_1_ph, "txch") - tx = ( + txs = ( await client_0.cat_spend( cr_cat_wallet_0.id(), wallet_environments.tx_config, @@ -357,8 +358,7 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None: uint64(2000000000), memos=["hey"], ) - ).transaction - [tx] = await wallet_node_0.wallet_state_manager.add_pending_transactions([tx]) + ).transactions await wallet_environments.process_pending_states( [ WalletStateTransition( @@ -421,7 +421,7 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None: ] ) assert await wallet_node_1.wallet_state_manager.wallets[env_1.dealias_wallet_id("crcat")].match_hinted_coin( - next(c for c in tx.additions if c.amount == 90), wallet_1_ph + next(c for tx in txs for c in tx.additions if c.amount == 90), wallet_1_ph ) pending_tx = await client_1.get_transactions( env_1.dealias_wallet_id("crcat"), @@ -655,9 +655,10 @@ async def test_self_revoke(wallet_environments: WalletTestFramework) -> None: } # Generate DID as an "authorized provider" - did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( - wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG - ) + async with wallet_0.wallet_state_manager.new_action_scope(push=True) as action_scope: + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1), DEFAULT_TX_CONFIG, action_scope + ) did_id: bytes32 = bytes32.from_hexstr(did_wallet.get_my_DID()) vc_record = ( @@ -687,16 +688,19 @@ async def test_self_revoke(wallet_environments: WalletTestFramework) -> None: # Test a negative case real quick (mostly unrelated) with pytest.raises(ValueError, match="at the same time"): - await (await wallet_node_0.wallet_state_manager.get_or_create_vc_wallet()).generate_signed_transaction( - new_vc_record.vc.launcher_id, - wallet_environments.tx_config, - new_proof_hash=bytes32([0] * 32), - self_revoke=True, - ) + async with wallet_node_0.wallet_state_manager.new_action_scope(push=False) as action_scope: + await (await wallet_node_0.wallet_state_manager.get_or_create_vc_wallet()).generate_signed_transaction( + new_vc_record.vc.launcher_id, + wallet_environments.tx_config, + action_scope, + new_proof_hash=bytes32([0] * 32), + self_revoke=True, + ) # Send the DID to oblivion - txs = await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, wallet_environments.tx_config) - txs = await did_wallet.wallet_state_manager.add_pending_transactions(txs) + async with did_wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, wallet_environments.tx_config, action_scope) + await wallet_environments.process_pending_states( [ WalletStateTransition( diff --git a/chia/cmds/cmd_classes.py b/chia/cmds/cmd_classes.py index 498f8d5abd96..663d51e06874 100644 --- a/chia/cmds/cmd_classes.py +++ b/chia/cmds/cmd_classes.py @@ -252,7 +252,7 @@ def _chia_command(cls: Type[ChiaCommand]) -> Type[ChiaCommand]: kw_only=True, )(cls) - cmd.command(name, help=help)(_convert_class_to_function(wrapped_cls)) + cmd.command(name, short_help=help)(_convert_class_to_function(wrapped_cls)) return wrapped_cls return _chia_command diff --git a/chia/cmds/init.py b/chia/cmds/init.py index 0c7baf3d4327..ce53a197d4dc 100644 --- a/chia/cmds/init.py +++ b/chia/cmds/init.py @@ -40,7 +40,7 @@ def init_cmd( - Make a copy of your Farming Machine CA directory: ~/.chia/[version]/config/ssl/ca - Shut down all chia daemon processes with `chia stop all -d` - Run `chia init -c [directory]` on your remote harvester, - where [directory] is the the copy of your Farming Machine CA directory + where [directory] is the copy of your Farming Machine CA directory - Get more details on remote harvester on Chia wiki: https://github.com/Chia-Network/chia-blockchain/wiki/Farming-on-many-machines """ diff --git a/chia/cmds/peer_funcs.py b/chia/cmds/peer_funcs.py index cf8e3877f7a6..b678a661e037 100644 --- a/chia/cmds/peer_funcs.py +++ b/chia/cmds/peer_funcs.py @@ -102,6 +102,8 @@ async def print_connections(rpc_client: RpcClient, trusted_peers: Dict[str, Any] f"{last_connect} " f"{mb_up:7.1f}|{mb_down:<7.1f}" ) + if trusted: + con_str += f" -Trusted: {trusted}" print(con_str) diff --git a/chia/cmds/sim_funcs.py b/chia/cmds/sim_funcs.py index 070dc3dce2ca..053e5664de5c 100644 --- a/chia/cmds/sim_funcs.py +++ b/chia/cmds/sim_funcs.py @@ -80,6 +80,14 @@ def create_chia_directory( config["selected_network"] = "simulator0" config["wallet"]["selected_network"] = "simulator0" config["full_node"]["selected_network"] = "simulator0" + config["seeder"]["selected_network"] = "simulator0" + config["harvester"]["selected_network"] = "simulator0" + config["pool"]["selected_network"] = "simulator0" + config["farmer"]["selected_network"] = "simulator0" + config["timelord"]["selected_network"] = "simulator0" + config["ui"]["selected_network"] = "simulator0" + config["introducer"]["selected_network"] = "simulator0" + config["data_layer"]["selected_network"] = "simulator0" if not docker_mode: # We want predictable ports for our docker image. # set ports and networks, we don't want to cause a port conflict. port_offset = randint(1, 20000) diff --git a/chia/data_layer/data_layer.py b/chia/data_layer/data_layer.py index ef2c920edf23..76e3c59386dd 100644 --- a/chia/data_layer/data_layer.py +++ b/chia/data_layer/data_layer.py @@ -3,6 +3,7 @@ import asyncio import contextlib import dataclasses +import functools import json import logging import os @@ -124,6 +125,9 @@ class DataLayer: _wallet_rpc: Optional[WalletRpcClient] = None subscription_lock: asyncio.Lock = dataclasses.field(default_factory=asyncio.Lock) subscription_update_concurrency: int = 5 + client_timeout: aiohttp.ClientTimeout = dataclasses.field( + default_factory=functools.partial(aiohttp.ClientTimeout, total=45, sock_connect=5) + ) @property def server(self) -> ChiaServer: @@ -185,6 +189,9 @@ def create( maximum_full_file_count=config.get("maximum_full_file_count", 1), subscription_update_concurrency=config.get("subscription_update_concurrency", 5), unsubscribe_data_queue=[], + client_timeout=aiohttp.ClientTimeout( + total=config.get("client_timeout", 45), sock_connect=config.get("connect_timeout", 5) + ), ) self.db_path.parent.mkdir(parents=True, exist_ok=True) @@ -586,7 +593,6 @@ async def fetch_and_validate(self, store_id: bytes32) -> None: max_generation=singleton_record.generation, ) try: - timeout = self.config.get("client_timeout", 15) proxy_url = self.config.get("proxy_url", None) success = await insert_from_delta_file( self.data_store, @@ -595,7 +601,7 @@ async def fetch_and_validate(self, store_id: bytes32) -> None: [record.root for record in reversed(to_download)], server_info, self.server_files_location, - timeout, + self.client_timeout, self.log, proxy_url, await self.get_downloader(store_id, url), diff --git a/chia/data_layer/data_layer_util.py b/chia/data_layer/data_layer_util.py index ba1bd8e19c1c..84298622dc48 100644 --- a/chia/data_layer/data_layer_util.py +++ b/chia/data_layer/data_layer_util.py @@ -5,8 +5,7 @@ from enum import Enum, IntEnum from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union -# TODO: remove or formalize this -import aiosqlite as aiosqlite +import aiosqlite from typing_extensions import final from chia.data_layer.data_layer_errors import ProofIntegrityError @@ -768,7 +767,7 @@ class PluginRemote: def unmarshal(cls, marshalled: Dict[str, Any]) -> PluginRemote: return cls( url=marshalled["url"], - headers=marshalled["headers"], + headers=marshalled.get("headers", {}), ) diff --git a/chia/data_layer/data_layer_wallet.py b/chia/data_layer/data_layer_wallet.py index 0825e1e37024..8ca682d42ee7 100644 --- a/chia/data_layer/data_layer_wallet.py +++ b/chia/data_layer/data_layer_wallet.py @@ -62,6 +62,7 @@ from chia.wallet.util.wallet_sync_utils import fetch_coin_spend, fetch_coin_spend_for_coin_state from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_protocol import GSTOptionalArgs, WalletProtocol @@ -302,9 +303,10 @@ async def generate_new_reporter( self, initial_root: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[TransactionRecord, bytes32]: + ) -> bytes32: """ Creates the initial singleton, which includes spending an origin coin, the launcher, and creating a singleton """ @@ -324,17 +326,18 @@ async def generate_new_reporter( ) announcement_message: bytes32 = genesis_launcher_solution.get_tree_hash() announcement = AssertCoinAnnouncement(asserted_id=launcher_coin.name(), asserted_msg=announcement_message) - [create_launcher_tx_record] = await self.standard_wallet.generate_signed_transaction( + + await self.standard_wallet.generate_signed_transaction( amount=uint64(1), puzzle_hash=SINGLETON_LAUNCHER.get_tree_hash(), tx_config=tx_config, + action_scope=action_scope, fee=fee, origin_id=launcher_parent.name(), coins=coins, primaries=None, extra_conditions=(*extra_conditions, announcement), ) - assert create_launcher_tx_record is not None and create_launcher_tx_record.spend_bundle is not None launcher_cs: CoinSpend = CoinSpend( launcher_coin, @@ -342,10 +345,10 @@ async def generate_new_reporter( SerializedProgram.from_program(genesis_launcher_solution), ) launcher_sb: SpendBundle = SpendBundle([launcher_cs], G2Element()) - full_spend: SpendBundle = SpendBundle.aggregate([create_launcher_tx_record.spend_bundle, launcher_sb]) - # Delete from standard transaction so we don't push duplicate spends - std_record: TransactionRecord = dataclasses.replace(create_launcher_tx_record, spend_bundle=full_spend) + async with action_scope.use() as interface: + interface.side_effects.extra_spends.append(launcher_sb) + singleton_record = SingletonRecord( coin_id=Coin(launcher_coin.name(), full_puzzle.get_tree_hash(), uint64(1)).name(), launcher_id=launcher_coin.name(), @@ -365,37 +368,38 @@ async def generate_new_reporter( await self.wallet_state_manager.dl_store.add_singleton_record(singleton_record) await self.wallet_state_manager.add_interested_puzzle_hashes([singleton_record.launcher_id], [self.id()]) - return std_record, launcher_coin.name() + return launcher_coin.name() async def create_tandem_xch_tx( self, fee: uint64, announcement_to_assert: AssertAnnouncement, tx_config: TXConfig, - ) -> TransactionRecord: - [chia_tx] = await self.standard_wallet.generate_signed_transaction( + action_scope: WalletActionScope, + ) -> None: + await self.standard_wallet.generate_signed_transaction( amount=uint64(0), puzzle_hash=await self.standard_wallet.get_puzzle_hash(new=not tx_config.reuse_puzhash), tx_config=tx_config, + action_scope=action_scope, fee=fee, negative_change_allowed=False, extra_conditions=(announcement_to_assert,), ) - assert chia_tx.spend_bundle is not None - return chia_tx async def create_update_state_spend( self, launcher_id: bytes32, root_hash: Optional[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, new_puz_hash: Optional[bytes32] = None, new_amount: Optional[uint64] = None, fee: uint64 = uint64(0), add_pending_singleton: bool = True, announce_new_state: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: singleton_record, parent_lineage = await self.get_spendable_singleton_info(launcher_id) if root_hash is None: @@ -585,16 +589,12 @@ async def create_update_state_spend( ) assert dl_tx.spend_bundle is not None if fee > 0: - chia_tx = await self.create_tandem_xch_tx( - fee, AssertAnnouncement(True, asserted_origin_id=current_coin.name(), asserted_msg=b"$"), tx_config + await self.create_tandem_xch_tx( + fee, + AssertAnnouncement(True, asserted_origin_id=current_coin.name(), asserted_msg=b"$"), + tx_config, + action_scope, ) - assert chia_tx.spend_bundle is not None - aggregate_bundle = SpendBundle.aggregate([dl_tx.spend_bundle, chia_tx.spend_bundle]) - dl_tx = dataclasses.replace(dl_tx, spend_bundle=aggregate_bundle, name=aggregate_bundle.name()) - chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) - txs: List[TransactionRecord] = [dl_tx, chia_tx] - else: - txs = [dl_tx] if add_pending_singleton: await self.wallet_state_manager.dl_store.add_singleton_record( @@ -605,19 +605,21 @@ async def create_update_state_spend( second_singleton_record, ) - return txs + async with action_scope.use() as interface: + interface.side_effects.transactions.append(dl_tx) async def generate_signed_transaction( self, amounts: List[uint64], puzzle_hashes: List[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Set[Coin] = set(), memos: Optional[List[List[bytes]]] = None, # ignored extra_conditions: Tuple[Condition, ...] = tuple(), **kwargs: Unpack[GSTOptionalArgs], - ) -> List[TransactionRecord]: + ) -> None: launcher_id: Optional[bytes32] = kwargs.get("launcher_id", None) new_root_hash: Optional[bytes32] = kwargs.get("new_root_hash", None) add_pending_singleton: bool = kwargs.get("add_pending_singleton", True) @@ -639,10 +641,11 @@ async def generate_signed_transaction( 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( + await self.create_update_state_spend( launcher_id, new_root_hash, tx_config, + action_scope, puzzle_hashes[0], amounts[0], fee, @@ -711,29 +714,30 @@ async def create_new_mirror( amount: uint64, urls: List[bytes], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: - [create_mirror_tx_record] = await self.standard_wallet.generate_signed_transaction( + ) -> None: + await self.standard_wallet.generate_signed_transaction( amount=amount, puzzle_hash=create_mirror_puzzle().get_tree_hash(), tx_config=tx_config, + action_scope=action_scope, fee=fee, primaries=[], memos=[launcher_id, *(url for url in urls)], extra_conditions=extra_conditions, ) - assert create_mirror_tx_record.spend_bundle is not None - return [create_mirror_tx_record] async def delete_mirror( self, mirror_id: bytes32, peer: WSChiaConnection, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: mirror: Mirror = await self.get_mirror(mirror_id) mirror_coin: Coin = (await self.wallet_state_manager.wallet_node.get_coin_state([mirror.coin_id], peer=peer))[ 0 @@ -767,46 +771,39 @@ async def delete_mirror( ), ) mirror_bundle: SpendBundle = SpendBundle([mirror_spend], G2Element()) - txs = [ - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=new_puzhash, - amount=uint64(mirror_coin.amount), - fee_amount=fee, - confirmed=False, - sent=uint32(10), - spend_bundle=mirror_bundle, - additions=mirror_bundle.additions(), - removals=mirror_bundle.removals(), - memos=list(compute_memos(mirror_bundle).items()), - wallet_id=self.id(), # This is being called before the wallet is created so we're using a temp ID of 0 - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=mirror_bundle.name(), - valid_times=parse_timelock_info(extra_conditions), + + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=new_puzhash, + amount=uint64(mirror_coin.amount), + fee_amount=fee, + confirmed=False, + sent=uint32(10), + spend_bundle=mirror_bundle, + additions=mirror_bundle.additions(), + removals=mirror_bundle.removals(), + memos=list(compute_memos(mirror_bundle).items()), + wallet_id=self.id(), # This is being called before the wallet is created so we're using a ID of 0 + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=mirror_bundle.name(), + valid_times=parse_timelock_info(extra_conditions), + ) ) - ] if excess_fee > 0: - [chia_tx] = await self.wallet_state_manager.main_wallet.generate_signed_transaction( + await self.wallet_state_manager.main_wallet.generate_signed_transaction( uint64(1), new_puzhash, tx_config, + action_scope, fee=uint64(excess_fee), extra_conditions=(AssertCoinAnnouncement(asserted_id=mirror_coin.name(), asserted_msg=b"$"),), ) - assert txs[0].spend_bundle is not None - assert chia_tx.spend_bundle is not None - txs = [ - dataclasses.replace( - txs[0], spend_bundle=SpendBundle.aggregate([txs[0].spend_bundle, chia_tx.spend_bundle]) - ), - dataclasses.replace(chia_tx, spend_bundle=None), - ] - - return txs ########### # SYNCING # @@ -969,19 +966,17 @@ async def potentially_handle_resubmit(self, launcher_id: bytes32) -> None: # pr if not root_changed: # The root never changed so let's attempt a rebase try: - all_txs: List[TransactionRecord] = [] - for singleton in unconfirmed_singletons: - for tx in relevant_dl_txs: - if any(c.name() == singleton.coin_id for c in tx.additions): - if tx.spend_bundle is not None: - # This executes the puzzles - fee = uint64(estimate_fees(tx.spend_bundle)) - else: - fee = uint64(0) - - assert self.wallet_state_manager.wallet_node.logged_in_fingerprint is not None - - all_txs.extend( + async with self.wallet_state_manager.new_action_scope(push=True) as action_scope: + for singleton in unconfirmed_singletons: + for tx in relevant_dl_txs: + if any(c.name() == singleton.coin_id for c in tx.additions): + if tx.spend_bundle is not None: + # This executes the puzzles + fee = uint64(estimate_fees(tx.spend_bundle)) + else: + fee = uint64(0) + + assert self.wallet_state_manager.wallet_node.logged_in_fingerprint is not None await self.create_update_state_spend( launcher_id, singleton.root, @@ -992,10 +987,9 @@ async def potentially_handle_resubmit(self, launcher_id: bytes32) -> None: # pr self.wallet_state_manager.wallet_node.logged_in_fingerprint ), ), + action_scope=action_scope, fee=fee, ) - ) - await self.wallet_state_manager.add_pending_transactions(all_txs) except Exception as e: self.log.warning(f"Something went wrong during attempted DL resubmit: {str(e)}") # Something went wrong so let's delete anything pending that was created @@ -1131,9 +1125,10 @@ async def make_update_offer( driver_dict: Dict[bytes32, PuzzleInfo], solver: Solver, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[Offer, List[TransactionRecord]]: + ) -> Offer: dl_wallet = None for wallet in wallet_state_manager.wallets.values(): if wallet.type() == WalletType.DATA_LAYER.value: @@ -1144,7 +1139,6 @@ async def make_update_offer( offered_launchers: List[bytes32] = [k for k, v in offer_dict.items() if v < 0 and k is not None] fee_left_to_pay: uint64 = fee - all_bundles: List[SpendBundle] = [] all_transactions: List[TransactionRecord] = [] for launcher in offered_launchers: try: @@ -1153,47 +1147,62 @@ async def make_update_offer( this_solver = solver["0x" + launcher.hex()] new_root: bytes32 = this_solver["new_root"] new_ph: bytes32 = await wallet_state_manager.main_wallet.get_puzzle_hash(new=not tx_config.reuse_puzhash) - txs: List[TransactionRecord] = await dl_wallet.generate_signed_transaction( - [uint64(1)], - [new_ph], - tx_config, - fee=fee_left_to_pay, - launcher_id=launcher, - new_root_hash=new_root, - add_pending_singleton=False, - announce_new_state=True, - extra_conditions=extra_conditions, - ) - fee_left_to_pay = uint64(0) - extra_conditions = tuple() + async with wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await dl_wallet.generate_signed_transaction( + [uint64(1)], + [new_ph], + tx_config, + inner_action_scope, + fee=fee_left_to_pay, + launcher_id=launcher, + new_root_hash=new_root, + add_pending_singleton=False, + announce_new_state=True, + extra_conditions=extra_conditions, + ) + fee_left_to_pay = uint64(0) + extra_conditions = tuple() + + async with inner_action_scope.use() as interface: + dl_spend: CoinSpend = next( + cs + for tx in interface.side_effects.transactions + for cs in tx.spend_bundle.coin_spends + if tx.spend_bundle is not None and match_dl_singleton(cs.puzzle_reveal)[0] + ) + dl_solution: Program = dl_spend.solution.to_program() + old_graftroot: Program = dl_solution.at("rrffrf") + new_graftroot: Program = create_graftroot_offer_puz( + [bytes32(dep["launcher_id"]) for dep in this_solver["dependencies"]], + [list(v for v in dep["values_to_prove"]) for dep in this_solver["dependencies"]], + old_graftroot, + ) - assert txs[0].spend_bundle is not None - dl_spend: CoinSpend = next( - cs for cs in txs[0].spend_bundle.coin_spends if match_dl_singleton(cs.puzzle_reveal)[0] - ) - all_other_spends: List[CoinSpend] = [cs for cs in txs[0].spend_bundle.coin_spends if cs != dl_spend] - dl_solution: Program = dl_spend.solution.to_program() - old_graftroot: Program = dl_solution.at("rrffrf") - new_graftroot: Program = create_graftroot_offer_puz( - [bytes32(dep["launcher_id"]) for dep in this_solver["dependencies"]], - [list(v for v in dep["values_to_prove"]) for dep in this_solver["dependencies"]], - old_graftroot, - ) + new_solution: Program = dl_solution.replace(rrffrf=new_graftroot, rrffrrf=Program.to([None] * 5)) + new_spend: CoinSpend = dl_spend.replace(solution=SerializedProgram.from_program(new_solution)) + async with inner_action_scope.use() as interface: + for i, tx in enumerate(interface.side_effects.transactions): + if tx.spend_bundle is not None and dl_spend in tx.spend_bundle.coin_spends: + break + else: + # No test coverage for this line because it should never be reached + raise RuntimeError("Internal logic error while constructing update offer") # pragma: no cover + new_bundle = SpendBundle( + [ + *( + cs + for cs in interface.side_effects.transactions[i].spend_bundle.coin_spends + if cs != dl_spend + ), + new_spend, + ], + G2Element(), + ) + interface.side_effects.transactions[i] = dataclasses.replace( + interface.side_effects.transactions[i], spend_bundle=new_bundle, name=new_bundle.name() + ) - new_solution: Program = dl_solution.replace(rrffrf=new_graftroot, rrffrrf=Program.to([None] * 5)) - new_spend: CoinSpend = dl_spend.replace(solution=SerializedProgram.from_program(new_solution)) - new_bundle: SpendBundle = txs[0].spend_bundle.replace( - coin_spends=[*all_other_spends, new_spend], - ) - all_bundles.append(new_bundle) - all_transactions.append( - dataclasses.replace( - txs[0], - spend_bundle=new_bundle, - name=new_bundle.name(), - ) - ) - all_transactions.extend(txs[1:]) + all_transactions.extend(inner_action_scope.side_effects.transactions) # create some dummy requested payments requested_payments = { @@ -1201,7 +1210,15 @@ async def make_update_offer( for k, v in offer_dict.items() if v > 0 } - return Offer(requested_payments, SpendBundle.aggregate(all_bundles), driver_dict), all_transactions + + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(all_transactions) + + return Offer( + requested_payments, + SpendBundle.aggregate([tx.spend_bundle for tx in all_transactions if tx.spend_bundle is not None]), + driver_dict, + ) @staticmethod async def finish_graftroot_solutions(offer: Offer, solver: Solver) -> Offer: diff --git a/chia/data_layer/download_data.py b/chia/data_layer/download_data.py index b597693e0698..0b45fee181e6 100644 --- a/chia/data_layer/download_data.py +++ b/chia/data_layer/download_data.py @@ -145,7 +145,7 @@ async def insert_from_delta_file( root_hashes: List[bytes32], server_info: ServerInfo, client_foldername: Path, - timeout: int, + timeout: aiohttp.ClientTimeout, log: logging.Logger, proxy_url: str, downloader: Optional[PluginRemote], @@ -164,7 +164,14 @@ async def insert_from_delta_file( if downloader is None: # use http downloader - this raises on any error try: - await http_download(client_foldername, filename, proxy_url, server_info, timeout, log) + await http_download( + client_foldername, + filename, + proxy_url, + server_info, + timeout, + log, + ) except (asyncio.TimeoutError, aiohttp.ClientError): new_server_info = await data_store.server_misses_file(store_id, server_info, timestamp) log.info( @@ -252,7 +259,7 @@ async def http_download( filename: str, proxy_url: str, server_info: ServerInfo, - timeout: int, + timeout: aiohttp.ClientTimeout, log: logging.Logger, ) -> None: """ @@ -262,7 +269,10 @@ async def http_download( async with aiohttp.ClientSession() as session: headers = {"accept-encoding": "gzip"} async with session.get( - server_info.url + "/" + filename, headers=headers, timeout=timeout, proxy=proxy_url + server_info.url + "/" + filename, + headers=headers, + timeout=timeout, + proxy=proxy_url, ) as resp: resp.raise_for_status() size = int(resp.headers.get("content-length", 0)) diff --git a/chia/data_layer/util/plugin.py b/chia/data_layer/util/plugin.py new file mode 100644 index 000000000000..6ea26376fc6b --- /dev/null +++ b/chia/data_layer/util/plugin.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import logging +from pathlib import Path +from typing import List + +import yaml + +from chia.data_layer.data_layer_util import PluginRemote +from chia.util.log_exceptions import log_exceptions + + +async def load_plugin_configurations(root_path: Path, config_type: str, log: logging.Logger) -> List[PluginRemote]: + """ + Loads plugin configurations from the specified directory and validates that the contents + are in the expected JSON format (an array of PluginRemote objects). It gracefully handles errors + and ensures that the necessary directories exist, creating them if they do not. + + Args: + root_path (Path): The root path where the plugins directory is located. + config_type (str): The type of plugins to load ('downloaders' or 'uploaders'). + + Returns: + List[PluginRemote]: A list of valid PluginRemote instances for the specified plugin type. + """ + config_path = root_path / "plugins" / config_type + config_path.mkdir(parents=True, exist_ok=True) # Ensure the config directory exists + + valid_configs = [] + for conf_file in config_path.glob("*.conf"): + with log_exceptions( + log=log, + consume=True, + message=f"Skipping config file due to failure loading or parsing: {conf_file}", + ): + with open(conf_file) as file: + data = yaml.safe_load(file) + + valid_configs.extend([PluginRemote.unmarshal(marshalled=item) for item in data]) + log.info(f"loaded plugin configuration: {conf_file}") + return valid_configs diff --git a/chia/pools/pool_config.py b/chia/pools/pool_config.py index cc106546615d..1efafbdc5419 100644 --- a/chia/pools/pool_config.py +++ b/chia/pools/pool_config.py @@ -92,7 +92,7 @@ def update_pool_config_entry( updated = False for pool_config_dict in pool_list: try: - if hexstr_to_bytes(pool_config_dict["owner_public_key"]) == bytes(pool_wallet_config.owner_public_key): + if hexstr_to_bytes(pool_config_dict["launcher_id"]) == bytes(pool_wallet_config.launcher_id): if update_closure(pool_config_dict): updated = True except Exception as e: @@ -101,6 +101,8 @@ def update_pool_config_entry( log.info(update_log_message) config["pool"]["pool_list"] = pool_list save_config(root_path, "config.yaml", config) + else: + log.error(f"Failed to update pool config entry for launcher_id {pool_wallet_config.launcher_id}") async def update_pool_config(root_path: Path, pool_config_list: List[PoolWalletConfig]) -> None: diff --git a/chia/pools/pool_wallet.py b/chia/pools/pool_wallet.py index 695f27a2f1db..84e35fb7f3fd 100644 --- a/chia/pools/pool_wallet.py +++ b/chia/pools/pool_wallet.py @@ -42,15 +42,16 @@ from chia.types.blockchain_format.serialized_program import SerializedProgram from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend, compute_additions -from chia.types.spend_bundle import SpendBundle, estimate_fees +from chia.types.spend_bundle import SpendBundle from chia.util.ints import uint32, uint64, uint128 -from chia.wallet.conditions import AssertCoinAnnouncement, Condition, ConditionValidTimes, parse_timelock_info +from chia.wallet.conditions import AssertCoinAnnouncement, Condition, ConditionValidTimes from chia.wallet.derive_keys import find_owner_sk from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, CoinSelectionConfig, TXConfig from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo @@ -387,11 +388,12 @@ async def create_new_pool_wallet_transaction( main_wallet: Wallet, initial_target_state: PoolState, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), p2_singleton_delay_time: Optional[uint64] = None, p2_singleton_delayed_ph: Optional[bytes32] = None, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[TransactionRecord, bytes32, bytes32]: + ) -> Tuple[bytes32, bytes32]: """ A "plot NFT", or pool wallet, represents the idea of a set of plots that all pay to the same pooling puzzle. This puzzle is a `chia singleton` that is @@ -401,7 +403,6 @@ async def create_new_pool_wallet_transaction( Call under the wallet state manager lock """ - amount = 1 standard_wallet = main_wallet if p2_singleton_delayed_ph is None: @@ -419,7 +420,7 @@ async def create_new_pool_wallet_transaction( # Verify Parameters - raise if invalid PoolWallet._verify_initial_target_state(initial_target_state) - spend_bundle, singleton_puzzle_hash, launcher_coin_id = await PoolWallet.generate_launcher_spend( + singleton_puzzle_hash, launcher_coin_id = await PoolWallet.generate_launcher_spend( standard_wallet, uint64(1), fee, @@ -428,35 +429,15 @@ async def create_new_pool_wallet_transaction( p2_singleton_delay_time, p2_singleton_delayed_ph, tx_config, + action_scope, extra_conditions=extra_conditions, ) - if spend_bundle is None: - raise ValueError("failed to generate ID for wallet") - - standard_wallet_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=singleton_puzzle_hash, - amount=uint64(amount), - fee_amount=fee, - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=spend_bundle.additions(), - removals=spend_bundle.removals(), - wallet_id=wallet_state_manager.main_wallet.id(), - sent_to=[], - memos=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=spend_bundle.name(), - valid_times=parse_timelock_info(extra_conditions), - ) p2_singleton_puzzle_hash: bytes32 = launcher_id_to_p2_puzzle_hash( launcher_coin_id, p2_singleton_delay_time, p2_singleton_delayed_ph ) - return standard_wallet_record, p2_singleton_puzzle_hash, launcher_coin_id + + return p2_singleton_puzzle_hash, launcher_coin_id async def _get_owner_key_cache(self) -> Tuple[PrivateKey, uint32]: if self._owner_sk_and_index is None: @@ -474,23 +455,24 @@ async def generate_fee_transaction( self, fee: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: - [fee_tx] = await self.standard_wallet.generate_signed_transaction( + ) -> None: + await self.standard_wallet.generate_signed_transaction( uint64(0), (await self.standard_wallet.get_new_puzzlehash()), tx_config, + action_scope, fee=fee, origin_id=None, coins=None, primaries=None, extra_conditions=extra_conditions, ) - return fee_tx async def generate_travel_transactions( - self, fee: uint64, tx_config: TXConfig - ) -> Tuple[TransactionRecord, Optional[TransactionRecord]]: + self, fee: uint64, tx_config: TXConfig, action_scope: WalletActionScope + ) -> None: # target_state is contained within pool_wallet_state pool_wallet_info: PoolWalletInfo = await self.get_current_state() @@ -558,34 +540,31 @@ async def generate_travel_transactions( unsigned_spend_bundle = SpendBundle([outgoing_coin_spend], G2Element()) assert unsigned_spend_bundle.removals()[0].puzzle_hash == singleton.puzzle_hash assert unsigned_spend_bundle.removals()[0].name() == singleton.name() - fee_tx: Optional[TransactionRecord] = None if fee > 0: - fee_tx = await self.generate_fee_transaction(fee, tx_config) - assert fee_tx.spend_bundle is not None - unsigned_spend_bundle = SpendBundle.aggregate([unsigned_spend_bundle, fee_tx.spend_bundle]) - fee_tx = dataclasses.replace(fee_tx, spend_bundle=None) - - tx_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=new_full_puzzle.get_tree_hash(), - amount=uint64(1), - fee_amount=fee, - confirmed=False, - sent=uint32(0), - spend_bundle=unsigned_spend_bundle, - additions=unsigned_spend_bundle.additions(), - removals=unsigned_spend_bundle.removals(), - wallet_id=self.id(), - sent_to=[], - trade_id=None, - memos=[], - type=uint32(TransactionType.OUTGOING_TX.value), - name=unsigned_spend_bundle.name(), - valid_times=ConditionValidTimes(), - ) - - return tx_record, fee_tx + await self.generate_fee_transaction(fee, tx_config, action_scope) + + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=new_full_puzzle.get_tree_hash(), + amount=uint64(1), + fee_amount=fee, + confirmed=False, + sent=uint32(0), + spend_bundle=unsigned_spend_bundle, + additions=unsigned_spend_bundle.additions(), + removals=unsigned_spend_bundle.removals(), + wallet_id=self.id(), + sent_to=[], + trade_id=None, + memos=[], + type=uint32(TransactionType.OUTGOING_TX.value), + name=unsigned_spend_bundle.name(), + valid_times=ConditionValidTimes(), + ) + ) @staticmethod async def generate_launcher_spend( @@ -597,8 +576,9 @@ async def generate_launcher_spend( delay_time: uint64, delay_ph: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[SpendBundle, bytes32, bytes32]: + ) -> Tuple[bytes32, bytes32]: """ Creates the initial singleton, which includes spending an origin coin, the launcher, and creating a singleton with the "pooling" inner state, which can be either self pooling or using a pool @@ -644,10 +624,20 @@ async def generate_launcher_spend( pool_state_bytes = Program.to([("p", bytes(initial_target_state)), ("t", delay_time), ("h", delay_ph)]) announcement_message = Program.to([puzzle_hash, amount, pool_state_bytes]).get_tree_hash() - [create_launcher_tx_record] = await standard_wallet.generate_signed_transaction( + genesis_launcher_solution: Program = Program.to([puzzle_hash, amount, pool_state_bytes]) + + launcher_cs: CoinSpend = CoinSpend( + launcher_coin, + SerializedProgram.from_program(genesis_launcher_puz), + SerializedProgram.from_program(genesis_launcher_solution), + ) + launcher_sb: SpendBundle = SpendBundle([launcher_cs], G2Element()) + + await standard_wallet.generate_signed_transaction( amount, genesis_launcher_puz.get_tree_hash(), tx_config, + action_scope, fee, coins, None, @@ -657,24 +647,15 @@ async def generate_launcher_spend( AssertCoinAnnouncement(asserted_id=launcher_coin.name(), asserted_msg=announcement_message), ), ) - assert create_launcher_tx_record.spend_bundle is not None - genesis_launcher_solution: Program = Program.to([puzzle_hash, amount, pool_state_bytes]) - - launcher_cs: CoinSpend = CoinSpend( - launcher_coin, - SerializedProgram.from_program(genesis_launcher_puz), - SerializedProgram.from_program(genesis_launcher_solution), - ) - launcher_sb: SpendBundle = SpendBundle([launcher_cs], G2Element()) + async with action_scope.use() as interface: + interface.side_effects.extra_spends.append(launcher_sb) - # Current inner will be updated when state is verified on the blockchain - full_spend: SpendBundle = SpendBundle.aggregate([create_launcher_tx_record.spend_bundle, launcher_sb]) - return full_spend, puzzle_hash, launcher_coin.name() + return puzzle_hash, launcher_coin.name() async def join_pool( - self, target_state: PoolState, fee: uint64, tx_config: TXConfig - ) -> Tuple[uint64, TransactionRecord, Optional[TransactionRecord]]: + self, target_state: PoolState, fee: uint64, tx_config: TXConfig, action_scope: WalletActionScope + ) -> uint64: if target_state.state != FARMING_TO_POOL.value: raise ValueError(f"join_pool must be called with target_state={FARMING_TO_POOL} (FARMING_TO_POOL)") if self.target_state is not None: @@ -716,12 +697,10 @@ async def join_pool( self.target_state = target_state self.next_transaction_fee = fee self.next_tx_config = tx_config - travel_tx, fee_tx = await self.generate_travel_transactions(fee, tx_config) - return total_fee, travel_tx, fee_tx + await self.generate_travel_transactions(fee, tx_config, action_scope) + return total_fee - async def self_pool( - self, fee: uint64, tx_config: TXConfig - ) -> Tuple[uint64, TransactionRecord, Optional[TransactionRecord]]: + async def self_pool(self, fee: uint64, tx_config: TXConfig, action_scope: WalletActionScope) -> uint64: if await self.have_unconfirmed_transaction(): raise ValueError( "Cannot self pool due to unconfirmed transaction. If this is stuck, delete the unconfirmed transaction." @@ -756,12 +735,12 @@ async def self_pool( ) self.next_transaction_fee = fee self.next_tx_config = tx_config - travel_tx, fee_tx = await self.generate_travel_transactions(fee, tx_config) - return total_fee, travel_tx, fee_tx + await self.generate_travel_transactions(fee, tx_config, action_scope) + return total_fee async def claim_pool_rewards( - self, fee: uint64, max_spends_in_tx: Optional[int], tx_config: TXConfig - ) -> Tuple[TransactionRecord, Optional[TransactionRecord]]: + self, fee: uint64, max_spends_in_tx: Optional[int], tx_config: TXConfig, action_scope: WalletActionScope + ) -> None: # Search for p2_puzzle_hash coins, and spend them with the singleton if await self.have_unconfirmed_transaction(): raise ValueError( @@ -830,45 +809,40 @@ async def claim_pool_rewards( claim_spend: SpendBundle = SpendBundle(all_spends, G2Element()) # If fee is 0, no signatures are required to absorb - full_spend: SpendBundle = claim_spend - - fee_tx = None if fee > 0: - fee_tx = await self.generate_fee_transaction( + await self.generate_fee_transaction( fee, tx_config, + action_scope, extra_conditions=( AssertCoinAnnouncement(asserted_id=first_coin_record.coin.name(), asserted_msg=b"$"), ), ) - assert fee_tx.spend_bundle is not None - full_spend = SpendBundle.aggregate([fee_tx.spend_bundle, claim_spend]) - fee_tx = dataclasses.replace(fee_tx, spend_bundle=None) - assert estimate_fees(full_spend) == fee current_time = uint64(int(time.time())) # The claim spend, minus the fee amount from the main wallet - absorb_transaction: TransactionRecord = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=current_time, - to_puzzle_hash=current_state.current.target_puzzle_hash, - amount=uint64(total_amount), - fee_amount=fee, # This will not be double counted in self.standard_wallet - confirmed=False, - sent=uint32(0), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), - wallet_id=uint32(self.wallet_id), - sent_to=[], - memos=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=full_spend.name(), - valid_times=ConditionValidTimes(), - ) - - return absorb_transaction, fee_tx + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=current_time, + to_puzzle_hash=current_state.current.target_puzzle_hash, + amount=uint64(total_amount), + fee_amount=fee, # This will not be double counted in self.standard_wallet + confirmed=False, + sent=uint32(0), + spend_bundle=claim_spend, + additions=claim_spend.additions(), + removals=claim_spend.removals(), + wallet_id=uint32(self.wallet_id), + sent_to=[], + memos=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=claim_spend.name(), + valid_times=ConditionValidTimes(), + ) + ) async def new_peak(self, peak_height: uint32) -> None: # This gets called from the WalletStateManager whenever there is a new peak @@ -912,13 +886,10 @@ async def new_peak(self, peak_height: uint32) -> None: assert self.target_state.relative_lock_height >= self.MINIMUM_RELATIVE_LOCK_HEIGHT assert self.target_state.pool_url is not None - travel_tx, fee_tx = await self.generate_travel_transactions( - self.next_transaction_fee, self.next_tx_config - ) - txs = [travel_tx] - if fee_tx is not None: - txs.append(fee_tx) - await self.wallet_state_manager.add_pending_transactions(txs) + async with self.wallet_state_manager.new_action_scope(push=True) as action_scope: + await self.generate_travel_transactions( + self.next_transaction_fee, self.next_tx_config, action_scope + ) async def have_unconfirmed_transaction(self) -> bool: unconfirmed: List[TransactionRecord] = await self.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( diff --git a/chia/rpc/util.py b/chia/rpc/util.py index 1d8394971d2a..924a12bc830b 100644 --- a/chia/rpc/util.py +++ b/chia/rpc/util.py @@ -152,24 +152,29 @@ async def rpc_endpoint(self, request: Dict[str, Any], *args, **kwargs) -> Dict[s ): raise ValueError("Relative timelocks are not currently supported in the RPC") - response: Dict[str, Any] = await func( - self, - request, - *args, - *([push] if requires_default_information else []), - tx_config=tx_config, - extra_conditions=extra_conditions, - **kwargs, - ) + async with self.service.wallet_state_manager.new_action_scope( + push=request.get("push", push), + merge_spends=request.get("merge_spends", merge_spends), + sign=request.get("sign", self.service.config.get("auto_sign_txs", True)), + ) as action_scope: + response: Dict[str, Any] = await func( + self, + request, + *args, + action_scope, + *([push] if requires_default_information else []), + tx_config=tx_config, + extra_conditions=extra_conditions, + **kwargs, + ) if func.__name__ == "create_new_wallet" and "transactions" not in response: # unfortunately, this API isn't solely a tx endpoint return response - tx_records: List[TransactionRecord] = [ - TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"] - ] - unsigned_txs = await self.service.wallet_state_manager.gather_signing_info_for_txs(tx_records) + unsigned_txs = await self.service.wallet_state_manager.gather_signing_info_for_txs( + action_scope.side_effects.transactions + ) if request.get("CHIP-0029", False): response["unsigned_transactions"] = [ @@ -184,39 +189,16 @@ async def rpc_endpoint(self, request: Dict[str, Any], *args, **kwargs) -> Dict[s else: response["unsigned_transactions"] = [tx.to_json_dict() for tx in unsigned_txs] - new_txs: List[TransactionRecord] = [] - if request.get("sign", self.service.config.get("auto_sign_txs", True)): - new_txs, signing_responses = await self.service.wallet_state_manager.sign_transactions( - tx_records, response.get("signing_responses", []), "signing_responses" in response - ) - if request.get("CHIP-0029", False): - response["signing_responses"] = [ - json_serialize_with_clvm_streamable( - sr, - translation_layer=( - ALL_TRANSLATION_LAYERS[request["translation"]] if "translation" in request else None - ), - ) - for sr in signing_responses - ] - else: - response["signing_responses"] = [sr.to_json_dict() for sr in signing_responses] - else: - new_txs = tx_records # pragma: no cover - - if request.get("push", push): - new_txs = await self.service.wallet_state_manager.add_pending_transactions( - new_txs, merge_spends=merge_spends, sign=False - ) - response["transactions"] = [ - TransactionRecord.to_json_dict_convenience(tx, self.service.config) for tx in new_txs + TransactionRecord.to_json_dict_convenience(tx, self.service.config) + for tx in action_scope.side_effects.transactions ] # Some backwards compatibility code here because transaction information being returned was not uniform # until the "transactions" key was applied to all of them. Unfortunately, since .add_pending_transactions # now applies transformations to the transactions, we have to special case edit all of the previous # spots where the information was being surfaced outside of the knowledge of this wrapper. + new_txs = action_scope.side_effects.transactions if "transaction" in response: if ( func.__name__ == "create_new_wallet" @@ -226,14 +208,18 @@ async def rpc_endpoint(self, request: Dict[str, Any], *args, **kwargs) -> Dict[s or func.__name__ == "pw_absorb_rewards" ): # Theses RPCs return not "convenience" for some reason - response["transaction"] = new_txs[0].to_json_dict() + response["transaction"] = new_txs[-1].to_json_dict() else: response["transaction"] = response["transactions"][0] if "tx_record" in response: response["tx_record"] = response["transactions"][0] - if "fee_transaction" in response and response["fee_transaction"] is not None: + if "fee_transaction" in response: # Theses RPCs return not "convenience" for some reason - response["fee_transaction"] = new_txs[1].to_json_dict() + fee_transactions = [tx for tx in new_txs if tx.wallet_id == 1] + if len(fee_transactions) == 0: + response["fee_transaction"] = None + else: + response["fee_transaction"] = fee_transactions[0].to_json_dict() if "transaction_id" in response: response["transaction_id"] = new_txs[0].name if "transaction_ids" in response: diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 451712ef8d11..b03f37b996eb 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -122,6 +122,7 @@ from chia.wallet.vc_wallet.vc_store import VCProofs from chia.wallet.vc_wallet.vc_wallet import VCWallet from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_coin_store import CoinRecordOrder, GetCoinRecords, unspent_range from chia.wallet.wallet_info import WalletInfo @@ -697,6 +698,7 @@ async def get_wallets(self, request: Dict[str, Any]) -> EndpointResult: async def create_new_wallet( self, request: Dict[str, Any], + action_scope: WalletActionScope, push: bool = True, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), @@ -716,12 +718,13 @@ async def create_new_wallet( if not push: raise ValueError("Test CAT minting must be pushed automatically") # pragma: no cover async with self.service.wallet_state_manager.lock: - cat_wallet, txs = await CATWallet.create_new_cat_wallet( + cat_wallet = await CATWallet.create_new_cat_wallet( wallet_state_manager, main_wallet, {"identifier": "genesis_by_id"}, uint64(request["amount"]), tx_config, + action_scope, fee, name, ) @@ -731,7 +734,7 @@ async def create_new_wallet( "type": cat_wallet.type(), "asset_id": asset_id, "wallet_id": cat_wallet.id(), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } else: raise ValueError( @@ -762,9 +765,6 @@ async def create_new_wallet( if type(request["metadata"]) is dict: metadata = request["metadata"] - if not push: - raise ValueError("Creation of DID wallet must be automatically pushed for now.") - async with self.service.wallet_state_manager.lock: did_wallet_name: str = request.get("wallet_name", None) if did_wallet_name is not None: @@ -774,6 +774,7 @@ async def create_new_wallet( main_wallet, uint64(request["amount"]), tx_config, + action_scope, backup_dids, uint64(num_needed), metadata, @@ -782,23 +783,24 @@ async def create_new_wallet( extra_conditions=extra_conditions, ) - my_did_id = encode_puzzle_hash( - bytes32.fromhex(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config) - ) - nft_wallet_name = did_wallet_name - if nft_wallet_name is not None: - nft_wallet_name = f"{nft_wallet_name} NFT Wallet" - await NFTWallet.create_new_nft_wallet( - wallet_state_manager, - main_wallet, - bytes32.fromhex(did_wallet.get_my_DID()), - nft_wallet_name, - ) + my_did_id = encode_puzzle_hash( + bytes32.fromhex(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config) + ) + nft_wallet_name = did_wallet_name + if nft_wallet_name is not None: + nft_wallet_name = f"{nft_wallet_name} NFT Wallet" + await NFTWallet.create_new_nft_wallet( + wallet_state_manager, + main_wallet, + bytes32.fromhex(did_wallet.get_my_DID()), + nft_wallet_name, + ) return { "success": True, "type": did_wallet.type(), "my_did": my_did_id, "wallet_id": did_wallet.id(), + "transactions": None, # tx_endpoint wrapper will take care of this } elif request["did_type"] == "recovery": @@ -838,12 +840,13 @@ async def create_new_wallet( else: raise ValueError("DAO rules must be specified for wallet creation") async with self.service.wallet_state_manager.lock: - dao_wallet, txs = await DAOWallet.create_new_dao_and_wallet( + dao_wallet = await DAOWallet.create_new_dao_and_wallet( wallet_state_manager, main_wallet, uint64(request.get("amount_of_cats", None)), dao_rules, tx_config, + action_scope, uint64(request.get("filter_amount", 1)), name, uint64(request.get("fee", 0)), @@ -868,9 +871,7 @@ async def create_new_wallet( "treasury_id": dao_wallet.dao_info.treasury_id, "cat_wallet_id": dao_wallet.dao_info.cat_wallet_id, "dao_cat_wallet_id": dao_wallet.dao_info.dao_cat_wallet_id, - "transactions": ( - [tx.to_json_dict_convenience(self.service.config) for tx in txs] if mode == "new" else [] - ), + "transactions": None, # tx_endpoint wrapper will take care of this } elif request["wallet_type"] == "nft_wallet": for wallet in self.service.wallet_state_manager.wallets.values(): @@ -936,11 +937,12 @@ async def create_new_wallet( if "p2_singleton_delayed_ph" in request: delayed_address = bytes32.from_hexstr(request["p2_singleton_delayed_ph"]) - tr, p2_singleton_puzzle_hash, launcher_id = await PoolWallet.create_new_pool_wallet_transaction( + p2_singleton_puzzle_hash, launcher_id = await PoolWallet.create_new_pool_wallet_transaction( wallet_state_manager, main_wallet, initial_target_state, tx_config, + action_scope, fee, request.get("p2_singleton_delay_time", None), delayed_address, @@ -951,8 +953,8 @@ async def create_new_wallet( raise ValueError(str(e)) return { "total_fee": fee * 2, - "transaction": tr, - "transactions": [tr.to_json_dict_convenience(self.service.config)], + "transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this "launcher_id": launcher_id.hex(), "p2_singleton_puzzle_hash": p2_singleton_puzzle_hash.hex(), } @@ -1141,6 +1143,7 @@ async def get_next_address(self, request: Dict[str, Any]) -> EndpointResult: async def send_transaction( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -1167,10 +1170,11 @@ async def send_transaction( fee: uint64 = uint64(request.get("fee", 0)) async with self.service.wallet_state_manager.lock: - [tx] = await wallet.generate_signed_transaction( + await wallet.generate_signed_transaction( amount, puzzle_hash, tx_config, + action_scope, fee, memos=memos, puzzle_decorator_override=request.get("puzzle_decorator", None), @@ -1178,11 +1182,10 @@ async def send_transaction( ) # Transaction may not have been included in the mempool yet. Use get_transaction to check. - json_tx = tx.to_json_dict_convenience(self.service.config) return { - "transaction": json_tx, - "transactions": [json_tx], - "transaction_id": tx.name, + "transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this + "transaction_id": None, # tx_endpoint wrapper will take care of this } async def send_transaction_multi(self, request: Dict[str, Any]) -> EndpointResult: @@ -1219,6 +1222,7 @@ async def send_transaction_multi(self, request: Dict[str, Any]) -> EndpointResul async def spend_clawback_coins( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -1245,38 +1249,45 @@ async def spend_clawback_coins( batch_size = request.get( "batch_size", self.service.wallet_state_manager.config.get("auto_claim", {}).get("batch_size", 50) ) - tx_list: List[TransactionRecord] = [] for coin_id, coin_record in coin_records.coin_id_to_record.items(): try: metadata = coin_record.parsed_metadata() assert isinstance(metadata, ClawbackMetadata) coins[coin_record.coin] = metadata if len(coins) >= batch_size: - new_txs = await self.service.wallet_state_manager.spend_clawback_coins( - coins, tx_fee, tx_config, request.get("force", False), extra_conditions=extra_conditions - ) - tx_list.extend(new_txs) - tx_config = dataclasses.replace( + await self.service.wallet_state_manager.spend_clawback_coins( + coins, + tx_fee, tx_config, - excluded_coin_ids=[ - *tx_config.excluded_coin_ids, - *(c.name() for tx in new_txs for c in tx.removals), - ], + action_scope, + request.get("force", False), + extra_conditions=extra_conditions, ) + async with action_scope.use() as interface: + tx_config = dataclasses.replace( + tx_config, + excluded_coin_ids=[ + *tx_config.excluded_coin_ids, + *(c.name() for tx in interface.side_effects.transactions for c in tx.removals), + ], + ) coins = {} except Exception as e: log.error(f"Failed to spend clawback coin {coin_id.hex()}: %s", e) if len(coins) > 0: - tx_list.extend( - await self.service.wallet_state_manager.spend_clawback_coins( - coins, tx_fee, tx_config, request.get("force", False), extra_conditions=extra_conditions - ) + await self.service.wallet_state_manager.spend_clawback_coins( + coins, + tx_fee, + tx_config, + action_scope, + request.get("force", False), + extra_conditions=extra_conditions, ) return { "success": True, - "transaction_ids": [tx.name.hex() for tx in tx_list if tx.type == TransactionType.OUTGOING_CLAWBACK.value], - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_list], + "transaction_ids": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } async def delete_unconfirmed_transactions(self, request: Dict[str, Any]) -> EndpointResult: @@ -1514,20 +1525,21 @@ async def delete_notifications(self, request: Dict[str, Any]) -> EndpointResult: async def send_notification( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: - tx: TransactionRecord = await self.service.wallet_state_manager.notification_manager.send_new_notification( + await self.service.wallet_state_manager.notification_manager.send_new_notification( bytes32.from_hexstr(request["target"]), bytes.fromhex(request["message"]), uint64(request["amount"]), tx_config, + action_scope, request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) - json_tx = tx.to_json_dict_convenience(self.service.config) - return {"tx": json_tx, "transactions": [json_tx]} + return {"tx": None, "transactions": None} # tx_endpoint wrapper will take care of this async def verify_signature(self, request: Dict[str, Any]) -> EndpointResult: """ @@ -1709,6 +1721,7 @@ async def get_stray_cats(self, request: Dict[str, Any]) -> EndpointResult: async def cat_spend( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), hold_lock: bool = True, @@ -1766,10 +1779,11 @@ async def cat_spend( ) if hold_lock: async with self.service.wallet_state_manager.lock: - txs: List[TransactionRecord] = await wallet.generate_signed_transaction( + await wallet.generate_signed_transaction( amounts, puzzle_hashes, tx_config, + action_scope, fee, cat_discrepancy=cat_discrepancy, coins=coins, @@ -1777,10 +1791,11 @@ async def cat_spend( extra_conditions=extra_conditions, ) else: - txs = await wallet.generate_signed_transaction( + await wallet.generate_signed_transaction( amounts, puzzle_hashes, tx_config, + action_scope, fee, cat_discrepancy=cat_discrepancy, coins=coins, @@ -1788,12 +1803,10 @@ async def cat_spend( extra_conditions=extra_conditions, ) - # Return the first transaction, which is expected to be the CAT spend. If a fee is - # included, it is currently ordered after the CAT spend. return { - "transaction": txs[0].to_json_dict_convenience(self.service.config), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], - "transaction_id": txs[0].name, + "transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this + "transaction_id": None, # tx_endpoint wrapper will take care of this } async def cat_get_asset_id(self, request: Dict[str, Any]) -> EndpointResult: @@ -1816,6 +1829,7 @@ async def cat_asset_id_to_name(self, request: Dict[str, Any]) -> EndpointResult: async def create_offer_for_ids( self, request: Dict[str, Any], + action_scope: WalletActionScope, push: bool = False, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), @@ -1860,6 +1874,7 @@ async def create_offer_for_ids( result = await self.service.wallet_state_manager.trade_manager.create_offer_for_ids( modified_offer, tx_config, + action_scope, driver_dict, solver=solver, fee=fee, @@ -1867,11 +1882,11 @@ async def create_offer_for_ids( extra_conditions=extra_conditions, ) if result[0]: - success, trade_record, tx_records, error = result + success, trade_record, error = result return { "offer": Offer.from_bytes(trade_record.offer).to_bech32(), "trade_record": trade_record.to_json_dict_convenience(), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_records], + "transactions": None, # tx_endpoint wrapper will take care of this } raise ValueError(result[2]) @@ -1987,6 +2002,7 @@ async def check_offer_validity(self, request: Dict[str, Any]) -> EndpointResult: async def take_offer( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2020,22 +2036,27 @@ async def take_offer( else: solver = Solver(info=maybe_marshalled_solver) - async with self.service.wallet_state_manager.lock: - peer = self.service.get_full_node_peer() - trade_record, tx_records = await self.service.wallet_state_manager.trade_manager.respond_to_offer( - offer, - peer, - tx_config, - fee=fee, - solver=solver, - extra_conditions=extra_conditions, + peer = self.service.get_full_node_peer() + trade_record = await self.service.wallet_state_manager.trade_manager.respond_to_offer( + offer, + peer, + tx_config, + action_scope, + fee=fee, + solver=solver, + extra_conditions=extra_conditions, + ) + + async with action_scope.use() as interface: + interface.side_effects.signing_responses.append( + SigningResponse(bytes(offer._bundle.aggregated_signature), trade_record.trade_id) ) return { "trade_record": trade_record.to_json_dict_convenience(), "offer": Offer.from_bytes(trade_record.offer).to_bech32(), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_records], - "signing_responses": [SigningResponse(bytes(offer._bundle.aggregated_signature), trade_record.trade_id)], + "transactions": None, # tx_endpoint wrapper will take care of this + "signing_responses": None, # tx_endpoint wrapper will take care of this } async def get_offer(self, request: Dict[str, Any]) -> EndpointResult: @@ -2093,6 +2114,7 @@ async def get_offers_count(self, request: Dict[str, Any]) -> EndpointResult: async def cancel_offer( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2101,16 +2123,17 @@ async def cancel_offer( trade_id = bytes32.from_hexstr(request["trade_id"]) fee: uint64 = uint64(request.get("fee", 0)) async with self.service.wallet_state_manager.lock: - txs = await wsm.trade_manager.cancel_pending_offers( - [bytes32(trade_id)], tx_config, fee=fee, secure=secure, extra_conditions=extra_conditions + await wsm.trade_manager.cancel_pending_offers( + [bytes32(trade_id)], tx_config, action_scope, fee=fee, secure=secure, extra_conditions=extra_conditions ) - return {"transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs]} + return {"transactions": None} # tx_endpoint wrapper will take care of this @tx_endpoint(push=True, merge_spends=False) async def cancel_offers( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2123,8 +2146,6 @@ async def cancel_offers( else: asset_id = request.get("asset_id", "xch") - all_txs: List[TransactionRecord] = [] - start: int = 0 end: int = start + batch_size trade_mgr = self.service.wallet_state_manager.trade_manager @@ -2157,10 +2178,14 @@ async def cancel_offers( break async with self.service.wallet_state_manager.lock: - all_txs.extend( - await trade_mgr.cancel_pending_offers( - list(records.keys()), tx_config, batch_fee, secure, records, extra_conditions=extra_conditions - ) + await trade_mgr.cancel_pending_offers( + list(records.keys()), + tx_config, + action_scope, + batch_fee, + secure, + records, + extra_conditions=extra_conditions, ) log.info(f"Cancelled offers {start} to {end} ...") @@ -2170,7 +2195,7 @@ async def cancel_offers( start = end end += batch_size - return {"success": True, "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in all_txs]} + return {"transactions": None} # tx_endpoint wrapper will take care of this ########################################################################################## # Distributed Identities @@ -2192,6 +2217,7 @@ async def did_get_wallet_name(self, request: Dict[str, Any]) -> EndpointResult: async def did_update_recovery_ids( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2208,12 +2234,12 @@ async def did_update_recovery_ids( update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required) # Update coin with new ID info if update_success: - txs = await wallet.create_update_spend( - tx_config, fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions + await wallet.create_update_spend( + tx_config, action_scope, fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions ) return { "success": True, - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } else: return {"success": False, "transactions": []} # pragma: no cover @@ -2222,14 +2248,16 @@ async def did_update_recovery_ids( async def did_message_spend( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) - tx = await wallet.create_message_spend( + await wallet.create_message_spend( tx_config, + action_scope, extra_conditions=( *extra_conditions, *(CreateCoinAnnouncement(hexstr_to_bytes(ca)) for ca in request.get("coin_announcements", [])), @@ -2238,8 +2266,8 @@ async def did_message_spend( ) return { "success": True, - "spend_bundle": tx.spend_bundle, - "transactions": [tx.to_json_dict_convenience(self.service.config)], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } async def did_get_info(self, request: Dict[str, Any]) -> EndpointResult: @@ -2486,6 +2514,7 @@ async def did_find_lost_did(self, request: Dict[str, Any]) -> EndpointResult: async def did_update_metadata( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2498,16 +2527,14 @@ async def did_update_metadata( update_success = await wallet.update_metadata(metadata) # Update coin with new ID info if update_success: - txs = await wallet.create_update_spend( - tx_config, uint64(request.get("fee", 0)), extra_conditions=extra_conditions + await wallet.create_update_spend( + tx_config, action_scope, uint64(request.get("fee", 0)), extra_conditions=extra_conditions ) return { "wallet_id": wallet_id, "success": True, - "spend_bundle": SpendBundle.aggregate( - [tx.spend_bundle for tx in txs if tx.spend_bundle is not None] - ), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } else: return {"success": False, "error": f"Couldn't update metadata with input: {metadata}"} @@ -2554,7 +2581,6 @@ async def did_recovery_spend(self, request: Dict[str, Any]) -> EndpointResult: wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed: return {"success": False, "reason": "insufficient messages"} - spend_bundle = None async with self.service.wallet_state_manager.lock: ( info_list, @@ -2574,25 +2600,23 @@ async def did_recovery_spend(self, request: Dict[str, Any]) -> EndpointResult: puzhash = wallet.did_info.temp_puzhash assert wallet.did_info.temp_coin is not None - tx = ( + async with self.service.wallet_state_manager.new_action_scope( + push=request.get("push", True) + ) as action_scope: await wallet.recovery_spend( wallet.did_info.temp_coin, puzhash, info_list, pubkey, message_spend_bundle, + action_scope, ) - )[0] - if request.get("push", True): - await self.service.wallet_state_manager.add_pending_transactions([tx]) - if spend_bundle: - return { - "success": True, - "spend_bundle": tx.spend_bundle, - "transactions": [tx.to_json_dict_convenience(self.service.config)], - } - else: - return {"success": False} + [tx] = action_scope.side_effects.transactions + return { + "success": True, + "spend_bundle": tx.spend_bundle, + "transactions": [tx.to_json_dict_convenience(self.service.config)], + } async def did_get_pubkey(self, request: Dict[str, Any]) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) @@ -2605,6 +2629,7 @@ async def did_get_pubkey(self, request: Dict[str, Any]) -> EndpointResult: async def did_create_attest( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: # pragma: no cover @@ -2614,21 +2639,21 @@ async def did_create_attest( info = await wallet.get_info_for_recovery() coin = bytes32.from_hexstr(request["coin_name"]) pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"])) - tx, message_spend_bundle, attest_data = await wallet.create_attestment( + message_spend_bundle, attest_data = await wallet.create_attestment( coin, bytes32.from_hexstr(request["puzhash"]), pubkey, tx_config, + action_scope, extra_conditions=extra_conditions, ) if info is not None: - assert tx.spend_bundle is not None return { "success": True, "message_spend_bundle": bytes(message_spend_bundle).hex(), "info": [info[0].hex(), info[1].hex(), info[2]], "attest_data": attest_data, - "transactions": [tx.to_json_dict_convenience(self.service.config)], + "transactions": None, # tx_endpoint wrapper will take care of this } else: return {"success": False} @@ -2679,6 +2704,7 @@ async def did_create_backup_file(self, request: Dict[str, Any]) -> EndpointResul async def did_transfer_did( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2688,19 +2714,20 @@ async def did_transfer_did( did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"]) async with self.service.wallet_state_manager.lock: - txs: List[TransactionRecord] = await did_wallet.transfer_did( + await did_wallet.transfer_did( puzzle_hash, uint64(request.get("fee", 0)), request.get("with_recovery_info", True), tx_config, + action_scope, extra_conditions=extra_conditions, ) return { "success": True, - "transaction": txs[0].to_json_dict_convenience(self.service.config), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], - "transaction_id": txs[0].name, + "transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this + "transaction_id": None, # tx_endpoint wrapper will take care of this } ########################################################################################## @@ -2720,6 +2747,7 @@ async def dao_adjust_filter_level(self, request: Dict[str, Any]) -> EndpointResu async def dao_add_funds_to_treasury( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2731,18 +2759,19 @@ async def dao_add_funds_to_treasury( assert amount if wallet_type not in [WalletType.STANDARD_WALLET, WalletType.CAT]: # pragma: no cover raise ValueError(f"Cannot fund a treasury with assets from a {wallet_type.name} wallet") - funding_tx = await dao_wallet.create_add_funds_to_treasury_spend( + await dao_wallet.create_add_funds_to_treasury_spend( uint64(amount), tx_config, + action_scope, fee=uint64(request.get("fee", 0)), funding_wallet_id=funding_wallet_id, extra_conditions=extra_conditions, ) return { "success": True, - "tx_id": funding_tx.name, - "tx": funding_tx, - "transactions": [funding_tx.to_json_dict_convenience(self.service.config)], + "tx_id": None, # tx_endpoint wrapper will take care of this + "tx": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } async def dao_get_treasury_balance(self, request: Dict[str, Any]) -> EndpointResult: @@ -2777,6 +2806,7 @@ async def dao_get_rules(self, request: Dict[str, Any]) -> EndpointResult: async def dao_send_to_lockup( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2787,17 +2817,18 @@ async def dao_send_to_lockup( ) amount = uint64(request["amount"]) fee = uint64(request.get("fee", 0)) - txs = await dao_cat_wallet.enter_dao_cat_voting_mode( + await dao_cat_wallet.enter_dao_cat_voting_mode( amount, tx_config, + action_scope, fee=fee, extra_conditions=extra_conditions, ) return { "success": True, - "tx_id": txs[0].name, - "txs": txs, - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "tx_id": None, + "txs": None, + "transactions": None, # tx_endpoint wrapper will take care of this } async def dao_get_proposals(self, request: Dict[str, Any]) -> EndpointResult: @@ -2827,6 +2858,7 @@ async def dao_get_proposal_state(self, request: Dict[str, Any]) -> EndpointResul async def dao_exit_lockup( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2851,23 +2883,25 @@ async def dao_exit_lockup( fee = uint64(request.get("fee", 0)) if not coins: # pragma: no cover raise ValueError("There are not coins available to exit lockup") - [exit_tx] = await dao_cat_wallet.exit_vote_state( + await dao_cat_wallet.exit_vote_state( coins, tx_config, + action_scope, fee=fee, extra_conditions=extra_conditions, ) return { "success": True, - "tx_id": exit_tx.name, - "tx": exit_tx, - "transactions": [exit_tx.to_json_dict_convenience(self.service.config)], + "tx_id": None, # tx_endpoint wrapper will take care of this + "tx": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def dao_create_proposal( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2935,33 +2969,39 @@ async def dao_create_proposal( vote_amount = request.get("vote_amount") fee = uint64(request.get("fee", 0)) - [proposal_tx] = await dao_wallet.generate_new_proposal( + await dao_wallet.generate_new_proposal( proposed_puzzle, tx_config, + action_scope, vote_amount=vote_amount, fee=fee, extra_conditions=extra_conditions, ) - assert proposal_tx is not None - assert isinstance(proposal_tx.removals, List) - for coin in proposal_tx.removals: - if coin.puzzle_hash == SINGLETON_LAUNCHER_PUZZLE_HASH: - proposal_id = coin.name() - break - else: # pragma: no cover - raise ValueError("Could not find proposal ID in transaction") + async with action_scope.use() as interface: + found: bool = False + for tx in interface.side_effects.transactions: + for coin in tx.removals: + if coin.puzzle_hash == SINGLETON_LAUNCHER_PUZZLE_HASH: + proposal_id = coin.name() + found = True + if found: + break + else: # pragma: no cover + raise ValueError("Could not find proposal ID in transaction") return { "success": True, - "proposal_id": proposal_id, - "tx_id": proposal_tx.name.hex(), - "tx": proposal_tx, - "transactions": [proposal_tx.to_json_dict_convenience(self.service.config)], + # Semantics guarantee proposal_id here + "proposal_id": proposal_id, # pylint: disable=possibly-used-before-assignment + "tx_id": None, # tx_endpoint wrapper will take care of this + "tx": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def dao_vote_on_proposal( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -2972,20 +3012,20 @@ async def dao_vote_on_proposal( if "vote_amount" in request: vote_amount = uint64(request["vote_amount"]) fee = uint64(request.get("fee", 0)) - [vote_tx] = await dao_wallet.generate_proposal_vote_spend( + await dao_wallet.generate_proposal_vote_spend( bytes32.from_hexstr(request["proposal_id"]), vote_amount, request["is_yes_vote"], # bool tx_config, + action_scope, fee, extra_conditions=extra_conditions, ) - assert vote_tx is not None return { "success": True, - "tx_id": vote_tx.name, - "tx": vote_tx, - "transactions": [vote_tx.to_json_dict_convenience(self.service.config)], + "tx_id": None, # tx_endpoint wrapper will take care of this + "tx": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } async def dao_parse_proposal(self, request: Dict[str, Any]) -> EndpointResult: @@ -3001,6 +3041,7 @@ async def dao_parse_proposal(self, request: Dict[str, Any]) -> EndpointResult: async def dao_close_proposal( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3013,26 +3054,27 @@ async def dao_close_proposal( else: genesis_id = None self_destruct = request.get("self_destruct", None) - tx = await dao_wallet.create_proposal_close_spend( + await dao_wallet.create_proposal_close_spend( bytes32.from_hexstr(request["proposal_id"]), tx_config, + action_scope, genesis_id, fee=fee, self_destruct=self_destruct, extra_conditions=extra_conditions, ) - assert tx is not None return { "success": True, - "tx_id": tx.name, - "tx": tx, - "transactions": [tx.to_json_dict_convenience(self.service.config)], + "tx_id": None, # tx_endpoint wrapper will take care of this + "tx": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def dao_free_coins_from_finished_proposals( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3040,18 +3082,18 @@ async def dao_free_coins_from_finished_proposals( fee = uint64(request.get("fee", 0)) dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet) assert dao_wallet is not None - tx = await dao_wallet.free_coins_from_finished_proposals( + await dao_wallet.free_coins_from_finished_proposals( tx_config, + action_scope, fee=fee, extra_conditions=extra_conditions, ) - assert tx is not None return { "success": True, - "tx_id": tx.name, - "tx": tx, - "transactions": [tx.to_json_dict_convenience(self.service.config)], + "tx_id": None, # tx_endpoint wrapper will take care of this + "tx": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } ########################################################################################## @@ -3061,6 +3103,7 @@ async def dao_free_coins_from_finished_proposals( async def nft_mint_nft( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3114,9 +3157,10 @@ async def nft_mint_nft( else: did_id = decode_puzzle_hash(did_id) - txs = await nft_wallet.generate_new_nft( + nft_id = await nft_wallet.generate_new_nft( metadata, tx_config, + action_scope, target_puzhash, royalty_puzhash, royalty_amount, @@ -3124,17 +3168,13 @@ async def nft_mint_nft( fee, extra_conditions=extra_conditions, ) - nft_id = None - spend_bundle = SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]) - for cs in spend_bundle.coin_spends: - if cs.coin.puzzle_hash == nft_puzzles.LAUNCHER_PUZZLE_HASH: - nft_id = encode_puzzle_hash(cs.coin.name(), AddressType.NFT.hrp(self.service.config)) + nft_id_bech32 = encode_puzzle_hash(nft_id, AddressType.NFT.hrp(self.service.config)) return { "wallet_id": wallet_id, "success": True, - "spend_bundle": spend_bundle, - "nft_id": nft_id, - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "nft_id": nft_id_bech32, + "transactions": None, # tx_endpoint wrapper will take care of this } async def nft_count_nfts(self, request: Dict[str, Any]) -> EndpointResult: @@ -3180,6 +3220,7 @@ async def nft_get_nfts(self, request: Dict[str, Any]) -> EndpointResult: async def nft_set_nft_did( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3195,24 +3236,26 @@ async def nft_set_nft_did( return {"success": False, "error": "The NFT doesn't support setting a DID."} fee = uint64(request.get("fee", 0)) - txs = await nft_wallet.set_nft_did( + await nft_wallet.set_nft_did( nft_coin_info, did_id, tx_config, + action_scope, fee=fee, extra_conditions=extra_conditions, ) return { "wallet_id": wallet_id, "success": True, - "spend_bundle": SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def nft_set_did_bulk( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3231,7 +3274,6 @@ async def nft_set_did_bulk( if did_id != b"": did_id = decode_puzzle_hash(did_id) nft_dict: Dict[uint32, List[NFTCoinInfo]] = {} - tx_list: List[TransactionRecord] = [] coin_ids = [] nft_ids = [] fee = uint64(request.get("fee", 0)) @@ -3265,48 +3307,36 @@ async def nft_set_did_bulk( for wallet_id, nft_list in nft_dict.items(): nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet) if not first: - tx_list.extend( - await nft_wallet.set_bulk_nft_did(nft_list, did_id, tx_config, extra_conditions=extra_conditions) + await nft_wallet.set_bulk_nft_did( + nft_list, did_id, tx_config, action_scope, extra_conditions=extra_conditions ) else: - tx_list.extend( - await nft_wallet.set_bulk_nft_did( - nft_list, did_id, tx_config, fee, nft_ids, extra_conditions=extra_conditions - ) + await nft_wallet.set_bulk_nft_did( + nft_list, did_id, tx_config, action_scope, fee, nft_ids, extra_conditions=extra_conditions ) for coin in nft_list: coin_ids.append(coin.coin.name()) first = False - spend_bundles: List[SpendBundle] = [] - refined_tx_list: List[TransactionRecord] = [] - for tx in tx_list: - if tx.spend_bundle is not None: - spend_bundles.append(tx.spend_bundle) - refined_tx_list.append(dataclasses.replace(tx, spend_bundle=None)) - - if len(spend_bundles) > 0: - spend_bundle = SpendBundle.aggregate(spend_bundles) - # Add all spend bundles to the first tx - refined_tx_list[0] = dataclasses.replace(refined_tx_list[0], spend_bundle=spend_bundle) - - for id in coin_ids: - await nft_wallet.update_coin_status(id, True) - for wallet_id in nft_dict.keys(): - self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id) + + for id in coin_ids: + await nft_wallet.update_coin_status(id, True) + for wallet_id in nft_dict.keys(): + self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id) + + async with action_scope.use() as interface: return { "wallet_id": list(nft_dict.keys()), "success": True, - "spend_bundle": spend_bundle, - "tx_num": len(refined_tx_list), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in refined_tx_list], + "spend_bundle": None, # Backwards compat code in @tx_endpoint wrapper will fix this + "tx_num": len(interface.side_effects.transactions), + "transactions": None, # tx_endpoint wrapper will take care of this } - else: - raise ValueError("Couldn't set DID on given NFT") @tx_endpoint(push=True) async def nft_transfer_bulk( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3327,7 +3357,6 @@ async def nft_transfer_bulk( else: return dict(success=False, error="target_address parameter missing") nft_dict: Dict[uint32, List[NFTCoinInfo]] = {} - tx_list: List[TransactionRecord] = [] coin_ids = [] fee = uint64(request.get("fee", 0)) @@ -3354,44 +3383,29 @@ async def nft_transfer_bulk( for wallet_id, nft_list in nft_dict.items(): nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet) if not first: - tx_list.extend( - await nft_wallet.bulk_transfer_nft( - nft_list, puzzle_hash, tx_config, extra_conditions=extra_conditions - ) + await nft_wallet.bulk_transfer_nft( + nft_list, puzzle_hash, tx_config, action_scope, extra_conditions=extra_conditions ) else: - tx_list.extend( - await nft_wallet.bulk_transfer_nft( - nft_list, puzzle_hash, tx_config, fee, extra_conditions=extra_conditions - ) + await nft_wallet.bulk_transfer_nft( + nft_list, puzzle_hash, tx_config, action_scope, fee, extra_conditions=extra_conditions ) for coin in nft_list: coin_ids.append(coin.coin.name()) first = False - spend_bundles: List[SpendBundle] = [] - refined_tx_list: List[TransactionRecord] = [] - for tx in tx_list: - if tx.spend_bundle is not None: - spend_bundles.append(tx.spend_bundle) - refined_tx_list.append(dataclasses.replace(tx, spend_bundle=None)) - - if len(spend_bundles) > 0: - spend_bundle = SpendBundle.aggregate(spend_bundles) - # Add all spend bundles to the first tx - refined_tx_list[0] = dataclasses.replace(refined_tx_list[0], spend_bundle=spend_bundle) - for id in coin_ids: - await nft_wallet.update_coin_status(id, True) - for wallet_id in nft_dict.keys(): - self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id) + + for id in coin_ids: + await nft_wallet.update_coin_status(id, True) + for wallet_id in nft_dict.keys(): + self.service.wallet_state_manager.state_changed("nft_coin_did_set", wallet_id) + async with action_scope.use() as interface: return { "wallet_id": list(nft_dict.keys()), "success": True, - "spend_bundle": spend_bundle, - "tx_num": len(refined_tx_list), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in refined_tx_list], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "tx_num": len(interface.side_effects.transactions), + "transactions": None, # tx_endpoint wrapper will take care of this } - else: - raise ValueError("Couldn't transfer given NFTs") async def nft_get_by_did(self, request: Dict[str, Any]) -> EndpointResult: did_id: Optional[bytes32] = None @@ -3452,6 +3466,7 @@ async def nft_set_nft_status(self, request: Dict[str, Any]) -> EndpointResult: async def nft_transfer_nft( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3473,26 +3488,23 @@ async def nft_transfer_nft( assert nft_coin_info is not None fee = uint64(request.get("fee", 0)) - txs = await nft_wallet.generate_signed_transaction( + await nft_wallet.generate_signed_transaction( [uint64(nft_coin_info.coin.amount)], [puzzle_hash], tx_config, + action_scope, coins={nft_coin_info.coin}, fee=fee, new_owner=b"", new_did_inner_hash=b"", extra_conditions=extra_conditions, ) - spend_bundle: Optional[SpendBundle] = None - for tx in txs: - if tx.spend_bundle is not None: - spend_bundle = tx.spend_bundle await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True) return { "wallet_id": wallet_id, "success": True, - "spend_bundle": spend_bundle, - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } except Exception as e: log.exception(f"Failed to transfer NFT: {e}") @@ -3568,6 +3580,7 @@ async def nft_get_info(self, request: Dict[str, Any]) -> EndpointResult: async def nft_add_uri( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3585,14 +3598,14 @@ async def nft_add_uri( nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id) fee = uint64(request.get("fee", 0)) - txs = await nft_wallet.update_metadata( - nft_coin_info, key, uri, tx_config, fee=fee, extra_conditions=extra_conditions + await nft_wallet.update_metadata( + nft_coin_info, key, uri, tx_config, action_scope, fee=fee, extra_conditions=extra_conditions ) return { "wallet_id": wallet_id, "success": True, - "spend_bundle": SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "spend_bundle": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } async def nft_calculate_royalties(self, request: Dict[str, Any]) -> EndpointResult: @@ -3608,6 +3621,7 @@ async def nft_calculate_royalties(self, request: Dict[str, Any]) -> EndpointResu async def nft_mint_bulk( self, request: Dict[str, Any], + action_scope: WalletActionScope, push: bool = False, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), @@ -3694,7 +3708,7 @@ async def nft_mint_bulk( fee = uint64(request.get("fee", 0)) if mint_from_did: - txs = await nft_wallet.mint_from_did( + await nft_wallet.mint_from_did( metadata_list, mint_number_start=mint_number_start, mint_total=mint_total, @@ -3707,10 +3721,11 @@ async def nft_mint_bulk( did_lineage_parent=did_lineage_parent, fee=fee, tx_config=tx_config, + action_scope=action_scope, extra_conditions=extra_conditions, ) else: - txs = await nft_wallet.mint_from_xch( + await nft_wallet.mint_from_xch( metadata_list, mint_number_start=mint_number_start, mint_total=mint_total, @@ -3719,23 +3734,23 @@ async def nft_mint_bulk( xch_change_ph=xch_change_ph, fee=fee, tx_config=tx_config, + action_scope=action_scope, extra_conditions=extra_conditions, ) - sb = txs[0].spend_bundle - assert sb is not None + async with action_scope.use() as interface: + sb = SpendBundle.aggregate( + [tx.spend_bundle for tx in interface.side_effects.transactions if tx.spend_bundle is not None] + ) nft_id_list = [] for cs in sb.coin_spends: if cs.coin.puzzle_hash == nft_puzzles.LAUNCHER_PUZZLE_HASH: nft_id_list.append(encode_puzzle_hash(cs.coin.name(), AddressType.NFT.hrp(self.service.config))) - ### - # Temporary signing workaround (delete when minting functions return transaction records) - sb, _ = await self.service.wallet_state_manager.sign_bundle(sb.coin_spends) - ### + return { "success": True, - "spend_bundle": sb, + "spend_bundle": None, # tx_endpoint wrapper will take care of this "nft_id_list": nft_id_list, - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } async def get_coin_records(self, request: Dict[str, Any]) -> EndpointResult: @@ -3828,6 +3843,7 @@ async def get_farmed_amount(self, request: Dict[str, Any]) -> EndpointResult: async def create_signed_transaction( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), hold_lock: bool = True, @@ -3873,10 +3889,11 @@ async def create_signed_transaction( async def _generate_signed_transaction() -> EndpointResult: if isinstance(wallet, Wallet): - [tx] = await wallet.generate_signed_transaction( + await wallet.generate_signed_transaction( amount_0, bytes32(puzzle_hash_0), tx_config, + action_scope, fee, coins=coins, primaries=additional_outputs, @@ -3907,16 +3924,17 @@ async def _generate_signed_transaction() -> EndpointResult: ), ), ) - signed_tx = tx.to_json_dict_convenience(self.service.config) - return {"signed_txs": [signed_tx], "signed_tx": signed_tx, "transactions": [signed_tx]} + # tx_endpoint wrapper will take care of this + return {"signed_txs": None, "signed_tx": None, "transactions": None} else: assert isinstance(wallet, CATWallet) - txs = await wallet.generate_signed_transaction( + await wallet.generate_signed_transaction( [amount_0] + [output.amount for output in additional_outputs], [bytes32(puzzle_hash_0)] + [output.puzzle_hash for output in additional_outputs], tx_config, + action_scope, fee, coins=coins, memos=[memos_0] + [output.memos for output in additional_outputs], @@ -3946,9 +3964,8 @@ async def _generate_signed_transaction() -> EndpointResult: ), ), ) - signed_txs = [tx.to_json_dict_convenience(self.service.config) for tx in txs] - - return {"signed_txs": signed_txs, "signed_tx": signed_txs[0], "transactions": signed_txs} + # tx_endpoint wrapper will take care of this + return {"signed_txs": None, "signed_tx": None, "transactions": None} if hold_lock: async with self.service.wallet_state_manager.lock: @@ -3963,6 +3980,7 @@ async def _generate_signed_transaction() -> EndpointResult: async def pw_join_pool( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -3989,22 +4007,19 @@ async def pw_join_pool( ) async with self.service.wallet_state_manager.lock: - total_fee, tx, fee_tx = await wallet.join_pool(new_target_state, fee, tx_config) + total_fee = await wallet.join_pool(new_target_state, fee, tx_config, action_scope) return { "total_fee": total_fee, - "transaction": tx, - "fee_transaction": fee_tx, - "transactions": [ - transaction.to_json_dict_convenience(self.service.config) - for transaction in (tx, fee_tx) - if transaction is not None - ], + "transaction": None, # tx_endpoint wrapper will take care of this + "fee_transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def pw_self_pool( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4019,22 +4034,19 @@ async def pw_self_pool( raise ValueError("Wallet needs to be fully synced.") async with self.service.wallet_state_manager.lock: - total_fee, tx, fee_tx = await wallet.self_pool(fee, tx_config) + total_fee = await wallet.self_pool(fee, tx_config, action_scope) return { "total_fee": total_fee, - "transaction": tx, - "fee_transaction": fee_tx, - "transactions": [ - transaction.to_json_dict_convenience(self.service.config) - for transaction in (tx, fee_tx) - if transaction is not None - ], + "transaction": None, # tx_endpoint wrapper will take care of this + "fee_transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def pw_absorb_rewards( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4048,15 +4060,13 @@ async def pw_absorb_rewards( assert isinstance(wallet, PoolWallet) async with self.service.wallet_state_manager.lock: - transaction, fee_tx = await wallet.claim_pool_rewards(fee, max_spends_in_tx, tx_config) + await wallet.claim_pool_rewards(fee, max_spends_in_tx, tx_config, action_scope) state: PoolWalletInfo = await wallet.get_current_state() return { "state": state.to_json_dict(), - "transaction": transaction, - "fee_transaction": fee_tx, - "transactions": [ - tx.to_json_dict_convenience(self.service.config) for tx in (transaction, fee_tx) if tx is not None - ], + "transaction": None, # tx_endpoint wrapper will take care of this + "fee_transaction": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this } async def pw_status(self, request: Dict[str, Any]) -> EndpointResult: @@ -4079,6 +4089,7 @@ async def pw_status(self, request: Dict[str, Any]) -> EndpointResult: async def create_new_dl( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4094,9 +4105,10 @@ async def create_new_dl( try: async with self.service.wallet_state_manager.lock: - std_tx, launcher_id = await dl_wallet.generate_new_reporter( + launcher_id = await dl_wallet.generate_new_reporter( bytes32.from_hexstr(request["root"]), tx_config, + action_scope, fee=request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) @@ -4105,8 +4117,7 @@ async def create_new_dl( return {"success": False, "error": str(e)} return { - "success": True, - "transactions": [std_tx.to_json_dict_convenience(self.service.config)], + "transactions": None, # tx_endpoint wrapper will take care of this "launcher_id": launcher_id, } @@ -4172,6 +4183,7 @@ async def dl_singletons_by_root(self, request: Dict[str, Any]) -> EndpointResult async def dl_update_root( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4181,22 +4193,25 @@ async def dl_update_root( wallet = self.service.wallet_state_manager.get_dl_wallet() async with self.service.wallet_state_manager.lock: - records = await wallet.create_update_state_spend( + await wallet.create_update_state_spend( bytes32.from_hexstr(request["launcher_id"]), bytes32.from_hexstr(request["new_root"]), tx_config, + action_scope, fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions, ) - return { - "tx_record": records[0].to_json_dict_convenience(self.service.config), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in records], - } + + return { + "tx_record": None, # tx_endpoint wrapper will take care of this + "transactions": None, # tx_endpoint wrapper will take care of this + } @tx_endpoint(push=True) async def dl_update_multiple( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4208,20 +4223,19 @@ async def dl_update_multiple( async with self.service.wallet_state_manager.lock: # TODO: This method should optionally link the singletons with announcements. # Otherwise spends are vulnerable to signature subtraction. - tx_records: List[TransactionRecord] = [] fee_per_launcher = uint64(request.get("fee", 0) // len(request["updates"])) for launcher, root in request["updates"].items(): - records = await wallet.create_update_state_spend( + await wallet.create_update_state_spend( bytes32.from_hexstr(launcher), bytes32.from_hexstr(root), tx_config, + action_scope, fee=fee_per_launcher, extra_conditions=extra_conditions, ) - tx_records.extend(records) return { - "transactions": [rec.to_json_dict_convenience(self.service.config) for rec in tx_records], + "transactions": None, # tx_endpoint wrapper will take care of this } async def dl_history(self, request: Dict[str, Any]) -> EndpointResult: @@ -4270,6 +4284,7 @@ async def dl_get_mirrors(self, request: Dict[str, Any]) -> EndpointResult: async def dl_new_mirror( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4279,23 +4294,25 @@ async def dl_new_mirror( dl_wallet = self.service.wallet_state_manager.get_dl_wallet() async with self.service.wallet_state_manager.lock: - txs = await dl_wallet.create_new_mirror( + await dl_wallet.create_new_mirror( bytes32.from_hexstr(request["launcher_id"]), request["amount"], [bytes(url, "utf8") for url in request["urls"]], tx_config, + action_scope, fee=request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) return { - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def dl_delete_mirror( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4306,16 +4323,17 @@ async def dl_delete_mirror( dl_wallet = self.service.wallet_state_manager.get_dl_wallet() async with self.service.wallet_state_manager.lock: - txs = await dl_wallet.delete_mirror( + await dl_wallet.delete_mirror( bytes32.from_hexstr(request["coin_id"]), self.service.get_full_node_peer(), tx_config, + action_scope, fee=request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) return { - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } async def dl_verify_proof( @@ -4338,6 +4356,7 @@ async def dl_verify_proof( async def vc_mint( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4364,12 +4383,12 @@ class VCMint(Streamable): puzhash = decode_puzzle_hash(parsed_request.target_address) vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet() - vc_record, tx_list = await vc_wallet.launch_new_vc( - did_id, tx_config, puzhash, parsed_request.fee, extra_conditions=extra_conditions + vc_record = await vc_wallet.launch_new_vc( + did_id, tx_config, action_scope, puzhash, parsed_request.fee, extra_conditions=extra_conditions ) return { "vc_record": vc_record.to_json_dict(), - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_list], + "transactions": None, # tx_endpoint wrapper will take care of this } async def vc_get(self, request: Dict[str, Any]) -> EndpointResult: @@ -4423,6 +4442,7 @@ class VCGetList(Streamable): async def vc_spend( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4447,9 +4467,10 @@ class VCSpend(Streamable): vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet() - txs = await vc_wallet.generate_signed_transaction( + await vc_wallet.generate_signed_transaction( parsed_request.vc_id, tx_config, + action_scope, parsed_request.fee, parsed_request.new_puzhash, new_proof_hash=parsed_request.new_proof_hash, @@ -4458,7 +4479,7 @@ class VCSpend(Streamable): ) return { - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } async def vc_add_proofs(self, request: Dict[str, Any]) -> EndpointResult: @@ -4498,6 +4519,7 @@ class VCGetProofsForRoot(Streamable): async def vc_revoke( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4516,22 +4538,24 @@ class VCRevoke(Streamable): parsed_request = VCRevoke.from_json_dict(request) vc_wallet: VCWallet = await self.service.wallet_state_manager.get_or_create_vc_wallet() - txs = await vc_wallet.revoke_vc( + await vc_wallet.revoke_vc( parsed_request.vc_parent_id, self.service.get_full_node_peer(), tx_config, + action_scope, parsed_request.fee, extra_conditions=extra_conditions, ) return { - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } @tx_endpoint(push=True) async def crcat_approve_pending( self, request: Dict[str, Any], + action_scope: WalletActionScope, tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), ) -> EndpointResult: @@ -4554,15 +4578,16 @@ class CRCATApprovePending(Streamable): cr_cat_wallet = self.service.wallet_state_manager.wallets[parsed_request.wallet_id] assert isinstance(cr_cat_wallet, CRCATWallet) - txs = await cr_cat_wallet.claim_pending_approval_balance( + await cr_cat_wallet.claim_pending_approval_balance( parsed_request.min_amount_to_claim, tx_config, + action_scope, fee=parsed_request.fee, extra_conditions=extra_conditions, ) return { - "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transactions": None, # tx_endpoint wrapper will take care of this } @marshal diff --git a/chia/rpc/wallet_rpc_client.py b/chia/rpc/wallet_rpc_client.py index d2b459948930..f13aa196eaea 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -68,6 +68,7 @@ def parse_result_transactions(result: Dict[str, Any]) -> Dict[str, Any]: result["transaction"] = TransactionRecord.from_json_dict(result["transaction"]) + result["transactions"] = [TransactionRecord.from_json_dict_convenience(tx) for tx in result["transactions"]] if result["fee_transaction"]: result["fee_transaction"] = TransactionRecord.from_json_dict(result["fee_transaction"]) return result diff --git a/chia/seeder/crawl_store.py b/chia/seeder/crawl_store.py index 0e9156d6b4d6..e740e8c83e3a 100644 --- a/chia/seeder/crawl_store.py +++ b/chia/seeder/crawl_store.py @@ -5,6 +5,7 @@ import random import time from dataclasses import dataclass, field, replace +from datetime import datetime, timedelta from typing import Dict, List import aiosqlite @@ -259,19 +260,19 @@ def get_reliable_peers(self) -> int: return self.reliable_peers async def load_to_db(self) -> None: - log.info("Saving peers to DB...") + log.warning("Saving peers to DB...") for peer_id in list(self.host_to_reliability.keys()): if peer_id in self.host_to_reliability and peer_id in self.host_to_records: reliability = self.host_to_reliability[peer_id] record = self.host_to_records[peer_id] await self.add_peer(record, reliability, True) await self.crawl_db.commit() - log.info(" - Done saving peers to DB") + log.warning(" - Done saving peers to DB") async def unload_from_db(self) -> None: self.host_to_records = {} self.host_to_reliability = {} - log.info("Loading peer reliability records...") + log.warning("Loading peer reliability records...") cursor = await self.crawl_db.execute( "SELECT * from peer_reliability", ) @@ -301,8 +302,8 @@ async def unload_from_db(self) -> None: row[19], ) self.host_to_reliability[reliability.peer_id] = reliability - log.info(" - Done loading peer reliability records...") - log.info("Loading peer records...") + log.warning(" - Done loading peer reliability records...") + log.warning("Loading peer records...") cursor = await self.crawl_db.execute( "SELECT * from peer_records", ) @@ -313,7 +314,7 @@ async def unload_from_db(self) -> None: row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11] ) self.host_to_records[peer.peer_id] = peer - log.info(" - Done loading peer records...") + log.warning(" - Done loading peer records...") # Crawler -> DNS. async def load_reliable_peers_to_db(self) -> None: @@ -322,13 +323,13 @@ async def load_reliable_peers_to_db(self) -> None: if reliability.is_reliable(): peers.append(peer_id) self.reliable_peers = len(peers) - log.info("Deleting old good_peers from DB...") + log.warning("Deleting old good_peers from DB...") cursor = await self.crawl_db.execute( "DELETE from good_peers", ) await cursor.close() - log.info(" - Done deleting old good_peers...") - log.info("Saving new good_peers to DB...") + log.warning(" - Done deleting old good_peers...") + log.warning("Saving new good_peers to DB...") for peer_id in peers: cursor = await self.crawl_db.execute( "INSERT OR REPLACE INTO good_peers VALUES(?)", @@ -336,7 +337,7 @@ async def load_reliable_peers_to_db(self) -> None: ) await cursor.close() await self.crawl_db.commit() - log.info(" - Done saving new good_peers to DB...") + log.warning(" - Done saving new good_peers to DB...") def load_host_to_version(self) -> tuple[dict[str, str], dict[str, uint64]]: versions = {} @@ -380,3 +381,47 @@ async def get_good_peers(self) -> list[str]: # This is for the DNS server if len(result) > 0: random.shuffle(result) # mix up the peers return result + + async def prune_old_peers(self, older_than_days: int) -> None: + cutoff = int((datetime.now() - timedelta(days=older_than_days)).timestamp()) + + # Deletes the old records from the DB + await self.crawl_db.execute("delete from peer_records where best_timestamp < ?", (cutoff,)) + await self.crawl_db.execute( + """ + delete from peer_reliability + where not exists ( + select peer_records.peer_id + from peer_records + where peer_records.peer_id = peer_reliability.peer_id + ) + """ + ) + await self.crawl_db.execute( + """ + delete from good_peers + where not exists ( + select peer_records.ip_address + from peer_records + where peer_records.ip_address = good_peers.ip + ) + """ + ) + await self.crawl_db.commit() + await self.crawl_db.execute("VACUUM") + + to_delete: List[str] = [] + + # Deletes the old records from the in memory Dicts + for peer_id, peer_record in self.host_to_records.items(): + if peer_record.best_timestamp < cutoff: + to_delete.append(peer_id) + + for peer_id in to_delete: + del self.host_to_records[peer_id] + + if peer_id in self.host_to_selected_time: + del self.host_to_selected_time[peer_id] + + if peer_id in self.host_to_reliability: + del self.host_to_reliability[peer_id] diff --git a/chia/seeder/crawler.py b/chia/seeder/crawler.py index b1a2c810717e..318ee309b86e 100644 --- a/chia/seeder/crawler.py +++ b/chia/seeder/crawler.py @@ -72,6 +72,7 @@ class Crawler: version_cache: List[Tuple[str, str]] = field(default_factory=list) handshake_time: Dict[str, uint64] = field(default_factory=dict) best_timestamp_per_peer: Dict[str, uint64] = field(default_factory=dict) + start_crawler_loop: bool = True @property def server(self) -> ChiaServer: @@ -90,9 +91,10 @@ async def manage(self) -> AsyncIterator[None]: # Connect to the DB self.crawl_store: CrawlStore = await CrawlStore.create(await aiosqlite.connect(self.db_path)) - # Bootstrap the initial peers - await self.load_bootstrap_peers() - self.crawl_task = asyncio.create_task(self.crawl()) + if self.start_crawler_loop: + # Bootstrap the initial peers + await self.load_bootstrap_peers() + self.crawl_task = asyncio.create_task(self.crawl()) try: yield finally: @@ -212,8 +214,9 @@ async def crawl(self) -> None: # Sometimes, the daemon connection + state changed callback isn't up and ready # by the time we get to the first _state_changed call, so this just ensures it's there before moving on while self.state_changed_callback is None: - self.log.info("Waiting for state changed callback...") + self.log.warning("Waiting for state changed callback...") await asyncio.sleep(0.1) + self.log.warning(" - Got state changed callback...") assert self.crawl_store is not None t_start = time.time() total_nodes = 0 @@ -221,6 +224,7 @@ async def crawl(self) -> None: try: while not self._shut_down: peers_to_crawl = await self.crawl_store.get_peers_to_crawl(25000, 250000) + self.log.warning(f"Crawling {len(peers_to_crawl)} peers...") tasks = set() for peer in peers_to_crawl: if peer.port == self.other_peers_port: @@ -304,7 +308,9 @@ async def crawl(self) -> None: if len(peers_to_crawl) == 0: continue + peer_cutoff = int(self.config.get("crawler", {}).get("prune_peer_days", 90)) await self.save_to_db() + await self.crawl_store.prune_old_peers(older_than_days=peer_cutoff) await self.print_summary(t_start, total_nodes, tried_nodes) await asyncio.sleep(15) # 15 seconds between db updates self._state_changed("crawl_batch_completed") diff --git a/chia/seeder/start_crawler.py b/chia/seeder/start_crawler.py index e57799aaeca5..6462e89b6915 100644 --- a/chia/seeder/start_crawler.py +++ b/chia/seeder/start_crawler.py @@ -31,14 +31,13 @@ def create_full_node_crawler_service( config: Dict[str, Any], consensus_constants: ConsensusConstants, connect_to_daemon: bool = True, + start_crawler_loop: bool = True, ) -> CrawlerService: service_config = config[SERVICE_NAME] crawler_config = service_config["crawler"] crawler = Crawler( - service_config, - root_path=root_path, - constants=consensus_constants, + service_config, root_path=root_path, constants=consensus_constants, start_crawler_loop=start_crawler_loop ) api = CrawlerAPI(crawler) diff --git a/chia/server/start_data_layer.py b/chia/server/start_data_layer.py index 3771b2ef2e72..cad0637b11ef 100644 --- a/chia/server/start_data_layer.py +++ b/chia/server/start_data_layer.py @@ -8,6 +8,7 @@ from chia.data_layer.data_layer import DataLayer from chia.data_layer.data_layer_api import DataLayerAPI from chia.data_layer.data_layer_util import PluginRemote +from chia.data_layer.util.plugin import load_plugin_configurations from chia.rpc.data_layer_rpc_api import DataLayerRpcApi from chia.rpc.wallet_rpc_client import WalletRpcClient from chia.server.outbound_message import NodeType @@ -100,19 +101,24 @@ async def async_main() -> int: ) plugins_config = config["data_layer"].get("plugins", {}) + service_dir = DEFAULT_ROOT_PATH / SERVICE_NAME old_uploaders = config["data_layer"].get("uploaders", []) new_uploaders = plugins_config.get("uploaders", []) + conf_file_uploaders = await load_plugin_configurations(service_dir, "uploaders", log) uploaders: List[PluginRemote] = [ *(PluginRemote(url=url) for url in old_uploaders), *(PluginRemote.unmarshal(marshalled=marshalled) for marshalled in new_uploaders), + *conf_file_uploaders, ] old_downloaders = config["data_layer"].get("downloaders", []) new_downloaders = plugins_config.get("downloaders", []) + conf_file_uploaders = await load_plugin_configurations(service_dir, "downloaders", log) downloaders: List[PluginRemote] = [ *(PluginRemote(url=url) for url in old_downloaders), *(PluginRemote.unmarshal(marshalled=marshalled) for marshalled in new_downloaders), + *conf_file_uploaders, ] service = create_data_layer_service(DEFAULT_ROOT_PATH, config, downloaders, uploaders) diff --git a/chia/simulator/full_node_simulator.py b/chia/simulator/full_node_simulator.py index df31f404f180..47b890ec20ec 100644 --- a/chia/simulator/full_node_simulator.py +++ b/chia/simulator/full_node_simulator.py @@ -682,15 +682,15 @@ async def create_coins_with_amounts( outputs_group = [output for _, output in zip(range(per_transaction_record_group), outputs_iterator)] if len(outputs_group) > 0: - async with wallet.wallet_state_manager.lock: - [tx] = await wallet.generate_signed_transaction( + async with wallet.wallet_state_manager.new_action_scope(push=True) as action_scope: + await wallet.generate_signed_transaction( amount=outputs_group[0].amount, puzzle_hash=outputs_group[0].puzzle_hash, tx_config=DEFAULT_TX_CONFIG, + action_scope=action_scope, primaries=outputs_group[1:], ) - [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) - transaction_records.append(tx) + transaction_records.extend(action_scope.side_effects.transactions) else: break diff --git a/chia/simulator/setup_services.py b/chia/simulator/setup_services.py index 93f2223d4c1d..71c2da53fd78 100644 --- a/chia/simulator/setup_services.py +++ b/chia/simulator/setup_services.py @@ -169,7 +169,7 @@ async def setup_full_node( @asynccontextmanager async def setup_crawler( - root_path_populated_with_config: Path, database_uri: str + root_path_populated_with_config: Path, database_uri: str, start_crawler_loop: bool = True ) -> AsyncGenerator[CrawlerService, None]: create_all_ssl( root_path=root_path_populated_with_config, @@ -193,6 +193,7 @@ async def setup_crawler( config, updated_constants, connect_to_daemon=False, + start_crawler_loop=start_crawler_loop, ) async with service.manage(): if not service_config["crawler"]["start_rpc_server"]: # otherwise the loops don't work. diff --git a/chia/util/initial-config.yaml b/chia/util/initial-config.yaml index 9c38d2d1b9c4..05d4ac2fd494 100644 --- a/chia/util/initial-config.yaml +++ b/chia/util/initial-config.yaml @@ -42,6 +42,18 @@ network_overrides: &network_overrides PLOT_FILTER_128_HEIGHT: 6029568 PLOT_FILTER_64_HEIGHT: 11075328 PLOT_FILTER_32_HEIGHT: 16121088 + testneta: + AGG_SIG_ME_ADDITIONAL_DATA: b0a306abe27407130586c8e13d06dc057d4538c201dbd36c8f8c481f5e51af5c + DIFFICULTY_CONSTANT_FACTOR: 10052721566054 + DIFFICULTY_STARTING: 30 + EPOCH_BLOCKS: 768 + GENESIS_CHALLENGE: b0a306abe27407130586c8e13d06dc057d4538c201dbd36c8f8c481f5e51af5c + GENESIS_PRE_FARM_FARMER_PUZZLE_HASH: 08296fc227decd043aee855741444538e4cc9a31772c4d1a9e6242d1e777e42a + GENESIS_PRE_FARM_POOL_PUZZLE_HASH: 08296fc227decd043aee855741444538e4cc9a31772c4d1a9e6242d1e777e42a + MEMPOOL_BLOCK_BUFFER: 10 + MIN_PLOT_SIZE: 18 + NETWORK_TYPE: 1 + SUB_SLOT_ITERS_STARTING: 67108864 config: mainnet: address_prefix: "xch" @@ -52,10 +64,11 @@ network_overrides: &network_overrides testnet11: address_prefix: "txch" default_full_node_port: 58444 + testneta: + address_prefix: "txch" + default_full_node_port: 58444 selected_network: &selected_network "mainnet" -ALERTS_URL: https://download.chia.net/notify/mainnet_alert.txt -CHIA_ALERTS_PUBKEY: 89b7fd87cb56e926ecefb879a29aae308be01f31980569f6a75a69d2a9a69daefd71fb778d865f7c50d6c967e3025937 # public ssl ca is included in source code # Private ssl ca is used for trusted connections between machines user owns @@ -118,6 +131,7 @@ seeder: crawler: start_rpc_server: True rpc_port: 8561 + prune_peer_days: 90 # Peers older than this many days will be removed from the crawler database ssl: private_crt: "config/ssl/crawler/private_crawler.crt" private_key: "config/ssl/crawler/private_crawler.key" @@ -601,7 +615,8 @@ data_layer: # The location where the server files will be stored. server_files_location: "data_layer/db/server_files_location_CHALLENGE" # The timeout for the client to download a file from a server - client_timeout: 15 + client_timeout: 45 + connect_timeout: 5 # If you need use a proxy for download data you can use this setting sample # proxy_url: http://localhost:8888 diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index 2e8131705899..d8ed0b6858b2 100644 --- a/chia/wallet/cat_wallet/cat_wallet.py +++ b/chia/wallet/cat_wallet/cat_wallet.py @@ -6,7 +6,7 @@ import traceback from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Set, Tuple, cast -from chia_rs import G1Element, G2Element +from chia_rs import G1Element from typing_extensions import Unpack from chia.consensus.default_constants import DEFAULT_CONSTANTS @@ -55,6 +55,7 @@ from chia.wallet.util.wallet_sync_utils import fetch_coin_spend_for_coin_state from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_protocol import GSTOptionalArgs, WalletProtocol @@ -109,9 +110,11 @@ async def create_new_cat_wallet( cat_tail_info: Dict[str, Any], amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), name: Optional[str] = None, - ) -> Tuple[CATWallet, List[TransactionRecord]]: + push: bool = True, + ) -> CATWallet: self = CATWallet() self.standard_wallet = wallet self.log = logging.getLogger(__name__) @@ -134,13 +137,12 @@ async def create_new_cat_wallet( self.wallet_info = await wallet_state_manager.user_store.create_wallet(name, WalletType.CAT, info_as_string) try: - chia_tx, spend_bundle = await ALL_LIMITATIONS_PROGRAMS[ - cat_tail_info["identifier"] - ].generate_issuance_bundle( + spend_bundle = await ALL_LIMITATIONS_PROGRAMS[cat_tail_info["identifier"]].generate_issuance_bundle( self, cat_tail_info, amount, tx_config, + action_scope, fee, ) assert self.cat_info.limitations_program_hash != empty_bytes @@ -183,20 +185,20 @@ async def create_new_cat_wallet( fee_amount=fee, confirmed=False, sent=uint32(10), - spend_bundle=None, + spend_bundle=spend_bundle, additions=[cat_coin], removals=list(filter(lambda rem: rem.name() == cat_pid, spend_bundle.removals())), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.INCOMING_TX.value), - name=bytes32.secret(), + name=spend_bundle.name(), memos=[], valid_times=ConditionValidTimes(), ) - chia_tx = dataclasses.replace(chia_tx, spend_bundle=spend_bundle, name=spend_bundle.name()) - tx_list = await self.wallet_state_manager.add_pending_transactions([chia_tx, cat_record]) - return self, tx_list + async with action_scope.use() as interface: + interface.side_effects.transactions.append(cat_record) + return self @staticmethod async def get_or_create_wallet_for_cat( @@ -562,70 +564,79 @@ async def create_tandem_xch_tx( fee: uint64, amount_to_claim: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[TransactionRecord, Optional[AssertCoinAnnouncement]]: + ) -> Optional[AssertCoinAnnouncement]: """ This function creates a non-CAT transaction to pay fees, contribute funds for issuance, and absorb melt value. It is meant to be called in `generate_unsigned_spendbundle` and as such should be called under the wallet_state_manager lock """ announcement: Optional[AssertCoinAnnouncement] = None - if fee > amount_to_claim: - chia_coins = await self.standard_wallet.select_coins( - fee, - tx_config.coin_selection_config, - ) - origin_id = list(chia_coins)[0].name() - [chia_tx] = await self.standard_wallet.generate_signed_transaction( - uint64(0), - (await self.standard_wallet.get_puzzle_hash(not tx_config.reuse_puzhash)), - tx_config, - fee=uint64(fee - amount_to_claim), - coins=chia_coins, - origin_id=origin_id, # We specify this so that we know the coin that is making the announcement - negative_change_allowed=False, - extra_conditions=extra_conditions, - ) - assert chia_tx.spend_bundle is not None - else: - chia_coins = await self.standard_wallet.select_coins( - fee, - tx_config.coin_selection_config, - ) - origin_id = list(chia_coins)[0].name() - selected_amount = sum(c.amount for c in chia_coins) - [chia_tx] = await self.standard_wallet.generate_signed_transaction( - uint64(selected_amount + amount_to_claim - fee), - (await self.standard_wallet.get_puzzle_hash(not tx_config.reuse_puzhash)), - tx_config, - coins=chia_coins, - negative_change_allowed=True, - extra_conditions=extra_conditions, - ) - assert chia_tx.spend_bundle is not None + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + if fee > amount_to_claim: + chia_coins = await self.standard_wallet.select_coins( + fee, + tx_config.coin_selection_config, + ) + origin_id = list(chia_coins)[0].name() + await self.standard_wallet.generate_signed_transaction( + uint64(0), + (await self.standard_wallet.get_puzzle_hash(not tx_config.reuse_puzhash)), + tx_config, + inner_action_scope, + fee=uint64(fee - amount_to_claim), + coins=chia_coins, + origin_id=origin_id, # We specify this so that we know the coin that is making the announcement + negative_change_allowed=False, + extra_conditions=extra_conditions, + ) + else: + chia_coins = await self.standard_wallet.select_coins( + fee, + tx_config.coin_selection_config, + ) + origin_id = list(chia_coins)[0].name() + selected_amount = sum(c.amount for c in chia_coins) + await self.standard_wallet.generate_signed_transaction( + uint64(selected_amount + amount_to_claim - fee), + (await self.standard_wallet.get_puzzle_hash(not tx_config.reuse_puzhash)), + tx_config, + inner_action_scope, + coins=chia_coins, + negative_change_allowed=True, + extra_conditions=extra_conditions, + ) - message = None - for spend in chia_tx.spend_bundle.coin_spends: + message = None + for tx in inner_action_scope.side_effects.transactions: + if tx.spend_bundle is None: + continue + for spend in tx.spend_bundle.coin_spends: if spend.coin.name() == origin_id: conditions = spend.puzzle_reveal.to_program().run(spend.solution.to_program()).as_python() for condition in conditions: if condition[0] == ConditionOpcode.CREATE_COIN_ANNOUNCEMENT: message = condition[1] - assert message is not None - announcement = AssertCoinAnnouncement(asserted_id=origin_id, asserted_msg=message) + assert message is not None + announcement = AssertCoinAnnouncement(asserted_id=origin_id, asserted_msg=message) + + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions) - return chia_tx, announcement + return announcement async def generate_unsigned_spendbundle( self, payments: List[Payment], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), cat_discrepancy: Optional[Tuple[int, Program, Program]] = None, # (extra_delta, tail_reveal, tail_solution) coins: Optional[Set[Coin]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[SpendBundle, Optional[TransactionRecord]]: + ) -> SpendBundle: if cat_discrepancy is not None: extra_delta, tail_reveal, tail_solution = cat_discrepancy else: @@ -673,7 +684,6 @@ async def generate_unsigned_spendbundle( # Loop through the coins we've selected and gather the information we need to spend them spendable_cat_list = [] - chia_tx = None first = True announcement: CreateCoinAnnouncement @@ -695,10 +705,11 @@ async def generate_unsigned_spendbundle( announcement = CreateCoinAnnouncement(std_hash(b"".join([c.name() for c in cat_coins])), coin.name()) if need_chia_transaction: if fee > regular_chia_to_claim: - chia_tx, _ = await self.create_tandem_xch_tx( + await self.create_tandem_xch_tx( fee, uint64(regular_chia_to_claim), tx_config, + action_scope, extra_conditions=(announcement.corresponding_assertion(),), ) innersol = self.standard_wallet.make_solution( @@ -706,10 +717,11 @@ async def generate_unsigned_spendbundle( conditions=(*extra_conditions, announcement), ) elif regular_chia_to_claim > fee: # pragma: no cover - chia_tx, xch_announcement = await self.create_tandem_xch_tx( + xch_announcement = await self.create_tandem_xch_tx( fee, uint64(regular_chia_to_claim), tx_config, + action_scope, ) assert xch_announcement is not None innersol = self.standard_wallet.make_solution( @@ -744,31 +756,21 @@ async def generate_unsigned_spendbundle( spendable_cat_list.append(new_spendable_cat) cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list) - chia_spend_bundle = SpendBundle([], G2Element()) - if chia_tx is not None and chia_tx.spend_bundle is not None: - chia_spend_bundle = chia_tx.spend_bundle - return ( - SpendBundle.aggregate( - [ - cat_spend_bundle, - chia_spend_bundle, - ] - ), - chia_tx, - ) + return cat_spend_bundle async def generate_signed_transaction( self, amounts: List[uint64], puzzle_hashes: List[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Optional[Set[Coin]] = None, memos: Optional[List[List[bytes]]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), **kwargs: Unpack[GSTOptionalArgs], - ) -> List[TransactionRecord]: + ) -> None: # (extra_delta, tail_reveal, tail_solution) cat_discrepancy: Optional[Tuple[int, Program, Program]] = kwargs.get("cat_discrepancy", None) if memos is None: @@ -784,67 +786,45 @@ async def generate_signed_transaction( payments.append(Payment(puzhash, amount, memos_with_hint)) payment_sum = sum(p.amount for p in payments) - spend_bundle, chia_tx = await self.generate_unsigned_spendbundle( + spend_bundle = await self.generate_unsigned_spendbundle( payments, tx_config, + action_scope, fee, cat_discrepancy=cat_discrepancy, # (extra_delta, tail_reveal, tail_solution) coins=coins, extra_conditions=extra_conditions, ) - if chia_tx is not None: - other_tx_removals: Set[Coin] = {removal for removal in chia_tx.removals} - other_tx_additions: Set[Coin] = {removal for removal in chia_tx.additions} - else: - other_tx_removals = set() - other_tx_additions = set() - tx_list = [ - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=puzzle_hashes[0], - amount=uint64(payment_sum), - fee_amount=fee, - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=list(set(spend_bundle.additions()) - other_tx_additions), - removals=list(set(spend_bundle.removals()) - other_tx_removals), - wallet_id=self.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=spend_bundle.name(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=parse_timelock_info(extra_conditions), - ) - ] - if chia_tx is not None: - tx_list.append( + async with action_scope.use() as interface: + other_tx_removals: Set[Coin] = { + removal for tx in interface.side_effects.transactions for removal in tx.removals + } + other_tx_additions: Set[Coin] = { + removal for tx in interface.side_effects.transactions for removal in tx.additions + } + interface.side_effects.transactions.append( TransactionRecord( - confirmed_at_height=chia_tx.confirmed_at_height, - created_at_time=chia_tx.created_at_time, - to_puzzle_hash=chia_tx.to_puzzle_hash, - amount=chia_tx.amount, - fee_amount=chia_tx.fee_amount, - confirmed=chia_tx.confirmed, - sent=chia_tx.sent, - spend_bundle=None, - additions=chia_tx.additions, - removals=chia_tx.removals, - wallet_id=chia_tx.wallet_id, - sent_to=chia_tx.sent_to, - trade_id=chia_tx.trade_id, - type=chia_tx.type, - name=chia_tx.name, - memos=[], + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=puzzle_hashes[0], + amount=uint64(payment_sum), + fee_amount=fee, + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle, + additions=list(set(spend_bundle.additions()) - other_tx_additions), + removals=list(set(spend_bundle.removals()) - other_tx_removals), + wallet_id=self.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=spend_bundle.name(), + memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) ) - return tx_list - async def add_lineage(self, name: bytes32, lineage: Optional[LineageProof]) -> None: """ Lineage proofs are stored as a list of parent coins and the lineage proof you will need if they are the diff --git a/chia/wallet/cat_wallet/dao_cat_wallet.py b/chia/wallet/cat_wallet/dao_cat_wallet.py index b7e3540e9201..704ccc026eef 100644 --- a/chia/wallet/cat_wallet/dao_cat_wallet.py +++ b/chia/wallet/cat_wallet/dao_cat_wallet.py @@ -39,6 +39,7 @@ from chia.wallet.util.wallet_sync_utils import fetch_coin_spend from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo @@ -363,6 +364,7 @@ async def enter_dao_cat_voting_mode( self, amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), ) -> List[TransactionRecord]: @@ -381,6 +383,7 @@ async def enter_dao_cat_voting_mode( [amount], [lockup_puzzle.get_tree_hash()], tx_config, + action_scope, fee=fee, extra_conditions=extra_conditions, ) @@ -394,9 +397,10 @@ async def exit_vote_state( self, coins: List[LockedCoinInfo], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: extra_delta, limitations_solution = 0, Program.to([]) limitations_program_reveal = Program.to([]) spendable_cat_list = [] @@ -452,14 +456,11 @@ async def exit_vote_state( spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list) if fee > 0: # pragma: no cover - chia_tx = await self.standard_wallet.create_tandem_xch_tx( + await self.standard_wallet.create_tandem_xch_tx( fee, tx_config, + action_scope, ) - assert chia_tx.spend_bundle is not None - full_spend = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - else: - full_spend = spend_bundle record = TransactionRecord( confirmed_at_height=uint32(0), @@ -469,14 +470,14 @@ async def exit_vote_state( fee_amount=fee, confirmed=False, sent=uint32(10), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), + spend_bundle=spend_bundle, + additions=spend_bundle.additions(), + removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.INCOMING_TX.value), - name=full_spend.name(), + name=spend_bundle.name(), memos=[], valid_times=parse_timelock_info(extra_conditions), ) @@ -493,10 +494,15 @@ async def exit_vote_state( new_locked_coins, ) await self.save_info(dao_cat_info) - return [record] + async with action_scope.use() as interface: + interface.side_effects.transactions.append(record) async def remove_active_proposal( - self, proposal_id_list: List[bytes32], tx_config: TXConfig, fee: uint64 = uint64(0) + self, + proposal_id_list: List[bytes32], + tx_config: TXConfig, + action_scope: WalletActionScope, + fee: uint64 = uint64(0), ) -> SpendBundle: locked_coins: List[Tuple[LockedCoinInfo, List[bytes32]]] = [] for lci in self.dao_cat_info.locked_coins: @@ -557,13 +563,9 @@ async def remove_active_proposal( spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list) if fee > 0: # pragma: no cover - chia_tx = await self.standard_wallet.create_tandem_xch_tx(fee, tx_config=tx_config) - assert chia_tx.spend_bundle is not None - full_spend = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - else: - full_spend = spend_bundle + await self.standard_wallet.create_tandem_xch_tx(fee, tx_config=tx_config, action_scope=action_scope) - return full_spend + return spend_bundle def get_asset_id(self) -> str: return bytes(self.dao_cat_info.limitations_program_hash).hex() diff --git a/chia/wallet/dao_wallet/dao_wallet.py b/chia/wallet/dao_wallet/dao_wallet.py index bf7f2f5de6d8..649cc7b64843 100644 --- a/chia/wallet/dao_wallet/dao_wallet.py +++ b/chia/wallet/dao_wallet/dao_wallet.py @@ -73,6 +73,7 @@ from chia.wallet.util.wallet_sync_utils import fetch_coin_spend from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo @@ -120,11 +121,12 @@ async def create_new_dao_and_wallet( amount_of_cats: uint64, dao_rules: DAORules, tx_config: TXConfig, + action_scope: WalletActionScope, filter_amount: uint64 = uint64(1), name: Optional[str] = None, fee: uint64 = uint64(0), fee_for_cat: uint64 = uint64(0), - ) -> Tuple[DAOWallet, List[TransactionRecord]]: + ) -> DAOWallet: """ Create a brand new DAO wallet This must be called under the wallet state manager lock @@ -173,9 +175,10 @@ async def create_new_dao_and_wallet( std_wallet_id = self.standard_wallet.wallet_id try: - txs = await self.generate_new_dao( + await self.generate_new_dao( amount_of_cats, tx_config, + action_scope, fee=fee, fee_for_cat=fee_for_cat, ) @@ -198,7 +201,7 @@ async def create_new_dao_and_wallet( ) await self.save_info(dao_info) - return self, txs + return self @staticmethod async def create_new_dao_wallet_for_existing_dao( @@ -605,11 +608,12 @@ async def generate_new_dao( self, amount_of_cats_to_create: Optional[uint64], tx_config: TXConfig, + action_scope: WalletActionScope, cat_tail_hash: Optional[bytes32] = None, fee: uint64 = uint64(0), fee_for_cat: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: """ Create a new DAO treasury using the dao_rules object. This does the first spend to create the launcher and eve coins. @@ -685,13 +689,15 @@ async def generate_new_dao( "treasury_id": launcher_coin.name(), "coins": different_coins, } - new_cat_wallet, _ = await CATWallet.create_new_cat_wallet( + new_cat_wallet = await CATWallet.create_new_cat_wallet( self.wallet_state_manager, self.standard_wallet, cat_tail_info, amount_of_cats_to_create, DEFAULT_TX_CONFIG, + action_scope, fee=fee_for_cat, + push=False, ) assert new_cat_wallet is not None else: # pragma: no cover @@ -726,10 +732,12 @@ async def generate_new_dao( full_treasury_puzzle_hash = full_treasury_puzzle.get_tree_hash() announcement_message = Program.to([full_treasury_puzzle_hash, 1, bytes(0x80)]).get_tree_hash() - tx_records: List[TransactionRecord] = await self.standard_wallet.generate_signed_transaction( + + await self.standard_wallet.generate_signed_transaction( uint64(1), genesis_launcher_puz.get_tree_hash(), tx_config, + action_scope, fee, origin_id=origin.name(), coins=set(coins), @@ -738,7 +746,6 @@ async def generate_new_dao( AssertCoinAnnouncement(asserted_id=launcher_coin.name(), asserted_msg=announcement_message), ), ) - tx_record: TransactionRecord = tx_records[0] genesis_launcher_solution = Program.to([full_treasury_puzzle_hash, 1, bytes(0x80)]) @@ -752,8 +759,6 @@ async def generate_new_dao( ) await self.add_parent(launcher_coin.name(), launcher_proof) - assert tx_record.spend_bundle is not None - eve_coin = Coin(launcher_coin.name(), full_treasury_puzzle_hash, uint64(1)) dao_info = DAOInfo( launcher_coin.name(), @@ -770,8 +775,7 @@ async def generate_new_dao( ) await self.save_info(dao_info) eve_spend = await self.generate_treasury_eve_spend(dao_treasury_puzzle, eve_coin) - - full_spend = SpendBundle.aggregate([tx_record.spend_bundle, launcher_sb, eve_spend]) + new_spend = SpendBundle.aggregate([launcher_sb, eve_spend]) treasury_record = TransactionRecord( confirmed_at_height=uint32(0), @@ -781,18 +785,17 @@ async def generate_new_dao( fee_amount=fee, confirmed=False, sent=uint32(10), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), + spend_bundle=new_spend, + additions=new_spend.additions(), + removals=new_spend.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.INCOMING_TX.value), - name=full_spend.name(), + name=eve_coin.name(), memos=[], valid_times=parse_timelock_info(extra_conditions), ) - regular_record = dataclasses.replace(tx_record, spend_bundle=None) funding_inner_puzhash = get_p2_singleton_puzhash(self.dao_info.treasury_id) await self.wallet_state_manager.add_interested_puzzle_hashes([funding_inner_puzhash], [self.id()]) @@ -800,7 +803,8 @@ async def generate_new_dao( await self.wallet_state_manager.add_interested_coin_ids([launcher_coin.name()], [self.wallet_id]) await self.wallet_state_manager.add_interested_coin_ids([eve_coin.name()], [self.wallet_id]) - return [treasury_record, regular_record] + async with action_scope.use() as interface: + interface.side_effects.transactions.append(treasury_record) async def generate_treasury_eve_spend( self, inner_puz: Program, eve_coin: Coin, fee: uint64 = uint64(0) @@ -843,10 +847,11 @@ async def generate_new_proposal( self, proposed_puzzle: Program, tx_config: TXConfig, + action_scope: WalletActionScope, vote_amount: Optional[uint64] = None, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: dao_rules = get_treasury_rules_from_puzzle(self.dao_info.current_treasury_innerpuz) coins = await self.standard_wallet.select_coins( uint64(fee + dao_rules.proposal_minimum_amount), @@ -886,10 +891,11 @@ async def generate_new_proposal( [full_proposal_puzzle_hash, dao_rules.proposal_minimum_amount, bytes(0x80)] ).get_tree_hash() - tx_records: List[TransactionRecord] = await self.standard_wallet.generate_signed_transaction( + await self.standard_wallet.generate_signed_transaction( uint64(dao_rules.proposal_minimum_amount), genesis_launcher_puz.get_tree_hash(), tx_config, + action_scope, fee, origin_id=origin.name(), coins=coins, @@ -897,7 +903,6 @@ async def generate_new_proposal( AssertCoinAnnouncement(asserted_id=launcher_coin.name(), asserted_msg=announcement_message), ), ) - tx_record: TransactionRecord = tx_records[0] genesis_launcher_solution = Program.to( [full_proposal_puzzle_hash, dao_rules.proposal_minimum_amount, bytes(0x80)] @@ -929,31 +934,31 @@ async def generate_new_proposal( launcher_coin=launcher_coin, vote_amount=vote_amount, ) - assert tx_record - assert tx_record.spend_bundle is not None - - full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend, launcher_sb]) - record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=full_proposal_puzzle.get_tree_hash(), - amount=uint64(dao_rules.proposal_minimum_amount), - fee_amount=fee, - confirmed=False, - sent=uint32(10), - spend_bundle=full_spend, - additions=full_spend.additions(), - removals=full_spend.removals(), - wallet_id=self.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.INCOMING_TX.value), - name=full_spend.name(), - memos=[], - valid_times=parse_timelock_info(extra_conditions), - ) - return [record] + full_spend = SpendBundle.aggregate([eve_spend, launcher_sb]) + + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=full_proposal_puzzle.get_tree_hash(), + amount=uint64(dao_rules.proposal_minimum_amount), + fee_amount=fee, + confirmed=False, + sent=uint32(10), + spend_bundle=full_spend, + additions=full_spend.additions(), + removals=full_spend.removals(), + wallet_id=self.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.INCOMING_TX.value), + name=full_spend.name(), + memos=[], + valid_times=parse_timelock_info(extra_conditions), + ) + ) async def generate_proposal_eve_spend( self, @@ -1026,9 +1031,10 @@ async def generate_proposal_vote_spend( vote_amount: Optional[uint64], is_yes_vote: bool, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: self.log.info(f"Trying to create a proposal close spend with ID: {proposal_id}") proposal_info = None for pi in self.dao_info.proposals_list: @@ -1105,17 +1111,17 @@ async def generate_proposal_vote_spend( ] ) full_proposal_puzzle = curry_singleton(proposal_id, proposal_info.current_innerpuz) - list_of_coinspends = [make_spend(proposal_info.current_coin, full_proposal_puzzle, fullsol)] - unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) + list_of_coinspends = [ + make_spend(proposal_info.current_coin, full_proposal_puzzle, fullsol), + *dao_cat_spend.coin_spends, + ] + spend_bundle = SpendBundle(list_of_coinspends, G2Element()) if fee > 0: - chia_tx = await self.standard_wallet.create_tandem_xch_tx( + await self.standard_wallet.create_tandem_xch_tx( fee, tx_config, + action_scope, ) - assert chia_tx.spend_bundle is not None - spend_bundle = unsigned_spend_bundle.aggregate([unsigned_spend_bundle, dao_cat_spend, chia_tx.spend_bundle]) - else: - spend_bundle = unsigned_spend_bundle.aggregate([unsigned_spend_bundle, dao_cat_spend]) record = TransactionRecord( confirmed_at_height=uint32(0), @@ -1136,17 +1142,19 @@ async def generate_proposal_vote_spend( memos=[], valid_times=parse_timelock_info(extra_conditions), ) - return [record] + async with action_scope.use() as interface: + interface.side_effects.transactions.append(record) async def create_proposal_close_spend( self, proposal_id: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, genesis_id: Optional[bytes32] = None, fee: uint64 = uint64(0), self_destruct: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> None: self.log.info(f"Trying to create a proposal close spend with ID: {proposal_id}") proposal_info = None for pi in self.dao_info.proposals_list: @@ -1474,11 +1482,8 @@ async def create_proposal_close_spend( # pylint: disable-next=E0606 spend_bundle = SpendBundle([proposal_cs, timer_cs, treasury_cs], AugSchemeMPL.aggregate([])) if fee > 0: - chia_tx = await self.standard_wallet.create_tandem_xch_tx(fee, tx_config) - assert chia_tx.spend_bundle is not None - full_spend = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - else: - full_spend = SpendBundle.aggregate([spend_bundle]) + await self.standard_wallet.create_tandem_xch_tx(fee, tx_config, action_scope) + full_spend = spend_bundle if cat_spend_bundle is not None: full_spend = full_spend.aggregate([full_spend, cat_spend_bundle]) if delegated_puzzle_sb is not None: @@ -1503,7 +1508,8 @@ async def create_proposal_close_spend( memos=[], valid_times=parse_timelock_info(extra_conditions), ) - return record + async with action_scope.use() as interface: + interface.side_effects.transactions.append(record) async def fetch_proposed_puzzle_reveal(self, proposal_id: bytes32) -> Program: wallet_node: Any = self.wallet_state_manager.wallet_node @@ -1535,16 +1541,18 @@ async def _create_treasury_fund_transaction( funding_wallet: WalletProtocol[Any], amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: if funding_wallet.type() == WalletType.STANDARD_WALLET.value: p2_singleton_puzhash = get_p2_singleton_puzhash(self.dao_info.treasury_id, asset_id=None) wallet: Wallet = funding_wallet # type: ignore[assignment] - return await wallet.generate_signed_transaction( + await wallet.generate_signed_transaction( amount, p2_singleton_puzhash, tx_config, + action_scope, fee=fee, memos=[p2_singleton_puzhash], ) @@ -1553,14 +1561,14 @@ async def _create_treasury_fund_transaction( # generate_signed_transaction has a different type signature in Wallet and CATWallet # CATWallet uses a List of amounts and a List of puzhashes as the first two arguments p2_singleton_puzhash = get_p2_singleton_puzhash(self.dao_info.treasury_id) - tx_records: List[TransactionRecord] = await cat_wallet.generate_signed_transaction( + await cat_wallet.generate_signed_transaction( [amount], [p2_singleton_puzhash], tx_config, + action_scope, fee=fee, extra_conditions=extra_conditions, ) - return tx_records else: # pragma: no cover raise ValueError(f"Assets of type {funding_wallet.type()} are not currently supported.") @@ -1568,16 +1576,16 @@ async def create_add_funds_to_treasury_spend( self, amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), funding_wallet_id: uint32 = uint32(1), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> None: # set up the p2_singleton funding_wallet = self.wallet_state_manager.wallets[funding_wallet_id] - tx_record = await self._create_treasury_fund_transaction( - funding_wallet, amount, tx_config, fee, extra_conditions=extra_conditions + await self._create_treasury_fund_transaction( + funding_wallet, amount, tx_config, action_scope, fee, extra_conditions=extra_conditions ) - return tx_record[0] async def fetch_singleton_lineage_proof(self, coin: Coin) -> LineageProof: wallet_node: Any = self.wallet_state_manager.wallet_node @@ -1595,11 +1603,11 @@ async def fetch_singleton_lineage_proof(self, coin: Coin) -> LineageProof: async def free_coins_from_finished_proposals( self, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> None: dao_cat_wallet: DAOCATWallet = self.wallet_state_manager.wallets[self.dao_info.dao_cat_wallet_id] - full_spend = None spends = [] closed_list = [] finished_puz = None @@ -1618,7 +1626,7 @@ async def free_coins_from_finished_proposals( prop_sb = SpendBundle([cs], AugSchemeMPL.aggregate([])) spends.append(prop_sb) - sb = await dao_cat_wallet.remove_active_proposal(closed_list, tx_config=tx_config) + sb = await dao_cat_wallet.remove_active_proposal(closed_list, tx_config=tx_config, action_scope=action_scope) spends.append(sb) if not spends: # pragma: no cover @@ -1626,9 +1634,7 @@ async def free_coins_from_finished_proposals( full_spend = SpendBundle.aggregate(spends) if fee > 0: - chia_tx = await self.standard_wallet.create_tandem_xch_tx(fee, tx_config) - assert chia_tx.spend_bundle is not None - full_spend = full_spend.aggregate([full_spend, chia_tx.spend_bundle]) + await self.standard_wallet.create_tandem_xch_tx(fee, tx_config, action_scope) assert isinstance(finished_puz, Program) record = TransactionRecord( @@ -1650,7 +1656,8 @@ async def free_coins_from_finished_proposals( memos=[], valid_times=parse_timelock_info(extra_conditions), ) - return record + async with action_scope.use() as interface: + interface.side_effects.transactions.append(record) async def parse_proposal(self, proposal_id: bytes32) -> Dict[str, Any]: for prop_info in self.dao_info.proposals_list: @@ -1765,9 +1772,10 @@ async def enter_dao_cat_voting_mode( self, amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, ) -> List[TransactionRecord]: dao_cat_wallet: DAOCATWallet = self.wallet_state_manager.wallets[self.dao_info.dao_cat_wallet_id] - return await dao_cat_wallet.enter_dao_cat_voting_mode(amount, tx_config) + return await dao_cat_wallet.enter_dao_cat_voting_mode(amount, tx_config, action_scope) @staticmethod def get_next_interesting_coin(spend: CoinSpend) -> Optional[Coin]: # pragma: no cover diff --git a/chia/wallet/did_wallet/did_wallet.py b/chia/wallet/did_wallet/did_wallet.py index bcf418da7fde..2573625b535e 100644 --- a/chia/wallet/did_wallet/did_wallet.py +++ b/chia/wallet/did_wallet/did_wallet.py @@ -52,6 +52,7 @@ from chia.wallet.util.wallet_sync_utils import fetch_coin_spend, fetch_coin_spend_for_coin_state from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_protocol import WalletProtocol @@ -77,6 +78,7 @@ async def create_new_did_wallet( wallet: Wallet, amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, backups_ids: List[bytes32] = [], num_of_backup_ids_needed: uint64 = None, metadata: Dict[str, str] = {}, @@ -140,13 +142,11 @@ async def create_new_did_wallet( raise ValueError("Not enough balance") try: - txs = await self.generate_new_decentralised_id(amount, tx_config, fee, extra_conditions) + await self.generate_new_decentralised_id(amount, tx_config, action_scope, fee, extra_conditions) except Exception: await wallet_state_manager.user_store.delete_wallet(self.id()) raise - await self.wallet_state_manager.add_pending_transactions(txs) - await self.wallet_state_manager.add_new_wallet(self) return self @@ -557,8 +557,6 @@ def get_my_DID(self) -> str: return core.hex() async def set_name(self, new_name: str): - import dataclasses - new_info = dataclasses.replace(self.wallet_info, name=new_name) self.wallet_info = new_info await self.wallet_state_manager.user_store.update_wallet(self.wallet_info) @@ -567,8 +565,12 @@ def get_name(self): return self.wallet_info.name async def create_update_spend( - self, tx_config: TXConfig, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple() - ) -> List[TransactionRecord]: + self, + tx_config: TXConfig, + action_scope: WalletActionScope, + fee: uint64 = uint64(0), + extra_conditions: Tuple[Condition, ...] = tuple(), + ) -> None: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coin = await self.get_coin() @@ -632,16 +634,13 @@ async def create_update_spend( spend_bundle = SpendBundle(list_of_coinspends, G2Element()) if fee > 0: coin_name = coin.name() - chia_tx = await self.standard_wallet.create_tandem_xch_tx( + await self.standard_wallet.create_tandem_xch_tx( fee, tx_config, + action_scope, extra_conditions=(AssertCoinAnnouncement(asserted_id=coin_name, asserted_msg=coin_name),), ) - else: - chia_tx = None - if chia_tx is not None and chia_tx.spend_bundle is not None: - spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) + did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -662,11 +661,8 @@ async def create_update_spend( valid_times=parse_timelock_info(extra_conditions), ) - txs = [did_record] - if chia_tx is not None: - txs.append(chia_tx) - - return txs + async with action_scope.use() as interface: + interface.side_effects.transactions.append(did_record) async def transfer_did( self, @@ -674,8 +670,9 @@ async def transfer_did( fee: uint64, with_recovery: bool, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: """ Transfer the current DID to another owner :param new_puzhash: New owner's p2_puzzle @@ -731,16 +728,13 @@ async def transfer_did( spend_bundle = SpendBundle(list_of_coinspends, G2Element()) if fee > 0: coin_name = coin.name() - chia_tx = await self.standard_wallet.create_tandem_xch_tx( + await self.standard_wallet.create_tandem_xch_tx( fee, tx_config, + action_scope, extra_conditions=(AssertCoinAnnouncement(asserted_id=coin_name, asserted_msg=coin_name),), ) - else: - chia_tx = None - if chia_tx is not None and chia_tx.spend_bundle is not None: - spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) + did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -760,17 +754,17 @@ async def transfer_did( memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) - txs = [did_record] - if chia_tx is not None: - txs.append(chia_tx) - return txs + + async with action_scope.use() as interface: + interface.side_effects.transactions.append(did_record) # The message spend can tests\wallet\rpc\test_wallet_rpc.py send messages and also change your innerpuz async def create_message_spend( self, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> None: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coin = await self.get_coin() @@ -817,7 +811,7 @@ async def create_message_spend( ) list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - return TransactionRecord( + tx = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=p2_ph, @@ -836,9 +830,11 @@ async def create_message_spend( memos=list(compute_memos(unsigned_spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) + async with action_scope.use() as interface: + interface.side_effects.transactions.append(tx) # This is used to cash out, or update the id_list - async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig) -> List[TransactionRecord]: + async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig, action_scope: WalletActionScope) -> None: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coin = await self.get_coin() @@ -869,26 +865,28 @@ async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig) -> List list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - did_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=await self.standard_wallet.get_puzzle_hash(False), - amount=uint64(coin.amount), - fee_amount=uint64(0), - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=spend_bundle.additions(), - removals=spend_bundle.removals(), - wallet_id=self.wallet_info.id, - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=bytes32.secret(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=ConditionValidTimes(), - ) - return [did_record] + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=await self.standard_wallet.get_puzzle_hash(False), + amount=uint64(coin.amount), + fee_amount=uint64(0), + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle, + additions=spend_bundle.additions(), + removals=spend_bundle.removals(), + wallet_id=self.wallet_info.id, + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=bytes32.secret(), + memos=list(compute_memos(spend_bundle).items()), + valid_times=ConditionValidTimes(), + ) + ) # Pushes a SpendBundle to create a message coin on the blockchain # Returns a SpendBundle for the recoverer to spend the message coin @@ -898,8 +896,9 @@ async def create_attestment( newpuz: bytes32, pubkey: G1Element, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[TransactionRecord, SpendBundle, str]: + ) -> Tuple[SpendBundle, str]: """ Create an attestment TODO: @@ -971,9 +970,11 @@ async def create_attestment( memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) + async with action_scope.use() as interface: + interface.side_effects.transactions.append(did_record) attest_str: str = f"{self.get_my_DID()}:{bytes(message_spend_bundle).hex()}:{coin.parent_coin_info.hex()}:" attest_str += f"{self.did_info.current_inner.get_tree_hash().hex()}:{coin.amount}" - return did_record, message_spend_bundle, attest_str + return message_spend_bundle, attest_str async def get_info_for_recovery(self) -> Optional[Tuple[bytes32, bytes32, uint64]]: assert self.did_info.current_inner is not None @@ -1025,7 +1026,8 @@ async def recovery_spend( parent_innerpuzhash_amounts_for_recovery_ids: List[Tuple[bytes, bytes, int]], pubkey: G1Element, spend_bundle: SpendBundle, - ) -> List[TransactionRecord]: + action_scope: WalletActionScope, + ) -> None: assert self.did_info.origin_coin is not None # innersol is mode new_amount_or_p2_solution new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey recovery_list_reveal my_id) # noqa @@ -1064,25 +1066,28 @@ async def recovery_spend( spend_bundle = spend_bundle.aggregate([spend_bundle, SpendBundle(list_of_coinspends, G2Element())]) - did_record = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=await self.standard_wallet.get_puzzle_hash(False), - amount=uint64(coin.amount), - fee_amount=uint64(0), - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=spend_bundle.additions(), - removals=spend_bundle.removals(), - wallet_id=self.wallet_info.id, - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=bytes32.secret(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=ConditionValidTimes(), - ) + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=await self.standard_wallet.get_puzzle_hash(False), + amount=uint64(coin.amount), + fee_amount=uint64(0), + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle, + additions=spend_bundle.additions(), + removals=spend_bundle.removals(), + wallet_id=self.wallet_info.id, + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=bytes32.secret(), + memos=list(compute_memos(spend_bundle).items()), + valid_times=ConditionValidTimes(), + ) + ) new_did_info = DIDInfo( origin_coin=self.did_info.origin_coin, backup_ids=self.did_info.backup_ids, @@ -1096,7 +1101,6 @@ async def recovery_spend( metadata=self.did_info.metadata, ) await self.save_info(new_did_info) - return [did_record] async def get_new_p2_inner_hash(self) -> bytes32: puzzle = await self.get_new_p2_inner_puzzle() @@ -1203,9 +1207,10 @@ async def generate_new_decentralised_id( self, amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: """ This must be called under the wallet state manager lock """ @@ -1223,10 +1228,11 @@ async def generate_new_decentralised_id( announcement_message = Program.to([did_puzzle_hash, amount, bytes(0x80)]).get_tree_hash() - [tx_record] = await self.standard_wallet.generate_signed_transaction( + await self.standard_wallet.generate_signed_transaction( amount=amount, puzzle_hash=genesis_launcher_puz.get_tree_hash(), tx_config=tx_config, + action_scope=action_scope, fee=fee, coins=coins, primaries=None, @@ -1270,8 +1276,7 @@ async def generate_new_decentralised_id( ) await self.save_info(did_info) eve_spend = await self.generate_eve_spend(eve_coin, did_full_puz, did_inner) - assert tx_record.spend_bundle is not None - full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend, launcher_sb]) + full_spend = SpendBundle.aggregate([eve_spend, launcher_sb]) assert self.did_info.origin_coin is not None assert self.did_info.current_inner is not None @@ -1294,8 +1299,8 @@ async def generate_new_decentralised_id( memos=[], valid_times=ConditionValidTimes(), ) - regular_record = dataclasses.replace(tx_record, spend_bundle=None) - return [did_record, regular_record] + async with action_scope.use() as interface: + interface.side_effects.transactions.append(did_record) async def generate_eve_spend( self, diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 312faaa6de71..fb1fee9c4917 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -55,6 +55,7 @@ from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig from chia.wallet.util.wallet_types import WalletType from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_nft_store import WalletNftStore @@ -300,8 +301,9 @@ async def get_did_approval_info( self, nft_ids: List[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, did_id: Optional[bytes32] = None, - ) -> Tuple[bytes32, SpendBundle]: + ) -> bytes32: """Get DID spend with announcement created we need to transfer NFT with did with current inner hash of DID We also store `did_id` and then iterate to find the did wallet as we'd otherwise have to subscribe to @@ -309,35 +311,34 @@ async def get_did_approval_info( """ if did_id is None: did_id = self.did_id + did_inner_hash: bytes32 for _, wallet in self.wallet_state_manager.wallets.items(): self.log.debug("Checking wallet type %s", wallet.type()) if wallet.type() == WalletType.DECENTRALIZED_ID: self.log.debug("Found a DID wallet, checking did: %r == %r", wallet.get_my_DID(), did_id) if bytes32.fromhex(wallet.get_my_DID()) == did_id: self.log.debug("Creating announcement from DID for nft_ids: %s", nft_ids) - did_bundle = ( - await wallet.create_message_spend( - tx_config, extra_conditions=(CreatePuzzleAnnouncement(id) for id in nft_ids) - ) - ).spend_bundle - self.log.debug("Sending DID announcement from puzzle: %s", did_bundle.removals()) + await wallet.create_message_spend( + tx_config, action_scope, extra_conditions=(CreatePuzzleAnnouncement(id) for id in nft_ids) + ) did_inner_hash = wallet.did_info.current_inner.get_tree_hash() break else: raise ValueError(f"Missing DID Wallet for did_id: {did_id}") - return did_inner_hash, did_bundle + return did_inner_hash async def generate_new_nft( self, metadata: Program, tx_config: TXConfig, + action_scope: WalletActionScope, target_puzzle_hash: Optional[bytes32] = None, royalty_puzzle_hash: Optional[bytes32] = None, percentage: uint16 = uint16(0), did_id: Optional[bytes] = None, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> bytes32: """ This must be called under the wallet state manager lock """ @@ -388,10 +389,11 @@ async def generate_new_nft( "Creating transaction for launcher: %s and other coins: %s (%s)", origin, coins, announcement_message ) # store the launcher transaction in the wallet state - [tx_record] = await self.standard_wallet.generate_signed_transaction( + await self.standard_wallet.generate_signed_transaction( uint64(amount), nft_puzzles.LAUNCHER_PUZZLE_HASH, tx_config, + action_scope, fee, coins, None, @@ -409,17 +411,14 @@ async def generate_new_nft( eve_coin = Coin(launcher_coin.name(), eve_fullpuz_hash, uint64(amount)) - if tx_record.spend_bundle is None: - raise ValueError("Couldn't produce a launcher spend") # pragma: no cover - - bundles_to_agg = [tx_record.spend_bundle, launcher_sb] + async with action_scope.use() as interface: + interface.side_effects.extra_spends.append(launcher_sb) # Create inner solution for eve spend did_inner_hash = b"" if did_id is not None: if did_id != b"": - did_inner_hash, did_bundle = await self.get_did_approval_info([launcher_coin.name()], tx_config) - bundles_to_agg.append(did_bundle) + did_inner_hash = await self.get_did_approval_info([launcher_coin.name()], tx_config, action_scope) nft_coin = NFTCoinInfo( nft_id=launcher_coin.name(), coin=eve_coin, @@ -429,17 +428,18 @@ async def generate_new_nft( minter_did=bytes32(did_id) if did_id is not None and did_id != b"" else None, ) # Don't set fee, it is covered in the tx_record - txs = await self.generate_signed_transaction( + await self.generate_signed_transaction( [uint64(eve_coin.amount)], [target_puzzle_hash], tx_config, + action_scope, nft_coin=nft_coin, new_owner=did_id, new_did_inner_hash=did_inner_hash, memos=[[target_puzzle_hash]], ) - txs.append(dataclasses.replace(tx_record, spend_bundle=SpendBundle.aggregate(bundles_to_agg))) - return txs + + return launcher_coin.name() async def update_metadata( self, @@ -447,9 +447,10 @@ async def update_metadata( key: str, uri: str, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: uncurried_nft = UncurriedNFT.uncurry(*nft_coin_info.full_puzzle.uncurry()) assert uncurried_nft is not None puzzle_hash = uncurried_nft.p2_puzzle.get_tree_hash() @@ -459,10 +460,11 @@ async def update_metadata( nft_coin_info.coin.name(), uncurried_nft.metadata, ) - txs = await self.generate_signed_transaction( + await self.generate_signed_transaction( [uint64(nft_coin_info.coin.amount)], [puzzle_hash], tx_config, + action_scope, fee, {nft_coin_info.coin}, metadata_update=(key, uri), @@ -470,7 +472,6 @@ async def update_metadata( ) await self.update_coin_status(nft_coin_info.coin.name(), True) self.wallet_state_manager.state_changed("nft_coin_updated", self.wallet_info.id) - return txs async def get_current_nfts(self, start_index: int = 0, count: int = 50) -> List[NFTCoinInfo]: return await self.nft_store.get_nft_list(wallet_id=self.id(), start_index=start_index, count=count) @@ -574,12 +575,13 @@ async def generate_signed_transaction( amounts: List[uint64], puzzle_hashes: List[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Optional[Set[Coin]] = None, memos: Optional[List[List[bytes]]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), **kwargs: Unpack[GSTOptionalArgs], - ) -> List[TransactionRecord]: + ) -> None: nft_coin: Optional[NFTCoinInfo] = kwargs.get("nft_coin", None) new_owner: Optional[bytes] = kwargs.get("new_owner", None) new_did_inner_hash: Optional[bytes] = kwargs.get("new_did_inner_hash", None) @@ -599,9 +601,10 @@ async def generate_signed_transaction( payments.append(Payment(puzhash, amount, memos_with_hint)) payment_sum = sum(p.amount for p in payments) - unsigned_spend_bundle, chia_tx = await self.generate_unsigned_spendbundle( + unsigned_spend_bundle = await self.generate_unsigned_spendbundle( payments, tx_config, + action_scope, fee, coins=coins, nft_coin=nft_coin, @@ -612,17 +615,15 @@ async def generate_signed_transaction( extra_conditions=extra_conditions, ) spend_bundle = SpendBundle.aggregate([unsigned_spend_bundle] + additional_bundles) - if chia_tx is not None and chia_tx.spend_bundle is not None: - spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) - other_tx_removals: Set[Coin] = {removal for removal in chia_tx.removals} - other_tx_additions: Set[Coin] = {addition for addition in chia_tx.additions} - else: - other_tx_removals = set() - other_tx_additions = set() - tx_list = [ - TransactionRecord( + async with action_scope.use() as interface: + other_tx_removals: Set[Coin] = { + removal for tx in interface.side_effects.transactions for removal in tx.removals + } + other_tx_additions: Set[Coin] = { + addition for tx in interface.side_effects.transactions for addition in tx.additions + } + tx = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzzle_hashes[0], @@ -640,18 +641,15 @@ async def generate_signed_transaction( name=spend_bundle.name(), memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), - ), - ] - - if chia_tx is not None: - tx_list.append(chia_tx) + ) - return tx_list + interface.side_effects.transactions.append(tx) async def generate_unsigned_spendbundle( self, payments: List[Payment], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Optional[Set[Coin]] = None, new_owner: Optional[bytes] = None, @@ -660,7 +658,7 @@ async def generate_unsigned_spendbundle( metadata_update: Optional[Tuple[str, str]] = None, nft_coin: Optional[NFTCoinInfo] = None, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[SpendBundle, Optional[TransactionRecord]]: + ) -> SpendBundle: if nft_coin is None: if coins is None or not len(coins) == 1: # Make sure the user is specifying which specific NFT coin to use @@ -672,13 +670,12 @@ async def generate_unsigned_spendbundle( coin_name = nft_coin.coin.name() if fee > 0: - chia_tx = await self.standard_wallet.create_tandem_xch_tx( + await self.standard_wallet.create_tandem_xch_tx( fee, tx_config, + action_scope, extra_conditions=(AssertCoinAnnouncement(asserted_id=coin_name, asserted_msg=coin_name),), ) - else: - chia_tx = None unft = UncurriedNFT.uncurry(*nft_coin.full_puzzle.uncurry()) assert unft is not None @@ -730,7 +727,7 @@ async def generate_unsigned_spendbundle( nft_spend_bundle = SpendBundle([coin_spend], G2Element()) - return nft_spend_bundle, chia_tx + return nft_spend_bundle @staticmethod def royalty_calculation( @@ -758,9 +755,10 @@ async def make_nft1_offer( offer_dict: Dict[Optional[bytes32], int], driver_dict: Dict[bytes32, PuzzleInfo], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64, extra_conditions: Tuple[Condition, ...], - ) -> Tuple[Offer, List[TransactionRecord]]: + ) -> Offer: # First, let's take note of all the royalty enabled NFTs royalty_nft_asset_dict: Dict[bytes32, int] = {} for asset, amount in offer_dict.items(): @@ -904,45 +902,48 @@ async def make_nft1_offer( wallet = await wallet_state_manager.get_wallet_for_asset_id(asset.hex()) # First, sending all the coins to the OFFER_MOD - if wallet.type() == WalletType.STANDARD_WALLET: - payments = royalty_payments[asset] if asset in royalty_payments else [] - payment_sum = sum(p.amount for _, p in payments) - [tx] = await wallet.generate_signed_transaction( - abs(amount), - OFFER_MOD_HASH, - tx_config, - primaries=[Payment(OFFER_MOD_HASH, uint64(payment_sum))] if payment_sum > 0 else [], - fee=fee, - coins=offered_coins_by_asset[asset], - extra_conditions=(*extra_conditions, *announcements_to_assert), - ) - txs = [tx] - elif asset not in fungible_asset_dict: - assert asset is not None - txs = await wallet.generate_signed_transaction( - [abs(amount)], - [OFFER_MOD_HASH], - tx_config, - fee=fee_left_to_pay, - coins=offered_coins_by_asset[asset], - trade_prices_list=[ - list(price) - for price in trade_prices - if math.floor(price[0] * (offered_royalty_percentages[asset] / 10000)) != 0 - ], - extra_conditions=(*extra_conditions, *announcements_to_assert), - ) - else: - payments = royalty_payments[asset] if asset in royalty_payments else [] - txs = await wallet.generate_signed_transaction( - [abs(amount), sum(p.amount for _, p in payments)], - [OFFER_MOD_HASH, OFFER_MOD_HASH], - tx_config, - fee=fee_left_to_pay, - coins=offered_coins_by_asset[asset], - extra_conditions=(*extra_conditions, *announcements_to_assert), - ) - all_transactions.extend(txs) + async with wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + if wallet.type() == WalletType.STANDARD_WALLET: + payments = royalty_payments[asset] if asset in royalty_payments else [] + payment_sum = sum(p.amount for _, p in payments) + await wallet.generate_signed_transaction( + abs(amount), + OFFER_MOD_HASH, + tx_config, + inner_action_scope, + primaries=[Payment(OFFER_MOD_HASH, uint64(payment_sum))] if payment_sum > 0 else [], + fee=fee, + coins=offered_coins_by_asset[asset], + extra_conditions=(*extra_conditions, *announcements_to_assert), + ) + elif asset not in fungible_asset_dict: + assert asset is not None + await wallet.generate_signed_transaction( + [abs(amount)], + [OFFER_MOD_HASH], + tx_config, + inner_action_scope, + fee=fee_left_to_pay, + coins=offered_coins_by_asset[asset], + trade_prices_list=[ + list(price) + for price in trade_prices + if math.floor(price[0] * (offered_royalty_percentages[asset] / 10000)) != 0 + ], + extra_conditions=(*extra_conditions, *announcements_to_assert), + ) + else: + payments = royalty_payments[asset] if asset in royalty_payments else [] + await wallet.generate_signed_transaction( + [abs(amount), sum(p.amount for _, p in payments)], + [OFFER_MOD_HASH, OFFER_MOD_HASH], + tx_config, + inner_action_scope, + fee=fee_left_to_pay, + coins=offered_coins_by_asset[asset], + extra_conditions=(*extra_conditions, *announcements_to_assert), + ) + all_transactions.extend(inner_action_scope.side_effects.transactions) fee_left_to_pay = uint64(0) extra_conditions = tuple() @@ -996,7 +997,7 @@ async def make_nft1_offer( offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD) royalty_ph = offer_puzzle.get_tree_hash() if royalty_coin is None: - for tx in txs: + for tx in inner_action_scope.side_effects.transactions: if tx.spend_bundle is not None: for coin in tx.spend_bundle.additions(): royalty_payment_amount: int = sum(p.amount for _, p in payments) @@ -1053,29 +1054,36 @@ async def make_nft1_offer( txs_bundle = SpendBundle.aggregate([tx.spend_bundle for tx in all_transactions if tx.spend_bundle is not None]) aggregate_bundle = SpendBundle.aggregate([txs_bundle, *additional_bundles]) offer = Offer(notarized_payments, aggregate_bundle, driver_dict) - return offer, all_transactions + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(all_transactions) + + return offer async def set_bulk_nft_did( self, nft_list: List[NFTCoinInfo], did_id: bytes, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), announcement_ids: List[bytes32] = [], extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: self.log.debug("Setting NFT DID with parameters: nft=%s did=%s", nft_list, did_id) - did_inner_hash = b"" nft_ids = [] - nft_tx_record = [] - spend_bundles = [] first = True for nft_coin_info in nft_list: nft_ids.append(nft_coin_info.nft_id) - if did_id != b"": - did_inner_hash, did_bundle = await self.get_did_approval_info(announcement_ids, tx_config, bytes32(did_id)) - if len(announcement_ids) > 0: - spend_bundles.append(did_bundle) + if did_id != b"" and len(announcement_ids) > 0: + await self.get_did_approval_info(announcement_ids, tx_config, action_scope, bytes32(did_id)) + + for _, wallet in self.wallet_state_manager.wallets.items(): + if wallet.type() == WalletType.DECENTRALIZED_ID: + if bytes32.fromhex(wallet.get_my_DID()) == did_id: + did_inner_hash = wallet.did_info.current_inner.get_tree_hash() + break + else: + raise ValueError(f"No DID wallet with id: {did_id.hex()}") for nft_coin_info in nft_list: unft = UncurriedNFT.uncurry(*nft_coin_info.full_puzzle.uncurry()) @@ -1084,113 +1092,86 @@ async def set_bulk_nft_did( if not first: fee = uint64(0) extra_conditions = tuple() - nft_tx_record.extend( - await self.generate_signed_transaction( - [uint64(nft_coin_info.coin.amount)], - puzzle_hashes_to_sign, - tx_config, - fee, - {nft_coin_info.coin}, - new_owner=did_id, - new_did_inner_hash=did_inner_hash, - extra_conditions=extra_conditions, - ) + await self.generate_signed_transaction( + [uint64(nft_coin_info.coin.amount)], + puzzle_hashes_to_sign, + tx_config, + action_scope, + fee, + {nft_coin_info.coin}, + new_owner=did_id, + new_did_inner_hash=did_inner_hash, + extra_conditions=extra_conditions, ) first = False - refined_tx_list: List[TransactionRecord] = [] - for tx in nft_tx_record: - if tx.spend_bundle is not None: - spend_bundles.append(tx.spend_bundle) - refined_tx_list.append(dataclasses.replace(tx, spend_bundle=None)) - - if len(spend_bundles) > 0: - spend_bundle = SpendBundle.aggregate(spend_bundles) - # Add all spend bundles to the first tx - refined_tx_list[0] = dataclasses.replace(refined_tx_list[0], spend_bundle=spend_bundle) - return refined_tx_list - async def bulk_transfer_nft( self, nft_list: List[NFTCoinInfo], puzzle_hash: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: self.log.debug("Transfer NFTs %s to %s", nft_list, puzzle_hash.hex()) - nft_tx_record = [] - spend_bundles = [] first = True for nft_coin_info in nft_list: if not first: fee = uint64(0) extra_conditions = tuple() - nft_tx_record.extend( - await self.generate_signed_transaction( - [uint64(nft_coin_info.coin.amount)], - [puzzle_hash], - tx_config, - coins={nft_coin_info.coin}, - fee=fee, - new_owner=b"", - new_did_inner_hash=b"", - extra_conditions=extra_conditions, - ) + await self.generate_signed_transaction( + [uint64(nft_coin_info.coin.amount)], + [puzzle_hash], + tx_config, + action_scope, + coins={nft_coin_info.coin}, + fee=fee, + new_owner=b"", + new_did_inner_hash=b"", + extra_conditions=extra_conditions, ) first = False - refined_tx_list: List[TransactionRecord] = [] - for tx in nft_tx_record: - if tx.spend_bundle is not None: - spend_bundles.append(tx.spend_bundle) - refined_tx_list.append(dataclasses.replace(tx, spend_bundle=None)) - - if len(spend_bundles) > 0: - spend_bundle = SpendBundle.aggregate(spend_bundles) - # Add all spend bundles to the first tx - refined_tx_list[0] = dataclasses.replace(refined_tx_list[0], spend_bundle=spend_bundle) - return refined_tx_list async def set_nft_did( self, nft_coin_info: NFTCoinInfo, did_id: bytes, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: self.log.debug("Setting NFT DID with parameters: nft=%s did=%s", nft_coin_info, did_id) unft = UncurriedNFT.uncurry(*nft_coin_info.full_puzzle.uncurry()) assert unft is not None nft_id = unft.singleton_launcher_id puzzle_hashes_to_sign = [unft.p2_puzzle.get_tree_hash()] did_inner_hash = b"" - additional_bundles = [] if did_id != b"": - did_inner_hash, did_bundle = await self.get_did_approval_info([nft_id], tx_config, bytes32(did_id)) - additional_bundles.append(did_bundle) + did_inner_hash = await self.get_did_approval_info([nft_id], tx_config, action_scope, bytes32(did_id)) - nft_tx_record = await self.generate_signed_transaction( + await self.generate_signed_transaction( [uint64(nft_coin_info.coin.amount)], puzzle_hashes_to_sign, tx_config, + action_scope, fee, {nft_coin_info.coin}, new_owner=did_id, new_did_inner_hash=did_inner_hash, - additional_bundles=additional_bundles, extra_conditions=extra_conditions, ) await self.update_coin_status(nft_coin_info.coin.name(), True) self.wallet_state_manager.state_changed("nft_coin_did_set", self.wallet_info.id) - return nft_tx_record async def mint_from_did( self, metadata_list: List[Dict[str, Any]], tx_config: TXConfig, + action_scope: WalletActionScope, target_list: Optional[List[bytes32]] = [], mint_number_start: Optional[int] = 1, mint_total: Optional[int] = None, @@ -1202,7 +1183,7 @@ async def mint_from_did( did_lineage_parent: Optional[bytes32] = None, fee: Optional[uint64] = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: """ Minting NFTs from the DID linked wallet, also used for bulk minting NFTs. - The DID is spent along with an intermediate launcher puzzle which @@ -1289,7 +1270,6 @@ async def mint_from_did( intermediate_coin_spends = [] launcher_spends = [] launcher_ids = [] - eve_spends: List[SpendBundle] = [] p2_inner_puzzle = await self.standard_wallet.get_new_puzzle() p2_inner_ph = p2_inner_puzzle.get_tree_hash() @@ -1368,19 +1348,25 @@ async def mint_from_did( target_ph = target_list[mint_number - mint_number_start] else: target_ph = p2_inner_ph - eve_txs = await self.generate_signed_transaction( - [uint64(eve_coin.amount)], - [target_ph], - tx_config, - nft_coin=nft_coin, - new_owner=b"", - new_did_inner_hash=b"", - additional_bundles=[], - memos=[[target_ph]], + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await self.generate_signed_transaction( + [uint64(eve_coin.amount)], + [target_ph], + tx_config, + inner_action_scope, + nft_coin=nft_coin, + new_owner=b"", + new_did_inner_hash=b"", + additional_bundles=[], + memos=[[target_ph]], + ) + + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions) + + eve_sb = next( + tx.spend_bundle for tx in inner_action_scope.side_effects.transactions if tx.spend_bundle is not None ) - eve_sb = eve_txs[0].spend_bundle - assert eve_sb is not None - eve_spends.append(eve_sb) # Extract Puzzle Announcement from eve spend eve_sol = eve_sb.coin_spends[0].solution.to_program() conds = eve_fullpuz.run(eve_sol) @@ -1468,19 +1454,29 @@ async def mint_from_did( did_spend = make_spend(did_coin, did_full_puzzle, did_full_sol) # Collect up all the coin spends and sign them - list_of_coinspends = [did_spend] + intermediate_coin_spends + launcher_spends + list_of_coinspends = [did_spend] + intermediate_coin_spends + launcher_spends + xch_spend.coin_spends unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) # Aggregate everything into a single spend bundle - total_spend = SpendBundle.aggregate([unsigned_spend_bundle, xch_spend, *eve_spends]) - - tx_record: TransactionRecord = dataclasses.replace(eve_txs[0], spend_bundle=total_spend) - return [tx_record] + async with action_scope.use() as interface: + # This should not be looked to for best practice. I think many of the spends generated above could call + # wallet methods that generate transactions and prevent most of the need for this. Refactoring this function + # is out of scope so for now we're using this hack. + if interface.side_effects.transactions[0].spend_bundle is None: + new_spend = unsigned_spend_bundle + else: + new_spend = SpendBundle.aggregate( + [interface.side_effects.transactions[0].spend_bundle, unsigned_spend_bundle] + ) + interface.side_effects.transactions[0] = dataclasses.replace( + interface.side_effects.transactions[0], spend_bundle=new_spend, name=new_spend.name() + ) async def mint_from_xch( self, metadata_list: List[Dict[str, Any]], tx_config: TXConfig, + action_scope: WalletActionScope, target_list: Optional[List[bytes32]] = [], mint_number_start: Optional[int] = 1, mint_total: Optional[int] = None, @@ -1488,7 +1484,7 @@ async def mint_from_xch( xch_change_ph: Optional[bytes32] = None, fee: Optional[uint64] = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: """ Minting NFTs from a single XCH spend using intermediate launcher puzzle :param metadata_list: A list of dicts containing the metadata for each NFT to be minted @@ -1536,7 +1532,6 @@ async def mint_from_xch( intermediate_coin_spends = [] launcher_spends = [] launcher_ids = [] - eve_spends: List[SpendBundle] = [] p2_inner_puzzle = await self.standard_wallet.get_new_puzzle() p2_inner_ph = p2_inner_puzzle.get_tree_hash() @@ -1610,19 +1605,25 @@ async def mint_from_xch( target_ph = target_list[mint_number - mint_number_start] else: target_ph = p2_inner_ph - eve_txs = await self.generate_signed_transaction( - [uint64(eve_coin.amount)], - [target_ph], - tx_config, - nft_coin=nft_coin, - new_owner=b"", - new_did_inner_hash=b"", - additional_bundles=[], - memos=[[target_ph]], + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await self.generate_signed_transaction( + [uint64(eve_coin.amount)], + [target_ph], + tx_config, + inner_action_scope, + nft_coin=nft_coin, + new_owner=b"", + new_did_inner_hash=b"", + additional_bundles=[], + memos=[[target_ph]], + ) + + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions) + + eve_sb = next( + tx.spend_bundle for tx in inner_action_scope.side_effects.transactions if tx.spend_bundle is not None ) - eve_sb = eve_txs[0].spend_bundle - assert eve_sb is not None - eve_spends.append(eve_sb) # Extract Puzzle Announcement from eve spend eve_sol = eve_sb.coin_spends[0].solution.to_program() conds = eve_fullpuz.run(eve_sol) @@ -1662,16 +1663,25 @@ async def mint_from_xch( else: solution = self.standard_wallet.make_solution(primaries=[], conditions=(primary_announcement,)) xch_spends.append(make_spend(xch_coin, puzzle, solution)) - xch_spend = SpendBundle(xch_spends, G2Element()) - # Collect up all the coin spends - list_of_coinspends = intermediate_coin_spends + launcher_spends + # Collect up all the coin spends and sign them + list_of_coinspends = intermediate_coin_spends + launcher_spends + xch_spends unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) # Aggregate everything into a single spend bundle - total_spend = SpendBundle.aggregate([unsigned_spend_bundle, xch_spend, *eve_spends]) - tx_record: TransactionRecord = dataclasses.replace(eve_txs[0], spend_bundle=total_spend) - return [tx_record] + async with action_scope.use() as interface: + # This should not be looked to for best practice. I think many of the spends generated above could call + # wallet methods that generate transactions and prevent most of the need for this. Refactoring this function + # is out of scope so for now we're using this hack. + if interface.side_effects.transactions[0].spend_bundle is None: + new_spend = unsigned_spend_bundle + else: + new_spend = SpendBundle.aggregate( + [interface.side_effects.transactions[0].spend_bundle, unsigned_spend_bundle] + ) + interface.side_effects.transactions[0] = dataclasses.replace( + interface.side_effects.transactions[0], spend_bundle=new_spend, name=new_spend.name() + ) async def select_coins( self, diff --git a/chia/wallet/notification_manager.py b/chia/wallet/notification_manager.py index 2723f108dc79..bfff091183b2 100644 --- a/chia/wallet/notification_manager.py +++ b/chia/wallet/notification_manager.py @@ -1,6 +1,5 @@ from __future__ import annotations -import dataclasses import logging from typing import Any, Dict, List, Optional, Set, Tuple @@ -16,11 +15,11 @@ from chia.util.ints import uint32, uint64 from chia.wallet.conditions import AssertCoinAnnouncement, Condition from chia.wallet.notification_store import Notification, NotificationStore -from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.compute_memos import compute_memos_for_spend from chia.wallet.util.notifications import construct_notification from chia.wallet.util.tx_config import TXConfig from chia.wallet.util.wallet_types import WalletType +from chia.wallet.wallet_action_scope import WalletActionScope class NotificationManager: @@ -88,9 +87,10 @@ async def send_new_notification( msg: bytes, amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> None: coins: Set[Coin] = await self.wallet_state_manager.main_wallet.select_coins( uint64(amount + fee), tx_config.coin_selection_config ) @@ -104,10 +104,11 @@ async def send_new_notification( Program.to(None), ) extra_spend_bundle = SpendBundle([notification_spend], G2Element()) - [chia_tx] = await self.wallet_state_manager.main_wallet.generate_signed_transaction( + await self.wallet_state_manager.main_wallet.generate_signed_transaction( amount, notification_hash, tx_config, + action_scope, fee, coins=coins, origin_id=origin_coin, @@ -117,7 +118,5 @@ async def send_new_notification( AssertCoinAnnouncement(asserted_id=notification_coin.name(), asserted_msg=b""), ), ) - full_tx: TransactionRecord = dataclasses.replace( - chia_tx, spend_bundle=SpendBundle.aggregate([chia_tx.spend_bundle, extra_spend_bundle]) - ) - return full_tx + async with action_scope.use() as interface: + interface.side_effects.extra_spends.append(extra_spend_bundle) diff --git a/chia/wallet/puzzles/tails.py b/chia/wallet/puzzles/tails.py index b897f8495b29..a8519ad87719 100644 --- a/chia/wallet/puzzles/tails.py +++ b/chia/wallet/puzzles/tails.py @@ -23,6 +23,7 @@ from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.tx_config import TXConfig +from chia.wallet.wallet_action_scope import WalletActionScope GENESIS_BY_ID_MOD = load_clvm_maybe_recompile( "genesis_by_coin_id.clsp", package_or_requirement="chia.wallet.cat_wallet.puzzles" @@ -56,8 +57,8 @@ def solve(args: List[Program], solution_dict: Dict) -> Program: @classmethod async def generate_issuance_bundle( - cls, wallet, cat_tail_info: Dict, amount: uint64, tx_config: TXConfig - ) -> Tuple[TransactionRecord, SpendBundle]: + cls, wallet, cat_tail_info: Dict, amount: uint64, tx_config: TXConfig, action_scope: WalletActionScope + ) -> SpendBundle: raise NotImplementedError("Need to implement 'generate_issuance_bundle' on limitations programs") @@ -85,8 +86,14 @@ def solve(args: List[Program], solution_dict: Dict) -> Program: @classmethod async def generate_issuance_bundle( - cls, wallet, _: Dict, amount: uint64, tx_config: TXConfig, fee: uint64 = uint64(0) - ) -> Tuple[TransactionRecord, SpendBundle]: + cls, + wallet, + _: Dict, + amount: uint64, + tx_config: TXConfig, + action_scope: WalletActionScope, + fee: uint64 = uint64(0), + ) -> SpendBundle: coins = await wallet.standard_wallet.select_coins(amount + fee, tx_config.coin_selection_config) origin = coins.copy().pop() @@ -102,10 +109,13 @@ async def generate_issuance_bundle( minted_cat_puzzle_hash: bytes32 = construct_cat_puzzle(CAT_MOD, tail.get_tree_hash(), cat_inner).get_tree_hash() - [tx_record] = await wallet.standard_wallet.generate_signed_transaction( - amount, minted_cat_puzzle_hash, tx_config, fee, coins, origin_id=origin_id - ) - assert tx_record.spend_bundle is not None + async with wallet.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await wallet.standard_wallet.generate_signed_transaction( + amount, minted_cat_puzzle_hash, tx_config, inner_action_scope, fee, coins, origin_id=origin_id + ) + + async with action_scope.use() as interface: + interface.side_effects.transactions = inner_action_scope.side_effects.transactions inner_tree_hash = cat_inner.get_tree_hash() inner_solution = wallet.standard_wallet.add_condition_to_solution( @@ -116,7 +126,12 @@ async def generate_issuance_bundle( CAT_MOD, [ SpendableCAT( - list(filter(lambda a: a.amount == amount, tx_record.additions))[0], + list( + filter( + lambda a: a.amount == amount, + [add for tx in inner_action_scope.side_effects.transactions for add in tx.additions], + ) + )[0], tail.get_tree_hash(), cat_inner, inner_solution, @@ -128,7 +143,7 @@ async def generate_issuance_bundle( if wallet.cat_info.my_tail is None: await wallet.save_info(CATInfo(tail.get_tree_hash(), tail)) - return tx_record, SpendBundle.aggregate([tx_record.spend_bundle, eve_spend]) + return eve_spend class GenesisByPuzhash(LimitationsProgram): @@ -234,8 +249,14 @@ def solve(args: List[Program], solution_dict: Dict) -> Program: # pragma: no co @classmethod async def generate_issuance_bundle( - cls, wallet, tail_info: Dict, amount: uint64, tx_config: TXConfig, fee: uint64 = uint64(0) - ) -> Tuple[TransactionRecord, SpendBundle]: + cls, + wallet, + tail_info: Dict, + amount: uint64, + tx_config: TXConfig, + action_scope: WalletActionScope, + fee: uint64 = uint64(0), + ) -> SpendBundle: if "coins" in tail_info: coins: List[Coin] = tail_info["coins"] origin_id = coins.copy().pop().name() @@ -262,10 +283,20 @@ async def generate_issuance_bundle( minted_cat_puzzle_hash: bytes32 = construct_cat_puzzle(CAT_MOD, tail.get_tree_hash(), cat_inner).get_tree_hash() - tx_records: List[TransactionRecord] = await wallet.standard_wallet.generate_signed_transaction( - amount, minted_cat_puzzle_hash, tx_config, fee, coins=set(coins), origin_id=origin_id - ) - tx_record: TransactionRecord = tx_records[0] + async with wallet.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await wallet.standard_wallet.generate_signed_transaction( + amount, + minted_cat_puzzle_hash, + tx_config, + inner_action_scope, + fee, + coins=set(coins), + origin_id=origin_id, + ) + + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions) + tx_record: TransactionRecord = inner_action_scope.side_effects.transactions[0] assert tx_record.spend_bundle is not None payment = Payment(cat_inner.get_tree_hash(), amount) inner_solution = wallet.standard_wallet.add_condition_to_solution( @@ -290,7 +321,7 @@ async def generate_issuance_bundle( if wallet.cat_info.my_tail is None: await wallet.save_info(CATInfo(tail.get_tree_hash(), tail)) - return tx_record, SpendBundle.aggregate([tx_record.spend_bundle, eve_spend]) + return eve_spend # This should probably be much more elegant than just a dictionary with strings as identifiers diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 28928a71cd2d..4b7a0ca62439 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -6,7 +6,7 @@ from collections import deque from typing import TYPE_CHECKING, Any, Deque, Dict, List, Optional, Set, Tuple, Union -from typing_extensions import Literal, Never +from typing_extensions import Literal from chia.data_layer.data_layer_wallet import DataLayerWallet from chia.protocols.wallet_protocol import CoinState @@ -47,6 +47,7 @@ from chia.wallet.vc_wallet.cr_cat_drivers import ProofsChecker, construct_pending_approval_state from chia.wallet.vc_wallet.vc_wallet import VCWallet from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_protocol import WalletProtocol @@ -245,11 +246,12 @@ async def cancel_pending_offers( self, trades: List[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), secure: bool = True, # Cancel with a transaction on chain trade_cache: Dict[bytes32, TradeRecord] = {}, # Optional pre-fetched trade records for optimization extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: """This will create a transaction that includes coins that were offered""" # Need to do some pre-figuring of announcements that will be need to be made @@ -282,7 +284,6 @@ async def cancel_pending_offers( announcement_assertions.rotate(1) all_txs: List[TransactionRecord] = [] - bundles: List[SpendBundle] = [] fee_to_pay: uint64 = fee for trade, cancellation_coins in zip(trade_records, all_cancellation_coins): self.log.info(f"Secure-Cancel pending offer with id trade_id {trade.trade_id.hex()}") @@ -325,72 +326,86 @@ async def cancel_pending_offers( selected_coins.add(coin) else: selected_coins = {coin} - [tx] = await wallet.generate_signed_transaction( - uint64(sum(c.amount for c in selected_coins) - fee_to_pay), - new_ph, - tx_config.override( - excluded_coin_ids=[], - ), - origin_id=coin.name(), - fee=fee_to_pay, - coins=selected_coins, - extra_conditions=(*extra_conditions, *announcement_conditions), - ) - if tx is not None and tx.spend_bundle is not None: - bundles.append(tx.spend_bundle) - cancellation_additions.extend(tx.spend_bundle.additions()) - all_txs.append(dataclasses.replace(tx, spend_bundle=None)) + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await wallet.generate_signed_transaction( + uint64(sum(c.amount for c in selected_coins) - fee_to_pay), + new_ph, + tx_config.override( + excluded_coin_ids=[], + ), + inner_action_scope, + origin_id=coin.name(), + fee=fee_to_pay, + coins=selected_coins, + extra_conditions=(*extra_conditions, *announcement_conditions), + ) else: # ATTENTION: new_wallets assert isinstance(wallet, (CATWallet, DataLayerWallet, NFTWallet)) - txs = await wallet.generate_signed_transaction( - [coin.amount], - [new_ph], - tx_config.override( - excluded_coin_ids=[], - ), - fee=fee_to_pay, - coins={coin}, - extra_conditions=(*extra_conditions, *announcement_conditions), - ) - for tx in txs: - if tx is not None and tx.spend_bundle is not None: - bundles.append(tx.spend_bundle) - cancellation_additions.extend(tx.spend_bundle.additions()) - all_txs.append(dataclasses.replace(tx, spend_bundle=None)) + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + await wallet.generate_signed_transaction( + [coin.amount], + [new_ph], + tx_config.override( + excluded_coin_ids=[], + ), + inner_action_scope, + fee=fee_to_pay, + coins={coin}, + extra_conditions=(*extra_conditions, *announcement_conditions), + ) + + cancellation_additions.extend( + [ + add + for tx in inner_action_scope.side_effects.transactions + if tx.spend_bundle is not None + for add in tx.spend_bundle.additions() + ] + ) + all_txs.extend(inner_action_scope.side_effects.transactions) fee_to_pay = uint64(0) extra_conditions = tuple() - all_txs.append( - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=new_ph, - amount=uint64(coin.amount), - fee_amount=fee, - confirmed=False, - sent=uint32(10), - spend_bundle=None, - additions=cancellation_additions, - removals=[coin], - wallet_id=wallet.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.INCOMING_TX.value), - name=cancellation_additions[0].name(), - memos=[], - valid_times=valid_times, - ) + incoming_tx = TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=new_ph, + amount=uint64(coin.amount), + fee_amount=fee, + confirmed=False, + sent=uint32(10), + spend_bundle=None, + additions=cancellation_additions, + removals=[coin], + wallet_id=wallet.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.INCOMING_TX.value), + name=cancellation_additions[0].name(), + memos=[], + valid_times=valid_times, ) + all_txs.append(incoming_tx) await self.trade_store.set_status(trade.trade_id, TradeStatus.PENDING_CANCEL) - # Aggregate spend bundles to the first tx - if len(all_txs) > 0: - all_txs[0] = dataclasses.replace(all_txs[0], spend_bundle=SpendBundle.aggregate(bundles)) - all_txs = [dataclasses.replace(tx, fee_amount=fee) for tx in all_txs] - - return all_txs + if secure: + async with action_scope.use() as interface: + # We have to combine the spend bundle for these since they are tied with announcements + all_tx_names = [tx.name for tx in all_txs] + interface.side_effects.transactions = [ + tx for tx in interface.side_effects.transactions if tx.name not in all_tx_names + ] + final_spend_bundle = SpendBundle.aggregate( + [tx.spend_bundle for tx in all_txs if tx.spend_bundle is not None] + ) + interface.side_effects.transactions.append( + dataclasses.replace(all_txs[0], spend_bundle=final_spend_bundle, name=final_spend_bundle.name()) + ) + interface.side_effects.transactions.extend( + [dataclasses.replace(tx, spend_bundle=None, fee_amount=fee) for tx in all_txs[1:]] + ) async def save_trade(self, trade: TradeRecord, offer: Offer) -> None: offer_name: bytes32 = offer.name() @@ -410,15 +425,14 @@ async def create_offer_for_ids( self, offer: Dict[Union[int, bytes32], int], tx_config: TXConfig, + action_scope: WalletActionScope, driver_dict: Optional[Dict[bytes32, PuzzleInfo]] = None, solver: Optional[Solver] = None, fee: uint64 = uint64(0), validate_only: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), taking: bool = False, - ) -> Union[ - Tuple[Literal[True], TradeRecord, List[TransactionRecord], None], Tuple[Literal[False], None, List[Never], str] - ]: + ) -> Union[Tuple[Literal[True], TradeRecord, None], Tuple[Literal[False], None, str]]: if driver_dict is None: driver_dict = {} if solver is None: @@ -426,6 +440,7 @@ async def create_offer_for_ids( result = await self._create_offer_for_ids( offer, tx_config, + action_scope, driver_dict, solver, fee=fee, @@ -435,7 +450,7 @@ async def create_offer_for_ids( if not result[0] or result[1] is None: raise Exception(f"Error creating offer: {result[2]}") - success, created_offer, tx_records, error = result + success, created_offer, error = result now = uint64(int(time.time())) trade_offer: TradeRecord = TradeRecord( @@ -456,20 +471,19 @@ async def create_offer_for_ids( if success is True and trade_offer is not None and not validate_only: await self.save_trade(trade_offer, created_offer) - return success, trade_offer, tx_records, error + return success, trade_offer, error async def _create_offer_for_ids( self, offer_dict: Dict[Union[int, bytes32], int], tx_config: TXConfig, + action_scope: WalletActionScope, driver_dict: Optional[Dict[bytes32, PuzzleInfo]] = None, solver: Optional[Solver] = None, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), taking: bool = False, - ) -> Union[ - Tuple[Literal[True], Offer, List[TransactionRecord], None], Tuple[Literal[False], None, List[Never], str] - ]: + ) -> Union[Tuple[Literal[True], Offer, None], Tuple[Literal[False], None, str]]: """ Offer is dictionary of wallet ids and amount """ @@ -573,19 +587,18 @@ async def _create_offer_for_ids( requested_payments, driver_dict, taking ) - potential_special_offer: Optional[Tuple[Offer, List[TransactionRecord]]] = ( - await self.check_for_special_offer_making( - offer_dict_no_ints, - driver_dict, - tx_config, - solver, - fee, - extra_conditions, - ) + potential_special_offer: Optional[Offer] = await self.check_for_special_offer_making( + offer_dict_no_ints, + driver_dict, + tx_config, + action_scope, + solver, + fee, + extra_conditions, ) if potential_special_offer is not None: - return True, potential_special_offer[0], potential_special_offer[1], None + return True, potential_special_offer, None all_coins: List[Coin] = [c for coins in coins_to_offer.values() for c in coins] notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( @@ -603,61 +616,67 @@ async def _create_offer_for_ids( wallet = self.wallet_state_manager.wallets.get(uint32(id)) else: wallet = await self.wallet_state_manager.get_wallet_for_asset_id(id.hex()) - # This should probably not switch on whether or not we're spending XCH but it has to for now - assert wallet is not None - if wallet.type() == WalletType.STANDARD_WALLET: - assert isinstance(wallet, Wallet) - [tx] = await wallet.generate_signed_transaction( - uint64(abs(offer_dict[id])), - Offer.ph(), - tx_config, - fee=fee_left_to_pay, - coins=selected_coins, - extra_conditions=(*extra_conditions, *announcements_to_assert), - ) - all_transactions.append(tx) - elif wallet.type() == WalletType.NFT: - assert isinstance(wallet, NFTWallet) - # This is to generate the tx for specific nft assets, i.e. not using - # wallet_id as the selector which would select any coins from nft_wallet - amounts = [coin.amount for coin in selected_coins] - txs = await wallet.generate_signed_transaction( - # [abs(offer_dict[id])], - amounts, - [Offer.ph()], - tx_config, - fee=fee_left_to_pay, - coins=selected_coins, - extra_conditions=(*extra_conditions, *announcements_to_assert), - ) - all_transactions.extend(txs) - else: - # ATTENTION: new_wallets - assert isinstance(wallet, (CATWallet, DataLayerWallet)) - txs = await wallet.generate_signed_transaction( - [uint64(abs(offer_dict[id]))], - [Offer.ph()], - tx_config, - fee=fee_left_to_pay, - coins=selected_coins, - extra_conditions=(*extra_conditions, *announcements_to_assert), - add_authorizations_to_cr_cats=False, - ) - all_transactions.extend(txs) + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + # This should probably not switch on whether or not we're spending XCH but it has to for now + assert wallet is not None + if wallet.type() == WalletType.STANDARD_WALLET: + assert isinstance(wallet, Wallet) + await wallet.generate_signed_transaction( + uint64(abs(offer_dict[id])), + Offer.ph(), + tx_config, + inner_action_scope, + fee=fee_left_to_pay, + coins=selected_coins, + extra_conditions=(*extra_conditions, *announcements_to_assert), + ) + elif wallet.type() == WalletType.NFT: + assert isinstance(wallet, NFTWallet) + # This is to generate the tx for specific nft assets, i.e. not using + # wallet_id as the selector which would select any coins from nft_wallet + amounts = [coin.amount for coin in selected_coins] + await wallet.generate_signed_transaction( + # [abs(offer_dict[id])], + amounts, + [Offer.ph()], + tx_config, + inner_action_scope, + fee=fee_left_to_pay, + coins=selected_coins, + extra_conditions=(*extra_conditions, *announcements_to_assert), + ) + else: + # ATTENTION: new_wallets + assert isinstance(wallet, (CATWallet, DataLayerWallet)) + await wallet.generate_signed_transaction( + [uint64(abs(offer_dict[id]))], + [Offer.ph()], + tx_config, + inner_action_scope, + fee=fee_left_to_pay, + coins=selected_coins, + extra_conditions=(*extra_conditions, *announcements_to_assert), + add_authorizations_to_cr_cats=False, + ) + + all_transactions.extend(inner_action_scope.side_effects.transactions) fee_left_to_pay = uint64(0) extra_conditions = tuple() + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(all_transactions) + total_spend_bundle = SpendBundle.aggregate( [x.spend_bundle for x in all_transactions if x.spend_bundle is not None] ) offer = Offer(notarized_payments, total_spend_bundle, driver_dict) - return True, offer, all_transactions, None + return True, offer, None except Exception as e: self.log.exception("Error creating trade offer") - return False, None, [], str(e) + return False, None, str(e) async def maybe_create_wallets_for_offer(self, offer: Offer) -> None: for key in offer.arbitrage(): @@ -814,10 +833,11 @@ async def respond_to_offer( offer: Offer, peer: WSChiaConnection, tx_config: TXConfig, + action_scope: WalletActionScope, solver: Optional[Solver] = None, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[TradeRecord, List[TransactionRecord]]: + ) -> TradeRecord: if solver is None: solver = Solver({}) take_offer_dict: Dict[Union[bytes32, int], int] = {} @@ -843,23 +863,26 @@ async def respond_to_offer( valid: bool = await self.check_offer_validity(offer, peer) if not valid: raise ValueError("This offer is no longer valid") - result = await self._create_offer_for_ids( - take_offer_dict, - tx_config, - offer.driver_dict, - solver, - fee=fee, - extra_conditions=extra_conditions, - taking=True, - ) - if not result[0] or result[1] is None: - raise ValueError(result[2]) + # We need to sandbox the transactions here because we're going to make our own + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + result = await self._create_offer_for_ids( + take_offer_dict, + tx_config, + inner_action_scope, + offer.driver_dict, + solver, + fee=fee, + extra_conditions=extra_conditions, + taking=True, + ) + if not result[0] or result[1] is None: + raise ValueError(result[2]) - success, take_offer, _, error = result + success, take_offer, error = result - complete_offer, valid_spend_solver = await self.check_for_final_modifications( - Offer.aggregate([offer, take_offer]), solver, tx_config - ) + complete_offer, valid_spend_solver = await self.check_for_final_modifications( + Offer.aggregate([offer, take_offer]), solver, tx_config, inner_action_scope + ) self.log.info("COMPLETE OFFER: %s", complete_offer.to_bech32()) assert complete_offer.is_valid() final_spend_bundle: SpendBundle = complete_offer.to_valid_spend( @@ -907,17 +930,21 @@ async def respond_to_offer( valid_times=ConditionValidTimes(), ) - return trade_record, [push_tx, *tx_records] + async with action_scope.use() as interface: + interface.side_effects.transactions.extend([push_tx, *tx_records]) + + return trade_record async def check_for_special_offer_making( self, offer_dict: Dict[Optional[bytes32], int], driver_dict: Dict[bytes32, PuzzleInfo], tx_config: TXConfig, + action_scope: WalletActionScope, solver: Solver, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Optional[Tuple[Offer, List[TransactionRecord]]]: + ) -> Optional[Offer]: for puzzle_info in driver_dict.values(): if ( puzzle_info.check_type([AssetType.SINGLETON.value, AssetType.METADATA.value, AssetType.OWNERSHIP.value]) @@ -926,7 +953,7 @@ async def check_for_special_offer_making( == AssetType.ROYALTY_TRANSFER_PROGRAM.value ): return await NFTWallet.make_nft1_offer( - self.wallet_state_manager, offer_dict, driver_dict, tx_config, fee, extra_conditions + self.wallet_state_manager, offer_dict, driver_dict, tx_config, action_scope, fee, extra_conditions ) elif ( puzzle_info.check_type( @@ -938,7 +965,14 @@ async def check_for_special_offer_making( and puzzle_info.also()["updater_hash"] == ACS_MU_PH # type: ignore ): return await DataLayerWallet.make_update_offer( - self.wallet_state_manager, offer_dict, driver_dict, solver, tx_config, fee, extra_conditions + self.wallet_state_manager, + offer_dict, + driver_dict, + solver, + tx_config, + action_scope, + fee, + extra_conditions, ) return None @@ -997,7 +1031,7 @@ async def get_offer_summary(self, offer: Offer) -> Dict[str, Any]: } async def check_for_final_modifications( - self, offer: Offer, solver: Solver, tx_config: TXConfig + self, offer: Offer, solver: Solver, tx_config: TXConfig, action_scope: WalletActionScope ) -> Tuple[Offer, Solver]: for puzzle_info in offer.driver_dict.values(): if ( @@ -1020,7 +1054,7 @@ async def check_for_final_modifications( for _, wallet in self.wallet_state_manager.wallets.items(): if WalletType(wallet.type()) == WalletType.VC: assert isinstance(wallet, VCWallet) - return await wallet.add_vc_authorization(offer, solver, tx_config) + return await wallet.add_vc_authorization(offer, solver, tx_config, action_scope) else: raise ValueError("No VCs to approve CR-CATs with") # pragma: no cover diff --git a/chia/wallet/util/clvm_streamable.py b/chia/wallet/util/clvm_streamable.py index 66a8233a5c5d..12270eb2d8c2 100644 --- a/chia/wallet/util/clvm_streamable.py +++ b/chia/wallet/util/clvm_streamable.py @@ -2,6 +2,7 @@ import dataclasses import functools +from types import MappingProxyType from typing import Any, Callable, Dict, Generic, List, Optional, Type, TypeVar, Union, get_args, get_type_hints from hsms.clvm_serde import from_program_for_type, to_program_for_type @@ -27,6 +28,7 @@ def clvm_streamable(cls: Type[Streamable]) -> Type[Streamable]: hints = get_type_hints(cls) # no way to hint that wrapped_cls is a dataclass here but @streamable checks that for field in dataclasses.fields(wrapped_cls): # type: ignore[arg-type] + field.metadata = MappingProxyType({"key": field.name, **field.metadata}) if is_type_Tuple(hints[field.name]): raise ValueError("@clvm_streamable does not support tuples") diff --git a/chia/wallet/vc_wallet/cr_cat_wallet.py b/chia/wallet/vc_wallet/cr_cat_wallet.py index d1ca78df6e04..736369a24c44 100644 --- a/chia/wallet/vc_wallet/cr_cat_wallet.py +++ b/chia/wallet/vc_wallet/cr_cat_wallet.py @@ -1,6 +1,5 @@ from __future__ import annotations -import dataclasses import logging import time import traceback @@ -56,6 +55,7 @@ from chia.wallet.vc_wallet.vc_drivers import VerifiedCredential from chia.wallet.vc_wallet.vc_wallet import VCWallet from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import MetadataTypes, WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_protocol import GSTOptionalArgs, WalletProtocol @@ -86,9 +86,11 @@ async def create_new_cat_wallet( cat_tail_info: Dict[str, Any], amount: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), name: Optional[str] = None, - ) -> Tuple[CATWallet, List[TransactionRecord]]: # pragma: no cover + push: bool = False, + ) -> CATWallet: # pragma: no cover raise NotImplementedError("create_new_cat_wallet is a legacy method and is not available on CR-CAT wallets") @staticmethod @@ -399,12 +401,13 @@ async def _generate_unsigned_spendbundle( self, payments: List[Payment], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), cat_discrepancy: Optional[Tuple[int, Program, Program]] = None, # (extra_delta, tail_reveal, tail_solution) coins: Optional[Set[Coin]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), add_authorizations_to_cr_cats: bool = True, - ) -> Tuple[SpendBundle, List[TransactionRecord]]: + ) -> SpendBundle: if cat_discrepancy is not None: extra_delta, tail_reveal, tail_solution = cat_discrepancy else: @@ -474,7 +477,6 @@ async def _generate_unsigned_spendbundle( vc: Optional[VerifiedCredential] = None vc_announcements_to_make: List[bytes] = [] inner_spends: List[Tuple[CRCAT, int, Program, Program]] = [] - chia_tx = None first = True announcement: CreateCoinAnnouncement coin_ids: List[bytes32] = [coin.name() for coin in cat_coins] @@ -509,10 +511,11 @@ async def _generate_unsigned_spendbundle( announcement = CreateCoinAnnouncement(std_hash(b"".join([c.name() for c in cat_coins])), coin.name()) if need_chia_transaction: if fee > regular_chia_to_claim: - chia_tx, _ = await self.create_tandem_xch_tx( + await self.create_tandem_xch_tx( fee, uint64(regular_chia_to_claim), tx_config, + action_scope, extra_conditions=(announcement.corresponding_assertion(),), ) innersol = self.standard_wallet.make_solution( @@ -520,10 +523,11 @@ async def _generate_unsigned_spendbundle( conditions=(*extra_conditions, announcement), ) elif regular_chia_to_claim > fee: - chia_tx, xch_announcement = await self.create_tandem_xch_tx( + xch_announcement = await self.create_tandem_xch_tx( fee, uint64(regular_chia_to_claim), tx_config, + action_scope, ) assert xch_announcement is not None innersol = self.standard_wallet.make_solution( @@ -581,50 +585,31 @@ async def _generate_unsigned_spendbundle( vc.wrap_inner_with_backdoor().get_tree_hash() if add_authorizations_to_cr_cats else None, ) if add_authorizations_to_cr_cats: - vc_txs: List[TransactionRecord] = await vc_wallet.generate_signed_transaction( + await vc_wallet.generate_signed_transaction( vc.launcher_id, tx_config, + action_scope, extra_conditions=( *expected_announcements, announcement, *(CreatePuzzleAnnouncement(ann) for ann in vc_announcements_to_make), ), ) - else: - vc_txs = [] - - return ( - SpendBundle( - [ - *coin_spends, - *(spend for tx in vc_txs if tx.spend_bundle is not None for spend in tx.spend_bundle.coin_spends), - *( - ( - spend - for bundle in [chia_tx.spend_bundle] - if bundle is not None - for spend in bundle.coin_spends - ) - if chia_tx is not None - else [] - ), - ], - G2Element(), - ), - [*vc_txs, *([chia_tx] if chia_tx is not None else [])], - ) + + return SpendBundle(coin_spends, G2Element()) async def generate_signed_transaction( self, amounts: List[uint64], puzzle_hashes: List[bytes32], tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Optional[Set[Coin]] = None, memos: Optional[List[List[bytes]]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), **kwargs: Unpack[GSTOptionalArgs], - ) -> List[TransactionRecord]: + ) -> None: # (extra_delta, tail_reveal, tail_solution) cat_discrepancy: Optional[Tuple[int, Program, Program]] = kwargs.get("cat_discrepancy", None) add_authorizations_to_cr_cats: bool = kwargs.get("add_authorizations_to_cr_cats", True) @@ -652,9 +637,10 @@ async def generate_signed_transaction( ) ) - spend_bundle, other_txs = await self._generate_unsigned_spendbundle( + spend_bundle = await self._generate_unsigned_spendbundle( payments, tx_config, + action_scope, fee, cat_discrepancy=cat_discrepancy, # (extra_delta, tail_reveal, tail_solution) coins=coins, @@ -662,37 +648,43 @@ async def generate_signed_transaction( add_authorizations_to_cr_cats=add_authorizations_to_cr_cats, ) - other_tx_removals: Set[Coin] = {removal for tx in other_txs for removal in tx.removals} - other_tx_additions: Set[Coin] = {removal for tx in other_txs for removal in tx.additions} - tx_list = [ - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=payment.puzzle_hash, - amount=payment.amount, - fee_amount=fee, - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle if i == 0 else None, - additions=list(set(spend_bundle.additions()) - other_tx_additions) if i == 0 else [], - removals=list(set(spend_bundle.removals()) - other_tx_removals) if i == 0 else [], - wallet_id=self.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=spend_bundle.name(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=parse_timelock_info(extra_conditions), - ) - for i, payment in enumerate(payments) - ] + async with action_scope.use() as interface: + other_tx_removals: Set[Coin] = { + removal for tx in interface.side_effects.transactions for removal in tx.removals + } + other_tx_additions: Set[Coin] = { + addition for tx in interface.side_effects.transactions for addition in tx.additions + } + tx_list = [ + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=payment.puzzle_hash, + amount=payment.amount, + fee_amount=fee, + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle if i == 0 else None, + additions=list(set(spend_bundle.additions()) - other_tx_additions) if i == 0 else [], + removals=list(set(spend_bundle.removals()) - other_tx_removals) if i == 0 else [], + wallet_id=self.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=spend_bundle.name() if i == 0 else payment.as_condition().get_tree_hash(), + memos=list(compute_memos(spend_bundle).items()), + valid_times=parse_timelock_info(extra_conditions), + ) + for i, payment in enumerate(payments) + ] - return [*tx_list, *(dataclasses.replace(tx, spend_bundle=None) for tx in other_txs)] + interface.side_effects.transactions.extend(tx_list) async def claim_pending_approval_balance( self, min_amount_to_claim: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Optional[Set[Coin]] = None, min_coin_amount: Optional[uint64] = None, @@ -700,7 +692,7 @@ async def claim_pending_approval_balance( excluded_coin_amounts: Optional[List[uint64]] = None, reuse_puzhash: Optional[bool] = None, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: # Select the relevant CR-CAT coins crcat_records: Set[WalletCoinRecord] = await self.wallet_state_manager.coin_store.get_unspent_coins_for_wallet( self.id(), CoinType.CRCAT_PENDING @@ -770,22 +762,19 @@ async def claim_pending_approval_balance( # Make the Fee TX if fee > 0: - chia_tx, _ = await self.create_tandem_xch_tx( + await self.create_tandem_xch_tx( fee, uint64(0), tx_config, + action_scope, extra_conditions=tuple(expected_announcements), ) - if chia_tx.spend_bundle is None: - raise RuntimeError("Did not get spendbundle for fee transaction") # pragma: no cover - claim_bundle = SpendBundle.aggregate([claim_bundle, chia_tx.spend_bundle]) - else: - chia_tx = None # Make the VC TX - vc_txs: List[TransactionRecord] = await vc_wallet.generate_signed_transaction( + await vc_wallet.generate_signed_transaction( vc.launcher_id, tx_config, + action_scope, extra_conditions=( *extra_conditions, *expected_announcements, @@ -793,38 +782,31 @@ async def claim_pending_approval_balance( *(CreatePuzzleAnnouncement(crcat.expected_announcement()) for crcat, _ in crcats_and_puzhashes), ), ) - claim_bundle = SpendBundle.aggregate( - [claim_bundle, *(tx.spend_bundle for tx in vc_txs if tx.spend_bundle is not None)] - ) - other_txs: List[TransactionRecord] = [ - *(dataclasses.replace(tx, spend_bundle=None) for tx in vc_txs), - *((dataclasses.replace(chia_tx, spend_bundle=None),) if chia_tx is not None else []), - ] - other_additions: Set[Coin] = {rem for tx in other_txs for rem in tx.additions} - other_removals: Set[Coin] = {rem for tx in other_txs for rem in tx.removals} - return [ - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=uint64(int(time.time())), - to_puzzle_hash=await self.wallet_state_manager.main_wallet.get_puzzle_hash(False), - amount=uint64(sum(c.amount for c in coins)), - fee_amount=fee, - confirmed=False, - sent=uint32(0), - spend_bundle=claim_bundle, - additions=list(set(claim_bundle.additions()) - other_additions), - removals=list(set(claim_bundle.removals()) - other_removals), - wallet_id=self.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.INCOMING_TX.value), - name=claim_bundle.name(), - memos=list(compute_memos(claim_bundle).items()), - valid_times=parse_timelock_info(extra_conditions), - ), - *other_txs, - ] + async with action_scope.use() as interface: + other_additions: Set[Coin] = {rem for tx in interface.side_effects.transactions for rem in tx.additions} + other_removals: Set[Coin] = {rem for tx in interface.side_effects.transactions for rem in tx.removals} + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=uint64(int(time.time())), + to_puzzle_hash=await self.wallet_state_manager.main_wallet.get_puzzle_hash(False), + amount=uint64(sum(c.amount for c in coins)), + fee_amount=fee, + confirmed=False, + sent=uint32(0), + spend_bundle=claim_bundle, + additions=list(set(claim_bundle.additions()) - other_additions), + removals=list(set(claim_bundle.removals()) - other_removals), + wallet_id=self.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.INCOMING_TX.value), + name=claim_bundle.name(), + memos=list(compute_memos(claim_bundle).items()), + valid_times=parse_timelock_info(extra_conditions), + ) + ) async def match_puzzle_info(self, puzzle_driver: PuzzleInfo) -> bool: if ( diff --git a/chia/wallet/vc_wallet/vc_wallet.py b/chia/wallet/vc_wallet/vc_wallet.py index d607dd3cf592..9af19f509a34 100644 --- a/chia/wallet/vc_wallet/vc_wallet.py +++ b/chia/wallet/vc_wallet/vc_wallet.py @@ -1,6 +1,5 @@ from __future__ import annotations -import dataclasses import logging import time import traceback @@ -45,6 +44,7 @@ from chia.wallet.vc_wallet.vc_drivers import VerifiedCredential from chia.wallet.vc_wallet.vc_store import VCProofs, VCRecord, VCStore from chia.wallet.wallet import Wallet +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_protocol import GSTOptionalArgs, WalletProtocol @@ -164,10 +164,11 @@ async def launch_new_vc( self, provider_did: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, inner_puzzle_hash: Optional[bytes32] = None, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[VCRecord, List[TransactionRecord]]: + ) -> VCRecord: """ Given the DID ID of a proof provider, mint a brand new VC with an empty slot for proofs. @@ -206,37 +207,41 @@ async def launch_new_vc( add_list: List[Coin] = list(spend_bundle.additions()) rem_list: List[Coin] = list(spend_bundle.removals()) vc_record: VCRecord = VCRecord(vc, uint32(0)) - tx = TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=now, - to_puzzle_hash=inner_puzzle_hash, - amount=uint64(1), - fee_amount=uint64(fee), - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=add_list, - removals=rem_list, - wallet_id=uint32(1), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=spend_bundle.name(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=parse_timelock_info(extra_conditions), - ) + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=now, + to_puzzle_hash=inner_puzzle_hash, + amount=uint64(1), + fee_amount=uint64(fee), + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle, + additions=add_list, + removals=rem_list, + wallet_id=uint32(1), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=spend_bundle.name(), + memos=list(compute_memos(spend_bundle).items()), + valid_times=parse_timelock_info(extra_conditions), + ) + ) - return vc_record, [tx] + return vc_record async def generate_signed_transaction( self, vc_id: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), new_inner_puzhash: Optional[bytes32] = None, extra_conditions: Tuple[Condition, ...] = tuple(), **kwargs: Unpack[GSTOptionalArgs], - ) -> List[TransactionRecord]: + ) -> None: new_proof_hash: Optional[bytes32] = kwargs.get( "new_proof_hash", None ) # Requires that this key posesses the DID to update the specified VC @@ -265,14 +270,14 @@ async def generate_signed_transaction( if fee > 0: coin_name = vc_record.vc.coin.name() - chia_tx = await self.wallet_state_manager.main_wallet.create_tandem_xch_tx( + await self.wallet_state_manager.main_wallet.create_tandem_xch_tx( fee, tx_config, + action_scope, extra_conditions=(AssertCoinAnnouncement(asserted_id=coin_name, asserted_msg=coin_name),), ) extra_conditions += (CreateCoinAnnouncement(coin_name),) - else: - chia_tx = None + if new_proof_hash is not None: if self_revoke: raise ValueError("Cannot add new proofs and revoke at the same time") @@ -299,8 +304,7 @@ async def generate_signed_transaction( conditions=extra_conditions, ) did_announcement, coin_spend, vc = vc_record.vc.do_spend(inner_puzzle, innersol, new_proof_hash) - spend_bundles = [SpendBundle([coin_spend], G2Element())] - tx_list: List[TransactionRecord] = [] + spend_bundle = SpendBundle([coin_spend], G2Element()) if did_announcement is not None: # Need to spend DID for _, wallet in self.wallet_state_manager.wallets.items(): @@ -308,53 +312,48 @@ async def generate_signed_transaction( assert isinstance(wallet, DIDWallet) if bytes32.fromhex(wallet.get_my_DID()) == vc_record.vc.proof_provider: self.log.debug("Creating announcement from DID for vc: %s", vc_id.hex()) - did_tx = await wallet.create_message_spend(tx_config, extra_conditions=(did_announcement,)) - assert did_tx.spend_bundle is not None - spend_bundles.append(did_tx.spend_bundle) - tx_list.append(dataclasses.replace(did_tx, spend_bundle=None)) + await wallet.create_message_spend(tx_config, action_scope, extra_conditions=(did_announcement,)) break else: raise ValueError( f"Cannot find the required DID {vc_record.vc.proof_provider.hex()}." ) # pragma: no cover - add_list: List[Coin] = list(spend_bundles[0].additions()) - rem_list: List[Coin] = list(spend_bundles[0].removals()) - if chia_tx is not None and chia_tx.spend_bundle is not None: - spend_bundles.append(chia_tx.spend_bundle) - tx_list.append(dataclasses.replace(chia_tx, spend_bundle=None)) - spend_bundle = SpendBundle.aggregate(spend_bundles) + add_list: List[Coin] = list(spend_bundle.additions()) + rem_list: List[Coin] = list(spend_bundle.removals()) now = uint64(int(time.time())) - tx_list.append( - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=now, - to_puzzle_hash=new_inner_puzhash, - amount=uint64(1), - fee_amount=uint64(fee), - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=add_list, - removals=rem_list, - wallet_id=self.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=spend_bundle.name(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=parse_timelock_info(extra_conditions), + + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=now, + to_puzzle_hash=new_inner_puzhash, + amount=uint64(1), + fee_amount=uint64(fee), + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle, + additions=add_list, + removals=rem_list, + wallet_id=self.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=spend_bundle.name(), + memos=list(compute_memos(spend_bundle).items()), + valid_times=parse_timelock_info(extra_conditions), + ) ) - ) - return tx_list async def revoke_vc( self, parent_id: bytes32, peer: WSChiaConnection, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: vc_coin_states: List[CoinState] = await self.wallet_state_manager.wallet_node.get_coin_state( [parent_id], peer=peer ) @@ -373,12 +372,14 @@ async def revoke_vc( did_wallet = wallet break else: - return await self.generate_signed_transaction( + await self.generate_signed_transaction( vc.launcher_id, tx_config, + action_scope, fee, self_revoke=True, ) + return recovery_info: Optional[Tuple[bytes32, bytes32, uint64]] = await did_wallet.get_info_for_recovery() if recovery_info is None: @@ -395,29 +396,24 @@ async def revoke_vc( nonce: bytes32 = SerializedProgram.to(sorted_coin_list).get_tree_hash() vc_announcement: AssertCoinAnnouncement = AssertCoinAnnouncement(asserted_id=vc.coin.name(), asserted_msg=nonce) + if fee > 0: + await self.wallet_state_manager.main_wallet.create_tandem_xch_tx( + fee, tx_config, action_scope, extra_conditions=(vc_announcement,) + ) + # Assemble final bundle expected_did_announcement, vc_spend = vc.activate_backdoor(provider_inner_puzhash, announcement_nonce=nonce) - did_tx: TransactionRecord = await did_wallet.create_message_spend( + await did_wallet.create_message_spend( tx_config, + action_scope, extra_conditions=(*extra_conditions, expected_did_announcement, vc_announcement), ) - assert did_tx.spend_bundle is not None - final_bundle: SpendBundle = SpendBundle.aggregate([SpendBundle([vc_spend], G2Element()), did_tx.spend_bundle]) - did_tx = dataclasses.replace(did_tx, spend_bundle=final_bundle, name=final_bundle.name()) - if fee > 0: - chia_tx: TransactionRecord = await self.wallet_state_manager.main_wallet.create_tandem_xch_tx( - fee, tx_config, extra_conditions=(vc_announcement,) - ) - assert did_tx.spend_bundle is not None - assert chia_tx.spend_bundle is not None - new_bundle = SpendBundle.aggregate([chia_tx.spend_bundle, did_tx.spend_bundle]) - did_tx = dataclasses.replace(did_tx, spend_bundle=new_bundle, name=new_bundle.name()) - chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) - return [did_tx, chia_tx] - else: - return [did_tx] # pragma: no cover + async with action_scope.use() as interface: + interface.side_effects.extra_spends.append(SpendBundle([vc_spend], G2Element())) - async def add_vc_authorization(self, offer: Offer, solver: Solver, tx_config: TXConfig) -> Tuple[Offer, Solver]: + async def add_vc_authorization( + self, offer: Offer, solver: Solver, tx_config: TXConfig, action_scope: WalletActionScope + ) -> Tuple[Offer, Solver]: """ This method takes an existing offer and adds a VC authorization spend to it where it can/is willing. The only coins types that it looks for to approve are CR-CATs at the moment. @@ -541,26 +537,20 @@ async def add_vc_authorization(self, offer: Offer, solver: Solver, tx_config: TX else: raise ValueError("Wallet cannot verify all spends in specified offer") # pragma: no cover - vc_spends: List[SpendBundle] = [] - for launcher_id, vc in vcs.items(): - vc_spends.append( - SpendBundle.aggregate( - [ - tx.spend_bundle - for tx in ( - await self.generate_signed_transaction( - launcher_id, - tx_config, - extra_conditions=( - *announcements_to_assert[launcher_id], - *announcements_to_make[launcher_id], - ), - ) - ) - if tx.spend_bundle is not None - ] + async with self.wallet_state_manager.new_action_scope(push=False) as inner_action_scope: + for launcher_id, vc in vcs.items(): + await self.generate_signed_transaction( + launcher_id, + tx_config, + inner_action_scope, + extra_conditions=( + *announcements_to_assert[launcher_id], + *announcements_to_make[launcher_id], + ), ) - ) + + async with action_scope.use() as interface: + interface.side_effects.transactions.extend(inner_action_scope.side_effects.transactions) return Offer.from_spend_bundle( SpendBundle.aggregate( @@ -576,7 +566,11 @@ async def add_vc_authorization(self, offer: Offer, solver: Solver, tx_config: TX ], offer._bundle.aggregated_signature, ), - *vc_spends, + *[ + tx.spend_bundle + for tx in inner_action_scope.side_effects.transactions + if tx.spend_bundle is not None + ], ] ) ), Solver({"vc_authorizations": coin_args}) diff --git a/chia/wallet/wallet.py b/chia/wallet/wallet.py index e91b9fb68780..cbfcd9365aad 100644 --- a/chia/wallet/wallet.py +++ b/chia/wallet/wallet.py @@ -54,6 +54,7 @@ from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES, TransactionType from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig from chia.wallet.util.wallet_types import WalletIdentifier, WalletType +from chia.wallet.wallet_action_scope import WalletActionScope from chia.wallet.wallet_coin_record import WalletCoinRecord from chia.wallet.wallet_info import WalletInfo from chia.wallet.wallet_protocol import GSTOptionalArgs, WalletProtocol @@ -258,6 +259,7 @@ async def _generate_unsigned_transaction( amount: uint64, newpuzzlehash: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), origin_id: Optional[bytes32] = None, coins: Optional[Set[Coin]] = None, @@ -394,6 +396,7 @@ async def generate_signed_transaction( amount: uint64, puzzle_hash: bytes32, tx_config: TXConfig, + action_scope: WalletActionScope, fee: uint64 = uint64(0), coins: Optional[Set[Coin]] = None, primaries: Optional[List[Payment]] = None, @@ -401,7 +404,7 @@ async def generate_signed_transaction( puzzle_decorator_override: Optional[List[Dict[str, Any]]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), **kwargs: Unpack[GSTOptionalArgs], - ) -> List[TransactionRecord]: + ) -> None: origin_id: Optional[bytes32] = kwargs.get("origin_id", None) negative_change_allowed: bool = kwargs.get("negative_change_allowed", False) """ @@ -419,6 +422,7 @@ async def generate_signed_transaction( amount, puzzle_hash, tx_config, + action_scope, fee, origin_id, coins, @@ -442,45 +446,46 @@ async def generate_signed_transaction( else: assert output_amount == input_amount - return [ - TransactionRecord( - confirmed_at_height=uint32(0), - created_at_time=now, - to_puzzle_hash=puzzle_hash, - amount=uint64(non_change_amount), - fee_amount=uint64(fee), - confirmed=False, - sent=uint32(0), - spend_bundle=spend_bundle, - additions=add_list, - removals=rem_list, - wallet_id=self.id(), - sent_to=[], - trade_id=None, - type=uint32(TransactionType.OUTGOING_TX.value), - name=spend_bundle.name(), - memos=list(compute_memos(spend_bundle).items()), - valid_times=parse_timelock_info(extra_conditions), + async with action_scope.use() as interface: + interface.side_effects.transactions.append( + TransactionRecord( + confirmed_at_height=uint32(0), + created_at_time=now, + to_puzzle_hash=puzzle_hash, + amount=uint64(non_change_amount), + fee_amount=uint64(fee), + confirmed=False, + sent=uint32(0), + spend_bundle=spend_bundle, + additions=add_list, + removals=rem_list, + wallet_id=self.id(), + sent_to=[], + trade_id=None, + type=uint32(TransactionType.OUTGOING_TX.value), + name=spend_bundle.name(), + memos=list(compute_memos(spend_bundle).items()), + valid_times=parse_timelock_info(extra_conditions), + ) ) - ] async def create_tandem_xch_tx( self, fee: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> None: chia_coins = await self.select_coins(fee, tx_config.coin_selection_config) - [chia_tx] = await self.generate_signed_transaction( + await self.generate_signed_transaction( uint64(0), (await self.get_puzzle_hash(not tx_config.reuse_puzhash)), tx_config, + action_scope, fee=fee, coins=chia_coins, extra_conditions=extra_conditions, ) - assert chia_tx.spend_bundle is not None - return chia_tx async def get_coins_to_offer( self, diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index b837f78c1b14..d869da70cf25 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -927,41 +927,44 @@ async def auto_claim_coins(self) -> None: stop=tx_config.coin_selection_config.max_coin_amount, ), ) - all_txs: List[TransactionRecord] = [] - for coin in unspent_coins.records: - try: - metadata: MetadataTypes = coin.parsed_metadata() - assert isinstance(metadata, ClawbackMetadata) - if await metadata.is_recipient(self.puzzle_store): - coin_timestamp = await self.wallet_node.get_timestamp_for_height(coin.confirmed_block_height) - if current_timestamp - coin_timestamp >= metadata.time_lock: - clawback_coins[coin.coin] = metadata - if len(clawback_coins) >= self.config.get("auto_claim", {}).get("batch_size", 50): - txs = await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config) - all_txs.extend(txs) - tx_config = dataclasses.replace( - tx_config, - excluded_coin_ids=[ - *tx_config.excluded_coin_ids, - *(c.name() for tx in txs for c in tx.removals), - ], - ) - clawback_coins = {} - except Exception as e: - self.log.error(f"Failed to claim clawback coin {coin.coin.name().hex()}: %s", e) - if len(clawback_coins) > 0: - all_txs.extend(await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config)) - - await self.add_pending_transactions(all_txs) + async with self.new_action_scope(push=True) as action_scope: + for coin in unspent_coins.records: + try: + metadata: MetadataTypes = coin.parsed_metadata() + assert isinstance(metadata, ClawbackMetadata) + if await metadata.is_recipient(self.puzzle_store): + coin_timestamp = await self.wallet_node.get_timestamp_for_height(coin.confirmed_block_height) + if current_timestamp - coin_timestamp >= metadata.time_lock: + clawback_coins[coin.coin] = metadata + if len(clawback_coins) >= self.config.get("auto_claim", {}).get("batch_size", 50): + await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config, action_scope) + async with action_scope.use() as interface: + tx_config = dataclasses.replace( + tx_config, + excluded_coin_ids=[ + *tx_config.excluded_coin_ids, + *( + c.name() + for tx in interface.side_effects.transactions + for c in tx.removals + ), + ], + ) + clawback_coins = {} + except Exception as e: + self.log.error(f"Failed to claim clawback coin {coin.coin.name().hex()}: %s", e) + if len(clawback_coins) > 0: + await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config, action_scope) async def spend_clawback_coins( self, clawback_coins: Dict[Coin, ClawbackMetadata], fee: uint64, tx_config: TXConfig, + action_scope: WalletActionScope, force: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[TransactionRecord]: + ) -> None: assert len(clawback_coins) > 0 coin_spends: List[CoinSpend] = [] message: bytes32 = std_hash(b"".join([c.name() for c in clawback_coins.keys()])) @@ -1013,20 +1016,35 @@ async def spend_clawback_coins( except Exception as e: self.log.error(f"Failed to create clawback spend bundle for {coin.name().hex()}: {e}") if len(coin_spends) == 0: - return [] + return spend_bundle: SpendBundle = SpendBundle(coin_spends, G2Element()) - tx_list: List[TransactionRecord] = [] if fee > 0: - chia_tx = await self.main_wallet.create_tandem_xch_tx( - fee, - tx_config, - extra_conditions=( - AssertCoinAnnouncement(asserted_id=coin_spends[0].coin.name(), asserted_msg=message), - ), + async with self.new_action_scope(push=False) as inner_action_scope: + await self.main_wallet.create_tandem_xch_tx( + fee, + tx_config, + inner_action_scope, + extra_conditions=( + AssertCoinAnnouncement(asserted_id=coin_spends[0].coin.name(), asserted_msg=message), + ), + ) + async with action_scope.use() as interface: + # This should not be looked to for best practice. Ideally, the two spend bundles can exist separately on + # each tx record until they are pushed. This is not very supported behavior at the moment so to avoid + # any potential backwards compatibility issues, we're moving the spend bundle from this TX to the main + interface.side_effects.transactions.extend( + [dataclasses.replace(tx, spend_bundle=None) for tx in inner_action_scope.side_effects.transactions] + ) + spend_bundle = SpendBundle.aggregate( + [ + spend_bundle, + *( + tx.spend_bundle + for tx in inner_action_scope.side_effects.transactions + if tx.spend_bundle is not None + ), + ] ) - assert chia_tx.spend_bundle is not None - spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) - tx_list.append(dataclasses.replace(chia_tx, spend_bundle=None)) assert derivation_record is not None tx_record = TransactionRecord( confirmed_at_height=uint32(0), @@ -1047,8 +1065,8 @@ async def spend_clawback_coins( memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) - tx_list.append(tx_record) - return tx_list + async with action_scope.use() as interface: + interface.side_effects.transactions.append(tx_record) async def filter_spam(self, new_coin_state: List[CoinState]) -> List[CoinState]: xch_spam_amount = self.config.get("xch_spam_amount", 1000000) diff --git a/install.sh b/install.sh index b589d65b3a1b..adcb88d8a01a 100755 --- a/install.sh +++ b/install.sh @@ -170,8 +170,6 @@ fi ./setup-poetry.sh -c "${INSTALL_PYTHON_PATH}" .penv/bin/poetry env use "${INSTALL_PYTHON_PATH}" -# TODO: Decide if this is needed or should be handled automatically in some way -.penv/bin/pip install "poetry-dynamic-versioning[plugin]" # shellcheck disable=SC2086 .penv/bin/poetry install ${EXTRAS} ln -s -f .venv venv @@ -199,7 +197,7 @@ echo "For assistance join us on Discord in the #support chat channel:" echo "https://discord.gg/chia" echo "" echo "Try the Quick Start Guide to running chia-blockchain:" -echo "https://github.com/Chia-Network/chia-blockchain/wiki/Quick-Start-Guide" +echo "https://docs.chia.net/introduction" echo "" echo "To install the GUI run '. ./activate' then 'sh install-gui.sh'." echo "" diff --git a/poetry.lock b/poetry.lock index 930edabcc598..39d55bdab36d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,98 +2,98 @@ [[package]] name = "aiofiles" -version = "23.2.1" +version = "24.1.0" description = "File support for asyncio." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, - {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, ] [[package]] name = "aiohttp" -version = "3.9.4" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"}, - {file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"}, - {file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"}, - {file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"}, - {file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"}, - {file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"}, - {file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd"}, - {file = "aiohttp-3.9.4-cp38-cp38-win32.whl", hash = "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e"}, - {file = "aiohttp-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"}, - {file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"}, - {file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"}, - {file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -950,13 +950,13 @@ files = [ [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -1016,18 +1016,17 @@ dev = ["pytest"] [[package]] name = "clvm-tools-rs" -version = "0.1.40" +version = "0.1.43" description = "tools for working with chialisp language; compiler, repl, python and wasm bindings" optional = false python-versions = "*" files = [ - {file = "clvm_tools_rs-0.1.40-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:742ae2667163ba365df3413a0c0394793df708043e482f1a7be775ecb6a2475d"}, - {file = "clvm_tools_rs-0.1.40-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:1eca4f1bf8005f37666cc87c0dcd945d0d3d65ff51918f7ace86d428aab74cc6"}, - {file = "clvm_tools_rs-0.1.40-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:7a3bdf661e4ad77f04dceb96d0f097f8a3192b7a28dc0a5a56dfa18e66617d91"}, - {file = "clvm_tools_rs-0.1.40-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e3b88443e823318cbcef37161152159307e385923118c48851fcbd91e13101"}, - {file = "clvm_tools_rs-0.1.40-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b4edd053f895366f0dedae6aa22c26db7fd78bad161baf4f81b76a7b824af4"}, - {file = "clvm_tools_rs-0.1.40-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:4eba19e55054c635ac99f0ce37d6dff16f4400af065965ccee315dfebffa260f"}, - {file = "clvm_tools_rs-0.1.40-cp38-abi3-win_amd64.whl", hash = "sha256:b7a9ee6000bd437a6898241574f37e225027c4fe1781ff9286cb71fd30d07a4b"}, + {file = "clvm_tools_rs-0.1.43-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:0dc68bdc7704d502d0193a9634764fffd2d618207b4a0260dbb32938881dad6c"}, + {file = "clvm_tools_rs-0.1.43-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:49f5065a64a560e9d5ffaf5d30f074cf65a1196a2d9c554724bfff646a8697cc"}, + {file = "clvm_tools_rs-0.1.43-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbc10526fc6abd606d337f93ca89ea52e95e390134d6d15620a3ad9d1a122ba5"}, + {file = "clvm_tools_rs-0.1.43-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3548f9c870e20c1dcdf90820046803d3a9b487a12c0c5d0563031ae7677e64a8"}, + {file = "clvm_tools_rs-0.1.43-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0d905bb57c3fca1e9b227ed233974a630ac912929091287800c82fdd6a51150a"}, + {file = "clvm_tools_rs-0.1.43-cp38-abi3-win_amd64.whl", hash = "sha256:423915b4098d38112ed8e7b8fcac1eafacb7fb2ac11cf5c371d7853a85577d4f"}, ] [[package]] @@ -1141,43 +1140,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.5" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] @@ -1621,13 +1620,13 @@ files = [ [[package]] name = "keyring" -version = "25.1.0" +version = "25.2.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.1.0-py3-none-any.whl", hash = "sha256:26fc12e6a329d61d24aa47b22a7c5c3f35753df7d8f2860973cf94f4e1fb3427"}, - {file = "keyring-25.1.0.tar.gz", hash = "sha256:7230ea690525133f6ad536a9b5def74a4bd52642abe594761028fc044d7c7893"}, + {file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"}, + {file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"}, ] [package.dependencies] @@ -1643,7 +1642,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +testing = ["pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "keyrings-cryptfile" @@ -2918,18 +2917,19 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "70.0.0" +version = "71.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, + {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -3424,4 +3424,4 @@ upnp = ["miniupnpc"] [metadata] lock-version = "2.0" python-versions = ">=3.8.10, <3.13" -content-hash = "61b8a8719851ff032c50f535d7b9adb9f30e68deea78531cb5ddf9ef6cb09fd2" +content-hash = "23e8b4c54c10094af595a25815bf23e792f9865a6a0302919cf7bc0ace233adb" diff --git a/poetry.toml b/poetry.toml index ab1033bd3722..f0ca6bc041a7 100644 --- a/poetry.toml +++ b/poetry.toml @@ -1,2 +1,7 @@ [virtualenvs] in-project = true + +[keyring] +# avoiding silent hangs with no indication +# https://github.com/python-poetry/poetry/issues/8623 +enabled = false diff --git a/pylintrc b/pylintrc index 01252902fddd..7913c3742373 100644 --- a/pylintrc +++ b/pylintrc @@ -515,6 +515,8 @@ max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 +# The following functions mutate other functions' signatures and need a E1120 exception +signature-mutators=chia.rpc.util.tx_endpoint [IMPORTS] diff --git a/pyproject.toml b/pyproject.toml index 24be6eb5637d..9ea4a3bd5680 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ pyupgrade = { version = "*", optional = true } [tool.poetry.extras] -dev = ["aiohttp_cors", "black", "build", "coverage", "diff-cover", "flake8", "isort", "mypy", "pre-commit", "py3createtorrent", "pyinstaller", "pylint", "pytest", "pytest-cov", "pytest-mock", "pytest-monitor", "pytest-xdist", "twine", "types-aiofiles", "types-cryptography", "types-pyyaml", "types-setuptools", "pyupgrade", "lxml"] +dev = ["aiohttp_cors", "black", "build", "coverage", "diff-cover", "flake8", "isort", "mypy", "pre-commit", "py3createtorrent", "pyinstaller", "pylint", "pytest", "pytest-cov", "pytest-mock", "pytest-monitor", "pytest-xdist", "types-aiofiles", "types-cryptography", "types-pyyaml", "types-setuptools", "pyupgrade", "lxml"] upnp = ["miniupnpc"] legacy_keyring = ["keyrings.cryptfile"] diff --git a/setup-poetry.sh b/setup-poetry.sh index 38faa07b5e74..682140ff2535 100755 --- a/setup-poetry.sh +++ b/setup-poetry.sh @@ -17,7 +17,7 @@ PYTHON_COMMAND=python while getopts c:h flag; do case "${flag}" in - c) PYTHON_COMMAND=${OPTARG} ;; + c) PYTHON_COMMAND="${OPTARG}" ;; h) usage exit 0 @@ -30,7 +30,7 @@ while getopts c:h flag; do esac done -$PYTHON_COMMAND -m venv .penv +"$PYTHON_COMMAND" -m venv .penv .penv/bin/python -m pip install --upgrade pip setuptools wheel # TODO: maybe make our own zipapp/shiv/pex of poetry and download that? -.penv/bin/python -m pip install poetry +.penv/bin/python -m pip install poetry "poetry-dynamic-versioning[plugin]"